Bounded vertical route partition / streaming for handcrafted spiral climb maps (Only Up / Only Climb). Not World Partition, not Level Instances as core arch. - Z-cell segmentation + optional XY subcells, editor builder + commandlet - NonDestructive visibility backend + streaming-level backend - Offline HLOD proxies: MergedMesh / SimplifiedProxy (ProxyLOD) - Universal Level Instance + Packed Level Actor + HISM support (flatten+merge) - Vertical-aware runtime streamer (full/preload/HLOD/unload, hysteresis, velocity prediction, fall-risk), WP-style debug viz + vp.* console - Safe-by-default: only static decor streams; sky/lights/gameplay stay persistent Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
120 lines
4.4 KiB
C++
120 lines
4.4 KiB
C++
// Copyright IHY. Vertical Partition Streaming plugin.
|
|
//
|
|
// The runtime streaming engine. World-scoped, holds the per-cell state machine,
|
|
// the async load queue, the proxy actors and the live statistics. Driven each
|
|
// update tick by AVerticalPartitionManager (subsystems are not ticked directly).
|
|
#pragma once
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "Subsystems/WorldSubsystem.h"
|
|
#include "VerticalPartitionTypes.h"
|
|
#include "VerticalPartitionStatics.h"
|
|
#include "VerticalPartitionRuntimeSubsystem.generated.h"
|
|
|
|
class UVerticalPartitionDescriptor;
|
|
class ULevelStreaming;
|
|
class AStaticMeshActor;
|
|
|
|
/** Per-cell live runtime record. Plain struct (C++-only, same-module access). */
|
|
struct FVerticalCellRuntime
|
|
{
|
|
FVerticalCellId Id;
|
|
int32 DescriptorIndex = INDEX_NONE;
|
|
FBox Bounds = FBox(ForceInit);
|
|
|
|
EVerticalCellState State = EVerticalCellState::Unloaded;
|
|
EVerticalCellDesiredState Desired = EVerticalCellDesiredState::Unloaded;
|
|
|
|
/** Backend B (DestructiveCommit): the streaming sub-level for the full actors. */
|
|
TWeakObjectPtr<ULevelStreaming> StreamingLevel;
|
|
/** Backend A (NonDestructive): the original persistent-level actors of this cell. */
|
|
TArray<TWeakObjectPtr<AActor>> ResolvedActors;
|
|
/** Shown while the cell is HLOD. */
|
|
TWeakObjectPtr<AStaticMeshActor> ProxyActor;
|
|
|
|
bool bUsesStreamingBackend = false; // true => backend B
|
|
bool bPreloadRequested = false;
|
|
bool bProxyMissingWarned = false;
|
|
|
|
/** Debug lock: when set, Desired is pinned to ForcedState. */
|
|
bool bForced = false;
|
|
EVerticalCellDesiredState ForcedState = EVerticalCellDesiredState::Full;
|
|
|
|
float EstimatedMemoryMB = 0.f;
|
|
double LastChangeTime = 0.0;
|
|
};
|
|
|
|
UCLASS()
|
|
class VERTICALPARTITION_API UVerticalPartitionRuntimeSubsystem : public UWorldSubsystem
|
|
{
|
|
GENERATED_BODY()
|
|
|
|
public:
|
|
static UVerticalPartitionRuntimeSubsystem* Get(const UObject* WorldContextObject);
|
|
|
|
virtual void Deinitialize() override;
|
|
|
|
// ---- lifecycle (called by the manager) ----
|
|
void RegisterDescriptor(UVerticalPartitionDescriptor* InDescriptor, const FBox& InVolumeBounds);
|
|
void UnregisterDescriptor();
|
|
bool IsReady() const { return Descriptor != nullptr; }
|
|
|
|
/** Main entry point. Recomputes desired states and steps the machine within the
|
|
* per-update async budget. bSnap forces immediate convergence (teleport/checkpoint). */
|
|
void UpdateStreaming(const FVerticalStreamingContext& Ctx, float DeltaSeconds, bool bSnap = false);
|
|
|
|
// ---- live range overrides (console) ----
|
|
FVerticalPartitionSettings& GetMutableSettings() { return EffectiveSettings; }
|
|
const FVerticalPartitionSettings& GetSettings() const { return EffectiveSettings; }
|
|
void SetFullRange(int32 Below, int32 Above);
|
|
void SetHLODRange(int32 Below, int32 Above);
|
|
void SetCellHeight(float NewHeight);
|
|
|
|
// ---- debug forcing ----
|
|
void ForceCellFull(int32 Z);
|
|
void ForceCellHLOD(int32 Z);
|
|
void UnforceCell(int32 Z);
|
|
void UnforceAll();
|
|
void ShowOnlyCell(int32 Z); // force one Full, everything else Unloaded
|
|
|
|
// ---- introspection ----
|
|
const FVerticalRuntimeStats& GetStats() const { return Stats; }
|
|
const TMap<FVerticalCellId, FVerticalCellRuntime>& GetCellRuntimes() const { return Cells; }
|
|
const FBox& GetVolumeBounds() const { return VolumeBounds; }
|
|
UVerticalPartitionDescriptor* GetDescriptor() const { return Descriptor; }
|
|
int32 GetPlayerCellZ() const { return Stats.PlayerCellZ; }
|
|
|
|
// ---- reporting (console) ----
|
|
void DumpRuntimeStats() const;
|
|
void DumpCells() const;
|
|
void DumpWarnings() const;
|
|
|
|
private:
|
|
UPROPERTY(Transient)
|
|
TObjectPtr<UVerticalPartitionDescriptor> Descriptor = nullptr;
|
|
|
|
FVerticalPartitionSettings EffectiveSettings;
|
|
FBox VolumeBounds = FBox(ForceInit);
|
|
|
|
TMap<FVerticalCellId, FVerticalCellRuntime> Cells;
|
|
FVerticalRuntimeStats Stats;
|
|
|
|
bool bShowOnlyMode = false;
|
|
int32 ShowOnlyZ = 0;
|
|
|
|
// ---- state-machine helpers (the spec's key runtime functions) ----
|
|
void ApplyDesiredState(FVerticalCellRuntime& Cell, bool bSnap, int32& LoadBudget, int32& UnloadBudget);
|
|
void LoadCellFullAsync(FVerticalCellRuntime& Cell, bool bVisible);
|
|
void ShowCellHLOD(FVerticalCellRuntime& Cell);
|
|
void UnloadCell(FVerticalCellRuntime& Cell);
|
|
void PollStreaming(FVerticalCellRuntime& Cell);
|
|
|
|
void SetActorGroupActive(FVerticalCellRuntime& Cell, bool bVisible, bool bCollision, bool bTick);
|
|
AStaticMeshActor* EnsureProxy(FVerticalCellRuntime& Cell);
|
|
void HideProxy(FVerticalCellRuntime& Cell);
|
|
ULevelStreaming* EnsureStreamingLevel(FVerticalCellRuntime& Cell);
|
|
|
|
void RebuildStatsFromCells();
|
|
FVerticalCellRuntime* FindCellByZ(int32 Z); // first cell at this Z (XY=0 path)
|
|
};
|