Skip to main content

World Assembly Subsystem

Base:
UTickableWorldSubsystem
Type:
UNWorldAssemblySubsystem
Header File:
NexusWorldAssembly/Public/NWorldAssemblySubsystem.h

UNWorldAssemblySubsystem is the game-only UTickableWorldSubsystem that hosts every World Assembly operation kicked off during play. It owns the per-player ANWorldAssemblyRelay actors, keeps a strong reference to every in-flight UNAssemblyOperation so build tasks don't get collected mid-pass, and acts as the INAssemblyOperationOwner for operations created via Generate().

Kicking Off Generation​

/**
* Kick off a new generation pass with the supplied per-operation settings.
* @param Settings Operation-level settings (seed, level-instance behavior); taken by reference so the caller can reuse the struct.
*/
UFUNCTION(BlueprintCallable, DisplayName="Generate", Category = "NEXUS|WorldAssembly")
void Generate(UPARAM(ref) FNAssemblyOperationSettings& Settings);

Generate is safe to call at any time — it does not gate on IsReady() or on any outstanding operation, so callers can queue work freely.

Clearing​

Clear is the counterpart to Generate — it tears down everything the subsystem assembled and returns it to an empty state.

/**
* Tear down every assembled object owned by the subsystem and return it to an empty state.
* Cancels any in-flight operations, destroys every ANCellProxy in the world along with its streamed
* level instance, destroys any actors previously enrolled via RegisterOperationActor, then empties
* the tracked-actor list and broadcasts OnCleared.
*/
UFUNCTION(BlueprintCallable, DisplayName="Clear", Category = "NEXUS|WorldAssembly")
void Clear();

Clear does not destroy the per-player relays — those are tied to player-controller lifetime, not generation lifetime. In editor builds the global selection is cleared first so the typed-element registry does not assert on a stale handle after sub-level actors are torn down.

To have Clear also dispose of actors it didn't spawn, enroll them with RegisterOperationActor. Each actor is tracked under an Operation Ticket — the ticket of the operation that spawned it (0, the default, is the unassociated bucket) — so a single operation's actors can be torn down on their own without waiting for a full Clear:

/**
* Track an externally-owned actor under an Operation Ticket so it will be destroyed by the next Clear() pass, or
* by a DestroyOperationActors call for that ticket.
* Stored as a weak reference, so the actor is free to be destroyed by other systems first without leaving a
* dangling entry. Safe to call repeatedly with the same actor — duplicates within a ticket are ignored.
* @param OperationTicket Ticket of the operation that spawned the actor; 0 (the default) is the unassociated bucket.
*/
UFUNCTION(BlueprintCallable, DisplayName="Register Operation Actor", Category = "NEXUS|WorldAssembly")
void RegisterOperationActor(AActor* Actor, int32 OperationTicket = 0);

/**
* Stop tracking an actor for Clear()-driven destruction, regardless of which ticket it was registered under.
* Call when the actor's lifetime is taken over elsewhere, or when it has already been destroyed and
* the slot should be reclaimed early. A no-op if the actor was never registered.
*/
UFUNCTION(BlueprintCallable, DisplayName="Unregister Operation Actor", Category = "NEXUS|WorldAssembly")
void UnregisterOperationActor(AActor* Actor);

/**
* Stop tracking an actor for cleanup under a specific Operation Ticket — a direct-lookup alternative to
* UnregisterOperationActor that avoids scanning every ticket bucket.
* @param OperationTicket Ticket the actor was registered under; 0 (the default) is the unassociated bucket.
*/
UFUNCTION(BlueprintCallable, DisplayName="Unregister Operation Actor (By Ticket)", Category = "NEXUS|WorldAssembly")
void UnregisterOperationActorByTicket(AActor* Actor, int32 OperationTicket = 0);

/**
* Destroy and stop tracking every actor enrolled under a single Operation Ticket, leaving other operations' actors untouched.
* @param OperationTicket Ticket whose tracked actors should be torn down. A no-op if nothing was registered for it.
*/
UFUNCTION(BlueprintCallable, DisplayName="Destroy Operation Actors", Category = "NEXUS|WorldAssembly")
void DestroyOperationActors(int32 OperationTicket);

Enrolled actors are held weakly, so an entry becomes inert rather than dangling if the actor is destroyed by another system first. All calls tolerate a null actor.

Per-Player Relays​

The subsystem spawns one ANWorldAssemblyRelay per logged-in player controller. Relays carry per-player generation state (nearby cells, completion notifications) over the wire so a multiplayer session can coordinate generation results without every client redoing the work.

/** @return Relay associated with the local player, or nullptr if it has not yet been spawned. */
UFUNCTION(BlueprintCallable, DisplayName="Get Local Relay", Category = "NEXUS|WorldAssembly")
ANWorldAssemblyRelay* GetLocalRelay() const;

Readiness​

/**
* @param bWaitOnStreaming When true, also report not-ready while any level streaming is still in
* flight (see FNWorldUtils::IsStreaming). Pass false to ignore streaming and gate purely on
* operation/relay state.
* @return true when the local procgen view is settled relative to the server.
* @remark Server path: no operations are currently in flight.
* Client path: LocalRelay has replicated, the nearby-cell payload has been received,
* and no operations the client has been notified of are pending.
* @note Does not gate Generate() — that can be called at any time regardless of this value.
*/
UFUNCTION(BlueprintCallable, DisplayName="Is Ready?", Category = "NEXUS|WorldAssembly")
bool IsReady(bool bWaitOnStreaming = true);

Use IsReady for UI gating ("ready to start"), not as a precondition for issuing more work. It surfaces in Blueprint graphs as Is Ready?. By default (bWaitOnStreaming = true) it also stays not-ready until level streaming settles; pass false to skip that streaming check.

Events​

Three BlueprintAssignable dynamic multicast delegates broadcast the generation lifecycle transitions:

DelegateFires When
OnOperationStartedA new operation begins being tracked by the subsystem, immediately before its build is kicked off.
OnOperationsCompletedThe last tracked operation finishes (or is destroyed) — i.e. the tracked-operation set transitions from non-empty to empty.
OnClearedA Clear() pass finishes, once tracked operations have been cancelled and all cell proxies in the world have been destroyed.

Bind these to drive demo / sample logic that needs to react to "world is generated, you can start playing now" without polling.

Useful Examples​

Hookup Actor Pool Subsystem​

UNWorldAssemblySubsystem* WorldAssemblySubsystem = UNWorldAssemblySubsystem::Get(InWorld);
UNActorPoolSubsystem* ActorPoolSubsystem = UNActorPoolSubsystem::Get(InWorld);
WorldAssemblySubsystem->OnCleared.AddDynamic(ActorPoolSubsystem, &UNActorPoolSubsystem::ReturnAllActors);