NebulaWorker
Runs authoritative game simulation for the containers assigned to this worker.
Nebula · Packages/com.1by3.nebula/Runtime/Worker/NebulaWorker.cspublic sealed partial class NebulaWorker : MonoBehaviour, IRpcSink, IWorkerMessagingRuns authoritative game simulation for the containers assigned to this worker. It creates ghost copies near boundaries, transfers entities when they cross into another worker's container, and sends entity state to the gateway. One worker can control any number of containers. A transfer between two containers on the same worker does not use the network.
Fields
StateBatchBytes
public const int StateBatchBytes = WorldStateMsg.BatchBytes;Byte budget for one Sequenced world/ghost-state packet. Sequenced delivery cannot fragment, and a peer's MTU starts at LiteNetLib's floor (508 bytes) until discovery finishes, so batches are cut by size rather than by entry count. Entry size depends on the transform's selected axes and precision.
CrossingHoldTicks
public const int CrossingHoldTicks = 30;How long an entity handed over for a crossing stays with the pose owner before it may be handed back (ticks).
RuntimeTouchSeconds
public const float RuntimeTouchSeconds = 15f;How often a worker re-stamps a runtime container it wants but does not own (see RuntimeContainerIdleSeconds).
MaxCatchUpTicks
public const int MaxCatchUpTicks = 2;Most ticks the loop simulates in one FixedUpdate to catch up with the wall clock. What is left over is simulated by the next FixedUpdates (Unity runs several after a long frame), so a slow frame costs no tick; only a worker more than MaxLagTicks behind skips tick numbers.
MaxLagTicks
public const int MaxLagTicks = 15;Most ticks the worker may run behind the wall clock before it skips the missing tick numbers (everything runs a little slow for a moment) rather than simulate them back-to-back, which would only push the next frame further behind. A skipped tick is one an owner's client predicted and the worker never ran, so its entity ends that tick's step short of where its owner has it and is corrected (NEB-479). A quarter of a second at 60 Hz.
DefaultCatchUpBudgetMs
public const float DefaultCatchUpBudgetMs = 40f;Default for NebulaConfig.WorkerCatchUpBudgetMs: about two and a half tick periods at 60 Hz.
MaxSequencedRpcBytes
public const int MaxSequencedRpcBytes = 400;Largest EntityRpc message sent on the sequenced channel. Sequenced delivery cannot fragment and a link's MTU starts at 508 bytes, so a bigger call goes reliable instead of being dropped by the transport.
PoseAngleEpsilon
public const float PoseAngleEpsilon = 0.01f;Degrees below which an instance's rotation counts as none: it shares its host's axes.
MaxHoldSeconds
public const float MaxHoldSeconds = 120f;Longest a single HoldContainer can defer rebalancing, in seconds.
Properties
Config
public NebulaConfig Config { get; }ControlPlane
public IControlPlane ControlPlane { get; }Persistence
public NebulaPersistence Persistence { get; }Long-term storage for the entities that opted into it (PersistentEntity): checkpoints of what this worker owns, and restores of what the containers it leases held. Null when persistence is off (-nebula-persistence-mode off, or no store handed to Initialize).
ScopeLifecycleAgent
public WorkerScopeLifecycle ScopeLifecycleAgent { get; }This worker's half of the scope lifecycle: the idle clock of the scope parts it owns, the retire sequence for a scope the orchestrator is retiring, and the restore acknowledgement a restored scope waits on. See docs/scope-lifecycle.md.
WorkerId
public string WorkerId { get; }Incarnation
public uint Incarnation { get; }This start of the process (HelloMsg.Incarnation), so peers can tell a restart from a reconnect.
Sessions
public PlayerSessions Sessions { get; }Player sessions this worker knows about and which gateway currently speaks for each.
WorkerIndex
public ushort WorkerIndex { get; }Port
public ushort Port { get; }IsListening
public bool IsListening { get; }Failed
public bool Failed { get; }The worker could not start, most often because another process holds its port. A failed worker does nothing: it never registers and never simulates. FailureReason says why.
FailureReason
public string FailureReason { get; }Why the worker could not start, or null while it is fine.
CurrentTick
public uint CurrentTick { get; }TickCount
public ulong TickCount { get; }TickMs
public float TickMs { get; }HandoversOut
public int HandoversOut { get; }HandoversIn
public int HandoversIn { get; }LocalHandovers
public int LocalHandovers { get; }FrameCrossings
public int FrameCrossings { get; }Entities this worker moved across a physics frame's boundary as the frame's pose owner (docs/container-tree.md D15).
CrossingHandoffs
public int CrossingHandoffs { get; }Entities this worker handed to a frame's pose owner so it could cross them (D15).
RejectedAuthorityRpcSends
public int RejectedAuthorityRpcSends { get; }AuthorityRpc sends this process discarded because the caller held neither an authoritative nor a ghost copy of the target (see NebulaDiagnostics.RejectedAuthorityRpcSends). Reported as rpcRejected on the profile log line.
GhostsSent
public int GhostsSent { get; }GhostsHeld
public int GhostsHeld { get; }DormantCount
public int DormantCount { get; }Entities this worker has authority over that are asleep (NetworkIdentity.IsDormant), as of the start of the last tick. They are part of the authoritative count and cost almost nothing a tick.
AuthoritativeCount
public int AuthoritativeCount { get; }EntityCount
public int EntityCount { get; }AuthorityCallsApplied
public long AuthorityCallsApplied { get; }AuthorityRpc calls from other workers this worker has applied since it started (see the cross-worker call contract).
AuthorityCallsForwarded
public long AuthorityCallsForwarded { get; }AuthorityRpc calls this worker has forwarded to the entity's new owner since it started.
AuthorityCallsRejected
public long AuthorityCallsRejected { get; }AuthorityRpc calls this worker has rejected (stale epoch, unknown entity, hop limit, duplicate, unreachable) since it started.
AuthorityCallsPending
public int AuthorityCallsPending { get; }AuthorityRpc calls this worker sent with a reply requested and is still waiting on.
PlayerCount
public int PlayerCount { get; }Authoritative entities owned by human clients / by bot clients / by nobody (server-driven), as of the last heartbeat.
BotCount
public int BotCount { get; }ServerDrivenCount
public int ServerDrivenCount { get; }Telemetry
public WorkerTelemetry Telemetry { get; }What this worker reports to the dashboard's World map; null when telemetry is off (see WorkerTelemetry.Create).
CostMeter
public ContainerCostMeter CostMeter { get; }What each container this worker leases costs it, measured per tick (see ContainerCostMeter). Always on: it is two stopwatch reads and a field add per authoritative entity per tick, and the rows it produces are what the scaler and the dashboard explain a hot container with (docs/cost-telemetry.md).
Entities
public IEnumerable<NetworkIdentity> Entities { get; }Authoritative
public IReadOnlyList<NetworkIdentity> Authoritative { get; }ChunkStates
public Nebula.World.ChunkStateService ChunkStates { get; }Changes to the sparse per-object state of procedurally placed objects (Nebula.World.ChunkState), routed to the worker that holds each chunk's lease. Created with the worker, so every worker in a mesh answers the changes other workers send it.
ConnectedWorkerIds
public IEnumerable<string> ConnectedWorkerIds { get; }IDs of the workers that currently have a direct connection to this worker.
ConnectedWorkerIndices
public IEnumerable<ushort> ConnectedWorkerIndices { get; }Indices of the workers that currently have a direct connection to this worker.
MeshReady
public bool MeshReady { get; }Every container's owner is connected to this worker (or is this worker). Game code that spawns into containers it does not own should wait for this: the immediate handover needs a target.
IsRegistered
public bool IsRegistered { get; }Registered with the control plane; the mesh knows about this worker.
IsFenced
public bool IsFenced { get; }This worker cannot currently prove it is alive to the control plane: its own row, as its mirror shows it, has had no heartbeat for more than NebulaConfig.WorkerTimeoutSeconds, or the row is gone because the orchestrator declared it dead. The orchestrator applies the same test before it deals a worker's containers to someone else, so while this is true those containers may already be restored on another worker. A fenced worker keeps simulating, but it saves nothing, restores nothing and hands no entity over, so it cannot overwrite or duplicate the new owner's copy; it is unfenced when its next heartbeat lands. See docs/persistence-durability.md D8.
SequencedRpcsSentReliable
public long SequencedRpcsSentReliable { get; }ClientRpcs that asked for a sequenced delivery but were too large for one packet and went reliable.
PortalQueries
public PortalQueries PortalQueries { get; }Raycasts, sweeps and overlaps that continue through an open portal, on this worker, with an optional rewind of the history it keeps (docs/portals.md D32, D33).
HeldContainers
public int HeldContainers { get; }How many containers this worker is holding right now (expired holds are dropped when read).
InterestGrid
public InterestGrid InterestGrid { get; }The grid this worker makes region ids with; a gateway subscribing with a different one is rejected.
SubscribedRegions
public int SubscribedRegions { get; }Regions at least one gateway subscribes here.
HasGlobalEntities
public bool HasGlobalEntities { get; }Authoritative entities that are in every client's set, so gateways must link this worker to hear them.
HasFarEntities
public bool HasFarEntities { get; }Whether this worker is authoritative for an entity with a far relevance radius (reported in the heartbeat, NEB-388).
FarEntriesSent
public long FarEntriesSent { get; }Far-tier entries sent to gateways (one per entity per gateway mask send), for diagnostics and tests.
InterestEntriesSent
public long InterestEntriesSent { get; }State entries actually sent to gateways since start-up, and what an unfiltered worker would have sent.
InterestEntriesTotal
public long InterestEntriesTotal { get; }InterestBytesSent
public long InterestBytesSent { get; }Bytes of world state actually sent, and what the same tick would have cost sent to every gateway.
InterestBytesUnfiltered
public long InterestBytesUnfiltered { get; }InterestFilterMs
public float InterestFilterMs { get; }Smoothed milliseconds per tick spent deciding who hears about what.
PartitionWarning
public string PartitionWarning { get; } = "";Non-empty while this worker wants the world partitioned; shown on the dashboard and logged once a minute.
Events
EntitySpawned
public event Action<NetworkIdentity> EntitySpawned;EntityDespawned
public event Action<NetworkIdentity> EntityDespawned;AuthorityHandedOff
public event Action<NetworkIdentity, string> AuthorityHandedOff;AuthorityReceived
public event Action<NetworkIdentity, string> AuthorityReceived;Methods
RegisterEntityRequestHandler
public void RegisterEntityRequestHandler(ushort kind, EntityRequests.Handler handler);Answer entity requests of kind whenever this worker has authority over the target key. See EntityRequests.RegisterHandler.
RequestEntity
public uint RequestEntity(string persistentKey, ushort kind, byte[] payload, Action<EntityRequestResult> onDone, float timeoutSeconds = 5f);Ask whichever worker has authority over persistentKey and get a reply. See EntityRequests.Request.
RegisterMessageHandler
public void RegisterMessageHandler(ushort kind, WorkerMessageHandler handler);Receive worker messages of kind. One handler per kind; registering again replaces it.
UnregisterMessageHandler
public void UnregisterMessageHandler(ushort kind);IsWorkerConnected
public bool IsWorkerConnected(string workerId);Whether the direct connection to workerId is ready.
SendToWorker
public bool SendToWorker(string workerId, ushort kind, Action<NetworkWriter> write, Delivery delivery = Delivery.ReliableOrdered);
public bool SendToWorker(ushort workerIndex, ushort kind, Action<NetworkWriter> write, Delivery delivery = Delivery.ReliableOrdered);Send a game-defined message to workerId. write serialises the payload; the handler registered for kind on the other side reads it back. False (and nothing sent) when that worker is not connected. Sending to this worker's own id invokes the handler directly.
Initialize
public void Initialize(NebulaConfig config, IControlPlane controlPlane);
public void Initialize(NebulaConfig config, IControlPlane controlPlane, IPersistenceStore persistenceStore);Start the worker with a persistence store: entities carrying a PersistentEntity are checkpointed into it and restored from it (see Persistence). A null store turns persistence off; an unreachable one only delays the saves, it never stops the simulation.
RequestRuntimeContainer
public void RequestRuntimeContainer(ulong id, Bounds frameBounds);
public void RequestRuntimeContainer(ulong id, ContainerPlacement placement, InstanceContainerInfo instance = null);
public void RequestRuntimeContainer(ulong id, Bounds frameBounds, InstanceContainerInfo instance);
public void RequestRuntimeContainer(ulong id, Bounds frameBounds, in ContainerHint hint);Ask the mesh for a runtime container: a static box, named by an id the game chose, that is not in the baked set (a chunk of a landscape that is decided at runtime). frameBounds is the box in this process's frame. The lease row is created already assigned to this worker, so the container is simulated here from the first change anyone sees; if another worker asked first, its row stands and this call is a no-op. Every process registers the container from the row (ContainerRegistry.SyncRuntime), and ContainerRegistry.RuntimeRegistered fires here once it has. Idempotent: call it every time an entity approaches the box.
This overload says nothing about the hint, so it never touches one: a hint set from the dashboard or by the three-argument overload survives every later approach. Use that overload to change it.
ShouldWriteHint
public static bool ShouldWriteHint(bool explicitHint, bool rowHasHint, in ContainerHint rowHint, in ContainerHint wanted);Should this approach write the hint row? Only a caller that actually named a hint may, and only when what the row says differs from what was asked for - the call is made every time an entity comes near the box, and a write per tick would be a control-plane write per tick. A caller that named none leaves the row alone whatever is in it, so the dashboard's hint (or an earlier explicit one) is not erased by the next approach. Pure function.
| Parameter | Description |
|---|---|
explicitHint | The caller passed a hint (the three-argument overload), rather than defaulting. |
rowHasHint | The lease row carries a hint today. |
rowHint | What the row says (ignored when rowHasHint is false). |
wanted | The hint the caller asked for. |
RuntimeContainerIdleSeconds
public double RuntimeContainerIdleSeconds(ulong id);Seconds since anyone in the mesh last asked for or changed a runtime container: its lease row's age. Every worker that wants the box re-stamps the row while an entity of its is near it, so the owner can retire a box only once this exceeds its grace period. Infinity when the container is unknown.
ReleaseRuntimeContainer
public bool ReleaseRuntimeContainer(ulong id);Retire a runtime container this worker owns: its lease row is deleted, so every process forgets the box. Persistent entities still inside are checkpointed and despawned (they come back when the box is asked for again); anything else inside is despawned for good. That includes everything riding in a vehicle in the box (see EmptyContainer). Returns false when the container is not here or belongs to another worker.
EmptyContainer
public void EmptyContainer(Container container);Despawn everything this worker is authoritative for inside container, keeping the records of persistent entities (they come back when the box is asked for again) and losing everything else. The contents half of ReleaseRuntimeContainer, split out because the scope lifecycle empties a part without deleting its lease row — the orchestrator deletes the rows, and only once every part has reported its checkpoint done (docs/scope-lifecycle.md).
A vehicle's cargo leaves with the vehicle. Entities riding in the carried container (NetworkIdentity.Carried) of an entity in the box, at any depth, are despawned too, riders before their carrier. A persistent rider is checkpointed while it is still aboard, so its record names the carrier and it is restored with the carrier. Riders another worker owns (an interior pinned to it, a rider whose handover has not happened yet) are taken too: that worker despawns and saves them aboard the same way when it hears that the carrier left (docs/dynamic-worlds.md, "Retiring a chunk under a carrier", D5).
Spawn
public void Spawn(NetworkIdentity identity, Container container, ulong ownerClientId = 0);Allocate an id and make identity live on this worker. Call after instantiating a network prefab.
SpawnServerDriven
public void SpawnServerDriven(NetworkIdentity identity, Container container);Spawn a server-driven entity: one with no owning client whose PredictedBehaviour<TInput> (if any) gets its input from GatherServerInput on whichever worker holds authority. It ghosts and hands over like any other entity and costs nothing beyond the entity itself. The container need not be owned by this worker: the mesh hands the entity to its owner on the next tick (see MeshReady).
SpawnPrefab
public NetworkIdentity SpawnPrefab(GameObject prefab, Vector3 position, Quaternion rotation, Container container, ulong ownerClientId = 0);Despawn
public void Despawn(NetworkIdentity identity, bool keepPersisted = false);
public void Despawn(NetworkIdentity identity, bool keepPersisted, CargoPolicy cargo);Remove an entity from the mesh. A persistent entity (PersistentEntity) is also forgotten by the store: this is the entity ceasing to exist, not merely leaving this process. Pass keepPersisted true when the world should keep it (a player disconnecting, a cell unloading): its record is checkpointed one last time and left in place.
Whatever still rides in the entity's carried container (NetworkIdentity.Carried) is put down in the container the entity was in, here and on every worker that holds a ghost of it. To take the cargo along, despawn the riders first, or empty the box around the vehicle with EmptyContainer.
Find
public NetworkIdentity Find(ulong netId);TryGetStateAt
public bool TryGetStateAt(ulong netId, uint tick, out HistoricalState state);What the entity netId looked like at server tick tick, from this worker's own recorded history (NetworkIdentity.StateAt). Answers for anything this worker holds, authoritative or ghosted; false when it holds no copy or the tick is outside the recorded window (NebulaConfig.StateHistoryTicks). Nothing is extrapolated. See docs/state-history.md.
FindPlayer
public NetworkIdentity FindPlayer(ulong clientId);GetPlayerClaims
public IReadOnlyDictionary<string, string> GetPlayerClaims(ulong clientId);The claims of a player's verified sign-in token that the mesh forwards (NebulaConfig.ForwardedClaims), by session id, every value a string. This worker knows them for players whose gateway claimed the session here and for players whose pawn was handed over to it; for any entity, NetworkIdentity.OwnerClaims gives its owner's. Empty, never null, for anonymous players and players this worker does not know.
Kick
public bool Kick(ulong clientId, ushort code, string reason);
public bool Kick(NetworkIdentity player, ushort code, string reason);Remove a player from the game: the client is told why (code, a number of the game's own that Nebula passes through unchanged, and reason, fit to show the player), its link is closed, and the session ends at once, with no reclaim grace. The pawn is despawned the way a player who left is (NebulaGameMode.OnPlayerDespawn, then the despawn; a persistent pawn keeps its record). The client raises NebulaClient.Disconnected with DisconnectReason.Kicked and does not reconnect by itself.
Call it on any worker that knows the session: the one that owns the pawn, or one the pawn was handed on from. The worker tells the gateway that speaks for the session, which ends the session wherever the pawn is now. When that gateway cannot be told right now (its link to this worker is down, or the player is away), the session still ends here at once and the kick is held for the reclaim grace: it is sent when that gateway links again, and a claim of the session through any gateway in that time is answered with the kick instead of a pawn. Returns false when this worker has no session for clientId and does not own its pawn (a worker that holds only a ghost of the pawn cannot kick). A kick is not a ban: the player can connect again, as a new session. Keep a ban list in your own admission code (NebulaAdmission, or your sign-in service).
InstanceKey
public static ulong InstanceKey(string key);Stable identifier for an instance or one of its containers. Include a run ID in the key for temporary instances.
The derivation itself is ScopeKeys.Hash, so a gateway, an orchestrator or a matchmaking service can name the same scope without a worker and without Unity.
PrepareInstance
public ContainerRef[] PrepareInstance(InstanceTemplate template, string key, Vector3 origin);
public ContainerRef[] PrepareInstance(InstanceTemplate template, string key, Double3 absoluteOrigin);
public ContainerRef[] PrepareInstance(InstanceTemplate template, string key, Double3 absoluteOrigin, Quaternion rotation);Create or find stable instance container leases. Resolve the returned references after the control-plane update. This does not automatically persist entity state.
| Parameter | Description |
|---|---|
template | Layout with a stable ID, unique part IDs, and positive-size bounds. |
key | Game-selected home, party, or run key. The same template ID and key select the same scope. |
origin | Instance origin in this process's world frame. Existing keys must retain their bounds and content resource. |
TryCommitTransfers
public bool TryCommitTransfers(IReadOnlyList<InstanceTransfer> transfers, Vector3 translation);
public bool TryCommitTransfers(IReadOnlyList<InstanceTransfer> transfers, Double3 translation);Commit a group only when every member is ready. Each member must have its own preparation and admission check.
A member riding in another member's carried container (NetworkIdentity.Carried), at any depth (the crew of a ship that is itself in the group), keeps its seat: it moves with its carrier and stays inside it, so it arrives in the destination scope through the carrier instead of being put down in the destination container. Its preparation is still what readies its owner's client for the destination.
| Parameter | Description |
|---|---|
transfers | Distinct entity preparations owned by this worker. This is not a cross-worker transaction. |
translation | Displacement from the source scope's own space to the destination's, applied to each member's pose in its scope, preserving its rotation. A member inside a physics frame is read out of the frame first. |
PrepareTransfer
public InstanceTransfer PrepareTransfer(NetworkIdentity entity, Container destination, float timeoutSeconds = 15);Prepare an authorized crossing. The game must check admission before calling. Preparation grants no visibility of destination entities.
| Parameter | Description |
|---|---|
entity | Spawned entity simulated by this worker. Authored scene entities cannot commit a crossing. |
destination | Static destination container with a current owning lease. |
timeoutSeconds | Preparation timeout in unscaled seconds, clamped to at least one second. |
TryCommitTransfer
public bool TryCommitTransfer(InstanceTransfer transfer, Vector3 position, Quaternion rotation);
public bool TryCommitTransfer(InstanceTransfer transfer, Container frame, Double3 localPosition, Quaternion localRotation);Commit a prepared crossing at a pose in the destination scope's own space (world space when no physics frame is involved). A destination inside a physics frame gets the frame-local pose that matches it. Preserves identity and velocity. Returns false if readiness or authority changed.
HoldContainer
public void HoldContainer(string containerId, float seconds);
public void HoldContainer(Container container, float seconds);Ask the orchestrator to leave containerId where it is for seconds. Repeating the call extends the hold to the later of the two deadlines; seconds at or below zero releases it. Clamped to MaxHoldSeconds: a hold is a pause, not a pin - the permanent form is ContainerHint.Dedicated or a pinned carried container.
The hold reaches the orchestrator with the next telemetry document (at most WorkerTelemetry.IdleIntervalSeconds later) and is forgotten if this worker stops reporting, so it never outlives the worker that asked for it.
ReleaseHold
public void ReleaseHold(string containerId);Let a held container be rebalanced again before its hold expires.
HoldRemaining
public float HoldRemaining(string containerId);Seconds this worker's hold on a container still has to run, or 0 when it holds none.
CopyGatewayInterest
public void CopyGatewayInterest(List<GatewayInterest> result);Snapshot the per-gateway interest counters into result (cleared first). Main thread.
Nested types
WorkerMessageHandler
public delegate void WorkerMessageHandler(string fromWorkerId, ushort fromWorkerIndex, NetworkReader reader);Handler for a game-defined worker message. reader is positioned at the payload.
ContainerHold
public readonly struct ContainerHoldOne container this worker is asking the orchestrator not to move, and for how much longer.
Constructors
ContainerHold
public ContainerHold(string containerId, float secondsRemaining);Fields
ContainerId
public readonly string ContainerId;SecondsRemaining
public readonly float SecondsRemaining;Seconds still to run when the hold was read. Always greater than zero.
PortalBinding
public struct PortalBindingOne portal link that is not closed, as the worker reports it in the cohesion block of its telemetry (docs/portals.md D34, D36): the near face's container and the far containers its window touches, which the planner deals as one item, and the link's state and wait for the dashboard.
Fields
LinkId
public ulong LinkId;The link's id (PortalLink.Id).
Portal
public string Portal;The portal's authored id, for the dashboard. Never the party: a party can be a player's identity.
State
public PortalState State;Wait
public PortalWait Wait;Split
public bool Split;Some of the link's containers belong to another worker right now (docs/portals.md D35).
Containers
public List<string> Containers;The near face's container, then the far ones.
CohesionSpan
public struct CohesionSpanWhat one cohesion group costs this worker: which of its containers hold members of the group, and how many members it owns. The orchestrator unions the spans of every worker into the group the planner keeps on one worker (docs/cohesion-hints.md, D7).
Fields
Group
public uint Group;Members
public int Members;Containers
public List<string> Containers;GatewayInterest
public struct GatewayInterestPer gateway link, what interest management is doing for it. For telemetry and the debug overlay.
Fields
GatewayId
public string GatewayId;Regions
public int Regions;Foci
public int Foci;ExplicitEntities
public int ExplicitEntities;EntriesSent
public long EntriesSent;BytesSent
public long BytesSent;