// Copyright Epic Games, Inc. All Rights Reserved. #include "Game/DisplayClusterGameManager.h" #include "Engine/BlueprintGeneratedClass.h" #include "Kismet/GameplayStatics.h" #include "Misc/CommandLine.h" #include "Config/IPDisplayClusterConfigManager.h" #include "DisplayClusterConfigurationTypes.h" #include "DisplayClusterRootActor.h" #include "Camera/CameraComponent.h" #include "Blueprints/DisplayClusterBlueprint.h" #include "Components/SceneComponent.h" #include "Components/DisplayClusterCameraComponent.h" #include "Components/DisplayClusterScreenComponent.h" #include "Misc/DisplayClusterGlobals.h" #include "Misc/DisplayClusterHelpers.h" #include "Misc/DisplayClusterLog.h" #include "Misc/DisplayClusterStrings.h" #include "GameFramework/Actor.h" #include "GameFramework/PlayerStart.h" #include "Engine/Level.h" #include "Engine/LevelStreaming.h" #include "EngineUtils.h" #include "LevelUtils.h" FDisplayClusterGameManager::FDisplayClusterGameManager() : ConfigData(nullptr) , CurrentOperationMode(EDisplayClusterOperationMode::Disabled) , CurrentWorld(nullptr) { } FDisplayClusterGameManager::~FDisplayClusterGameManager() { } ////////////////////////////////////////////////////////////////////////////////////////////// // IPDisplayClusterManager ////////////////////////////////////////////////////////////////////////////////////////////// bool FDisplayClusterGameManager::Init(EDisplayClusterOperationMode OperationMode) { CurrentOperationMode = OperationMode; return true; } void FDisplayClusterGameManager::Release() { } bool FDisplayClusterGameManager::StartSession(UDisplayClusterConfigurationData* InConfigData, const FString& InNodeId) { ClusterNodeId = InNodeId; ConfigData = InConfigData; return true; } void FDisplayClusterGameManager::EndSession() { ClusterNodeId.Reset(); } bool FDisplayClusterGameManager::StartScene(UWorld* InWorld) { FScopeLock Lock(&InternalsSyncScope); if(!InWorld || !ConfigData) { return false; } CurrentWorld = InWorld; // Find the first DCRA instance that matches to the specified configuration ADisplayClusterRootActor* RootActor = FindRootActor(InWorld, ConfigData); if (GDisplayCluster->GetOperationMode() == EDisplayClusterOperationMode::Cluster) { // If a corresponding DCRA instance was found, overwrite its settings if (RootActor) { UE_LOG(LogDisplayClusterGame, Log, TEXT("Chose DCRA '%s' corresponding to asset '%s'"), *RootActor->GetName(), *ConfigData->Info.AssetPath ); RootActor->OverrideFromConfig(ConfigData); } // If no proper DCRA found, // 1. Detect spawn location and rotation // 2. Try to spawn a blueprint from a corresponding asset // 3. If the asset not found, spawn an empty DCRA instance and initialize it with specified configuration data. else { // 1. Detect spawn location and rotation FVector StartLocation = FVector::ZeroVector; FRotator StartRotation = FRotator::ZeroRotator; // Use PlayerStart transform if it exists in the world TActorIterator It(InWorld); if (It) { StartLocation = (*It)->GetActorLocation(); StartRotation = (*It)->GetActorRotation(); } // 2. Spawn the DCRA BP from a corresponding asset // We need to use generated class as it's the only one available in packaged buidls const FString AssetPath = (ConfigData->Info.AssetPath.EndsWith(TEXT("_C")) ? ConfigData->Info.AssetPath : ConfigData->Info.AssetPath + TEXT("_C")); if (UClass* ActorClass = Cast(StaticLoadObject(UClass::StaticClass(), nullptr, *AssetPath))) { // Spawn the actor if (AActor* NewActor = CurrentWorld->SpawnActor(ActorClass, StartLocation, StartRotation, FActorSpawnParameters())) { RootActor = Cast(NewActor); if (RootActor) { UE_LOG(LogDisplayClusterGame, Log, TEXT("Instantiated DCRA from asset '%s'"), *ConfigData->Info.AssetPath); // Override actor settings in case the config file contains some updates RootActor->OverrideFromConfig(ConfigData); } } } // 3. Still no root actor exists? Spawn the DCRA and initialize it with the config data if (!RootActor) { UE_LOG(LogDisplayClusterGame, Warning, TEXT("Couldn't find a configuration asset '%s'. Initializing default DCRA..."), *ConfigData->Info.AssetPath); RootActor = CurrentWorld->SpawnActor(ADisplayClusterRootActor::StaticClass(), StartLocation, StartRotation, FActorSpawnParameters()); if (RootActor) { RootActor->InitializeFromConfig(ConfigData); } } } } // Store the DCRA instance. It's now considered as an 'active' root actor, so any nDisplay subsystem // or some user game logic can refer it using game manager API DisplayClusterRootActorRef.SetSceneActor(RootActor); return true; } void FDisplayClusterGameManager::EndScene() { FScopeLock Lock(&InternalsSyncScope); DisplayClusterRootActorRef.ResetSceneActor(); CurrentWorld = nullptr; } void FDisplayClusterGameManager::PreTick(float DeltaSeconds) { // Here we check if the active DCRA instance is in a sublevel, and whether // the sublevel visibility is off. If so, we forcibly make the sublevel visible. if (const AActor* const DCRA = DisplayClusterRootActorRef.GetOrFindSceneActor()) { if (const ULevel* const Level = DCRA->GetLevel()) { if (!Level->bIsVisible) { if (ULevelStreaming* const StreamingLevel = FLevelUtils::FindStreamingLevel(Level)) { UE_LOG(LogDisplayClusterGame, Warning, TEXT("A streaming level containing the active DCRA instance is currently invisible. Forcing level visibility on.")); StreamingLevel->SetShouldBeVisible(true); } } } } } ////////////////////////////////////////////////////////////////////////////////////////////// // IDisplayClusterGameManager ////////////////////////////////////////////////////////////////////////////////////////////// ADisplayClusterRootActor* FDisplayClusterGameManager::GetRootActor() const { FScopeLock Lock(&InternalsSyncScope); return Cast(DisplayClusterRootActorRef.GetOrFindSceneActor()); } ////////////////////////////////////////////////////////////////////////////////////////////// // FDisplayClusterGameManager ////////////////////////////////////////////////////////////////////////////////////////////// TArray FDisplayClusterGameManager::GetAllRootActorsFromWorld(UWorld* InWorld) { TArray FoundActors; FoundActors.Reserve(16); // Find all DCRA instances in the persistent level FindRootActorsInWorld(InWorld, FoundActors); // Also search inside streamed levels const TArray& StreamingLevels = InWorld->GetStreamingLevels(); for (const ULevelStreaming* const StreamingLevel : StreamingLevels) { if (StreamingLevel && StreamingLevel->GetLevelStreamingState() == ELevelStreamingState::LoadedVisible) { // Look for the actor in those sub-levels that have been loaded already const TSoftObjectPtr& SubWorldAsset = StreamingLevel->GetWorldAsset(); FindRootActorsInWorld(SubWorldAsset.Get(), FoundActors); } } return FoundActors; } ADisplayClusterRootActor* FDisplayClusterGameManager::FindRootActor(UWorld* InWorld, UDisplayClusterConfigurationData* InConfigData) { TArray FoundActors = FDisplayClusterGameManager::GetAllRootActorsFromWorld(InWorld); #if WITH_EDITOR if (InWorld->IsPlayInEditor()) { /** We need to fix it in the following way : * - When multiple DCRA are available in the scene, and only one has Preview Node selected, then we should see the preview * - When multiple DCRA are available in the sceneand multiple Preview Node is selected, we should write a warning in the logand print the name of the selected DCRA for the preview. */ TArray FoundActorsForPIE; for (ADisplayClusterRootActor* RootActorIt : FoundActors) { if (RootActorIt && !(RootActorIt->PreviewNodeId.IsEmpty() || RootActorIt->PreviewNodeId == DisplayClusterConfigurationStrings::gui::preview::PreviewNodeNone)) { FoundActorsForPIE.Add(RootActorIt); } } if (FoundActorsForPIE.IsEmpty()) { // No DCRA for PIE preview return nullptr; } ADisplayClusterRootActor* ActiveRootActor = FoundActorsForPIE[0]; if (FoundActorsForPIE.Num() > 1) { FString PreviewActorsNames; for (ADisplayClusterRootActor* RootActorIt : FoundActorsForPIE) { if (PreviewActorsNames.IsEmpty()) { PreviewActorsNames.Appendf(TEXT("%s"), *RootActorIt->GetName()); } else { PreviewActorsNames.Appendf(TEXT(", %s"), *RootActorIt->GetName()); } } UE_LOG(LogDisplayClusterGame, Warning, TEXT("Multiple PIE Preview Nodes selected at DisplayClusterRootActors - %s. Active : %s"), *PreviewActorsNames , *ActiveRootActor->GetName()); } return ActiveRootActor; } #endif // Now iterate over all DCRA instances we have found to pick the one that corresponds to the config data for (ADisplayClusterRootActor* Actor : FoundActors) { // Check if it matches the config data if (DoesRootActorMatchTheAsset(Actor, InConfigData->Info.AssetPath)) { return Actor; } } return nullptr; } void FDisplayClusterGameManager::FindRootActorsInWorld(UWorld* InWorld, TArray& OutActors) { if (InWorld && InWorld->PersistentLevel) { UClass* DisplayClusterRootActorClass = StaticLoadClass(UObject::StaticClass(), nullptr, TEXT("/Script/DisplayCluster.DisplayClusterRootActor"), NULL, LOAD_None, NULL); if (DisplayClusterRootActorClass) { for (TActorIterator It(InWorld, DisplayClusterRootActorClass, EActorIteratorFlags::SkipPendingKill); It; ++It) { ADisplayClusterRootActor* RootActor = Cast(*It); if (RootActor != nullptr && !RootActor->IsTemplate()) { UE_LOG(LogDisplayClusterGame, VeryVerbose, TEXT("Found root actor - %s"), *RootActor->GetName()); OutActors.Add(RootActor); } } } } } bool FDisplayClusterGameManager::DoesRootActorMatchTheAsset(ADisplayClusterRootActor* RootActor, const FString& AssetReference) { // We only interested in DCRA blueprints if (RootActor->IsBlueprint()) { // Iterate over class hierarchy for (UClass* Class = RootActor->GetClass(); Class; Class = Class->GetSuperClass()) { // Get BP interface if (UBlueprintGeneratedClass* BPClass = Cast(Class)) { // Get asset path FString AssetPath = BPClass->GetPathName(); AssetPath.RemoveFromEnd(TEXT("_C")); // Check if this BP was made of a specific asset if (AssetPath.Equals(AssetReference, ESearchCase::IgnoreCase)) { // This DCRA instance matches our search criteria return true; } } } } return false; }