// Copyright Epic Games, Inc. All Rights Reserved. #include "Chaos/ChaosVDRemoteSessionsManager.h" #include "ChaosVDRuntimeModule.h" #include "IMessageBus.h" #include "IMessagingModule.h" #include "MessageEndpoint.h" #include "MessageEndpointBuilder.h" #include "ChaosVisualDebugger/ChaosVDOptionalDataChannel.h" #include "ChaosVisualDebugger/ChaosVisualDebuggerTrace.h" #include "Misc/App.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(ChaosVDRemoteSessionsManager) const FGuid FChaosVDRemoteSessionsManager::AllRemoteSessionsWrapperGUID = FGuid::NewGuid(); const FGuid FChaosVDRemoteSessionsManager::AllRemoteServersWrapperGUID = FGuid::NewGuid(); const FGuid FChaosVDRemoteSessionsManager::AllRemoteClientsWrapperGUID = FGuid::NewGuid(); const FGuid FChaosVDRemoteSessionsManager::AllSessionsWrapperGUID = FGuid::NewGuid(); const FGuid FChaosVDRemoteSessionsManager::CustomSessionsWrapperGUID = FGuid::NewGuid(); const FGuid FChaosVDRemoteSessionsManager::InvalidSessionGUID = FGuid(); const FGuid FChaosVDRemoteSessionsManager::LocalSessionID = FApp::GetInstanceId(); const FString FChaosVDRemoteSessionsManager::LocalEditorSessionName = TEXT("Local Editor"); const FGuid FChaosVDRemoteSessionsManager::LocalEditorSessionID = IsController() ? FApp::GetInstanceId() : InvalidSessionGUID; const FName FChaosVDRemoteSessionsManager::MessageBusEndPointName = FName("CVDSessionManagerEndPoint"); const FString FChaosVDRemoteSessionsManager::AllRemoteSessionsTargetName = TEXT("All Remote"); const FString FChaosVDRemoteSessionsManager::AllRemoteServersTargetName = TEXT("All Remote Servers"); const FString FChaosVDRemoteSessionsManager::AllRemoteClientsTargetName = TEXT("All Remote Clients"); const FString FChaosVDRemoteSessionsManager::AllSessionsTargetName = TEXT("All Sessions"); const FString FChaosVDRemoteSessionsManager::CustomSessionsTargetName = TEXT("Custom Selection"); DEFINE_LOG_CATEGORY(LogChaosVDRemoteSession) const FChaosVDTraceDetails& FChaosVDSessionInfo::GetConnectionDetails() { return LastKnownConnectionDetails; } bool FChaosVDSessionInfo::IsRecording() const { return LastKnownRecordingState.bIsRecording; } EChaosVDRecordingMode FChaosVDSessionInfo::GetRecordingMode() const { return LastKnownConnectionDetails.IsValid() ? LastKnownConnectionDetails.Mode : LastRequestedRecordingMode; } EChaosVDRecordingMode FChaosVDSessionInfo::GetLastRequestedRecordingMode() const { return LastRequestedRecordingMode; } void FChaosVDSessionInfo::SetLastRequestedRecordingMode(EChaosVDRecordingMode NewRecordingMode) { LastRequestedRecordingMode = NewRecordingMode; } bool FChaosVDSessionInfo::IsConnected() const { return false; } bool FChaosVDMultiSessionInfo::IsRecording() const { bool bIsRecording = false; EnumerateInnerSessions([&bIsRecording](const TSharedRef& InSessionRef) { if (InSessionRef->IsRecording()) { bIsRecording = true; return false; } return true; }); return bIsRecording; } EChaosVDRecordingMode FChaosVDMultiSessionInfo::GetRecordingMode() const { EChaosVDRecordingMode FirstValidInstanceRecordingMode = EChaosVDRecordingMode::Invalid; EnumerateInnerSessions([&FirstValidInstanceRecordingMode](const TSharedRef& InSessionRef) { EChaosVDRecordingMode RecordingMode = InSessionRef->GetRecordingMode(); if (RecordingMode == EChaosVDRecordingMode::Invalid) { // In multi-session, all recordings will have the same recording mode, but not of them might report connected state at the same time // Therefore we need to continue searching until one of the session has a valid state before giving up. return true; } FirstValidInstanceRecordingMode = RecordingMode; return false; }); return FirstValidInstanceRecordingMode; } void FChaosVDRemoteSessionsManager::RegisterBuiltInMessageTypes() { SupportedMessageTypes.Emplace(FChaosVDSessionPong::StaticStruct()); SupportedMessageTypes.Emplace(FChaosVDRecordingStatusMessage::StaticStruct()); SupportedMessageTypes.Emplace(FChaosVDSessionPing::StaticStruct()); SupportedMessageTypes.Emplace(FChaosVDStartRecordingCommandMessage::StaticStruct()); SupportedMessageTypes.Emplace(FChaosVDStopRecordingCommandMessage::StaticStruct()); SupportedMessageTypes.Emplace(FChaosVDChannelStateChangeCommandMessage::StaticStruct()); SupportedMessageTypes.Emplace(FChaosVDFullSessionInfoRequestMessage::StaticStruct()); SupportedMessageTypes.Emplace(FChaosVDFullSessionInfoResponseMessage::StaticStruct()); SupportedMessageTypes.Emplace(FChaosVDChannelStateChangeResponseMessage::StaticStruct()); SupportedMessageTypes.Emplace(FChaosVDTraceConnectionDetailsMessage::StaticStruct()); } FChaosVDRemoteSessionsManager::FChaosVDRemoteSessionsManager() { RegisterBuiltInMessageTypes(); } void FChaosVDRemoteSessionsManager::Initialize(const TSharedPtr& InMessageBus) { ensureMsgf(!InMessageBus, TEXT("Initialize(MessageBusPtr) is deprecated!. The provided message buss will be ignored")); Initialize(); } constexpr bool FChaosVDRemoteSessionsManager::IsController() { #if WITH_EDITOR return true; #else return false; #endif } TWeakPtr FChaosVDRemoteSessionsManager::GetSessionInfo(FGuid Id) { if (TSharedPtr* FoundSessionPtrPtr = ActiveSessionsByInstanceId.Find(Id)) { return *FoundSessionPtrPtr; } return nullptr; } TSharedPtr FChaosVDRemoteSessionsManager::CreatedWrapperSessionInfo(FGuid InstanceId, const FString& SessionName) { TSharedPtr NewSessionInfo = MakeShared(); NewSessionInfo->InstanceId = InstanceId; NewSessionInfo->SessionName = SessionName; return NewSessionInfo; } TSharedPtr FChaosVDRemoteSessionsManager::CreateEndPoint(const TSharedRef& InMessageBus) { FMessageEndpointBuilder EndPointBuilder = FMessageEndpoint::Builder(MessageBusEndPointName, InMessageBus) .Handling(this, &FChaosVDRemoteSessionsManager::HandleSessionPingMessage) .Handling(this, &FChaosVDRemoteSessionsManager::HandleRecordingStartCommandMessage) .Handling(this, &FChaosVDRemoteSessionsManager::HandleRecordingStopCommandMessage) .Handling(this, &FChaosVDRemoteSessionsManager::HandleChangeDataChannelStateCommandMessage) .Handling(this, &FChaosVDRemoteSessionsManager::HandleFullSessionStateRequestMessage); if (IsController()) { EndPointBuilder.Handling(this, &FChaosVDRemoteSessionsManager::HandleSessionPongMessage) .Handling(this, &FChaosVDRemoteSessionsManager::HandleRecordingStatusUpdateMessage) .Handling(this, &FChaosVDRemoteSessionsManager::HandleFullSessionStateResponseMessage) .Handling(this, &FChaosVDRemoteSessionsManager::HandleChangeDataChannelStateResponseMessage) .Handling(this, &FChaosVDRemoteSessionsManager::HandleConnectionDetailsUpdateMessage); } return EndPointBuilder.Build(); } void FChaosVDRemoteSessionsManager::ReInitializeMessagingSystem(const TSharedPtr& InMessageBus) { ShutdownMessagingSystem(); InitializeMessagingSystem(InMessageBus); } void FChaosVDRemoteSessionsManager::RegisterExternalSupportedMessageType(const UScriptStruct* ScriptStruct) { if (ensure(ScriptStruct)) { SupportedMessageTypes.Emplace(ScriptStruct); } } void FChaosVDRemoteSessionsManager::InitializeMessagingSystem(const TSharedPtr& InMessageBus) { if (!ensure(InMessageBus)) { return; } MessageBusPtr = InMessageBus; MessageEndpoint = CreateEndPoint(InMessageBus.ToSharedRef()); if (!ensure(MessageEndpoint)) { return; } if (IsController()) { MessageEndpoint->Subscribe(); MessageEndpoint->Subscribe(); MessageEndpoint->Subscribe(); MessageEndpoint->Subscribe(); MessageEndpoint->Subscribe(); } MessageEndpoint->Subscribe(); MessageEndpoint->Subscribe(); MessageEndpoint->Subscribe(); MessageEndpoint->Subscribe(); MessageEndpoint->Subscribe(); if (bInitialized) { MessageEndpoint->Enable(); } else { MessageEndpoint->Disable(); } MessagingInitializedDelegate.Broadcast(InMessageBus, MessageEndpoint); } void FChaosVDRemoteSessionsManager::ShutdownMessagingSystem() { if (MessageEndpoint) { MessageEndpoint->Unsubscribe(); MessageEndpoint->Unsubscribe(); MessageEndpoint->Unsubscribe(); MessageEndpoint->Unsubscribe(); MessageEndpoint->Unsubscribe(); MessageEndpoint->Unsubscribe(); MessageEndpoint->Unsubscribe(); MessageEndpoint->Unsubscribe(); MessageEndpoint->Unsubscribe(); } MessageBusPtr.Reset(); MessageEndpoint = nullptr; } void FChaosVDRemoteSessionsManager::Initialize() { #if !defined(WITH_CHAOS_VISUAL_DEBUGGER_EXTERNAL_MESSAGING) || !WITH_CHAOS_VISUAL_DEBUGGER_EXTERNAL_MESSAGING InitializeMessagingSystem(IMessagingModule::Get().GetDefaultBus()); #endif ActiveSessionsByInstanceId.Add(AllRemoteSessionsWrapperGUID, CreatedWrapperSessionInfo(AllRemoteSessionsWrapperGUID, AllRemoteSessionsTargetName)); ActiveSessionsByInstanceId.Add(AllRemoteServersWrapperGUID, CreatedWrapperSessionInfo(AllRemoteServersWrapperGUID, AllRemoteServersTargetName)); ActiveSessionsByInstanceId.Add(AllRemoteClientsWrapperGUID, CreatedWrapperSessionInfo(AllRemoteClientsWrapperGUID, AllRemoteClientsTargetName)); ActiveSessionsByInstanceId.Add(AllSessionsWrapperGUID, CreatedWrapperSessionInfo(AllSessionsWrapperGUID, AllSessionsTargetName)); ActiveSessionsByInstanceId.Add(CustomSessionsWrapperGUID, CreatedWrapperSessionInfo(CustomSessionsWrapperGUID, CustomSessionsTargetName)); if (MessageEndpoint) { MessageEndpoint->Enable(); } bInitialized = true; } void FChaosVDRemoteSessionsManager::Shutdown() { bInitialized = false; ShutdownMessagingSystem(); } void FChaosVDRemoteSessionsManager::EnumerateMessageTypes(const FVisitorFunction& InVisitor) { for (const UScriptStruct* MessageType : SupportedMessageTypes) { if (MessageType) { InVisitor(MessageType); } } } void FChaosVDRemoteSessionsManager::StartSessionDiscovery() { if (TickHandle.IsValid()) { UE_LOG(LogChaosVDRemoteSession, Warning, TEXT("[%hs] Session discovery already started"), __func__); return; } constexpr float TickInterval = 1.0f; TickHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateRaw(this, &FChaosVDRemoteSessionsManager::Tick), TickInterval); } void FChaosVDRemoteSessionsManager::StopSessionDiscovery() { if (TickHandle.IsValid()) { RemoveExpiredSessions(ERemoveSessionOptions::ForceRemoveAll); FTSTicker::RemoveTicker(TickHandle); TickHandle = FTSTicker::FDelegateHandle(); } } void FChaosVDRemoteSessionsManager::PublishRecordingStatusUpdate(const FChaosVDRecordingStatusMessage& InUpdateMessage) { if (ensure(MessageEndpoint)) { MessageEndpoint->Publish(FMessageEndpoint::MakeMessage(InUpdateMessage), EMessageScope::Network); } } void FChaosVDRemoteSessionsManager::PublishTraceConnectionDetailsUpdate(const FChaosVDTraceConnectionDetailsMessage& InUpdateMessage) { if (ensure(MessageEndpoint)) { MessageEndpoint->Publish(FMessageEndpoint::MakeMessage(InUpdateMessage), EMessageScope::Network); } } void FChaosVDRemoteSessionsManager::PublishDataChannelStateChangeUpdate(const FChaosVDChannelStateChangeResponseMessage& InNewStateData) { if (ensure(MessageEndpoint)) { MessageEndpoint->Publish(FMessageEndpoint::MakeMessage(InNewStateData), EMessageScope::Network); } } void FChaosVDRemoteSessionsManager::SendStartRecordingCommand(const FMessageAddress& InDestinationAddress, const FChaosVDStartRecordingCommandMessage& RecordingStartCommandParams) { if (!MessageEndpoint) { UE_LOG(LogChaosVDRemoteSession, Error, TEXT("[%hs] Failed to send command | Invalid endpoint."), __func__); return; } MessageEndpoint->Send( FMessageEndpoint::MakeMessage(RecordingStartCommandParams), FChaosVDStartRecordingCommandMessage::StaticStruct(), EMessageFlags::Reliable, nullptr, TArrayBuilder().Add(InDestinationAddress), FTimespan::Zero(), FDateTime::MaxValue()); } void FChaosVDRemoteSessionsManager::SendStopRecordingCommand(const FMessageAddress& InDestinationAddress) { if (!MessageEndpoint) { UE_LOG(LogChaosVDRemoteSession, Error, TEXT("[%s] Failed to send command | Invalid endpoint."), ANSI_TO_TCHAR(__FUNCTION__)); return; } MessageEndpoint->Send( FMessageEndpoint::MakeMessage(), FChaosVDStopRecordingCommandMessage::StaticStruct(), EMessageFlags::Reliable, nullptr, TArrayBuilder().Add(InDestinationAddress), FTimespan::Zero(), FDateTime::MaxValue()); } void FChaosVDRemoteSessionsManager::SendDataChannelStateChangeCommand(const FMessageAddress& InDestinationAddress,const FChaosVDChannelStateChangeCommandMessage& InNewStateData) { if (!MessageEndpoint) { UE_LOG(LogChaosVDRemoteSession, Error, TEXT("[%s] Failed to send command | Invalid endpoint."), ANSI_TO_TCHAR(__FUNCTION__)); return; } MessageEndpoint->Send( FMessageEndpoint::MakeMessage(InNewStateData), FChaosVDChannelStateChangeCommandMessage::StaticStruct(), EMessageFlags::Reliable, nullptr, TArrayBuilder().Add(InDestinationAddress), FTimespan::Zero(), FDateTime::MaxValue()); } void FChaosVDRemoteSessionsManager::SendFullSessionStateRequestCommand(const FMessageAddress& InDestinationAddress) { if (!MessageEndpoint) { UE_LOG(LogChaosVDRemoteSession, Error, TEXT("[%s] Failed to send command | Invalid endpoint."), ANSI_TO_TCHAR(__FUNCTION__)); return; } MessageEndpoint->Send( FMessageEndpoint::MakeMessage(), FChaosVDFullSessionInfoRequestMessage::StaticStruct(), EMessageFlags::Reliable, nullptr, TArrayBuilder().Add(InDestinationAddress), FTimespan::Zero(), FDateTime::MaxValue()); } bool FChaosVDRemoteSessionsManager::Tick(float DeltaTime) { if (IsController()) { SendPing(); RemoveExpiredSessions(); } return true; } void FChaosVDRemoteSessionsManager::SendPing() { if (ensure(MessageEndpoint)) { if (FChaosVDSessionPing* SessionPingData = FMessageEndpoint::MakeMessage()) { SessionPingData->ControllerInstanceId = FApp::GetInstanceId(); MessageEndpoint->Publish(SessionPingData, EMessageScope::Network); } } } void FChaosVDRemoteSessionsManager::SendPong(const FChaosVDSessionPing& InMessage) { if (!ensure(MessageEndpoint)) { return; } if (FChaosVDSessionPong* PongMessage = FMessageEndpoint::MakeMessage()) { PongMessage->InstanceId = FApp::GetInstanceId(); PongMessage->SessionId = FApp::GetSessionId(); PongMessage->BuildTargetType = static_cast(FApp::GetBuildTargetType()); if (InMessage.ControllerInstanceId == PongMessage->InstanceId) { PongMessage->SessionName = LocalEditorSessionName; } else { FString AppSessionName = FApp::GetSessionName(); PongMessage->SessionName = AppSessionName == TEXT("None") || AppSessionName.IsEmpty() ? FString::Format(TEXT("{0} {1} {2}"), { FApp::GetProjectName(), FString(LexToString(FApp::GetBuildTargetType())), FString::FromInt(FPlatformProcess::GetCurrentProcessId()) }) : AppSessionName; } MessageEndpoint->Publish(PongMessage, EMessageScope::Network); } } void FChaosVDRemoteSessionsManager::RegisterSessionInMultiSessionWrapper(const TSharedRef& InSessionInfoRef) { if (InSessionInfoRef->SessionName != LocalEditorSessionName ) { StaticCastSharedPtr(ActiveSessionsByInstanceId.FindChecked(AllRemoteSessionsWrapperGUID))->InnerSessionsByInstanceID.Emplace(InSessionInfoRef->InstanceId, InSessionInfoRef); if (InSessionInfoRef->BuildTargetType == EBuildTargetType::Server) { StaticCastSharedPtr(ActiveSessionsByInstanceId.FindChecked(AllRemoteServersWrapperGUID))->InnerSessionsByInstanceID.Emplace(InSessionInfoRef->InstanceId, InSessionInfoRef); } else { StaticCastSharedPtr(ActiveSessionsByInstanceId.FindChecked(AllRemoteClientsWrapperGUID))->InnerSessionsByInstanceID.Emplace(InSessionInfoRef->InstanceId, InSessionInfoRef); } } StaticCastSharedPtr(ActiveSessionsByInstanceId.FindChecked(AllSessionsWrapperGUID))->InnerSessionsByInstanceID.Emplace(InSessionInfoRef->InstanceId, InSessionInfoRef); } void FChaosVDRemoteSessionsManager::DeRegisterSessionInMultiSessionWrapper(const TSharedRef& InSessionInfoRef) { StaticCastSharedPtr(ActiveSessionsByInstanceId.FindChecked(AllRemoteSessionsWrapperGUID))->InnerSessionsByInstanceID.Remove(InSessionInfoRef->InstanceId); StaticCastSharedPtr(ActiveSessionsByInstanceId.FindChecked(AllSessionsWrapperGUID))->InnerSessionsByInstanceID.Remove(InSessionInfoRef->InstanceId); StaticCastSharedPtr(ActiveSessionsByInstanceId.FindChecked(AllRemoteServersWrapperGUID))->InnerSessionsByInstanceID.Remove(InSessionInfoRef->InstanceId); StaticCastSharedPtr(ActiveSessionsByInstanceId.FindChecked(AllRemoteClientsWrapperGUID))->InnerSessionsByInstanceID.Remove(InSessionInfoRef->InstanceId); } void FChaosVDRemoteSessionsManager::ProcessPendingMessagesForSession(const FChaosVDSessionPong& InMessage, const TSharedRef& InSessionInfoPtr) { PendingRecordingStatusMessages.RemoveAndCopyValue(InMessage.InstanceId, InSessionInfoPtr->LastKnownRecordingState); PendingRecordingConnectionDetailsMessages.RemoveAndCopyValue(InMessage.InstanceId, InSessionInfoPtr->LastKnownConnectionDetails); } void FChaosVDRemoteSessionsManager::HandleSessionPongMessage(const FChaosVDSessionPong& InMessage, const TSharedRef& InContext) { TSharedPtr& SessionInfoPtr = ActiveSessionsByInstanceId.FindOrAdd(InMessage.InstanceId); if (!SessionInfoPtr) { SessionInfoPtr = MakeShared(); SessionInfoPtr->Address = InContext->GetSender(); SessionInfoPtr->InstanceId = InMessage.InstanceId; SessionInfoPtr->SessionName = InMessage.SessionName; SessionInfoPtr->BuildTargetType = static_cast(InMessage.BuildTargetType); RegisterSessionInMultiSessionWrapper(SessionInfoPtr.ToSharedRef()); SessionDiscoveredDelegate.Broadcast(SessionInfoPtr->InstanceId); // This is the first time we see this session, so we need to request the rest of its state so we can properly populate the UI SendFullSessionStateRequestCommand(SessionInfoPtr->Address); } SessionInfoPtr->LastPingTime = FDateTime::UtcNow(); ProcessPendingMessagesForSession(InMessage, SessionInfoPtr.ToSharedRef()); SessionsUpdatedDelegate.Broadcast(); } void FChaosVDRemoteSessionsManager::HandleSessionPingMessage(const FChaosVDSessionPing& InMessage, const TSharedRef& InContext) { // If this instance is not running trace, don't answer to the ping as the CVD instance will not be able to do anything useful with it #if !CHAOS_VISUAL_DEBUGGER_WITHOUT_TRACE SendPong(InMessage); #endif } void FChaosVDRemoteSessionsManager::HandleRecordingStatusUpdateMessage(const FChaosVDRecordingStatusMessage& Message, const TSharedRef& InContext) { if (TSharedPtr* SessionInfoPtrPtr = ActiveSessionsByInstanceId.Find(Message.InstanceId)) { TSharedPtr& SessionInfoPtr = *SessionInfoPtrPtr; check(SessionInfoPtr); if (SessionInfoPtr->LastKnownRecordingState.bIsRecording != Message.bIsRecording) { if (Message.bIsRecording) { RecordingStartedDelegate.Broadcast(SessionInfoPtr); } else { RecordingStoppedDelegate.Broadcast(SessionInfoPtr); } } SessionInfoPtr->LastKnownRecordingState = Message; } else { PendingRecordingStatusMessages.FindOrAdd(Message.InstanceId) = Message; } } void FChaosVDRemoteSessionsManager::HandleConnectionDetailsUpdateMessage(const FChaosVDTraceConnectionDetailsMessage& InMessage, const TSharedRef& InContext) { if (TSharedPtr* SessionInfoPtrPtr = ActiveSessionsByInstanceId.Find(InMessage.InstanceId)) { TSharedPtr& SessionInfoPtr = *SessionInfoPtrPtr; check(SessionInfoPtr); SessionInfoPtr->LastKnownConnectionDetails = InMessage.TraceDetails; } else { PendingRecordingConnectionDetailsMessages.FindOrAdd(InMessage.InstanceId) = InMessage.TraceDetails; } } void FChaosVDRemoteSessionsManager::HandleRecordingStartCommandMessage(const FChaosVDStartRecordingCommandMessage& InMessage, const TSharedRef& InContext) { #if WITH_CHAOS_VISUAL_DEBUGGER UE_AUTORTFM_ONCOMMIT(InMessage, InContext) { FChaosVisualDebuggerTrace::OverrideDefaultEnabledDataChannels(InMessage.DataChannelsEnabledOverrideList); FChaosVDRuntimeModule::Get().StartRecording(InMessage); }; #endif } void FChaosVDRemoteSessionsManager::HandleRecordingStopCommandMessage(const FChaosVDStopRecordingCommandMessage& InMessage, const TSharedRef& InContext) { #if WITH_CHAOS_VISUAL_DEBUGGER UE_AUTORTFM_ONCOMMIT() { FChaosVDRuntimeModule::Get().StopRecording(); }; #endif } void FChaosVDRemoteSessionsManager::HandleChangeDataChannelStateCommandMessage(const FChaosVDChannelStateChangeCommandMessage& InMessage, const TSharedRef& InContext) { #if WITH_CHAOS_VISUAL_DEBUGGER UE_AUTORTFM_ONCOMMIT(InMessage) { using namespace Chaos::VisualDebugger; if (TSharedPtr ChannelInstance = FChaosVDDataChannelsManager::Get().GetChannelById(FName(InMessage.NewState.ChannelName))) { ChannelInstance->SetChannelEnabled(InMessage.NewState.bIsEnabled); } }; #endif } void FChaosVDRemoteSessionsManager::HandleChangeDataChannelStateResponseMessage(const FChaosVDChannelStateChangeResponseMessage& InMessage, const TSharedRef& InContext) { if (TSharedPtr* SessionInfoPtrPtr = ActiveSessionsByInstanceId.Find(InMessage.InstanceID)) { if (TSharedPtr& SessionInfoPtr = *SessionInfoPtrPtr) { if (FChaosVDDataChannelState* FoundChannelState = SessionInfoPtr->DataChannelsStatesByName.Find(InMessage.NewState.ChannelName)) { *FoundChannelState = InMessage.NewState; } } } } void FChaosVDRemoteSessionsManager::HandleFullSessionStateRequestMessage(const FChaosVDFullSessionInfoRequestMessage& InMessage, const TSharedRef& InContext) { if (!ensure(MessageEndpoint)) { return; } if (FChaosVDFullSessionInfoResponseMessage* FullSessionStateResponse = FMessageEndpoint::MakeMessage()) { FullSessionStateResponse->InstanceId = FApp::GetInstanceId(); #if WITH_CHAOS_VISUAL_DEBUGGER FullSessionStateResponse->bIsRecording = FChaosVDRuntimeModule::Get().IsRecording(); using namespace Chaos::VisualDebugger; FChaosVDDataChannelsManager::Get().EnumerateChannels([&FullSessionStateResponse](const TSharedRef& Channel) { FChaosVDChannelStateChangeCommandMessage DataChannelStateMessage = {Channel->GetId().ToString(), Channel->IsChannelEnabled(), Channel->CanChangeEnabledState() }; FullSessionStateResponse->DataChannelsStates.Emplace(DataChannelStateMessage.NewState); return true; }); #endif MessageEndpoint->Send( FullSessionStateResponse, FChaosVDFullSessionInfoResponseMessage::StaticStruct(), EMessageFlags::Reliable, nullptr, TArrayBuilder().Add(InContext->GetSender()), FTimespan::Zero(), FDateTime::MaxValue()); } } void FChaosVDRemoteSessionsManager::HandleFullSessionStateResponseMessage(const FChaosVDFullSessionInfoResponseMessage& InMessage, const TSharedRef& InContext) { if (TSharedPtr* SessionInfoPtrPtr = ActiveSessionsByInstanceId.Find(InMessage.InstanceId)) { if (TSharedPtr& SessionInfoPtr = *SessionInfoPtrPtr) { SessionInfoPtr->LastKnownRecordingState.bIsRecording = InMessage.bIsRecording; for (const FChaosVDDataChannelState& ChannelState : InMessage.DataChannelsStates) { SessionInfoPtr->DataChannelsStatesByName.Emplace(ChannelState.ChannelName, ChannelState); } } } } void FChaosVDRemoteSessionsManager::RemoveExpiredSessions(ERemoveSessionOptions Options) { bool bAnySessionRemoved = false; FDateTime CurrentTime = FDateTime::UtcNow(); for (TMap>::TIterator RemoveIterator = ActiveSessionsByInstanceId.CreateIterator(); RemoveIterator; ++RemoveIterator) { TSharedPtr& SessionInfoPtr = RemoveIterator.Value(); if (!SessionInfoPtr) { RemoveIterator.RemoveCurrent(); bAnySessionRemoved = true; continue; } if (!EnumHasAnyFlags(SessionInfoPtr->GetSessionTypeAttributes(), EChaosVDRemoteSessionAttributes::CanExpire)) { continue; } FTimespan ElapsedTime = CurrentTime - SessionInfoPtr->LastPingTime; // A session goes into busy state if we are attempting to issue a command that might stall the target, currently that only happens on recording start commands of complex maps // In these cases, we need to allow more time between pings. If a recording command failed, it is expected the state to be changed to Ready again const float MaxAllowedTimeBetweenPings = SessionInfoPtr->ReadyState == EChaosVDRemoteSessionReadyState::Busy || SessionInfoPtr->IsRecording() ? 60.0f : 3.0f; if (EnumHasAnyFlags(Options, ERemoveSessionOptions::ForceRemoveAll) || ElapsedTime > FTimespan::FromSeconds(MaxAllowedTimeBetweenPings)) { SessionExpiredDelegate.Broadcast(SessionInfoPtr->InstanceId); DeRegisterSessionInMultiSessionWrapper(SessionInfoPtr.ToSharedRef()); RemoveIterator.RemoveCurrent(); bAnySessionRemoved = true; } } if (bAnySessionRemoved) { SessionsUpdatedDelegate.Broadcast(); } }