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:
| Delegate | Fires When |
|---|---|
OnOperationStarted | A new operation begins being tracked by the subsystem, immediately before its build is kicked off. |
OnOperationsCompleted | The last tracked operation finishes (or is destroyed) — i.e. the tracked-operation set transitions from non-empty to empty. |
OnCleared | A 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);