Nebula
Containers

ContainerRegistry

The container graph of the loaded level: every static Container, indexed deterministically so all processes agree on indices, plus adjacency (adjacent = bounds touch or overlap), plus the dynamic containers currently carried by entities ...

generated from the sources
public static class ContainerRegistry

The container graph of the loaded level: every static Container, indexed deterministically so all processes agree on indices, plus adjacency (adjacent = bounds touch or overlap), plus the dynamic containers currently carried by entities on this process, resolved by their carrier's net id, plus the runtime containers the game registered while the mesh runs, resolved by the id the game gave them.

Two ways to populate the static part: Rebuild scans the loaded scene and sorts by id (single-scene games), or Load takes an already ordered list built from a WorldContainerManifest (partitioned worlds), in which case lookups go through a grid of cells instead of a linear scan. Dynamic containers come and go with their carriers (RegisterDynamic / UnregisterDynamic, called by NetworkIdentity when an entity with a container on its root spawns and despawns). Runtime containers come and go with their control-plane lease (RegisterRuntime / UnregisterRuntime, called by every role from SyncRuntime); a world whose shape is decided at runtime (a chunked landscape) has no baked set at all and lives entirely in them.

Fields

WorkerIdByIndex

public static Func<ushort, string> WorkerIdByIndex = index => NebulaRuntime.IsServer && index == NebulaRuntime.LocalWorkerIndex ? NebulaRuntime.LocalWorkerId : "";

How a worker index maps to a worker id, for the derived ownership of dynamic containers (Container.OwnerWorkerId). The worker installs one that knows its peers; elsewhere the default only recognises this process's own worker id.

RuntimeIdPrefix

public const string RuntimeIdPrefix = "rt_";

Prefix of a runtime container's string id (rt_<id>), the form leases and persistence records use.

Properties

PendingRuntimeCount

public static int PendingRuntimeCount { get; }

Runtime rows waiting for their parent to become resolvable on this process.

PendingRuntimeIds

public static IEnumerable<ulong> PendingRuntimeIds { get; }

The ids of the runtime rows waiting for their parent (see PendingRuntimeCount).

All

public static IReadOnlyList<Container> All { get; }

The static containers, in wire order.

Count

public static int Count { get; }

Dynamic

public static IReadOnlyList<Container> Dynamic { get; }

The dynamic containers present on this process (their carriers are resident here), in registration order.

Runtime

public static IReadOnlyList<Container> Runtime { get; }

The runtime containers registered on this process, in registration order.

IsGridded

public static bool IsGridded { get; }

A partitioned world's manifest is loaded: containers know their cell and lookups use the grid.

RuntimeBucketSize

public static float RuntimeBucketSize { get; set; }

Edge length, in meters, of the buckets runtime containers are hashed into. Set it before the first RegisterRuntime (a chunk size or a small multiple of it is right); changing it later rehashes.

RuntimeBoundsInFrame

public static Func<ulong, Bounds, Bounds> RuntimeBoundsInFrame { get; set; }

Optional mapping from a public runtime container's stable ID to bounds in the current origin frame. Called during registration and origin shifts. Calculate the center from precise coordinates before converting to floats. Origin shifts use only the returned center; container sizes remain unchanged. Clear this callback when its world unloads. It is reset when a new play session starts.

RuntimeClaims

public static Func<Container, Vector3, bool> RuntimeClaims { get; set; }

Optional say over whether a runtime container placed inside another (a chunk of a hosted grid, docs/container-tree.md D22) holds a point that its box holds: given the container and the point in its space, false keeps the container out of resolution (D3) for that point. A grid whose cells' boxes overlap, such as a curved surface's, decides membership by the cell instead of the box. Null, or true, keeps the box's answer. Nebula.World.NebulaChunks installs it. Reset when a new play session starts.

Events

Rebuilt

public static event Action Rebuilt;

LeasesChanged

public static event Action LeasesChanged;

Raised after a batch of leases was applied (the worker and client do this whenever the control plane changes).

DynamicRegistered

public static event Action<Container> DynamicRegistered;

A dynamic container became resolvable here (its carrier spawned). Messages that were waiting for it can be applied.

DynamicUnregistering

public static event Action<Container> DynamicUnregistering;

A dynamic container is about to go (its carrier is despawning). Its contents are moved to the container around it right after.

RuntimeRegistered

public static event Action<Container> RuntimeRegistered;

A runtime container became resolvable here. The game loads whatever content belongs in that box (terrain, colliders, visuals).

RuntimeUnregistering

public static event Action<Container> RuntimeUnregistering;

A runtime container is about to go. The game unloads its content; entities still inside are moved to the container around them right after.

Methods

