// Copyright Epic Games, Inc. All Rights Reserved. #include "LiveLinkHubMessagingModule.h" #include "CoreMinimal.h" #include "IMessageInterceptor.h" #include "LiveLinkHubConnectionManager.h" #include "LiveLinkHubControlChannel.h" #include "LiveLinkHubMessageBusSourceFactory.h" #include "LiveLinkMessageBusFinder.h" #include "LiveLinkMessageBusSourceFactory.h" #include "Logging/StructuredLog.h" #include "MessageEndpoint.h" #include "MessageEndpointBuilder.h" #include "Misc/CoreDelegates.h" #include "Misc/ConfigCacheIni.h" #include "Misc/EngineVersion.h" #include "Misc/ScopeLock.h" #include "Modules/ModuleManager.h" #if WITH_EDITOR #include "ISettingsModule.h" #endif DEFINE_LOG_CATEGORY(LogLiveLinkHubMessaging); #define LOCTEXT_NAMESPACE "LiveLinkHubMessaging" namespace Internal { // Source: https://en.cppreference.com/w/cpp/utility/variant/visit template struct TOverloaded : Ts... { using Ts::operator()...; }; template TOverloaded(Ts...) -> TOverloaded; } void FLiveLinkHubMessagingModule::StartupModule() { const bool bIsLiveLinkHubHost = GConfig->GetBoolOrDefault( TEXT("LiveLink"), TEXT("bCreateLiveLinkHubInstance"), false, GEngineIni); InstanceInfo.TopologyMode = bIsLiveLinkHubHost ? ELiveLinkTopologyMode::Hub : ELiveLinkTopologyMode::UnrealClient; #if WITH_LIVELINK_DISCOVERY_MANAGER_THREAD ConnectionManager = MakePimpl(InstanceInfo.TopologyMode, FLiveLinkHubConnectionManager::FOnGetTopologyMode::CreateRaw(this, &FLiveLinkHubMessagingModule::GetHostTopologyMode), FLiveLinkHubConnectionManager::FOnGetInstanceId::CreateRaw(this, &FLiveLinkHubMessagingModule::GetInstanceId), FLiveLinkHubConnectionManager::FOnDiscoveryRequest::CreateRaw(this, &FLiveLinkHubMessagingModule::OnDiscoveryRequest) ); ControlChannel = MakeShared(FLiveLinkHubControlChannel::EChannelMode::Global); ControlChannel->OnAuxRequest.BindRaw(this, &FLiveLinkHubMessagingModule::OnAuxRequest); FCoreDelegates::OnPostEngineInit.AddLambda([this]() { ControlChannel->Initialize(); }); #endif RegisterSettings(); SourceFilterDelegate = ILiveLinkModule::Get().RegisterMessageBusSourceFilter(FOnLiveLinkShouldDisplaySource::CreateRaw(this, &FLiveLinkHubMessagingModule::OnFilterMessageBusSource)); } void FLiveLinkHubMessagingModule::ShutdownModule() { if (ILiveLinkModule* LiveLinkModule = FModuleManager::Get().GetModulePtr("LiveLink")) { LiveLinkModule->UnregisterMessageBusSourceFilter(SourceFilterDelegate); } UnregisterSettings(); #if WITH_LIVELINK_DISCOVERY_MANAGER_THREAD ControlChannel->OnAuxRequest.Unbind(); ControlChannel.Reset(); ConnectionManager.Reset(); #endif } void FLiveLinkHubMessagingModule::SetHostTopologyMode(ELiveLinkTopologyMode InMode) { FScopeLock Lock(&InstanceInfoLock); InstanceInfo.TopologyMode = InMode; } FLiveLinkHubInstanceId FLiveLinkHubMessagingModule::GetInstanceId() const { FScopeLock Lock(&InstanceInfoLock); return InstanceInfo.Id; } void FLiveLinkHubMessagingModule::OnDiscoveryRequest(const FMessageAddress& RemoteAddress) const { // Just tell the LLH instance we exist so their address book has an entry for the Control Endpoint. // This could probably be improved if there was some mechanism for it in MessageBus itself. if (ControlChannel) { ControlChannel->SendBeacon(RemoteAddress); } } void FLiveLinkHubMessagingModule::SetInstanceId(const FLiveLinkHubInstanceId& Id) { FScopeLock Lock(&InstanceInfoLock); InstanceInfo.Id = Id; } ELiveLinkTopologyMode FLiveLinkHubMessagingModule::GetHostTopologyMode() const { FScopeLock Lock(&InstanceInfoLock); return InstanceInfo.TopologyMode; } bool FLiveLinkHubMessagingModule::RegisterAuxChannelRequestHandler(UScriptStruct* InRequestTypeStruct, FAuxChannelRequestHandlerFunc&& InHandlerFunc) { if (!ensure(InRequestTypeStruct)) { UE_LOGFMT(LogLiveLinkHubMessaging, Error, "Attempted to register handler for null request type"); return false; } if (!ensure(InRequestTypeStruct != FLiveLinkHubAuxChannelRequestMessage::StaticStruct())) { UE_LOGFMT(LogLiveLinkHubMessaging, Error, "Attempted to register handler for base request type"); return false; } if (!ensure(!AuxChannelRequestHandlers.Find(InRequestTypeStruct))) { UE_LOGFMT(LogLiveLinkHubMessaging, Error, "Handler already exists for request type {RequestTypeName}", InRequestTypeStruct->GetName()); return false; } AuxChannelRequestHandlers.Add(InRequestTypeStruct, MoveTemp(InHandlerFunc)); return true; } bool FLiveLinkHubMessagingModule::UnregisterAuxChannelRequestHandler(UScriptStruct* InRequestTypeStruct) { if (!ensure(InRequestTypeStruct)) { UE_LOGFMT(LogLiveLinkHubMessaging, Error, "Attempted to unregister null request type"); return false; } return AuxChannelRequestHandlers.Remove(InRequestTypeStruct) != 0; } bool FLiveLinkHubMessagingModule::OnFilterMessageBusSource(UClass* FactoryClass, TSharedPtr PollResult) { // Only display Hub/Spoke sources in "LiveLinkHub" section of the add source dropdown. bool bValidTopologyMode = false; if (FactoryClass == ULiveLinkHubMessageBusSourceFactory::StaticClass()) { bValidTopologyMode = LiveLinkHubConnectionManager::GetPollResultTopologyMode(PollResult) == ELiveLinkTopologyMode::Hub || LiveLinkHubConnectionManager::GetPollResultTopologyMode(PollResult) == ELiveLinkTopologyMode::Spoke; } else if (FactoryClass == ULiveLinkMessageBusSourceFactory::StaticClass()) { bValidTopologyMode = LiveLinkHubConnectionManager::GetPollResultTopologyMode(PollResult) != ELiveLinkTopologyMode::Hub; } return bValidTopologyMode && LiveLinkHubConnectionManager::ShouldAcceptConnectionFrom(InstanceInfo.TopologyMode, PollResult, InstanceInfo.Id); } TSharedPtr FLiveLinkHubMessagingModule::GetControlChannel() { return ControlChannel; } bool FLiveLinkHubMessagingModule::OnAuxRequest( const FLiveLinkHubAuxChannelRequestMessage& InMessage, const TSharedRef& InContext ) { const UScriptStruct* MessageTypeInfo = InContext->GetMessageTypeInfo().Get(); if (FAuxChannelRequestHandlerFunc* Handler = AuxChannelRequestHandlers.Find(MessageTypeInfo)) { (*Handler)(InMessage, InContext); return true; } else { // Returning false generates a FLiveLinkHubAuxChannelRejectMessage response. return false; } } void FLiveLinkHubMessagingModule::RegisterSettings() { #if WITH_EDITOR // Only create this section if in the hub. if (GConfig->GetBoolOrDefault(TEXT("LiveLink"), TEXT("bCreateLiveLinkHubInstance"), false, GEngineIni)) { if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) { SettingsModule->RegisterSettings("Project", "Application", "Messaging", LOCTEXT("LiveLinkSettingsName", "Messaging"), LOCTEXT("LiveLinkDescription", "Configure Live Link Hub Messaging"), GetMutableDefault() ); } } #endif } void FLiveLinkHubMessagingModule::UnregisterSettings() { #if WITH_EDITOR if (GConfig->GetBoolOrDefault(TEXT("LiveLink"), TEXT("bCreateLiveLinkHubInstance"), false, GEngineIni)) { if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) { SettingsModule->UnregisterSettings("Project", "Application", "Timing & Sync"); } } #endif } FLiveLinkHubInstanceId::FLiveLinkHubInstanceId(FGuid Guid) { Id.Set(Guid); } FLiveLinkHubInstanceId::FLiveLinkHubInstanceId(FStringView NamedId) { Id.Emplace(NamedId); } FString FLiveLinkHubInstanceId::ToString() const { auto Overload = Internal::TOverloaded{ [](FGuid InGuid) -> FString { TStringBuilder<20> LiveLinkHubName; LiveLinkHubName << TEXT("Live Link Hub") << TEXT(" (") << *InGuid.ToString().Right(4).ToLower() << TEXT(")"); return LiveLinkHubName.ToString(); }, [](const FString& InId) -> FString { return InId; } }; return Visit(Overload, Id); } IMPLEMENT_MODULE(FLiveLinkHubMessagingModule, LiveLinkHubMessaging); #undef LOCTEXT_NAMESPACE