Nebula
Orchestrator

MeshTelemetry

The data behind the World map page of the Nebula Dashboard (/map).

generated from the sources
public sealed class MeshTelemetry

The 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 ContainerRegistry and 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);
ParameterDescription
nowSeconds 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.

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 WorkerInterest

The 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 CohesionSpan

What 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 PortalLinkSpan

One 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;

On this page