#pragma once #include "CoreMinimal.h" class UWorld; class ADescentGameMode; /** * The boot sequence of ADescentGameMode::StartPlay (DESIGN_UE.md section 1.5) as an ordered registry. Each * system registers its own boot step from its own .cpp, with an order number, in the same static-registrar style * as scenarios and state sections: * * DESCENT_BOOT_STEP(SpawnCarrier, Descent::Boot::Order::SpawnCarrier) * { * Ctx.World->SpawnActor(...); * // Ctx.Fail(TEXT("why")) logs an error, marks the boot failed (a scenario then fails at once) and boot goes on. * } * * Steps run by (Order, Name): neither file order nor static-initialisation order can change the sequence. * They run also while a scenario is running; a step that should act differently under a scenario (section 1.5 * step 5: go straight to Foot) reads Ctx.bScenarioRunning. */ namespace Descent::Boot::Order { // Section 1.5, step by step. The gaps are for steps a system needs between two of these without renumbering. constexpr int32 BeforeAll = 0; constexpr int32 BuildWorldDef = 100; // 1. FDescentWorldDef::Build() constexpr int32 SpawnEnvironment = 200; // 2. spawn ADescentEnvironment ... constexpr int32 SpawnTerrainActor = 210; // ... ADescentTerrainActor ... constexpr int32 SpawnCarrier = 220; // ... ADescentCarrier at the start pose constexpr int32 SpawnTenders = 300; // 3. three AKazahTenders into the carousel cradles constexpr int32 SpawnFixtures = 310; // start fixtures ... constexpr int32 SpawnStartPlaceables = 320; // ... and start placeables (section 3.4) constexpr int32 StreamInitial = 400; // 4. UDescentTerrainSubsystem::StreamInitialBlocking(Focus = carrier, 120 m) constexpr int32 CreateUi = 450; // (not in 1.5) HUD and title widgets: after the world exists, before the player is placed constexpr int32 PlaceCharacter = 500; // 5. character on the bridge ... constexpr int32 EnterFirstMode = 510; // ... mode Title, then Foot on first click (at once when a scenario is running) constexpr int32 LoadSave = 600; // 6. load the save if Continue was chosen (section 6.12) constexpr int32 AfterAll = 900; // (not in 1.5) anything that must see the finished world } /** Handed to every boot step. */ struct THEDESCENT_API FDescentBootContext { UWorld* World = nullptr; ADescentGameMode* GameMode = nullptr; /** True when a scenario (-DescentScenario= or Descent.Scenario) is driving this run. Boot steps still run. */ bool bScenarioRunning = false; /** Reports that this step could not do its job. Logged as an error; boot continues with the next step. */ void Fail(const FString& Reason) { bFailed = true; FailReason = Reason; } bool HasFailed() const { return bFailed; } const FString& GetFailReason() const { return FailReason; } private: bool bFailed = false; FString FailReason; friend class FDescentBootRegistry; void ResetFailure() { bFailed = false; FailReason.Reset(); } }; using FDescentBootFn = TFunction; /** What a run of the registry did, in the order it did it. Kept by the game mode and dumped as the `boot` state section. */ struct THEDESCENT_API FDescentBootReport { struct FEntry { FString Name; int32 Order = 0; double Ms = 0.0; bool bOk = true; FString Error; }; TArray Ran; bool bOk = true; bool bRan = false; }; class THEDESCENT_API FDescentBootRegistry { public: struct FStep { FString Name; int32 Order = 0; FDescentBootFn Fn; }; /** The registry the game mode runs. Tests make their own instance instead. */ static FDescentBootRegistry& Get(); /** Adds (or replaces, by name) a step. */ void Register(const TCHAR* Name, int32 Order, FDescentBootFn Fn); bool Unregister(const FString& Name); /** The steps in execution order: by Order, then by Name. */ TArray GetOrdered() const; /** Runs every step in order, timing each; a failing step is logged and the rest still run. */ FDescentBootReport Run(FDescentBootContext& Context) const; private: TMap Steps; }; struct THEDESCENT_API FDescentBootRegistrar { FDescentBootRegistrar(const TCHAR* InName, int32 Order, FDescentBootFn Fn) : Name(InName) { FDescentBootRegistry::Get().Register(InName, Order, MoveTemp(Fn)); } ~FDescentBootRegistrar() { FDescentBootRegistry::Get().Unregister(Name); } FString Name; }; /** Declares and registers a boot step. The body is a function of `FDescentBootContext& Ctx`. */ #define DESCENT_BOOT_STEP(StepName, StepOrder) \ static void DescentBootStep_##StepName(FDescentBootContext& Ctx); \ static FDescentBootRegistrar DescentBootRegistrar_##StepName(TEXT(#StepName), (StepOrder), &DescentBootStep_##StepName); \ static void DescentBootStep_##StepName(FDescentBootContext& Ctx)