IsHeldForCarrier

public static bool IsHeldForCarrier(ulong runtimeId);

Whether the runtime container runtimeId has a row here held for a parent that is a carried container this process does not hold (a chunk of a grid hosted by a planet whose carrier this client has not been sent, docs/container-tree.md D22). Its content is that carrier's scope's, which arrives with the carrier once the process is in that scope.

Rebuild

public static void Rebuild();

Scan the loaded scene(s) for static containers and index them by id. Containers carried by entities (on an entity's root, Container.IsEntityObject) and runtime containers are skipped.

Load

public static void Load(IList<Container> ordered, bool gridded);

Index ordered as given (position = wire index). With gridded every container must carry its Container.Cell; adjacency is then only tested between containers of neighboring cells and Find only visits the cells around the point. Dynamic and runtime containers registered earlier are forgotten: this is a boot-time operation.

Overlapping

public static void Overlapping(Bounds box, List<Container> result, ulong instanceId = 0);

Every container whose box overlaps box, appended to result (cleared first). This is the query interest management uses to resolve a region to its owning workers, so it must never walk the whole world: a gridded world is answered from the cell grid, a runtime world from the spatial hash, and only the dynamic list — which is small by construction — is scanned linearly. An ungridded static set is scanned linearly too, which is bounded because an ungridded world is a handful of hand-placed containers.

InCell

public static IReadOnlyList<Container> InCell(Vector3Int cell);

Containers whose cell is cell (the cell container and its nested ones). Empty when not gridded.

Get

public static Container Get(ushort index);

The static container with this wire index, or null (also null for ContainerRef.DynamicIndex and ContainerRef.RuntimeIndex: use Resolve).

Resolve

public static Container Resolve(ContainerRef r);
public static Container Resolve(Vector3 worldPosition, Container current, float hysteresis, NetworkIdentity subject = null);

The container a wire reference names, static, dynamic or runtime, or null when it is not known on this process (a dynamic container whose carrier has not arrived yet, a runtime container whose lease has not).

FindDynamic

public static Container FindDynamic(ulong carrierNetId);

The dynamic container carried by entity carrierNetId, or null.

RefreshCaches

public static void RefreshCaches();

Re-read every container's transform (the worker does this once per tick; see Container.RefreshCache).

FindById

public static Container FindById(string id);

The static or runtime container with this id, or the dynamic container currently registered under it (label#netId), or null.

IsDynamicId

public static bool IsDynamicId(string id);

Whether a container id names a dynamic container (label#netId): static ids never contain '#'.

CarrierNetIdOf

public static ulong CarrierNetIdOf(string id);

The carrier net id a dynamic container id names, or 0.

RegisterDynamic

public static void RegisterDynamic(Container container, NetworkIdentity carrier);

Make container the dynamic container carried by carrier, resolvable by the carrier's net id from now on. NetworkIdentity calls this when an entity with a container on its root spawns on any process; game code does not normally need to. The authored Container.ContainerId becomes a label: the runtime id is label#netId, unique across the mesh.

UnregisterDynamic

public static void UnregisterDynamic(Container container);

Forget a dynamic container. Whatever is still inside it is moved to the container around it (resolved from each entity's world position, ignoring the departing box, among the boxes of the box's own scope) and re-parented, so a client destroying a despawned ship does not take the passengers' objects with it. The authority decides what actually happens to them on the next tick; despawn the contents first if they should not survive the carrier.

RuntimeContainerId

public static string RuntimeContainerId(ulong id);

The string id a runtime container registered as id has: rt_<id>.

IsRuntimeId

public static bool IsRuntimeId(string id);

Whether a container id names a runtime container (rt_<id>).

TryParseRuntimeId

public static bool TryParseRuntimeId(string id, out ulong runtimeId);

The 64-bit id a runtime container id (rt_<id>) names.

GetRuntime

public static Container GetRuntime(ulong id);

The runtime container registered as id on this process, or null.

RegisterRuntime

public static Container RegisterRuntime(ulong id, Bounds frameBounds, InstanceContainerInfo instance = null);
public static Container RegisterRuntime(ulong id, ContainerPlacement placement, InstanceContainerInfo instance = null);

Register a static box that is not in the baked set, named by a 64-bit id the game chose (a chunk coordinate hash, a plot number) and axis-aligned in this process's frame. Legal at any time after boot, on any role; every process that should resolve the container has to register it under the same id with the same box, which is what SyncRuntime does from the control-plane leases. Registering an id that is already here returns the existing container (its box is updated if it changed). Adjacency to baked and runtime containers whose boxes touch is computed at once, so ghosting and handover across the seam work like between baked containers.

PlacementOf

public static ContainerPlacement PlacementOf(Container c);

The placement a runtime container was registered with, as its lease row carries it: a root's absolute centre in double, a child's parent id and parent-local centre. What a worker writes when it re-creates a row it lost.

UnregisterRuntime

public static bool UnregisterRuntime(ulong id);

Forget a runtime container and destroy its object. Whatever is still inside is moved to the container around it first; the game normally retires a container only once it is empty, and persists or despawns the rest before calling this. Neighbours drop their adjacency to it.

SyncRuntime

public static void SyncRuntime(IReadOnlyList<LeaseInfo> leases);

Mirror the runtime containers the control plane names: register every lease that carries a box and is not here yet, forget every runtime container whose lease is gone. Boxes on the row are absolute; they are brought into this process's frame. Workers, the gateway and the orchestrator call this whenever the control plane changes, before applying the leases; clients do the same from the ownership message.

PruneRuntime

public static void PruneRuntime(HashSet<ulong> keep);

Forget every runtime container whose id is not in keep.

ToFrame

public static Bounds ToFrame(Bounds absolute);
public static Bounds ToFrame(Bounds absolute, ulong instanceId);
public static Vector3 ToFrame(Double3 absolute, ulong instanceId);

An absolute box (as leases and telemetry carry it) in the public world's frame.

ToAbsolutePrecise

public static Double3 ToAbsolutePrecise(Vector3 frame, ulong instanceId);
public static Double3 ToAbsolutePrecise(Double3 frame, ulong instanceId);

The inverse of ToFrame: a frame position as an absolute one, in double.

ToAbsolute

public static Bounds ToAbsolute(Bounds frame);
public static Bounds ToAbsolute(Bounds frame, ulong instanceId);

A box in the public world's frame as an absolute box: the inverse of ToFrame.

ShiftRuntime

public static void ShiftRuntime(Vector3 delta);
public static void ShiftRuntime(ulong frameId, Vector3 delta);

The public world's floating origin moved: every container in the public frame moves with it.

NeighborsOf

public static void NeighborsOf(Container container, List<Container> result);

Every container adjacent to container right now, into result (cleared first): its static Container.Neighbors (baked and runtime) plus every dynamic container whose box currently touches it, and, for a dynamic container, the container its carrier sits in and every container its box touches. This is what the ghost band walks; use it instead of Container.Neighbors wherever a vehicle could be parked next door.

Find

public static Container Find(Vector3 worldPosition, Container exclude = null, ulong instanceId = 0, NetworkIdentity subject = null);

The container whose volume holds the point; if none does, the nearest one. Containers may nest (an "outdoor" box enclosing per-building boxes, a ship inside the outdoor box): when several hold the point the smallest volume wins, so the most specific container is chosen. Pure geometry, so every process agrees. exclude leaves one container out of the search.

subject is the entity being placed, when there is one. The containers it carries, directly or through any chain of carriers (Container.IsCarriedBy), are skipped: its own box (its origin is inside it), and the box of a ship whose interior overlaps its own and which already rides inside it. The next smallest box holding the point wins instead, so two carriers whose interiors overlap end up one inside the other at most, never each inside the other.

FindInSpace

public static Container FindInSpace(Vector3 position, Container exclude, ulong instanceId, NetworkIdentity subject, Container space);

The deepest container of space space holding the point (a space is a framed container, whose frame-local coordinates the point is in, or null for the scope's own space), or null when none holds it: no nearest-box fallback. A framed container's own box is in the space around it, so it is a candidate there and never in its own space (see Container.SignedDistanceInner for that face).

Along

public static void Along(Vector3 a, Vector3 b, float margin, List<Container> result, ulong instanceId = 0);

Every container whose box (grown by margin) the segment a-b passes through, appended to result. Nested containers are all reported: an entity in a building is in the building's box and in the outdoor box around it. This is what a hitscan or a line of sight uses to find out which workers, besides itself, could own something along the ray. In a gridded world only the cells the segment's bounds touch are visited; runtime containers are found through their hash and dynamic containers are always tested.

ApplyLease

public static void ApplyLease(string containerId, string workerId, ushort workerIndex, ulong epoch, string state = LeaseState.Active);

Record a control-plane lease on its container. A lease for a dynamic or runtime container that is not resident yet is kept and applied when it registers. state is the lease state (LeaseState); for a dynamic container only LeaseState.Pinned changes who owns it.

ForgetLease

public static void ForgetLease(string containerId);

A lease row went away (a carrier despawned, a runtime container was retired): forget whatever was recorded for it.

NotifyLeasesChanged

public static void NotifyLeasesChanged();

Tell listeners (world streaming, overlays) that a batch of ApplyLease calls is complete.

On this page