// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "ChaosVDRuntimeModule.h" #include "ChaosVDRecordingDetails.h" #include "Containers/Ticker.h" #include "IMessageContext.h" #include "Misc/DateTime.h" #include "Misc/Guid.h" #include "Templates/SharedPointer.h" #include "ChaosVDRemoteSessionsManager.generated.h" class IMessageContext; struct FChaosVDSessionPong; class FMessageEndpoint; class IMessageBus; USTRUCT() struct FChaosVDSessionPing { GENERATED_BODY() UPROPERTY() FGuid ControllerInstanceId; }; USTRUCT() struct FChaosVDSessionPong { GENERATED_BODY() UPROPERTY() FGuid InstanceId; UPROPERTY() FGuid SessionId; UPROPERTY() FString SessionName; UPROPERTY() uint8 BuildTargetType = static_cast(EBuildTargetType::Unknown); }; USTRUCT() struct FChaosVDStartRecordingCommandMessage { GENERATED_BODY() UPROPERTY() EChaosVDRecordingMode RecordingMode = EChaosVDRecordingMode::Invalid; UPROPERTY() FString Target; }; USTRUCT() struct FChaosVDStopRecordingCommandMessage { GENERATED_BODY() }; USTRUCT() struct FChaosVDRecordingStatusMessage { GENERATED_BODY() FChaosVDRecordingStatusMessage() { } UPROPERTY() FGuid InstanceId = FGuid(); UPROPERTY() bool bIsRecording = false; UPROPERTY() float ElapsedTime = false; UPROPERTY() FChaosVDTraceDetails TraceDetails; }; USTRUCT() struct FChaosVDDataChannelState { GENERATED_BODY() UPROPERTY() FString ChannelName; UPROPERTY() bool bIsEnabled = false; UPROPERTY() bool bCanChangeChannelState = false; bool bWaitingUpdatedState = false; }; USTRUCT() struct FChaosVDChannelStateChangeCommandMessage { GENERATED_BODY() UPROPERTY() FChaosVDDataChannelState NewState; }; USTRUCT() struct FChaosVDChannelStateChangeResponseMessage { GENERATED_BODY() UPROPERTY() FGuid InstanceID = FGuid(); UPROPERTY() FChaosVDDataChannelState NewState; }; USTRUCT() struct FChaosVDFullSessionInfoRequestMessage { GENERATED_BODY() }; USTRUCT() struct FChaosVDFullSessionInfoResponseMessage { GENERATED_BODY() UPROPERTY() FGuid InstanceId; UPROPERTY() TArray DataChannelsStates; UPROPERTY() bool bIsRecording = false; }; UENUM() enum class EChaosVDRemoteSessionAttributes { None = 0, SupportsDataChannelChange = 1 << 0, CanExpire = 1 << 1, IsMultiSessionWrapper = 1 << 2, }; ENUM_CLASS_FLAGS(EChaosVDRemoteSessionAttributes) UENUM() enum class EChaosVDRemoteSessionReadyState : uint8 { /** The session is ready to execute commands */ Ready, /** We are executing a command in the session we expect to take a while without hearing anything from the target. */ Busy }; /** * Session object that contains all the information needed to communicate with a remote instance, and the state of that instance */ struct FChaosVDSessionInfo { explicit FChaosVDSessionInfo() :InstanceId(FGuid()), SessionTypeAttributes(EChaosVDRemoteSessionAttributes::CanExpire | EChaosVDRemoteSessionAttributes::SupportsDataChannelChange) { } virtual ~FChaosVDSessionInfo() = default; protected: explicit FChaosVDSessionInfo(EChaosVDRemoteSessionAttributes InSessionTypeAttributes) : SessionTypeAttributes(InSessionTypeAttributes) { } public: FGuid InstanceId; FString SessionName; FMessageAddress Address; FDateTime LastPingTime; EBuildTargetType BuildTargetType = EBuildTargetType::Unknown; EChaosVDRemoteSessionReadyState ReadyState = EChaosVDRemoteSessionReadyState::Ready; FChaosVDRecordingStatusMessage LastKnownRecordingState; TMap DataChannelsStatesByName; virtual bool IsRecording() const; virtual EChaosVDRecordingMode GetRecordingMode() const; virtual bool IsConnected() const; EChaosVDRemoteSessionAttributes GetSessionTypeAttributes() const { return SessionTypeAttributes; } protected: const EChaosVDRemoteSessionAttributes SessionTypeAttributes; }; /** * Session object that is able to control and provide information about multiple session objects. * Used to the UI can use the same API to control multiple session, than for single sessions. */ struct FChaosVDMultiSessionInfo : public FChaosVDSessionInfo { explicit FChaosVDMultiSessionInfo() : FChaosVDSessionInfo(EChaosVDRemoteSessionAttributes::IsMultiSessionWrapper) { } virtual ~FChaosVDMultiSessionInfo() override = default; virtual bool IsRecording() const override; virtual EChaosVDRecordingMode GetRecordingMode() const override; virtual bool IsConnected() const override; template void EnumerateInnerSessions(const TCallback& Callback) const { for (const TPair>& InnerSessionWithID : InnerSessionsByInstanceID) { if (const TSharedPtr SessionPtr = InnerSessionWithID.Value.Pin()) { if (!Callback(SessionPtr.ToSharedRef())) { return; } } } } TMap> InnerSessionsByInstanceID; }; DECLARE_LOG_CATEGORY_EXTERN(LogChaosVDRemoteSession, Log, Log); DECLARE_MULTICAST_DELEGATE_OneParam(FChaosVDRecordingStateChangeDelegate, TWeakPtr Session) /** Object that is able to discover, issue and execute commands back and forth between CVD and client/server/editor instances */ class FChaosVDRemoteSessionsManager { public: FChaosVDRemoteSessionsManager(); void Initialize(); void Shutdown(); FSimpleMulticastDelegate& OnSessionsUpdated() { return SessionsUpdatedDelegate; } /** * Issues a command to the provided address that will start a CVD recording * @param InDestinationAddress Message bus address that will execute the command * @param RecordingStartCommandParams Desired parameters to be used to start the recording */ CHAOSSOLVERENGINE_API void SendStartRecordingCommand(const FMessageAddress& InDestinationAddress, const FChaosVDStartRecordingCommandMessage& RecordingStartCommandParams); /** * Issues a command to the provided address to stop a CVD recording * @param InDestinationAddress Message bus address that will execute the command */ CHAOSSOLVERENGINE_API void SendStopRecordingCommand(const FMessageAddress& InDestinationAddress); /** * Issues a command to the provided address to change the state of a data channel * @param InDestinationAddress Message bus address that will execute the command * @param InNewStateData New data channel state to apply in the receiving instance */ CHAOSSOLVERENGINE_API void SendDataChannelStateChangeCommand(const FMessageAddress& InDestinationAddress, const FChaosVDChannelStateChangeCommandMessage& InNewStateData); /** * Returns the session info object for the provided ID * @param Id CVD SessionID */ CHAOSSOLVERENGINE_API TWeakPtr GetSessionInfo(FGuid Id); /** * Iterates trough all active and valid cvd sessions, and executes the provided callback to it. * if the callbacks returns false, the iteration will stop * */ template void EnumerateActiveSessions(const CallbackType& Callback) { for (const TPair>& ActiveSession : ActiveSessionsByInstanceId) { if (ActiveSession.Value) { bool bContinue = Callback(ActiveSession.Value.ToSharedRef()); if (!bContinue) { return; } } } } /** * Broadcast to the network a recording state update * @param UpdateMessage latest recording state of the issuing instance */ void PublishRecordingStatusUpdate(const FChaosVDRecordingStatusMessage& UpdateMessage); /** * Broadcast to the network a data channel state update * @param InNewStateData latest data channel state of the issuing instance */ void PublishDataChannelStateChangeUpdate(const FChaosVDChannelStateChangeResponseMessage& InNewStateData); /** * Delegate that broadcast when a recording was started in a session (either local or remote) */ FChaosVDRecordingStateChangeDelegate& OnSessionRecordingStarted() { return RecordingStartedDelegate; } /** * Delegate that broadcast when a recording stops in a session (either local or remote) */ FChaosVDRecordingStateChangeDelegate& OnSessionRecordingStopped() { return RecordingStoppedDelegate; } CHAOSSOLVERENGINE_API static const FString AllRemoteSessionsTargetName; CHAOSSOLVERENGINE_API static const FString AllRemoteServersTargetName; CHAOSSOLVERENGINE_API static const FString AllRemoteClientsTargetName; CHAOSSOLVERENGINE_API static const FString AllSessionsTargetName; CHAOSSOLVERENGINE_API static const FString CustomSessionsTargetName; CHAOSSOLVERENGINE_API static const FString LocalEditorSessionName; CHAOSSOLVERENGINE_API static const FName MessageBusEndPointName; CHAOSSOLVERENGINE_API static const FGuid AllRemoteSessionsWrapperGUID; CHAOSSOLVERENGINE_API static const FGuid AllRemoteServersWrapperGUID; CHAOSSOLVERENGINE_API static const FGuid AllRemoteClientsWrapperGUID; CHAOSSOLVERENGINE_API static const FGuid AllSessionsWrapperGUID; CHAOSSOLVERENGINE_API static const FGuid CustomSessionsWrapperGUID; CHAOSSOLVERENGINE_API static const FGuid InvalidSessionGUID; CHAOSSOLVERENGINE_API static const FGuid LocalEditorSessionID; private: /** * Returns true if this instance has controller capabilities (is either an editor or CVD Standalone, which is also an editor) */ static constexpr bool IsController(); /** * Sends a request to obtain the full session information to the provided message bus address * @param InDestinationAddress */ void SendFullSessionStateRequestCommand(const FMessageAddress& InDestinationAddress); /** * Registers a session object that is able to control multiple session instances * @param InSessionInfoRef Multi Session object */ void RegisterSessionInMultiSessionWrapper(const TSharedRef& InSessionInfoRef); /** * Deegisters a session object that is able to control multiple session instances * @param InSessionInfoRef Multi Session object */ void DeRegisterSessionInMultiSessionWrapper(const TSharedRef& InSessionInfoRef); /** * Creates a session object that is able to control multiple other session objects. * @param InstanceId Instance ID for this object * @param SessionName Session name that will be used in the UI * @return */ TSharedPtr CreatedWrapperSessionInfo(FGuid InstanceId, const FString& SessionName); /*** * Creates the messagebus endpoint this session manager will use */ TSharedPtr CreateEndPoint(const TSharedRef& InMessageBus); bool Tick(float DeltaTime); /** * Usually used by the controller. Broadcast to the network this controller exists. */ void SendPing(); /** * Broadcast to the network a small subset of this instance information, in to a received session ping. * @param InMessage Ping message that was received */ void SendPong(const FChaosVDSessionPing& InMessage); void HandleSessionPongMessage(const FChaosVDSessionPong& InMessage, const TSharedRef& InContext); void HandleSessionPingMessage(const FChaosVDSessionPing& InMessage, const TSharedRef& InContext); void HandleRecordingStatusUpdateMessage(const FChaosVDRecordingStatusMessage& InMessage, const TSharedRef& InContext); void HandleRecordingStartCommandMessage(const FChaosVDStartRecordingCommandMessage& InMessage, const TSharedRef& InContext); void HandleRecordingStopCommandMessage(const FChaosVDStopRecordingCommandMessage& InMessage, const TSharedRef& InContext); void HandleChangeDataChannelStateCommandMessage(const FChaosVDChannelStateChangeCommandMessage& InMessage, const TSharedRef& InContext); void HandleChangeDataChannelStateResponseMessage(const FChaosVDChannelStateChangeResponseMessage& InMessage, const TSharedRef& InContext); void HandleFullSessionStateRequestMessage(const FChaosVDFullSessionInfoRequestMessage& InMessage, const TSharedRef& InContext); void HandleFullSessionStateResponseMessage(const FChaosVDFullSessionInfoResponseMessage& InMessage, const TSharedRef& InContext); void RemoveExpiredSessions(); /** Holds the time at which the last ping was sent. */ FDateTime LastPingTime; /** Holds a pointer to the message bus. */ TWeakPtr MessageBusPtr; /** Holds the messaging endpoint. */ TSharedPtr MessageEndpoint; TMap> ActiveSessionsByInstanceId; TMap PendingRecordingStatusMessages; FSimpleMulticastDelegate SessionsUpdatedDelegate; FChaosVDRecordingStateChangeDelegate RecordingStartedDelegate; FChaosVDRecordingStateChangeDelegate RecordingStoppedDelegate; FTSTicker::FDelegateHandle TickHandle; };