MeshTelemetry
The data behind the World map page of the Nebula Dashboard (/map).
Nebula · Packages/com.1by3.nebula/Runtime/Orchestrator/MeshTelemetry.cspublic sealed class MeshTelemetryThe data behind the World map page of the Nebula Dashboard (/map). It has two halves:
- Geometry, built once on the orchestrator's main thread from
ContainerRegistryand the world definition: every static container's box in absolute world coordinates, its cell, the container that encloses it and its neighbors (GET /api/map/geometry). - Telemetry, the latest document each worker posted (
WorkerTelemetry,POST /api/telemetry): entity counts per container, the pose of every carried container whose carrier it simulates, the cells it has loaded and, while somebody has the map open, its authoritative entities (GET /api/map).
Thread-safe: workers post, and the page reads, on the dashboard's listener threads. Worker documents are kept as received and spliced into the map document unparsed (the runtime carries no JSON reader); the page does the aggregation.
Constructors
MeshTelemetry
public MeshTelemetry(Func<double> now = null);| Parameter | Description |
|---|---|
now | Seconds on a monotonic clock; a stopwatch by default. |
Fields
MaxDocumentChars
public const int MaxDocumentChars = 4 * 1024 * 1024;Largest worker document accepted, in characters.
DetailWindowSeconds
public const double DetailWindowSeconds = 5.0;Workers are asked for their entities while the map was read within this many seconds.
ExpireSeconds
public const double ExpireSeconds = 15.0;A worker document older than this is dropped: its worker died, retired or stopped posting.
TickPeriodMs
public float TickPeriodMs = WorkerLoadTracker.TickPeriodMs;The tick budget a container's measured simulation time is weighed against when its dominant cost component is decided (ContainerCost.Resolve). The orchestrator sets it from the mesh's tick rate; it is WorkerLoadTracker.TickPeriodMs by default.
LinkBytesPerSec
public double LinkBytesPerSec = NebulaConfig.DefaultCostLinkBytesPerSec;The outbound budget a container's bytes are weighed against, in bytes per second (NebulaConfig.CostLinkBudgetMbps). Only the comparison between the three components depends on it; the reported bytes are measured either way.
CapacitySaturation
public float CapacitySaturation;How saturated a container has to be before it counts as at capacity (NebulaConfig.CapacitySaturation, docs/capacity-admission.md). Only reported here; the orchestrator is what publishes the flag to the mesh. 0 leaves every row below capacity.
Properties
DetailWanted
public bool DetailWanted { get; }Somebody read the map within DetailWindowSeconds, so workers should include their entities.
GeometryVersion
public int GeometryVersion { get; }Bumped by every PublishGeometry; the page refetches the geometry when it changes.
GeometryJson
public string GeometryJson { get; }The static geometry document served at /api/map/geometry.
WorkerCount
public int WorkerCount { get; }Workers with a document on file (expired ones are only dropped when the map is read).
Methods
PublishGeometry
public void PublishGeometry(string json);Replace the static geometry document (see BuildGeometryJson).
Accept
public string Accept(string json, out bool detail);Store a worker's telemetry document, replacing its previous one. The document must be a JSON object whose first property is "worker", as WorkerTelemetry writes it. Returns null when it was stored, otherwise why it was refused. detail is the answer for the worker: whether its next document should include entities.
CopyOccupancy
public void CopyOccupancy(Dictionary<string, ContainerLoad> result);Snapshot the latest per-container counts into result (cleared first), dropping reports older than ExpireSeconds. Containers nobody reported are absent. This is what the cost-aware assignment policy reads once per pass.
CopyContainerCost
public void CopyContainerCost(Dictionary<string, ContainerCost> result);Snapshot the latest cost row of every container into result (cleared first), dropping reports older than ExpireSeconds. One row per container, which is one row per lease: this is the typed per-container signal the scaler explains a blocked grow with and GET /api/cost serves (docs/cost-telemetry.md).
BuildCostJson
public string BuildCostJson();The cost rows as one JSON document, newest first by cost: the body of GET /api/cost.
ParseContainers
public static void ParseContainers(string json, List<KeyValuePair<string, ContainerLoad>> result, List<ContainerCost> costs = null);Pull the per-container counts out of a worker document without parsing the rest of it (the entity list can be megabytes). The array looks like "containers":[{"id":"arena","players":1,"bots":0,...},...] as WorkerTelemetry writes it; the slot for entities in no container ("") is skipped.
When costs is given, the same pass also collects the cost row of each container (ContainerCost, docs/cost-telemetry.md). A document from a worker that does not write those keys yields rows of zeroes rather than nothing, which is what ContainerLoad.HasEntityCost is for: the counts still balance the mesh as they always did.
CopyInterest
public void CopyInterest(Dictionary<string, WorkerInterest> result);Snapshot each worker's interest summary into result (cleared first), dropping reports older than ExpireSeconds. This is what the dashboard's worker rows read.
ParseInterest
public static bool ParseInterest(string json, out WorkerInterest interest);Pull the "interest" object out of a worker document without parsing the rest of it. Deliberately narrow: it reads the flat numeric and string properties of that one object and stops at its nested "gateways" array, which only the map page needs.
Forget
public void Forget(string workerId);Drop a worker's document now instead of waiting for it to expire.
BuildMapJson
public string BuildMapJson();The live map document: every worker's latest telemetry, with its age in seconds, sorted by worker id. Reading it marks the map as watched, which asks workers for their entities (DetailWanted).
CopyHolds
public void CopyHolds(Dictionary<string, float> result);Snapshot the live holds into result (cleared first) as container id -> seconds still to run, dropping the ones that have expired. This is what the planner skips moves for.
HolderOf
public string HolderOf(string containerId);The worker that asked for a container's hold, or "" when it is not held.
CopyCohesion
public void CopyCohesion(List<CohesionGroupInfo> result);Snapshot the cohesion groups every live worker reported into result (cleared first), one row per group with the union of the containers its members sit in and the workers that reported it. Rows from a worker that stopped reporting are dropped after ExpireSeconds.
ParseHolds
public static void ParseHolds(string json, List<KeyValuePair<string, float>> result);Pull the "holds" array out of a worker document: [{"id":"arena","seconds":4.2},...], as WorkerTelemetry writes it before the container counts and the entity list.
ParsePortalLinks
public static void ParsePortalLinks(string json, List<PortalLinkSpan> result);Pull the "portalLinks" array out of a worker document: [{"id":"1f","portal":"door","state":"Open","wait":"None","split":0,"in":["a","b"]},...] (docs/portals.md D34). The containers are under "in" for the reason ParseCohesion gives.
ParseCohesion
public static void ParseCohesion(string json, List<CohesionSpan> result);Pull the "cohesion" array out of a worker document: [{"group":17,"members":3,"in":["a","b"]},...]. The containers are under "in" rather than "containers" so that ParseContainers, which scans for the first "containers" key in the document, cannot land in this block. Rows without a group are ignored.
BuildGeometryJson
public static string BuildGeometryJson(NebulaConfig config);Every container of the loaded level as the map draws it: its box in absolute world coordinates (ToAbsolute), its cell when it belongs to an authored partition, the smallest container enclosing it and its static neighbors, plus the world's cells and the ghost band and hysteresis the mesh runs with. A runtime-only world is partitioned even though its authored container grid is empty. Main thread.
EnclosingStatic
public static Container EnclosingStatic(Container c);The smallest static container whose box holds c's, or null. In a partitioned world only c's own cell is searched.
ToAbsolute
public static void ToAbsolute(Vector3 frame, out double x, out double y, out double z);
public static void ToAbsolute(Vector3 frame, Vector3Int originCell, Vector3 cellSize, out double x, out double y, out double z);Absolute world position of a position in this process's frame. Without a partitioned world the frame is the world. With one, every process keeps its own floating origin (WorldOrigin), so the origin cell's offset is added back: two workers report the same place with the same numbers.
Nested types
WorkerInterest
public struct WorkerInterestThe interest summary of one worker's latest document: how much of what it holds it actually sends, what that costs, and whether it is asking for the world to be partitioned.
Fields
Regions
public int Regions;Gateways
public int Gateways;FilterMs
public double FilterMs;Global
public bool Global;EntriesSent, EntriesTotal, BytesSent, BytesUnfiltered
public long EntriesSent, EntriesTotal, BytesSent, BytesUnfiltered;Warning
public string Warning;ReceivedAt
public double ReceivedAt;CohesionSpan
public struct CohesionSpanWhat one worker said about one cohesion group in its latest document.
Fields
Group
public uint Group;Members
public int Members;Containers
public List<string> Containers;WorkerId
public string WorkerId;ReceivedAt
public double ReceivedAt;PortalLinkSpan
public struct PortalLinkSpanOne portal link that was not closed in a worker's latest document (docs/portals.md D34, D36).
Fields
LinkId
public string LinkId;Portal
public string Portal;State
public string State;Wait
public string Wait;Split
public bool Split;Containers
public List<string> Containers;The near face's container, then the far containers its window touches.
WorkerId
public string WorkerId;ReceivedAt
public double ReceivedAt;LogBuffer
The mesh's recent log lines, kept on the orchestrator so one place holds what every process of the mesh said last: workers, gateways and the orchestrator's own shipper post batches to POST /api/logs ({"lines":[{"at","role","instance","le...
NebulaCapacity
Deriving the capacity signal from cost rows, and reading it back off the control plane.