// Copyright IHY. Vertical Partition Streaming plugin. // // Runtime module impl + the vp.* console command surface. Build/editor commands // route through FVerticalPartitionEditorBridge (editor builds only); runtime // commands talk to the world's UVerticalPartitionRuntimeSubsystem / manager. #include "Modules/ModuleManager.h" #include "HAL/IConsoleManager.h" #include "EngineUtils.h" #include "Engine/World.h" #include "VerticalPartitionLog.h" #include "VerticalPartitionTypes.h" #include "VerticalPartitionVolume.h" #include "VerticalPartitionManager.h" #include "VerticalPartitionRuntimeSubsystem.h" #if WITH_EDITOR #include "Editor.h" #include "Selection.h" #endif DEFINE_LOG_CATEGORY(LogVerticalPartition); namespace VPConsole { static AVerticalPartitionVolume* FindVolume(UWorld* World) { if (!World) { return nullptr; } #if WITH_EDITOR // Prefer an editor-selected volume when available. if (GEditor) { if (USelection* Sel = GEditor->GetSelectedActors()) { for (FSelectionIterator It(*Sel); It; ++It) { if (AVerticalPartitionVolume* V = Cast(*It)) { return V; } } } } #endif for (TActorIterator It(World); It; ++It) { return *It; } return nullptr; } static UVerticalPartitionRuntimeSubsystem* GetSub(UWorld* World) { return World ? World->GetSubsystem() : nullptr; } static AVerticalPartitionManager* FindManager(UWorld* World) { if (!World) { return nullptr; } for (TActorIterator It(World); It; ++It) { return *It; } return nullptr; } static void RouteEditor(UWorld* World, EVerticalPartitionAction Action, const TCHAR* Name) { #if WITH_EDITOR if (AVerticalPartitionVolume* V = FindVolume(World)) { FVerticalPartitionEditorBridge::Execute(V, Action); } else { UE_LOG(LogVerticalPartition, Warning, TEXT("[VP] %s: no AVerticalPartitionVolume in the level."), Name); } #else UE_LOG(LogVerticalPartition, Warning, TEXT("[VP] %s is an editor-only command."), Name); #endif } } class FVerticalPartitionModule : public IModuleInterface { public: virtual void StartupModule() override { using namespace VPConsole; IConsoleManager& CM = IConsoleManager::Get(); auto Add = [this, &CM](const TCHAR* Name, const TCHAR* Help, FConsoleCommandWithWorldAndArgsDelegate Del) { Commands.Add(CM.RegisterConsoleCommand(Name, Help, Del, ECVF_Default)); }; // ---- runtime: reporting ---- Add(TEXT("vp.DumpState"), TEXT("Dump runtime streaming stats."), FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray&, UWorld* W) { if (auto* S = GetSub(W)) { S->DumpRuntimeStats(); } })); Add(TEXT("vp.DumpCells"), TEXT("Dump every cell's state."), FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray&, UWorld* W) { if (auto* S = GetSub(W)) { S->DumpCells(); } })); Add(TEXT("vp.DumpWarnings"), TEXT("Dump build warnings/errors from the descriptor."), FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray&, UWorld* W) { if (auto* S = GetSub(W)) { S->DumpWarnings(); } })); Add(TEXT("vp.ProfileStreaming"), TEXT("Log a one-shot streaming profile snapshot."), FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray&, UWorld* W) { if (auto* S = GetSub(W)) { S->DumpRuntimeStats(); S->DumpCells(); } })); // ---- runtime: forcing / overrides ---- Add(TEXT("vp.ForceCellFull"), TEXT("vp.ForceCellFull - pin a cell to Full."), FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray& A, UWorld* W) { if (auto* S = GetSub(W)) { if (A.Num() > 0) { S->ForceCellFull(FCString::Atoi(*A[0])); } } })); Add(TEXT("vp.ForceCellHLOD"), TEXT("vp.ForceCellHLOD - pin a cell to HLOD."), FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray& A, UWorld* W) { if (auto* S = GetSub(W)) { if (A.Num() > 0) { S->ForceCellHLOD(FCString::Atoi(*A[0])); } } })); Add(TEXT("vp.UnforceCell"), TEXT("vp.UnforceCell - release a forced cell."), FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray& A, UWorld* W) { if (auto* S = GetSub(W)) { if (A.Num() > 0) { S->UnforceCell(FCString::Atoi(*A[0])); } } })); Add(TEXT("vp.UnforceAll"), TEXT("Release all forced cells and ShowOnly mode."), FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray&, UWorld* W) { if (auto* S = GetSub(W)) { S->UnforceAll(); } })); Add(TEXT("vp.ShowOnlyCell"), TEXT("vp.ShowOnlyCell - Full only this cell, unload the rest."), FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray& A, UWorld* W) { if (auto* S = GetSub(W)) { if (A.Num() > 0) { S->ShowOnlyCell(FCString::Atoi(*A[0])); } } })); Add(TEXT("vp.SetFullRange"), TEXT("vp.SetFullRange ."), FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray& A, UWorld* W) { if (auto* S = GetSub(W)) { if (A.Num() > 1) { S->SetFullRange(FCString::Atoi(*A[0]), FCString::Atoi(*A[1])); } } })); Add(TEXT("vp.SetHLODRange"), TEXT("vp.SetHLODRange ."), FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray& A, UWorld* W) { if (auto* S = GetSub(W)) { if (A.Num() > 1) { S->SetHLODRange(FCString::Atoi(*A[0]), FCString::Atoi(*A[1])); } } })); Add(TEXT("vp.SetCellHeight"), TEXT("vp.SetCellHeight (affects prediction only at runtime)."), FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray& A, UWorld* W) { if (auto* S = GetSub(W)) { if (A.Num() > 0) { S->SetCellHeight(FCString::Atof(*A[0])); } } })); Add(TEXT("vp.TeleportToCell"), TEXT("vp.TeleportToCell - move the tracked pawn to a cell."), FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray& A, UWorld* W) { if (auto* M = FindManager(W)) { if (A.Num() > 0) { M->TeleportToCell(FCString::Atoi(*A[0])); } } })); // ---- editor/build (routed via the bridge) ---- Add(TEXT("vp.Analyze"), TEXT("Analyze the current level (editor)."), FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray&, UWorld* W) { RouteEditor(W, EVerticalPartitionAction::Analyze, TEXT("vp.Analyze")); })); Add(TEXT("vp.BuildFromSelectedVolume"), TEXT("Build using the selected/first volume (editor)."), FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray&, UWorld* W) { RouteEditor(W, EVerticalPartitionAction::Build, TEXT("vp.BuildFromSelectedVolume")); })); Add(TEXT("vp.Validate"), TEXT("Validate cells against the level (editor)."), FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray&, UWorld* W) { RouteEditor(W, EVerticalPartitionAction::Validate, TEXT("vp.Validate")); })); Add(TEXT("vp.Rebuild"), TEXT("Rebuild changed cells (editor)."), FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray&, UWorld* W) { RouteEditor(W, EVerticalPartitionAction::RebuildChangedCells, TEXT("vp.Rebuild")); })); Add(TEXT("vp.RebuildHLOD"), TEXT("Rebuild HLOD proxies (editor)."), FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray&, UWorld* W) { RouteEditor(W, EVerticalPartitionAction::RebuildHLOD, TEXT("vp.RebuildHLOD")); })); Add(TEXT("vp.PreviewChunks"), TEXT("Draw coloured cell boxes in the editor viewport (editor)."), FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray&, UWorld* W) { RouteEditor(W, EVerticalPartitionAction::PreviewChunks, TEXT("vp.PreviewChunks")); })); Add(TEXT("vp.PreviewHLOD"), TEXT("Spawn baked HLOD proxy meshes in the editor viewport (editor)."), FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray&, UWorld* W) { RouteEditor(W, EVerticalPartitionAction::SpawnHLODPreview, TEXT("vp.PreviewHLOD")); })); Add(TEXT("vp.PreviewClear"), TEXT("Clear preview boxes + spawned HLOD preview actors (editor)."), FConsoleCommandWithWorldAndArgsDelegate::CreateLambda([](const TArray&, UWorld* W) { RouteEditor(W, EVerticalPartitionAction::ClearPreview, TEXT("vp.PreviewClear")); })); } virtual void ShutdownModule() override { IConsoleManager& CM = IConsoleManager::Get(); for (IConsoleCommand* Cmd : Commands) { if (Cmd) { CM.UnregisterConsoleObject(Cmd); } } Commands.Reset(); } private: TArray Commands; }; IMPLEMENT_MODULE(FVerticalPartitionModule, VerticalPartition);