// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "DSP/Dsp.h" #include "DSP/MultithreadedPatching.h" #include "MetasoundAudioBuffer.h" #include "MetasoundDataFactory.h" #include "MetasoundDataReference.h" #include "MetasoundLiteral.h" #include "MetasoundOperatorInterface.h" #include "MetasoundOperatorSettings.h" #include "Misc/Guid.h" #include "UObject/NameTypes.h" #include /** * METASOUND TRANSMISSION SYSTEM * This allows for transmission of arbitrary datatypes using addresses set by FNames. * This system is used by Send and Receive nodes in the Metasound graph, but can also be used in C++ for arbitrary message passing. * * Typical use case: * TSenderPtr FloatSender FDataTransmissionCenter::Get().RegisterNewSend(ExampleAddress, InitSettings); * TReceiverPtr FloatReceiver FDataTransmissionCenter::Get().RegisterNewReceiver(ExampleAddress, InitSettings); * * //... * FloatSender->Push(4.5f); * * //... elsewhere on a different thread: * float ReceivedFloat = FloatReceiver->Pop(); * * It's that easy! (hopefully!) * * In general, some performance considerations for this system: * 1) a circular buffer is allocated for each address a sender is set up for, and is sized to the max Delay of any connected sender. * 2) Senders and receivers are meant to have long lifecycles, and requesting them from the FDataTransmissionCenter may be expensive if done every tick. * 3) Most non-audio data types cannot be mixed, so multiple senders to the same address will cause only one of the senders to be effective. * 4) while the overall system is thread safe, individual TSender and TReceiver objects should only be used by a single thread. */ namespace Metasound { // Enum for which scope to use enum class ETransmissionScope : uint8 { ThisInstanceOnly, Global, Extension // This means that this metasound router talks to an implentation of IMetasoundTransmissionListener. }; /* TODO: Rename "Subsystem" to "Protocol". A "Protocol" defines how data is moved around * and can be used by multiple APIs. */ // This converts an ETransmissionScope into an FName that can be used in FSendAddress::Subsystem. // For ETransmissionScope::Extension, the FName of that extension should be used instead. FORCEINLINE FName GetSubsystemNameForSendScope(ETransmissionScope InScope) { switch (InScope) { case ETransmissionScope::ThisInstanceOnly: { static FName SubsystemName = TEXT("This Instance Only"); return SubsystemName; } case ETransmissionScope::Global: { static FName SubsystemName = TEXT("All Metasounds"); return SubsystemName; } default: { checkNoEntry(); return FName(); } } } struct METASOUNDFRONTEND_API FSendAddress { // The specific subsystem this send should be broadcast to. // This can be either local to this instance, or global to all metasounds, or a specific subsystem. FName Subsystem; // This is the name for the channel itself. FName ChannelName; // For instance-specific addresses, this FName is the instance of this metasound. uint64 MetasoundInstanceID = INDEX_NONE; friend uint32 GetTypeHash(const FSendAddress& Address) { uint32 HashedChannel = HashCombine(GetTypeHash(Address.Subsystem), GetTypeHash(Address.ChannelName)); uint32 HashedMetasoundInstance = HashCombine(HashedChannel, static_cast(Address.MetasoundInstanceID % TNumericLimits::Max())); return HashedMetasoundInstance; } FORCEINLINE bool operator==(const FSendAddress& Other) const { return ChannelName == Other.ChannelName; } FSendAddress(const FString& InAddress) : Subsystem(GetSubsystemNameForSendScope(ETransmissionScope::Global)) , ChannelName(*InAddress) { } FSendAddress() : Subsystem(GetSubsystemNameForSendScope(ETransmissionScope::Global)) , ChannelName(FName(TEXT("Default Channel"))) {} }; // Base class that defines shared state and utilities for senders, receivers, and the shared channel state. class METASOUNDFRONTEND_API IDataTransmissionBase { public: IDataTransmissionBase() = delete; IDataTransmissionBase(FName InDataType) : DataType(InDataType) { } virtual ~IDataTransmissionBase() = default; template bool CheckType() const { FName DestinationTypeName = TSenderImplementation::GetDataTypeName(); return ensureAlwaysMsgf(DataType == DestinationTypeName, TEXT("Tried to downcast type %s to %s!"), *DataType.ToString(), *DestinationTypeName.ToString()); } template const TSenderImplementation& GetAs() const { check(CheckType()); return static_cast(*this); } template TSenderImplementation& GetAs() { check(CheckType()); return static_cast(*this); } FName GetDataType() const { return DataType; } protected: // The data type for this sender. FName DataType; }; class IDataChannel; // This is a handle to something that will retrieve a datatype. // Intentionally opaque- to use, one will need to call IReceiver::GetAs>(). class METASOUNDFRONTEND_API IReceiver : public IDataTransmissionBase { public: IReceiver(TSharedPtr InDataChannel, FName DataType) : IDataTransmissionBase(DataType) , DataChannel(InDataChannel) {} virtual ~IReceiver(); protected: TSharedPtr DataChannel; }; // This is a handle to something that will retrieve a datatype. // Intentionally opaque- to use, one will need to call ISender::GetAs>(). class METASOUNDFRONTEND_API ISender : public IDataTransmissionBase { public: ISender(TSharedPtr InDataChannel, FName DataType) : IDataTransmissionBase(DataType) , DataChannel(InDataChannel) {} virtual ~ISender(); protected: TSharedPtr DataChannel; }; struct FSenderInitParams { FOperatorSettings OperatorSettings; float DelayTimeInSeconds; }; struct FReceiverInitParams { FOperatorSettings OperatorSettings; }; // This contains an intermediary buffer for use between the send and receive nodes. // Typically only used by ISender and IReceiver implementations. class METASOUNDFRONTEND_API IDataChannel : public IDataTransmissionBase, public TSharedFromThis { public: virtual ~IDataChannel() = default; IDataChannel(FName DataType) : IDataTransmissionBase(DataType) {} TUniquePtr NewReceiver(const FReceiverInitParams& InitParams) { NumAliveReceivers.Increment(); return ConstructNewReceiverImplementation(InitParams); } TUniquePtr NewSender(const FSenderInitParams& InitParams) { NumAliveSenders.Increment(); return ConstructNewSenderImplementation(InitParams); } uint32 GetNumActiveReceivers() const { return NumAliveReceivers.GetValue(); } uint32 GetNumActiveSenders() const { return NumAliveSenders.GetValue(); } virtual bool PushOpaque(ISender& InSender) { return true; }; virtual void PopOpaque(IReceiver& InReceiver) {}; virtual bool IsEmpty() const { return true; }; virtual FName GetDataType() { return FName(); }; virtual bool PushLiteral(const FLiteral& InParam) { return false; } protected: virtual TUniquePtr ConstructNewReceiverImplementation(const FReceiverInitParams& InitParams) { return nullptr; }; virtual TUniquePtr ConstructNewSenderImplementation(const FSenderInitParams& InitParams) { return nullptr; }; private: void OnSenderDestroyed() { check(NumAliveSenders.GetValue() > 0); NumAliveSenders.Decrement(); } void OnReceiverDestroyed() { check(NumAliveReceivers.GetValue() > 0); NumAliveReceivers.Decrement(); } friend class ISender; friend class IReceiver; FThreadSafeCounter NumAliveReceivers; FThreadSafeCounter NumAliveSenders; }; // Generic templates: template class TNonOperationalSender : public ISender { public: bool Push(const TDataType& InValue) { return true; } }; template class TNonOperationalReceiver : public IReceiver { public: bool Pop(TDataType& OutValue) { return true; } }; template class TNonOperationalDataChannel : public IDataChannel { public: TNonOperationalDataChannel(const FOperatorSettings& InOperatorSettings) : IDataChannel(FName()) {} virtual bool PushOpaque(ISender& InSender) override { return true; } virtual void PopOpaque(IReceiver& InReceiver) override { } virtual bool IsEmpty() const override { return true; } virtual FName GetDataType() override { return GetDataTypeName(); } static FName GetDataTypeName() { static FName TypeName = TDataReferenceTypeInfo::TypeName; return TypeName; } protected: virtual TUniquePtr ConstructNewReceiverImplementation(const FReceiverInitParams& InitParams) override { return TUniquePtr(); } virtual TUniquePtr ConstructNewSenderImplementation(const FSenderInitParams& InitParams) override { return TUniquePtr(); } }; // Specializations for trivially copyable types: template class TSender : public ISender { static_assert(std::is_copy_constructible::value, "The generic TDataChannel requires the DataType it is specialized with to be copy assignable."); // This can be changed to change when we reallocate the internal buffer for this sender. static constexpr float BufferResizeThreshold = 2.0f; public: bool Push(const TDataType& InElement) { SenderBuffer.Push(InElement); if (SenderBuffer.Num() >= NumElementsToDelayBy) { return PushToDataChannel(); } else { return true; } } // Resets the delay for this specific sender. if this goes beyond the threshold set by BufferResizeThreshold, we reallocate. bool SetDelay(float InSeconds) { Params.DelayTimeInSeconds = InSeconds; NumElementsToDelayBy = FMath::Min(InSeconds * Params.OperatorSettings.GetActualBlockRate(), 1.f); if (NumElementsToDelayBy >= SenderBuffer.GetCapacity()) { SenderBuffer.SetCapacity(NumElementsToDelayBy * BufferResizeThreshold); } return true; } static FName GetDataTypeName() { static FName TypeName = TDataReferenceTypeInfo::TypeName; return TypeName; } TSender(const FSenderInitParams& InitParams, TSharedPtr InDataChannel) : ISender(InDataChannel, GetDataTypeName()) , Params(InitParams) { SetDelay(InitParams.DelayTimeInSeconds); } TDataType RetrievePayload() { check(Payload.IsValid()); return MoveTempIfPossible(*Payload); } // Push from the SenderBuffer to the data channel. bool PushToDataChannel() { if (!Payload.IsValid()) { Payload.Reset(new TDataType(SenderBuffer.Pop())); } else { *Payload = SenderBuffer.Pop(); } return DataChannel->PushOpaque(*this); } private: // This buffer acts as a delay line for this sender specifically. Audio::TCircularAudioBuffer SenderBuffer; // lazily initialized holder for payload, allocated on first call to Push. // Placed on the heap so that we don't have to assume we know how to construct TDataType. TUniquePtr Payload; FSenderInitParams Params; // The amount of pop operations before elements before this sender pushed appear. uint32 NumElementsToDelayBy; }; template class TReceiver : public IReceiver { static_assert(std::is_copy_constructible::value, "The generic TDataChannel requires the DataType it is specialized with to be copy constructible."); public: bool CanPop() const { return !DataChannel->IsEmpty(); } // Pop the latest value from the data channel. bool Pop(TDataType& OutValue) { DataChannel->PopOpaque(*this); check(Payload.IsValid()); OutValue = *Payload; return true; } static FName GetDataTypeName() { static FName TypeName = TDataReferenceTypeInfo::TypeName; return TypeName; } TReceiver(const FReceiverInitParams& InitParams, TSharedPtr InDataChannel) : IReceiver(InDataChannel, GetDataTypeName()) , OperatorSettings(InitParams.OperatorSettings) { } void PushPayload(const TDataType& InDataPayload) { if (!Payload.IsValid()) { Payload.Reset(new TDataType(InDataPayload)); } else { *Payload = InDataPayload; } } private: TUniquePtr Payload; FOperatorSettings OperatorSettings; }; // Generic templated implementation of IDataChannel that can use for any copyable type. template class TCopyableDataChannel : public IDataChannel { static_assert(std::is_copy_constructible::value, "The generic TDataChannel requires the DataType it is specialized with to be copy constructible."); public: TCopyableDataChannel(const FOperatorSettings& InOperatorSettings) : IDataChannel(GetDataTypeName()) , OperatorSettings(InOperatorSettings) {} virtual bool PushOpaque(ISender& InSender) override { TSender& CastSender = InSender.GetAs>(); FScopeLock ScopeLock(&AtomicDataLock); if (!AtomicData.IsValid()) { AtomicData.Reset(new TDataType(CastSender.RetrievePayload())); } else { *AtomicData = CastSender.RetrievePayload(); } return true; } virtual void PopOpaque(IReceiver& InReceiver) override { TReceiver& CastReceiver = InReceiver.GetAs>(); FScopeLock ScopeLock(&AtomicDataLock); if (AtomicData.IsValid()) { CastReceiver.PushPayload(*AtomicData); } } virtual bool IsEmpty() const override { return !AtomicData.IsValid(); } static FName GetDataTypeName() { static FName TypeName = TDataReferenceTypeInfo::TypeName; return TypeName; } virtual FName GetDataType() override { return GetDataTypeName(); } virtual bool PushLiteral(const FLiteral& InParam) { if (TLiteralTraits::IsParsable(InParam)) { TDataType DataToPush = TDataTypeLiteralFactory::CreateExplicitArgs(OperatorSettings, InParam); FScopeLock ScopeLock(&AtomicDataLock); if (!AtomicData.IsValid()) { AtomicData.Reset(new TDataType(DataToPush)); } else { *AtomicData = DataToPush; } return true; } else { return false; } } protected: virtual TUniquePtr ConstructNewReceiverImplementation(const FReceiverInitParams& InitParams) override { return TUniquePtr(new TReceiver(InitParams, this->AsShared())); } virtual TUniquePtr ConstructNewSenderImplementation(const FSenderInitParams& InitParams) override { return TUniquePtr(new TSender(InitParams, this->AsShared())); } private: FThreadSafeCounter ReceiverCounter; // NOTE- In the future, this could be changed to use a TCircualrAudioBuffer or a TQueue for lockless operation. // The primary challenge with this is handling multiple senders and receivers. // In order to support multiple senders and receivers at scale, we'd need a good implementation of a bounded MPMC queue for arbitrary datatypes. TUniquePtr AtomicData; FCriticalSection AtomicDataLock; FOperatorSettings OperatorSettings; }; // AUDIO SPECIALIZATIONS: template class TAudioSender : public ISender { static constexpr float BufferResizeThreshold = 2.0f; static const int32 MaxChannels = 8; public: bool Push(const TDataType& InElement) { const int32 NumChannelsToPush = InElement.GetNumChannels(); check(NumChannelsToPush <= MaxChannels); const TArrayView InputBuffers = InElement.GetBuffers(); for (int32 ChannelIndex = 0; ChannelIndex < NumChannelsToPush; ChannelIndex++) { const FAudioBuffer& ChannelBuffer = *InputBuffers[ChannelIndex]; DelayLines[ChannelIndex].Push(ChannelBuffer.GetData(), ChannelBuffer.Num()); if (DelayLines[ChannelIndex].Num() > DelayTimeInFrames) { Audio::AlignedFloatBuffer& IntermediateBuffer = IntermediaryBuffers[ChannelIndex]; DelayLines[ChannelIndex].Pop(IntermediateBuffer.GetData(), IntermediateBuffer.Num()); DataChannelInputs[ChannelIndex].PushAudio(IntermediateBuffer.GetData(), IntermediateBuffer.Num()); } } return true; } // Resets the delay for this sender. bool SetDelay(float InSeconds) { Params.DelayTimeInSeconds = InSeconds; DelayTimeInFrames = FMath::Min(Params.DelayTimeInSeconds * Params.OperatorSettings.GetSampleRate(), Params.OperatorSettings.GetNumFramesPerBlock()); if (DelayTimeInFrames >= DelayLines[0].Num()) { int32 NumSamplesToBuffer = DelayTimeInFrames * BufferResizeThreshold; DelayLines.Reset(); IntermediaryBuffers.Reset(); // For now, all senders/receivers/data channels will just allocate 8 delay lines, but this can be changed in the future. for (int32 Index = 0; Index < MaxChannels; Index++) { DelayLines.Emplace(NumSamplesToBuffer); Audio::AlignedFloatBuffer BufferForChannel; BufferForChannel.AddZeroed(Params.OperatorSettings.GetNumFramesPerBlock()); IntermediaryBuffers.Add(MoveTemp(BufferForChannel)); } } return true; } static FName GetDataTypeName() { static FName TypeName = TDataReferenceTypeInfo::TypeName; return TypeName; } TAudioSender(const FSenderInitParams& InitParams, TSharedPtr InDataChannel, TArray&& Inputs) : ISender(InDataChannel, GetDataTypeName()) , DataChannelInputs(MoveTemp(Inputs)) , Params(InitParams) { SetDelay(InitParams.DelayTimeInSeconds); } private: // This buffer acts as a delay line for this sender specifically. TArray> DelayLines; uint32 DelayTimeInFrames; TArray DataChannelInputs; TArray IntermediaryBuffers; FSenderInitParams Params; }; template class TAudioReceiver : public IReceiver { static constexpr float BufferResizeThreshold = 2.0f; static const int32 MaxChannels = 8; public: bool CanPop() const { // Audio receivers can and should always pop audio, // because we zero out audio when a datachannel underruns. return true; } bool Pop(TDataType& OutElement) { const int32 NumChannelsToPop = OutElement.GetNumChannels(); check(NumChannelsToPop <= MaxChannels); DataChannel->PopOpaque(*this); TArrayView OutputBuffers = OutElement.GetBuffers(); for (int32 ChannelIndex = 0; ChannelIndex < NumChannelsToPop; ChannelIndex++) { FAudioBuffer& OutBuffer = *OutputBuffers[ChannelIndex]; DataChannelOutputs[ChannelIndex]->PopAudio(OutBuffer.GetData(), OutBuffer.Num(), false); } return true; } static FName GetDataTypeName() { static FName TypeName = TDataReferenceTypeInfo::TypeName; return TypeName; } TAudioReceiver(const FReceiverInitParams& InitParams, TSharedPtr InDataChannel, TArray&& Outputs) : IReceiver(InDataChannel, GetDataTypeName()) , DataChannelOutputs(MoveTemp(Outputs)) , Params(InitParams) { } private: TArray DataChannelOutputs; FReceiverInitParams Params; }; template class TAudioDataChannel : public IDataChannel { private: static const int32 MaxChannels = 8; public: TAudioDataChannel(const FOperatorSettings& InOperatorSettings) : IDataChannel(GetDataTypeName()) { AudioBuses.AddDefaulted(MaxChannels); } virtual bool PushOpaque(ISender& InSender) override { return true; } virtual void PopOpaque(IReceiver& InReceiver) override { for (Audio::FPatchMixerSplitter& Bus : AudioBuses) { Bus.ProcessAudio(); } } virtual bool IsEmpty() const override { return false; } static FName GetDataTypeName() { static FName TypeName = TDataReferenceTypeInfo::TypeName; return TypeName; } virtual FName GetDataType() override { return GetDataTypeName(); } protected: virtual TUniquePtr ConstructNewReceiverImplementation(const FReceiverInitParams& InitParams) override { TArray Outputs; for (Audio::FPatchMixerSplitter& Bus : AudioBuses) { Outputs.Add(Bus.AddNewOutput(8096, 1.0f)); } return TUniquePtr(new TAudioReceiver(InitParams, this->AsShared(), MoveTemp(Outputs))); } virtual TUniquePtr ConstructNewSenderImplementation(const FSenderInitParams& InitParams) override { TArray Inputs; for (Audio::FPatchMixerSplitter& Bus : AudioBuses) { Inputs.Add(Bus.AddNewInput(8096, 1.0f)); } return TUniquePtr(new TAudioSender(InitParams, this->AsShared(), MoveTemp(Inputs))); } private: TArray AudioBuses; }; // Coalesce both our standard copying Sender/Receivers and our audio-specific sender/receiver implementations into TSenderPtr and TReceiverPtr: template::Value, TAudioSender, TSender>::type> using TSenderPtr = TUniquePtr; template::Value, TAudioReceiver, TReceiver>::type> using TReceiverPtr = TUniquePtr; // SFINAE for creating the correct data channel type for the given datatype: template::value && !TIsDerivedFrom::Value, bool>::Type = true> TSharedRef MakeDataChannel(const FOperatorSettings& InSettings) { return MakeShareable(new TCopyableDataChannel(InSettings)); } template::Value, bool>::Type = true> TSharedRef MakeDataChannel(const FOperatorSettings& InSettings) { return MakeShareable(new TAudioDataChannel(InSettings)); } template::value && !TIsDerivedFrom::Value, bool>::Type = true> TSharedRef MakeDataChannel(const FOperatorSettings& InSettings) { return MakeShareable(new TNonOperationalDataChannel(InSettings)); } // Utility function for properly downcasting to the correct type based on whether TDataType is an audio stream: // For Audio Senders: template ::Value, bool>::Type = true> TSenderPtr Downcast(TUniquePtr&& InPtr) { checkf(InPtr->CheckType>(), TEXT("Tried to downcast an ISender of type %s to a TSender of type %s!"), *(InPtr->GetDataType().ToString()), *(TAudioSender::GetDataTypeName().ToString())); return TSenderPtr(static_cast*>(InPtr.Release())); } // For generic senders for copyable types: template ::value && !TIsDerivedFrom::Value, bool>::Type = true> TSenderPtr Downcast(TUniquePtr&& InPtr) { checkf(InPtr->CheckType>(), TEXT("Tried to downcast an ISender of type %s to a TSender of type %s!"), *(InPtr->GetDataType().ToString()), *(TSender::GetDataTypeName().ToString())); return TSenderPtr(static_cast*>(InPtr.Release())); } // For invalid types (non-copyable AND not an audio type): template ::value && !TIsDerivedFrom::Value, bool>::Type = true> TSenderPtr Downcast(TUniquePtr&& InPtr) { return nullptr; } // For audio receivers: template ::Value, bool>::Type = true> TReceiverPtr Downcast(TUniquePtr&& InPtr) { checkf(InPtr->CheckType>(), TEXT("Tried to downcast an IReceiver of type %s to a TSender of type %s!"), *(InPtr->GetDataType().ToString()), *(TAudioReceiver::GetDataTypeName().ToString())); return TReceiverPtr(static_cast*>(InPtr.Release())); } // For generic receivers for copyable types: template ::value && !TIsDerivedFrom::Value, bool>::Type = true> TReceiverPtr Downcast(TUniquePtr&& InPtr) { checkf(InPtr->CheckType>(), TEXT("Tried to downcast an IReceiver of type %s to a TSender of type %s!"), *(InPtr->GetDataType().ToString()), *(TReceiver::GetDataTypeName().ToString())); return TReceiverPtr(static_cast*>(InPtr.Release())); } // For invalid types (non-copyable AND not an audio type): template ::value && !TIsDerivedFrom::Value, bool>::Type = true> TReceiverPtr Downcast(TUniquePtr&& InPtr) { return nullptr; } // Basic router that takes an FName address, class METASOUNDFRONTEND_API FAddressRouter { public: TArray GetAvailableChannels(); FName GetDatatypeForChannel(FName InChannelName); template TSenderPtr RegisterNewSend(FName InChannelName, const FSenderInitParams& InitParams) { FScopeLock ScopeLock(&DataChannelMapMutationLock); if (TSharedRef* ExistingChannelPtr = DataChannelMap.Find(InChannelName)) { TSharedRef& ExistingChannel = *ExistingChannelPtr; // ensure that the existing data channel is of the same datatype as the send we're trying to register. return Downcast(ExistingChannel->NewSender(InitParams)); } else { // This is the first time we're seeing this, add it to the map. TSharedRef& ExistingChannel = DataChannelMap.Add(InChannelName, MakeDataChannel(InitParams.OperatorSettings)); return Downcast(ExistingChannel->NewSender(InitParams)); } } template TReceiverPtr RegisterNewReceiver(FName InChannelName, const FReceiverInitParams& InitParams) { FScopeLock ScopeLock(&DataChannelMapMutationLock); if (TSharedRef* ExistingChannelPtr = DataChannelMap.Find(InChannelName)) { TSharedRef& ExistingChannel = *ExistingChannelPtr; TUniquePtr BaseReceiver = ExistingChannel->NewReceiver(InitParams); return Downcast(MoveTemp(BaseReceiver)); } else { // This is the first time we're seeing this, add it to the map. TSharedRef& ExistingChannel = DataChannelMap.Add(InChannelName, MakeDataChannel(InitParams.OperatorSettings)); return Downcast(ExistingChannel->NewReceiver(InitParams)); } } bool PushLiteral(FName InChannelName, const FLiteral& InParam) { FScopeLock ScopeLock(&DataChannelMapMutationLock); if (TSharedRef* ExistingChannelPtr = DataChannelMap.Find(InChannelName)) { TSharedRef& ExistingChannel = *ExistingChannelPtr; return ExistingChannel->PushLiteral(InParam); } else { return false; } } FAddressRouter(const FAddressRouter& Other) : DataChannelMap(Other.DataChannelMap) { } FAddressRouter() {} private: TMap> DataChannelMap; FCriticalSection DataChannelMapMutationLock; }; // this class is simply a container of a map of instances to FAddressRouters. class FInstanceLocalRouter { public: TArray GetAvailableChannels(uint64 InInstanceID); FName GetDatatypeForChannel(uint64 InInstanceID, FName InChannelName); template TSenderPtr RegisterNewSend(uint64 InInstanceID, FName InChannelName, const FSenderInitParams& InitParams) { FScopeLock ScopeLock(&InstanceRouterMapMutationLock); if (FAddressRouter* AddressRouter = InstanceRouterMap.Find(InInstanceID)) { return AddressRouter->RegisterNewSend(InChannelName, InitParams); } else { // This is the first time we're seeing this, add it to the map. FAddressRouter& NewRouter = InstanceRouterMap.Add(InInstanceID, FAddressRouter()); return NewRouter.RegisterNewSend(InChannelName, InitParams); } } template TReceiverPtr RegisterNewReceiver(uint64 InInstanceID, FName InChannelName, const FReceiverInitParams& InitParams) { FScopeLock ScopeLock(&InstanceRouterMapMutationLock); if (FAddressRouter* AddressRouter = InstanceRouterMap.Find(InInstanceID)) { return AddressRouter->RegisterNewReceiver(InChannelName, InitParams); } else { // This is the first time we're seeing this, add it to the map. FAddressRouter& NewRouter = InstanceRouterMap.Add(InInstanceID, FAddressRouter()); return NewRouter.RegisterNewReceiver(InChannelName, InitParams); } } private: TMap InstanceRouterMap; FCriticalSection InstanceRouterMapMutationLock; }; // Interface for any subsystem that wants to register as an independently addressable subsystem. // The name provided to the ITransmissionSubsystem constructor will appear in the drop down menu for metasound send and receive nodes, // and your implementation of ITransmissionSubsystem will be notified whenever a new send or receive is created for it's subsystem. class METASOUNDFRONTEND_API ITransmissionSubsystem { public: virtual ~ITransmissionSubsystem(); protected: ITransmissionSubsystem() = delete; // Invoked by the implementing class. Automatically registers this subsystem with the FDataTransmissionCenter singleton. ITransmissionSubsystem(FName InSubsystemName); virtual void OnNewSendRegistered(const FSendAddress& SendAddress, FName DataType) {}; virtual void OnNewReceiverRegistered(const FSendAddress& SendAddress, FName DataType) {}; virtual bool CanSupportDataType(FName DataType) { return true; } private: FName SubsystemName; friend class FDataTransmissionCenter; }; // Main entry point for all sender/receiver registration. class METASOUNDFRONTEND_API FDataTransmissionCenter { struct FSubsystemData { ITransmissionSubsystem* SubsystemPtr; FAddressRouter AddressRouter; FSubsystemData(ITransmissionSubsystem* InSystemPtr) : SubsystemPtr(InSystemPtr) {} }; public: // Returns the universal router. static FDataTransmissionCenter& Get(); template bool DoesSubsytemSupportDatatype(FName InSubsystem) { if (InSubsystem == GetSubsystemNameForSendScope(ETransmissionScope::ThisInstanceOnly) || InSubsystem == GetSubsystemNameForSendScope(ETransmissionScope::Global)) { return true; } else if (FSubsystemData* FoundSubsystem = SubsystemRouters.Find(InSubsystem)) { ITransmissionSubsystem* SubsystemInterface = FoundSubsystem->SubsystemPtr; check(SubsystemInterface); // Ensure that this subsystem can support this send type. return SubsystemInterface->CanSupportDataType(FName(TDataReferenceTypeInfo::TypeName)); } else { // Otherwise, the subsystem FName was invalid. ensureAlways(false); return false; } } // Creates a new object to push data to an address. // Returns a new sender, or nullptr if registration failed. template TSenderPtr RegisterNewSend(const FSendAddress& InAddress, const FSenderInitParams& InitParams) { if (InAddress.Subsystem == GetSubsystemNameForSendScope(ETransmissionScope::ThisInstanceOnly)) { return InstanceRouter.RegisterNewSend(InAddress.MetasoundInstanceID, InAddress.ChannelName, InitParams); } else if (InAddress.Subsystem == GetSubsystemNameForSendScope(ETransmissionScope::Global)) { return GlobalRouter.RegisterNewSend(InAddress.ChannelName, InitParams); } else if (FSubsystemData* FoundSubsystem = SubsystemRouters.Find(InAddress.Subsystem)) { ITransmissionSubsystem* SubsystemInterface = FoundSubsystem->SubsystemPtr; // Ensure that this subsystem can support this send type. if (!ensureAlways(SubsystemInterface && SubsystemInterface->CanSupportDataType(FName(TDataReferenceTypeInfo::TypeName)))) { return TSenderPtr(); } FScopeLock ScopeLock(&SubsystemRoutersMutationLock); TSenderPtr Sender = FoundSubsystem->AddressRouter.RegisterNewSend(InAddress.ChannelName, InitParams); FoundSubsystem->SubsystemPtr->OnNewSendRegistered(InAddress, FName(TDataReferenceTypeInfo::TypeName)); return MoveTemp(Sender); } else { // Otherwise, the subsystem FName was invalid. ensureAlways(false); return TSenderPtr(); } } // Registers a new object to poll data from an address. // Returns a new receiver, or nullptr if registration failed. template TReceiverPtr RegisterNewReceiver(const FSendAddress& InAddress, const FReceiverInitParams& InitParams) { if (InAddress.Subsystem == GetSubsystemNameForSendScope(ETransmissionScope::ThisInstanceOnly)) { return InstanceRouter.RegisterNewReceiver(InAddress.MetasoundInstanceID, InAddress.ChannelName, InitParams); } else if (InAddress.Subsystem == GetSubsystemNameForSendScope(ETransmissionScope::Global)) { return GlobalRouter.RegisterNewReceiver(InAddress.ChannelName, InitParams); } else if (FSubsystemData* FoundSubsystem = SubsystemRouters.Find(InAddress.Subsystem)) { ITransmissionSubsystem* SubsystemInterface = FoundSubsystem->SubsystemPtr; // Ensure that this subsystem can support this send type. if (!ensureAlways(SubsystemInterface && SubsystemInterface->CanSupportDataType(FName(TDataReferenceTypeInfo::TypeName)))) { return TReceiverPtr(); } FScopeLock ScopeLock(&SubsystemRoutersMutationLock); TReceiverPtr Receiver = FoundSubsystem->AddressRouter.RegisterNewReceiver(InAddress.ChannelName, InitParams); FoundSubsystem->SubsystemPtr->OnNewReceiverRegistered(InAddress, FName(TDataReferenceTypeInfo::TypeName)); return MoveTemp(Receiver); } else { // Otherwise, the subsystem FName was invalid. ensureAlways(false); return TReceiverPtr(); } } // Pushes a literal parameter to a specific data channel in the global router. // returns false if the literal type isn't supported. bool PushLiteral(FName GlobalChannelName, const FLiteral& InParam) { return GlobalRouter.PushLiteral(GlobalChannelName, InParam); } private: // Single map of FNames to IDataChannels FAddressRouter GlobalRouter; // Map of instance IDs to FAddressRouter instances: FInstanceLocalRouter InstanceRouter; // Map of Subsystem Names to FAddressRouter instances. TMap SubsystemRouters; FCriticalSection SubsystemRoutersMutationLock; // These are used by the constructor and destructor of implementations of ITransmissionSubsystem. void RegisterSubsystem(ITransmissionSubsystem* InSystem, FName InSubsytemName) { FScopeLock ScopeLock(&SubsystemRoutersMutationLock); //check to make sure we're not adding a subsystem twice. check(!SubsystemRouters.Contains(InSubsytemName)); SubsystemRouters.Emplace(InSubsytemName, InSystem); } void UnregisterSubsystem(FName InSubsystemName) { FScopeLock ScopeLock(&SubsystemRoutersMutationLock); //check to make sure we're not adding a subsystem twice. check(SubsystemRouters.Contains(InSubsystemName)); SubsystemRouters.Remove(InSubsystemName); } FDataTransmissionCenter(); friend class ITransmissionSubsystem; }; DECLARE_METASOUND_DATA_REFERENCE_TYPES(FSendAddress, METASOUNDFRONTEND_API, FSendAddressTypeInfo, FSendAddressReadRef, FSendAddressWriteRef) }