IControlPlane
Provides process registration, container assignments with epochs, and game-defined settings.
Nebula · Packages/com.1by3.nebula/Runtime/ControlPlane/IControlPlane.cspublic interface IControlPlane : IDisposableProvides process registration, container assignments with epochs, and game-defined settings. Use ControlPlaneHost on the orchestrator and RemoteControlPlane on workers and gateways of a mesh. Use LocalControlPlane for tests and single-process runs. Call Tick to receive callbacks on the main thread. Do not use the control plane for per-tick entity state.
Properties
IsConnected
bool IsConnected { get; }Now
DateTime Now { get; }Best estimate of the control plane's clock (for heartbeat age checks).
DocumentId
string DocumentId { get; }Identity of the document containing process registrations, container assignments, and shared settings. Preserved when that document is restored from storage; replaced when the control plane starts empty or is reset. WorkerRegistration uses changes to this identity to reclaim missing assignments. A remote mirror reports an empty string until it receives a document with an identity.
Workers
IReadOnlyList<WorkerInfo> Workers { get; }Leases
IReadOnlyList<LeaseInfo> Leases { get; }Gateways
IReadOnlyList<GatewayInfo> Gateways { get; }Scopes
IReadOnlyList<ScopeInfo> Scopes { get; }The shared simulation scopes that have been activated (ActivateScope), one row per key, mirrored to every role. The public world has no row. Design of record: docs/scope-activation.md.
Settings
IReadOnlyDictionary<string, string> Settings { get; }Mesh-wide live settings: string key/values Nebula attaches no meaning to. The orchestrator seeds them (-nebula-settings k=v,k=v), the dashboard edits them, and game code on a worker may write them. They are the game's low-volume coordination channel across workers (e.g. a spawn budget every worker reconciles against); never per-tick state.
Events
Changed
event Action Changed;Methods
Connect
void Connect();Tick
void Tick();RegisterWorker
void RegisterWorker(string workerId, uint workerIndex, string address, ushort port);HeartbeatWorker
void HeartbeatWorker(string workerId, string status, in WorkerStats stats);UnregisterWorker
void UnregisterWorker(string workerId);RegisterGateway
void RegisterGateway(string gatewayId, string address, ushort port, uint incarnation = 0);| Parameter | Description |
|---|---|
incarnation | This start of the gateway process (GatewayInfo.Incarnation). A re-registration with a new incarnation clears a stale drain request. |
HeartbeatGateway
void HeartbeatGateway(string gatewayId, in GatewayStats stats);| Parameter | Description |
|---|---|
stats | What the gateway reports about itself (GatewayStats). |
UnregisterGateway
void UnregisterGateway(string gatewayId);SetGatewayDraining
void SetGatewayDraining(string gatewayId, bool draining);Ask a gateway to take itself out of service (or cancel that). The gateway refuses new clients and tells the ones it has to reconnect; whoever runs the fleet removes and stops it once GatewayStats.ActiveClients reaches zero or a drain timeout passes. Exposed by the orchestrator as POST /api/gateways/drain.
HeartbeatOrchestrator
void HeartbeatOrchestrator(string orchestratorId, uint desiredWorkers);SetSetting
void SetSetting(string key, string value);Set (or create) one mesh-wide setting. Every subscriber sees it through Changed.
ActivateScope
void ActivateScope(ScopeActivationRequest request);Ask the mesh for a shared simulation scope by key, creating its containers from ScopeActivationRequest.Definition the first time. Idempotent: the first caller creates the rows, every later caller with the same definition changes nothing, and a caller with a *different* definition under the same key is refused so two worlds never share one scope. A game-supplied unique key is how a private copy is asked for; there is no separate call.
Like every other control-plane write this is fire and forget: the answer is the Scopes row, which appears on this process (and on every other) once the orchestrator has applied it. Poll it with ControlPlaneExtensions.FindScope and ask whether it can be entered with ControlPlaneExtensions.IsScopeReady.
SetScopeState
void SetScopeState(string scopeKey, string state);Move a scope's lifecycle state on (ScopeState). The orchestrator's idle sweep is the single writer of the machine; nothing else in a mesh should call it. See docs/scope-lifecycle.md.
AckScopePart
void AckScopePart(string scopeKey, string containerId, string phase, int count, string workerId);A worker reporting that it finished the step the scope is in for one of its containers: the checkpoint before a retire, or the restore after a re-activation (ScopePhase). The orchestrator waits for one ack per container before taking the next step.
RemoveScope
void RemoveScope(string scopeKey);Drop a scope's row, its durable claim and the lease rows of its containers. The mechanism only: when a scope should go is the game's or NEB-240's decision, and entities still in it are not moved.
EnsureContainer
void EnsureContainer(string containerId, ContainerAuthority authority = ContainerAuthority.Auto, bool ownPhysicsFrame = false, FrameInterestMode frameInterest = FrameInterestMode.WithCarrier, float frameApproachMargin = 0f);Make sure a lease row exists for a container, unassigned. authority is recorded on a new row only: a carried container authored ContainerAuthority.Leased says so here, so the orchestrator re-deals it rather than letting it follow its carrier (docs/container-tree.md D6). The frame flags tell every gateway that a carried container has a physics frame of its own and how its contents are bucketed for interest (D18).
EnsureRuntimeContainer
void EnsureRuntimeContainer(string containerId, ContainerPlacement placement, string workerId, InstanceContainerInfo instance = null);Make sure a lease row exists for a runtime container, carrying its placement (a root's absolute box, or a child's parent id and parent-local box; a Bounds converts to a root) and, when workerId is given and the container is leased, already assigned to that worker (epoch 1, active), so the worker that asked for the container owns it from the first change anyone sees. An inherited container's row is never assigned. A no-op when the row exists: whoever asked first wins, and the second caller sees the row on the next change.
TouchContainer
void TouchContainer(string containerId);Stamp a lease row's LeaseInfo.UpdatedAt without changing anything else: a worker that still wants a runtime container it does not own says so, and the owner reads the age before retiring the box.
AssignContainer
void AssignContainer(string containerId, string workerId);Assign a container to a worker (epoch + 1, state active). Leaves a pinned lease alone.
PinContainer
void PinContainer(string containerId, string workerId);Pin a carried container to workerId: worker, epoch and LeaseState.Pinned in one change, so no subscriber ever sees the lease assigned but not yet pinned (the carrier's worker would reclaim it). Unpin with SetLeaseState.
SetLeaseState
void SetLeaseState(string containerId, string state);SetContainerHint
void SetContainerHint(string containerId, in ContainerHint hint);Attach a balancing hint to a container, creating the lease row if it does not exist yet: the game (or the dashboard) telling the planner something it cannot measure while the mesh runs. The row outlives an orchestrator restart, and the value wins over whatever was baked. Setting ContainerHint.Default clears the runtime hint and lets the baked one stand again.
SetContainerCapacity
void SetContainerCapacity(string containerId, float saturation, CostComponent dominant, bool atCapacity, SaturationCause cause = SaturationCause.None);Publish what the cost telemetry says about how full a container is, so a gateway can answer "is this target at capacity" from the document it already mirrors instead of asking the orchestrator (docs/capacity-admission.md). The orchestrator is the only writer. It does not stamp LeaseInfo.UpdatedAt: that is the mesh's idle clock, and a reading published every pass would keep every scope hot for ever.
ReleaseContainer
void ReleaseContainer(string containerId);RemoveContainer
void RemoveContainer(string containerId);Delete a lease row outright (a dynamic container whose carrier despawned).
ResetControlPlane
void ResetControlPlane();