Files
UnrealEngine/Engine/Source/Runtime/MessagingCommon/Public/MessageEndpoint.h
2025-05-18 13:04:45 +08:00

1066 lines
36 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreTypes.h"
#include "Async/TaskGraphInterfaces.h"
#include "Containers/Array.h"
#include "Containers/ArrayBuilder.h"
#include "Containers/Queue.h"
#include "IMessageAttachment.h"
#include "IMessageBus.h"
#include "IMessageContext.h"
#include "IMessageHandler.h"
#include "IMessageReceiver.h"
#include "IMessageSender.h"
#include "IMessageBusListener.h"
#include "Misc/Guid.h"
#include "Templates/SharedPointer.h"
#include "UObject/NameTypes.h"
#include "Misc/ScopeLock.h"
/**
* DEPRECATED: Delegate type for error notifications.
*
* The first parameter is the context of the sent message (only valid for the duration of the callback).
* The second parameter is the error string.
*/
DECLARE_DELEGATE_TwoParams(FOnMessageEndpointError, const IMessageContext&, const FString&);
/**
* Struct to propagate message bus notifications
*/
struct FMessageBusNotification
{
/** Notification type. */
EMessageBusNotification NotificationType;
/** Address of the un/registered. */
FMessageAddress RegistrationAddress;
};
/** Delegate type for MessageBus notifications. */
DECLARE_DELEGATE_OneParam(FOnBusNotification, const FMessageBusNotification&);
/**
* Implements a message endpoint for sending and receiving messages on a message bus.
*
* This class provides a convenient implementation of the IMessageReceiver and IMessageSender interfaces,
* which allow consumers to send and receive messages on a message bus. The endpoint allows for receiving
* messages asynchronously as they arrive, as well as synchronously through an inbox that can be polled.
*
* By default, messages are received synchronously on the thread that the endpoint was created on.
* If the message consumer is thread-safe, a more efficient message dispatch can be enabled by calling
* the SetRecipientThread() method with ENamedThreads::AnyThread.
*
* Endpoints that are destroyed or receive messages on non-Game threads should use the static function
* FMessageEndpoint::SafeRelease() to dispose of the endpoint. This will ensure that there are no race
* conditions between endpoint destruction and the receiving of messages.
*
* The underlying message bus will take ownership of all sent and published message objects. The memory
* held by the messages must therefore NOT be freed by the caller.
*/
class FMessageEndpoint
: public TSharedFromThis<FMessageEndpoint, ESPMode::ThreadSafe>
, public IMessageReceiver
, public IMessageSender
, public IBusListener
{
public:
/**
* Type definition for the endpoint builder.
*
* When building message endpoints that receive messages on AnyThread, use the SafeRelease
* helper function to avoid race conditions when destroying the objects that own the endpoints.
*
* @see SafeRelease
*/
typedef struct FMessageEndpointBuilder Builder;
/**
* Creates and initializes a new instance.
*
* @param InName The endpoint's name (for debugging purposes).
* @param InBus The message bus to attach this endpoint to.
* @param InHandlers The collection of message handlers to register.
*/
FMessageEndpoint(const FName& InName, const TSharedRef<IMessageBus, ESPMode::ThreadSafe>& InBus, const TArray<TSharedPtr<IMessageHandler, ESPMode::ThreadSafe>>& InHandlers, const FOnBusNotification InNotificationDelegate)
: Address(FMessageAddress::NewAddress())
, BusPtr(InBus)
, Enabled(true)
, Handlers(InHandlers)
, NotificationDelegate(InNotificationDelegate)
, Id(FGuid::NewGuid())
, InboxEnabled(false)
, Name(InName)
{
SetRecipientThread(FTaskGraphInterface::Get().GetCurrentThreadIfKnown());
}
/** Destructor. */
~FMessageEndpoint()
{
auto Bus = BusPtr.Pin();
if (Bus.IsValid())
{
Bus->Unregister(Address);
}
}
public:
/**
* Disables this endpoint.
*
* A disabled endpoint will not receive any subscribed messages until it is enabled again.
* Endpoints should be created in an enabled state by default.
*
* @see Enable, IsEnabled
*/
void Disable()
{
Enabled = false;
}
/**
* Enables this endpoint.
*
* An activated endpoint will receive subscribed messages.
* Endpoints should be created in an enabled state by default.
*
* @see Disable, IsEnabled
*/
void Enable()
{
Enabled = true;
}
/**
* Gets the endpoint's message address.
*
* @return Message address.
*/
const FMessageAddress& GetAddress() const
{
return Address;
}
/**
* Checks whether this endpoint is connected to the bus.
*
* @return true if connected, false otherwise.
* @see IsEnabled
*/
bool IsConnected() const
{
return BusPtr.IsValid();
}
/**
* Checks whether this endpoint is enabled.
*
* @return true if the endpoint is enabled, false otherwise.
* @see Disable, Enable, IsConnected
*/
bool IsEnabled() const
{
return Enabled;
}
/**
* Sets the name of the thread to receive messages on.
*
* Use this method to receive messages on a particular thread, for example, if the
* consumer owning this endpoint is not thread-safe. The default value is ThreadAny.
*
* ThreadAny is the fastest way to receive messages. It should be used if the receiving
* code is completely thread-safe and if it is sufficiently fast. ThreadAny MUST NOT
* be used if the receiving code is not thread-safe. It also SHOULD NOT be used if the
* code includes time consuming operations, because it will block the message router,
* causing no other messages to be delivered in the meantime.
*
* @param NamedThread The name of the thread to receive messages on.
*/
void SetRecipientThread(const ENamedThreads::Type& NamedThread)
{
RecipientThread = ENamedThreads::GetThreadIndex(NamedThread);
}
public:
/**
* Defers processing of the given message by the specified time delay.
*
* The message is effectively delivered again to this endpoint after the
* original sent time plus the time delay have elapsed.
*
* @param Context The context of the message to defer.
* @param Delay The time delay.
*/
void Defer(const TSharedRef<IMessageContext, ESPMode::ThreadSafe>& Context, const FTimespan& Delay)
{
TSharedPtr<IMessageBus, ESPMode::ThreadSafe> Bus = GetBusIfEnabled();
if (Bus.IsValid())
{
Bus->Forward(Context, TArrayBuilder<FMessageAddress>().Add(Address), Delay, AsShared());
}
}
/**
* Forwards a previously received message.
*
* Messages can only be forwarded to endpoints within the same process.
*
* @param Context The context of the message to forward.
* @param Recipients The list of message recipients to forward the message to.
* @param Delay The time delay.
*/
void Forward(const TSharedRef<IMessageContext, ESPMode::ThreadSafe>& Context, const TArray<FMessageAddress>& Recipients, const FTimespan& Delay)
{
TSharedPtr<IMessageBus, ESPMode::ThreadSafe> Bus = GetBusIfEnabled();
if (Bus.IsValid())
{
Bus->Forward(Context, Recipients, Delay, AsShared());
}
}
/**
* Publishes a message to all subscribed recipients within the specified scope.
*
* @param Message The message to publish.
* @param TypeInfo The message's type information.
* @param Scope The message scope.
* @param Fields The message content.
* @param Delay The delay after which to publish the message.
* @param Expiration The time at which the message expires.
*/
void Publish(void* Message, UScriptStruct* TypeInfo, EMessageScope Scope, const FTimespan& Delay, const FDateTime& Expiration)
{
Publish(Message, TypeInfo, Scope, TMap<FName, FString>(), Delay, Expiration);
}
/**
* Publishes a message to all subscribed recipients within the specified scope.
*
* @param Message The message to publish.
* @param TypeInfo The message's type information.
* @param Scope The message scope.
* @param Annotations An optional message annotations header.
* @param Fields The message content.
* @param Delay The delay after which to publish the message.
* @param Expiration The time at which the message expires.
*/
void Publish(void* Message, UScriptStruct* TypeInfo, EMessageScope Scope, const TMap<FName, FString> Annotations, const FTimespan& Delay, const FDateTime& Expiration)
{
TSharedPtr<IMessageBus, ESPMode::ThreadSafe> Bus = GetBusIfEnabled();
if (Bus.IsValid())
{
Bus->Publish(Message, TypeInfo, Scope, Annotations, Delay, Expiration, AsShared());
}
}
/**
* Sends a message to the specified list of recipients.
*
* @param Message The message to send.
* @param TypeInfo The message's type information.
* @param Attachment An optional binary data attachment.
* @param Recipients The message recipients.
* @param Delay The delay after which to send the message.
* @param Expiration The time at which the message expires.
*/
UE_DEPRECATED(4.21, "FMessageEndpoint::Send with 6 params is deprecated. Please use FMessageEndpoint::Send that takes additionnal EMessageFlags instead!")
void Send(void* Message, UScriptStruct* TypeInfo, const TSharedPtr<IMessageAttachment, ESPMode::ThreadSafe>& Attachment, const TArray<FMessageAddress>& Recipients, const FTimespan& Delay, const FDateTime& Expiration)
{
Send(Message, TypeInfo, EMessageFlags::None, Attachment, Recipients, Delay, Expiration);
}
/**
* Sends a message to the specified list of recipients.
* Allows to specify message flags
*
* @param Message The message to send.
* @param TypeInfo The message's type information.
* @param Flags The message's type information.
* @param Attachment An optional binary data attachment.
* @param Recipients The message recipients.
* @param Delay The delay after which to send the message.
* @param Expiration The time at which the message expires.
*/
void Send(void* Message, UScriptStruct* TypeInfo, EMessageFlags Flags, const TSharedPtr<IMessageAttachment, ESPMode::ThreadSafe>& Attachment, const TArray<FMessageAddress>& Recipients, const FTimespan& Delay, const FDateTime& Expiration)
{
TSharedPtr<IMessageBus, ESPMode::ThreadSafe> Bus = GetBusIfEnabled();
if (Bus.IsValid())
{
Bus->Send(Message, TypeInfo, Flags, TMap<FName, FString>(), Attachment, Recipients, Delay, Expiration, AsShared());
}
}
/**
* Sends a message to the specified list of recipients.
*
* @param Message The message to send.
* @param TypeInfo The message's type information.
* @param Flags The message's type information.
* @param Annotations An optional message annotations header.
* @param Attachment An optional binary data attachment.
* @param Recipients The message recipients.
* @param Delay The delay after which to send the message.
* @param Expiration The time at which the message expires.
*/
void Send(void* Message, UScriptStruct* TypeInfo, EMessageFlags Flags, const TMap<FName, FString> Annotations, const TSharedPtr<IMessageAttachment, ESPMode::ThreadSafe>& Attachment, const TArray<FMessageAddress>& Recipients, const FTimespan& Delay, const FDateTime& Expiration)
{
TSharedPtr<IMessageBus, ESPMode::ThreadSafe> Bus = GetBusIfEnabled();
if (Bus.IsValid())
{
Bus->Send(Message, TypeInfo, Flags, Annotations, Attachment, Recipients, Delay, Expiration, AsShared());
}
}
/**
* Subscribes a message handler.
*
* @param MessageType The type name of the messages to subscribe to.
* @param ScopeRange The range of message scopes to include in the subscription.
*/
UE_DEPRECATED(5.1, "Types names are now represented by path names. Please use a version of this function that takes an FTopLevelAssetPath as MessageType.")
void Subscribe(const FName& MessageType, const FMessageScopeRange& ScopeRange)
{
Subscribe(UClass::TryConvertShortTypeNameToPathName<UStruct>(MessageType.ToString()), ScopeRange);
}
/**
* Subscribes a message handler.
*
* @param MessageType The type name of the messages to subscribe to.
* @param ScopeRange The range of message scopes to include in the subscription.
*/
void Subscribe(const FTopLevelAssetPath& MessageType, const FMessageScopeRange& ScopeRange)
{
TSharedPtr<IMessageBus, ESPMode::ThreadSafe> Bus = GetBusIfEnabled();
if (Bus.IsValid())
{
Bus->Subscribe(AsShared(), MessageType, ScopeRange);
}
}
/**
* Unsubscribes this endpoint from the specified message type.
*
* @param MessageType The type of message to unsubscribe (IMessageBus::PATHNAME_All = all types).
* @see Subscribe
*/
UE_DEPRECATED(5.1, "Types names are now represented by path names. Please use a version of this function that takes an FTopLevelAssetPath as MessageType.")
void Unsubscribe(const FName& MessageType)
{
Unsubscribe(UClass::TryConvertShortTypeNameToPathName<UStruct>(MessageType.ToString()));
}
/**
* Unsubscribes this endpoint from the specified message type.
*
* @param MessageType The type of message to unsubscribe (IMessageBus::PATHNAME_All = all types).
* @see Subscribe
*/
void Unsubscribe(const FTopLevelAssetPath& MessageType)
{
TSharedPtr<IMessageBus, ESPMode::ThreadSafe> Bus = GetBusIfEnabled();
if (Bus.IsValid())
{
Bus->Unsubscribe(AsShared(), MessageType);
}
}
public:
/**
* Disables the inbox for unhandled messages.
*
* The inbox is disabled by default.
*
* @see EnableInbox, IsInboxEmpty, IsInboxEnabled, ProcessInbox, ReceiveFromInbox
*/
void DisableInbox()
{
InboxEnabled = false;
}
/**
* Enables the inbox for unhandled messages.
*
* If enabled, the inbox will queue up all received messages. Use ProcessInbox() to synchronously
* invoke the registered message handlers for all queued up messages, or ReceiveFromInbox() to
* manually receive one message from the inbox at a time. The inbox is disabled by default.
*
* @see DisableInbox, IsInboxEmpty, IsInboxEnabled, ProcessInbox, ReceiveFromInbox
*/
void EnableInbox()
{
InboxEnabled = true;
}
/**
* Checks whether the inbox is empty.
*
* @return true if the inbox is empty, false otherwise.
* @see DisableInbox, EnableInbox, IsInboxEnabled, ProcessInbox, ReceiveFromInbox
*/
bool IsInboxEmpty() const
{
return Inbox.IsEmpty();
}
/**
* Checks whether the inbox is enabled.
*
* @see DisableInbox, EnableInbox, IsInboxEmpty, ProcessInbox, ReceiveFromInbox
*/
bool IsInboxEnabled() const
{
return InboxEnabled;
}
/**
* Calls the matching message handlers for all messages queued up in the inbox.
*
* Note that an incoming message will only be queued up in the endpoint's inbox if the inbox has
* been enabled and no matching message handler handled it. The inbox is disabled by default and
* must be enabled using the EnableInbox() method.
*
* @see IsInboxEmpty, ReceiveFromInbox
*/
void ProcessInbox()
{
TSharedPtr<IMessageContext, ESPMode::ThreadSafe> Context;
while (Inbox.Dequeue(Context))
{
ProcessMessage(Context.ToSharedRef());
}
}
/**
* Receives a single message from the endpoint's inbox.
*
* Note that an incoming message will only be queued up in the endpoint's inbox if the inbox has
* been enabled and no matching message handler handled it. The inbox is disabled by default and
* must be enabled using the EnableInbox() method.
*
* @param OutContext Will hold the context of the received message.
* @return true if a message was received, false if the inbox was empty.
* @see DisableInbox, EnableInbox, IsInboxEnabled, ProcessInbox
*/
bool ReceiveFromInbox(TSharedPtr<IMessageContext, ESPMode::ThreadSafe>& OutContext)
{
return Inbox.Dequeue(OutContext);
}
public:
//~ IMessageReceiver interface
virtual FName GetDebugName() const override
{
return Name;
}
virtual const FGuid& GetRecipientId() const override
{
return Id;
}
virtual ENamedThreads::Type GetRecipientThread() const override
{
return RecipientThread;
}
virtual bool IsLocal() const override
{
return true;
}
virtual void ReceiveMessage(const TSharedRef<IMessageContext, ESPMode::ThreadSafe>& Context) override
{
if (!Enabled)
{
return;
}
if (InboxEnabled)
{
Inbox.Enqueue(Context);
}
else
{
ProcessMessage(Context);
}
}
//~ IBusListener interface
virtual ENamedThreads::Type GetListenerThread() const override
{
return RecipientThread;
}
virtual void NotifyRegistration(const FMessageAddress& InAddress, EMessageBusNotification InNotification)
{
if (!Enabled)
{
return;
}
NotificationDelegate.ExecuteIfBound(FMessageBusNotification{ InNotification, InAddress });
}
public:
//~ IMessageSender interface
virtual FMessageAddress GetSenderAddress() override
{
return Address;
}
virtual void NotifyMessageError(const TSharedRef<IMessageContext, ESPMode::ThreadSafe>& Context, const FString& Error) override
{
ErrorDelegate.ExecuteIfBound(Context.Get(), Error);
}
public:
/**
* Creates a message of the specified template type.
*
* Prefer using this helper rather than explicit new. See FEngineServicePong.
*
* @param Args The constructor arguments to the template type
*/
template<typename T, typename... InArgTypes>
static T* MakeMessage(InArgTypes&&... Args)
{
void* Buffer = FMemory::Malloc(sizeof(T));
T* Message = new (Buffer) T(::Forward<InArgTypes>(Args)...);
return Message;
}
/**
* Immediately forwards a previously received message to the specified recipient.
*
* Messages can only be forwarded to endpoints within the same process.
*
* @param Context The context of the message to forward.
* @param Recipient The address of the recipient to forward the message to.
*/
void Forward(const TSharedRef<IMessageContext, ESPMode::ThreadSafe>& Context, const FMessageAddress& Recipient)
{
Forward(Context, TArrayBuilder<FMessageAddress>().Add(Recipient), FTimespan::Zero());
}
/**
* Forwards a previously received message to the specified recipient after a given delay.
*
* Messages can only be forwarded to endpoints within the same process.
*
* @param Context The context of the message to forward.
* @param Recipient The address of the recipient to forward the message to.
* @param ForwardingScope The scope of the forwarded message.
* @param Delay The delay after which to publish the message.
*/
void Forward(const TSharedRef<IMessageContext, ESPMode::ThreadSafe>& Context, const FMessageAddress& Recipient, const FTimespan& Delay)
{
Forward(Context, TArrayBuilder<FMessageAddress>().Add(Recipient), Delay);
}
/**
* Immediately forwards a previously received message to the specified list of recipients.
*
* Messages can only be forwarded to endpoints within the same process.
*
* @param Context The context of the message to forward.
* @param Recipients The list of message recipients to forward the message to.
* @param ForwardingScope The scope of the forwarded message.
*/
void Forward(const TSharedRef<IMessageContext, ESPMode::ThreadSafe>& Context, const TArray<FMessageAddress>& Recipients)
{
Forward(Context, Recipients, FTimespan::Zero());
}
/**
* Immediately publishes a message to all subscribed recipients.
*
* @param Message The message to publish.
*/
template<typename MessageType>
void Publish(MessageType* Message)
{
Publish(Message, MessageType::StaticStruct(), EMessageScope::Network, FTimespan::Zero(), FDateTime::MaxValue());
}
/**
* Immediately pa message to all subscribed recipients within the specified scope.
*
* @param Message The message to publish.
* @param Scope The message scope.
*/
template<typename MessageType>
void Publish(MessageType* Message, EMessageScope Scope)
{
Publish(Message, MessageType::StaticStruct(), Scope, FTimespan::Zero(), FDateTime::MaxValue());
}
/**
* Immediately publishes a message to all subscribed recipients.
*
* @param Message The message to publish.
* @param Annotations An optional message annotations header.
*/
template<typename MessageType>
void Publish(MessageType* Message, const TMap<FName, FString> Annotations)
{
Publish(Message, MessageType::StaticStruct(), Annotations, EMessageScope::Network, FTimespan::Zero(), FDateTime::MaxValue());
}
/**
* Immediately pa message to all subscribed recipients within the specified scope.
*
* @param Message The message to publish.
* @param Annotations An optional message annotations header.
* @param Scope The message scope.
*/
template<typename MessageType>
void Publish(MessageType* Message, const TMap<FName, FString> Annotations, EMessageScope Scope)
{
Publish(Message, MessageType::StaticStruct(), Annotations, Scope, FTimespan::Zero(), FDateTime::MaxValue());
}
/**
* Publishes a message to all subscribed recipients after a given delay.
*
* @param Message The message to publish.
* @param Delay The delay after which to publish the message.
*/
template<typename MessageType>
void Publish(MessageType* Message, const FTimespan& Delay)
{
Publish(Message, MessageType::StaticStruct(), EMessageScope::Network, Delay, FDateTime::MaxValue());
}
/**
* Publishes a message to all subscribed recipients within the specified scope after a given delay.
*
* @param Message The message to publish.
* @param Scope The message scope.
* @param Delay The delay after which to publish the message.
*/
template<typename MessageType>
void Publish(MessageType* Message, EMessageScope Scope, const FTimespan& Delay)
{
Publish(Message, MessageType::StaticStruct(), Scope, Delay, FDateTime::MaxValue());
}
/**
* Publishes a message to all subscribed recipients within the specified scope.
*
* @param Message The message to publish.
* @param Scope The message scope.
* @param Fields The message content.
* @param Delay The delay after which to publish the message.
* @param Expiration The time at which the message expires.
*/
template<typename MessageType>
void Publish(MessageType* Message, EMessageScope Scope, const FTimespan& Delay, const FDateTime& Expiration)
{
Publish(Message, MessageType::StaticStruct(), Scope, Delay, Expiration);
}
/**
* Publishes a message to all subscribed recipients within the specified scope.
*
* @param Message The message to publish.
* @param Annotations An optional message annotations header.
* @param Scope The message scope.
* @param Fields The message content.
* @param Delay The delay after which to publish the message.
* @param Expiration The time at which the message expires.
*/
template<typename MessageType>
void Publish(MessageType* Message, const TMap<FName, FString> Annotations, EMessageScope Scope, const FTimespan& Delay, const FDateTime& Expiration)
{
Publish(Message, MessageType::StaticStruct(), Annotations, Scope, Delay, Expiration);
}
/**
* Immediately sends a message to the specified recipient.
*
* @param MessageType The type of message to send.
* @param Message The message to send.
* @param Recipient The message recipient.
*/
template<typename MessageType>
void Send(MessageType* Message, const FMessageAddress& Recipient)
{
Send(Message, MessageType::StaticStruct(), EMessageFlags::None, nullptr, TArrayBuilder<FMessageAddress>().Add(Recipient), FTimespan::Zero(), FDateTime::MaxValue());
}
/**
* Immediately sends a message to the specified recipient.
*
* @param MessageType The type of message to send.
* @param Message The message to send.
* @param Annotations An optional message annotations header.
* @param Recipient The message recipient.
*/
template<typename MessageType>
void Send(MessageType* Message, const TMap<FName, FString> Annotations, const FMessageAddress& Recipient)
{
Send(Message, MessageType::StaticStruct(), EMessageFlags::None, Annotations, nullptr, TArrayBuilder<FMessageAddress>().Add(Recipient), FTimespan::Zero(), FDateTime::MaxValue());
}
/**
* Sends a message to the specified recipient after a given delay.
*
* @param MessageType The type of message to send.
* @param Message The message to send.
* @param Recipient The message recipient.
* @param Delay The delay after which to send the message.
*/
template<typename MessageType>
void Send(MessageType* Message, const FMessageAddress& Recipient, const FTimespan& Delay)
{
Send(Message, MessageType::StaticStruct(), EMessageFlags::None, nullptr, TArrayBuilder<FMessageAddress>().Add(Recipient), Delay, FDateTime::MaxValue());
}
/**
* Sends a message with fields and expiration to the specified recipient after a given delay.
*
* @param MessageType The type of message to send.
* @param Message The message to send.
* @param Recipient The message recipient.
* @param Expiration The time at which the message expires.
* @param Delay The delay after which to send the message.
*/
template<typename MessageType>
void Send(MessageType* Message, const FMessageAddress& Recipient, const FTimespan& Delay, const FDateTime& Expiration)
{
Send(Message, MessageType::StaticStruct(), EMessageFlags::None, nullptr, TArrayBuilder<FMessageAddress>().Add(Recipient), Delay, Expiration);
}
/**
* Sends a message with fields and attachment to the specified recipient.
*
* @param MessageType The type of message to send.
* @param Message The message to send.
* @param Attachment An optional binary data attachment.
* @param Recipient The message recipient.
*/
template<typename MessageType>
void Send(MessageType* Message, const TSharedPtr<IMessageAttachment, ESPMode::ThreadSafe>& Attachment, const FMessageAddress& Recipient)
{
Send(Message, MessageType::StaticStruct(), EMessageFlags::None, Attachment, TArrayBuilder<FMessageAddress>().Add(Recipient), FTimespan::Zero(), FDateTime::MaxValue());
}
/**
* Sends a message with fields, attachment and expiration to the specified recipient after a given delay.
*
* @param MessageType The type of message to send.
* @param Message The message to send.
* @param Attachment An optional binary data attachment.
* @param Recipient The message recipient.
* @param Expiration The time at which the message expires.
* @param Delay The delay after which to send the message.
*/
template<typename MessageType>
void Send(MessageType* Message, const TSharedPtr<IMessageAttachment, ESPMode::ThreadSafe>& Attachment, const FMessageAddress& Recipient, const FDateTime& Expiration, const FTimespan& Delay)
{
Send(Message, MessageType::StaticStruct(), EMessageFlags::None, Attachment, TArrayBuilder<FMessageAddress>().Add(Recipient), Delay, Expiration);
}
/**
* Sends a message with fields, attachment and expiration to the specified recipient after a given delay.
*
* @param MessageType The type of message to send.
* @param Message The message to send.
* @param Annotations An optional message annotations header.
* @param Attachment An optional binary data attachment.
* @param Recipient The message recipient.
* @param Expiration The time at which the message expires.
* @param Delay The delay after which to send the message.
*/
template<typename MessageType>
void Send(MessageType* Message, const TMap<FName, FString> Annotations, const TSharedPtr<IMessageAttachment, ESPMode::ThreadSafe>& Attachment, const FMessageAddress& Recipient, const FDateTime& Expiration, const FTimespan& Delay)
{
Send(Message, MessageType::StaticStruct(), EMessageFlags::None, Annotations, Attachment, TArrayBuilder<FMessageAddress>().Add(Recipient), Delay, Expiration);
}
/**
* Immediately sends a message to the specified list of recipients.
*
* @param MessageType The type of message to send.
* @param Message The message to send.
* @param Recipients The message recipients.
*/
template<typename MessageType>
void Send(MessageType* Message, const TArray<FMessageAddress>& Recipients)
{
Send(Message, MessageType::StaticStruct(), EMessageFlags::None, nullptr, Recipients, FTimespan::Zero(), FDateTime::MaxValue());
}
/**
* Sends a message to the specified list of recipients after a given delay after a given delay.
*
* @param MessageType The type of message to send.
* @param Message The message to send.
* @param Recipients The message recipients.
* @param Delay The delay after which to send the message.
*/
template<typename MessageType>
void Send(MessageType* Message, const TArray<FMessageAddress>& Recipients, const FTimespan& Delay)
{
Send(Message, MessageType::StaticStruct(), EMessageFlags::None, nullptr, Recipients, Delay, FDateTime::MaxValue());
}
/**
* Sends a message with fields and attachment to the specified list of recipients after a given delay.
*
* @param MessageType The type of message to send.
* @param Message The message to send.
* @param Attachment An optional binary data attachment.
* @param Recipients The message recipients.
* @param Delay The delay after which to send the message.
*/
template<typename MessageType>
void Send(MessageType* Message, const TSharedPtr<IMessageAttachment, ESPMode::ThreadSafe>& Attachment, const TArray<FMessageAddress>& Recipients, const FTimespan& Delay)
{
Send(Message, MessageType::StaticStruct(), EMessageFlags::None, Attachment, Recipients, Delay, FDateTime::MaxValue());
}
/**
* Sends a message to the specified list of recipients.
*
* @param MessageType The type of message to send.
* @param Message The message to send.
* @param Attachment An optional binary data attachment.
* @param Recipients The message recipients.
* @param Delay The delay after which to send the message.
* @param Expiration The time at which the message expires.
*/
template<typename MessageType>
void Send(MessageType* Message, const TSharedPtr<IMessageAttachment, ESPMode::ThreadSafe>& Attachment, const TArray<FMessageAddress>& Recipients, const FTimespan& Delay, const FDateTime& Expiration)
{
Send(Message, MessageType::StaticStruct(), EMessageFlags::None, Attachment, Recipients, Delay, Expiration);
}
/**
* Sends a message to the specified list of recipients.
* Allows to specify message flags
*
* @param Message The message to send.
* @param TypeInfo The message's type information.
* @param Flags The message's type information.
* @param Attachment An optional binary data attachment.
* @param Recipients The message recipients.
* @param Delay The delay after which to send the message.
* @param Expiration The time at which the message expires.
*/
template<typename MessageType>
void Send(MessageType* Message, EMessageFlags Flags, const TSharedPtr<IMessageAttachment, ESPMode::ThreadSafe>& Attachment, const TArray<FMessageAddress>& Recipients, const FTimespan& Delay, const FDateTime& Expiration)
{
Send(Message, MessageType::StaticStruct(), Flags, Attachment, Recipients, Delay, Expiration);
}
/**
* Sends a message to the specified list of recipients.
* Allows to specify message flags
*
* @param Message The message to send.
* @param TypeInfo The message's type information.
* @param Flags The message's type information.
* @param Annotations An optional message annotations header.
* @param Attachment An optional binary data attachment.
* @param Recipients The message recipients.
* @param Delay The delay after which to send the message.
* @param Expiration The time at which the message expires.
*/
template<typename MessageType>
void Send(MessageType* Message, EMessageFlags Flags, const TMap<FName, FString> Annotations, const TSharedPtr<IMessageAttachment, ESPMode::ThreadSafe>& Attachment, const TArray<FMessageAddress>& Recipients, const FTimespan& Delay, const FDateTime& Expiration)
{
Send(Message, MessageType::StaticStruct(), Flags, Annotations, Attachment, Recipients, Delay, Expiration);
}
/**
* Template method to subscribe the message endpoint to the specified type of messages with the default message scope.
*
* The default message scope is all messages excluding loopback messages.
*
* @param HandlerType The type of the class handling the message.
* @param MessageType The type of messages to subscribe to.
* @param Handler The class handling the messages.
* @param HandlerFunc The class function handling the messages.
*/
template<class MessageType>
void Subscribe()
{
Subscribe(MessageType::StaticStruct()->GetStructPathName(), FMessageScopeRange::AtLeast(EMessageScope::Thread));
}
/**
* Template method to subscribe the message endpoint to the specified type and scope of messages.
*
* @param HandlerType The type of the class handling the message.
* @param MessageType The type of messages to subscribe to.
* @param Handler The class handling the messages.
* @param HandlerFunc The class function handling the messages.
* @param ScopeRange The range of message scopes to include in the subscription.
*/
template<class MessageType>
void Subscribe(const FMessageScopeRange& ScopeRange)
{
Subscribe(MessageType::StaticStruct()->GetStructPathName(), ScopeRange);
}
/**
* Unsubscribes this endpoint from all message types.
*
* @see Subscribe
*/
void Unsubscribe()
{
Unsubscribe(IMessageBus::PATHNAME_All);
}
/**
* Template method to unsubscribe the endpoint from the specified message type.
*
* @param MessageType The type of message to unsubscribe (IMessageBus::PATHNAME_All = all types).
* @see Subscribe
*/
template<class MessageType>
void Unsubscribe()
{
Unsubscribe(MessageType::StaticStruct()->GetStructPathName());
}
public:
/**
* Safely releases a message endpoint that is receiving messages on AnyThread.
*
* When an object that owns a message endpoint receiving on AnyThread is being destroyed,
* it is possible that the endpoint can outlive the object for a brief period of time if
* the Messaging system is dispatching messages to it. This helper function
* is to block the calling thread while any messages are being dispatched, so that the
* endpoint does not invoke any message handlers after the object has been destroyed.
*
* @param Endpoint The message endpoint to release.
*/
static void SafeRelease(TSharedPtr<FMessageEndpoint, ESPMode::ThreadSafe>& Endpoint)
{
if (Endpoint.IsValid())
{
Endpoint->ClearHandlers();
Endpoint.Reset();
}
}
protected:
/**
* Clears all handlers in a way that guarantees it won't overlap with message processing. This preserves internal integrity
* of the array and cases where our owner may be shutting down while receiving messages.
*/
void ClearHandlers()
{
FScopeLock Lock(&HandlersCS);
Handlers.Empty();
}
/**
* Gets a shared pointer to the message bus if this endpoint is enabled.
*
* @return The message bus.
*/
FORCEINLINE TSharedPtr<IMessageBus, ESPMode::ThreadSafe> GetBusIfEnabled() const
{
if (Enabled)
{
return BusPtr.Pin();
}
return nullptr;
}
/**
* Forwards the given message context to matching message handlers.
*
* @param Context The context of the message to handle.
*/
void ProcessMessage(const TSharedRef<IMessageContext, ESPMode::ThreadSafe>& Context)
{
if (!Context->IsValid())
{
return;
}
FScopeLock Lock(&HandlersCS);
for (int32 HandlerIndex = 0; HandlerIndex < Handlers.Num(); ++HandlerIndex)
{
Handlers[HandlerIndex]->HandleMessage(Context);
}
}
private:
/** Holds the endpoint's identifier. */
const FMessageAddress Address;
/** Holds a weak pointer to the message bus. */
TWeakPtr<IMessageBus, ESPMode::ThreadSafe> BusPtr;
/** Hold a flag indicating whether this endpoint is active. */
bool Enabled;
/** Holds the registered message handlers. */
TArray<TSharedPtr<IMessageHandler, ESPMode::ThreadSafe>> Handlers;
/** Holds a delegate that is invoked on disconnection events. */
FOnBusNotification NotificationDelegate;
/** Holds the endpoint's unique identifier (for debugging purposes). */
const FGuid Id;
/** Holds the endpoint's message inbox. */
TQueue<TSharedPtr<IMessageContext, ESPMode::ThreadSafe>, EQueueMode::Mpsc> Inbox;
/** Holds a flag indicating whether the inbox is enabled. */
bool InboxEnabled;
/** Holds the endpoint's name (for debugging purposes). */
const FName Name;
/** Holds the name of the thread on which to receive messages. */
ENamedThreads::Type RecipientThread;
private:
/** Holds a delegate that is invoked in case of messaging errors. */
FOnMessageEndpointError ErrorDelegate;
/** Sigfnifies that the handler array is being accessed and other threads should wait or skip */
FCriticalSection HandlersCS;
};