Nebula
Gateway

NebulaGateway

Accepts client connections and maintains a connection to every worker.

generated from the sources
public sealed partial class NebulaGateway : MonoBehaviour

Accepts client connections and maintains a connection to every worker. It forwards each client's inputs to the worker currently owns that client's entity, and fans the workers' replication streams out to the clients, de-duplicating by authority epoch so a handover is invisible to the client. Nothing here is authoritative: if the gateway dies, clients reconnect and the workers re-announce their entities.

Any number of gateways can serve one mesh. Each has a persistent id and an incarnation that changes on every start; sessions get mesh-wide ids (SessionIds) and a signed session token (SessionTokens) with which a client can reconnect through any gateway and keep its pawn. A gateway asked to drain refuses new clients and tells the ones it has to reconnect elsewhere. It reports its load on every control-plane heartbeat (GatewayStats) for whoever sizes the fleet.

The CLI runs this routing loop in a standalone .NET executable using exported container geometry. The Unity component remains available for compatibility and in-process tests.

Fields

SessionTokenLifetimeSeconds

public const long SessionTokenLifetimeSeconds = 24 * 3600;

How long a session token stays valid. The worker's reclaim grace (NebulaConfig.SessionReclaimSeconds) is what decides whether the pawn is still there.

ExtensionErrors

public uint ExtensionErrors;

Exceptions thrown by a game extension running in this gateway, since the process started: policy calls, client events, posted work. It is a total and not a rate, because the number that matters is "is it still zero". Set by GatewayExtensionHost; reported as GatewayStats.ExtensionErrors.

PreparedRowSeconds

public const double PreparedRowSeconds = 30;

How long a destination row stays pinned for a client after a preparation named it.

ContainerRowLingerSeconds

public const double ContainerRowLingerSeconds = 1;

How long a container row that has left a client's window, but whose lease still exists, is kept before it is withdrawn. Longer than any interpolation delay: a replica gliding out of a box through its buffer is still expressed in that box's frame, and the client despawns what stands in a withdrawn row.

HoldSeconds

public const double HoldSeconds = 10;

The longest an entity's updates wait for a container row this gateway does not have. The control plane delivers a new lease in one long-poll round; past this bound the updates are applied anyway and the entity fails closed exactly as an unknown container always did.

MaxFollowedPassengers

public const int MaxFollowedPassengers = 4096;

Most passengers named by FollowPassengers in one subscription pass, across all the carriers this gateway follows. It keeps the explicit list of an InterestSubscribeMsg well inside its count field; cargo past it is served as before, by region, and may flicker on a scope change.

MaxPortalWindows

public const int MaxPortalWindows = 4;

Most windows one client looks through at once, nearest first (docs/portals.md D22). A client standing where more links' windows overlap sees through the nearest this many.

Properties

Config

public NebulaConfig Config { get; }

ControlPlane

public IControlPlane ControlPlane { get; }

GatewayId

public string GatewayId { get; }

The gateway's persistent id (-nebula-gateway-id, "gw1" by default). Unique per gateway of a mesh.

Incarnation

public uint Incarnation { get; }

This start of the process (SessionIds.NewIncarnation): part of every session id it issues, and reported to workers and the control plane.

ClientCount

public int ClientCount { get; }

PendingJoinCount

public int PendingJoinCount { get; }

Welcomed clients with nowhere to spawn yet: the mesh has no worker holding an active lease, so they are held in JoinState.Starting rather than rejected. Reported on every control-plane heartbeat; the orchestrator treats it as demand and wakes a mesh that has scaled to zero.

WorkerCount

public int WorkerCount { get; }

EntityCount

public int EntityCount { get; }

IsReady

public bool IsReady { get; }

Registered with the control plane, connected to it, and not draining: fit to take clients.

IsListening

public bool IsListening { get; }

The client port is bound. False before Initialize and after a failed bind (Failed).

Failed

public bool Failed { get; }

The gateway could not start, most often because another process holds NebulaConfig.GatewayPort. A failed gateway does nothing: it never registers and never takes a client. FailureReason says why.

AuthKeyPath

public string AuthKeyPath { get; set; }

The file the random player signing key is kept in when neither NebulaConfig.AuthSigningKey nor NebulaConfig.MeshToken is set. Null or empty (the default) keeps it in nebula-auth.key next to the standalone gateway, or in Unity's persistent data folder. Set it before Initialize. The Multiplayer Play Mode dev loop sets it to a file of the project's, so a virtual player signs the same identities the main Editor saved last time.

FailureReason

public string FailureReason { get; }

Why the gateway could not start, or null while it is fine.

Draining

public bool Draining { get; }

Taking itself out of service: new clients are refused, existing ones were told to reconnect elsewhere.

LoopPeriodSeconds

public double LoopPeriodSeconds { get; set; } = 1.0 / 60;

The loop period the host runs Tick at, for the loop-lag figure in GatewayStats. A gap between two ticks longer than this counts as lag. Unity: one frame; the standalone service sets its own.

LastStats

public GatewayStats LastStats { get; }

What was last reported to the control plane (GatewayStats).

TokenValidator

public OidcTokenValidator TokenValidator { get; set; }

The OpenID token checker built from NebulaConfig.AuthIssuers, or null when the mesh trusts no provider. Replace it (before the first client) to supply keys by hand or a different fetcher.

AnonymousIdentities

public AnonymousIdentityIssuer AnonymousIdentities { get; }

Issues and checks anonymous identities, or null when NebulaConfig.AuthAnonymous is off.

Sessions

public SessionTokens Sessions { get; }

Issues and checks the session tokens clients reconnect with.

CertificateFingerprint

public string CertificateFingerprint { get; }

The SHA-256 fingerprint of the certificate's SubjectPublicKeyInfo public-key encoding for encrypted UDP connections, or null when UDP encryption is unavailable. Set NebulaConfig.GatewayFingerprint on clients to require this certificate key.

FarEntityCount

public int FarEntityCount { get; }

Far entities this gateway holds a pose for (NEB-388).

FarEntriesRelayed

public long FarEntriesRelayed { get; }

Far-tier entries sent to clients since start (enters, updates and leaves).

InterestPolicy

public IInterestPolicy InterestPolicy { get; set; }

The game's say in what each client hears about. Defaults to DefaultInterestPolicy: one focus at the pawn plus the client's validated hint. Compose several with InterestPolicies.Combine. If the policy also implements IFocusHintPolicy it gets the say over focus hints too.

Install it before the gateway's first tick — a policy is a security filter, and one installed while clients are already connected has not been asked about what they can already see. Assigning it is the next best thing, and it fails closed: every client's current set is re-authorized against the new policy synchronously, inside this assignment (RevalidateAllInterest), so nothing the new policy refuses is relayed to anybody afterwards. What the new policy newly allows arrives at the clients' next evaluations, which are still spread over the following ticks.

FocusHintsAccepted

public long FocusHintsAccepted { get; }

Focus hints from clients that survived validation and moved a set (FocusHintFilter).

FocusHintsClamped

public long FocusHintsClamped { get; }

Accepted hints that were pulled back to InterestSettings.HintMaxDistance of the pawn.

FocusHintsDroppedByRate

public long FocusHintsDroppedByRate { get; }

Hints dropped for being over InterestSettings.HintMaxHz. Counted per attempt, so a client whose hints are all refused for some other reason still shows up here rather than nowhere.

FocusHintsDroppedMalformed

public long FocusHintsDroppedMalformed { get; }

Hints dropped for being NaN, infinite, or further out than any world (FocusHintFilter.MaxMagnitude).

FocusHintsDroppedUnauthorized

public long FocusHintsDroppedUnauthorized { get; }

Hints dropped because this client may not have one: no pawn to clamp to in FocusMode.PawnClamped, FocusMode.Disabled, or an IFocusHintPolicy that refused it.

InterestSettingsInUse

public InterestSettings InterestSettingsInUse { get; }

The resolved interest knobs this gateway runs on (clamped; see InterestSettings.Validate).

InterestGridInUse

public InterestGrid InterestGridInUse { get; }

The region grid both ends must agree on; it travels in every subscription message.

CachedEntityCount

public int CachedEntityCount { get; }

Entity records cached: what this gateway's clients' subscriptions bring in, not the world.

SubscribedRegionCount

public int SubscribedRegionCount { get; }

Distinct regions subscribed across every worker link.

WorkerLinkCount

public int WorkerLinkCount { get; }

Worker links currently held, for a test or an operator that wants the number without the heartbeat.

ContainerRowCap

public int ContainerRowCap { get; }

Most container rows one of this gateway's clients may be told about in one evaluation (InterestSettings.MaxContainerRows at the world's cell size). Derived from the interest settings, not configured separately.

ContainerBudgetHits

public long ContainerBudgetHits { get; }

Evaluations in which a client's foci asked for more container rows than ContainerRowCap allows, so the far ones were withheld. A number that keeps climbing means a policy is handing out more or wider foci than the mesh is sized for; it is never a correctness problem, because the rows entities stand in are collected before the cap applies.

PortalCount

public int PortalCount { get; }

How many portals this gateway holds a record of (docs/portals.md D45).

PortalLinkCount

public int PortalLinkCount { get; }

How many portal links this gateway holds a record of.

PortalWindowCount

public int PortalWindowCount { get; }

How many windows this gateway's clients look through right now, in all (docs/portals.md D22): what an operator reads to see how much of the mesh's interest is spent on portals.

Events

ClientJoined

public event Action<GatewayClientInfo> ClientJoined;

An authenticated client has been welcomed: it has a session id and an identity, and its interest is about to be evaluated for the first time. This is where a server extension gives it its tags and its focus mode, because both are read by the very first evaluation.

Raised on the gateway loop, synchronously, and the gateway is mid-welcome: a handler may call the SetClient…/MarkInterestDirty methods and read the gateway's own state, but it must not block, and it must not throw (an exception is caught and logged, never passed to the client).

ClientLeft

public event Action<GatewayClientInfo> ClientLeft;

A welcomed client is gone from this gateway: it disconnected, was rejected, or its session was taken over somewhere else (in which case another gateway raises its own ClientJoined). Raised on the gateway loop under the same rules as ClientJoined; by the time it runs the client's interest state has already been let go, so its id no longer resolves.

Methods

Initialize

public void Initialize(NebulaConfig config, IControlPlane controlPlane, ITransport browserTransport = null, string gatewayId = null);
ParameterDescription
browserTransportA second transport clients arrive on, already listening: the standalone gateway's WebRTC listener for web builds. Null accepts UDP clients only.
gatewayIdThis gateway's id; null reads -nebula-gateway-id ("gw1" by default).

Drain

public void Drain();

Take this gateway out of service: refuse new clients and tell the connected ones to reconnect within NebulaConfig.GatewayDrainReconnectSeconds (a load balancer hands them to another gateway, where their session token gets them their pawn back). Called when the control plane carries a drain request for this gateway (IControlPlane.SetGatewayDraining), or directly.

StopDraining

public void StopDraining();

Cancel a drain (the fleet changed its mind): new clients are accepted again.

Kick

public bool Kick(ulong clientId, ushort code, string reason);

Remove a player: the client is sent the code and reason (KickedMsg), the link is closed a moment later, and the session ends at once: the worker that owns the pawn despawns it without waiting out the reclaim grace, calling NebulaGameMode.OnPlayerDespawn first. The code is the game's own; Nebula passes it through. Returns false when no welcomed client of this gateway has clientId. A kick is not a ban: the player may connect again as a new session.

ClientHoldsFar

public bool ClientHoldsFar(ulong clientId, ulong netId);

Whether a client holds an entity in its far tier (tests and diagnostics); false for an unknown client.

FarTierSize

public int FarTierSize(ulong clientId);

How many entities a client holds in its far tier; -1 for an unknown client.

IsEntityCached

public bool IsEntityCached(ulong netId);

Whether this gateway holds a record for one entity. The count above says how much is cached; this says what, which is the only way to tell "never sent here" from "sent and filtered out per client" — a distinction a client-side set cannot answer.

TryGetCachedPlacement

public bool TryGetCachedPlacement(ulong netId, out InterestPlacement placement, out ulong region);

Where this gateway currently has an entity bucketed: its effective placement, which for a carried entity is its root carrier's and not what its own prefab asked for. For tests and for the debug tooling; false when no record is held.

SetClientTag

public void SetClientTag(ulong clientId, byte team);

A byte the game attaches to a client for its policy to filter on (team, faction, party). It is server state: the client never sends it, which is what makes a team filter a security boundary rather than a suggestion. Changing it revokes at once and reveals shortly after: everything the policy no longer authorizes under the new tag is despawned inside this call (RevalidateInterest), and what the new tag newly allows arrives at the client's next evaluation.

GetClientTag

public byte GetClientTag(ulong clientId);

What SetClientTag last set for this client (0 by default).

SetClientTags

public void SetClientTags(ulong clientId, ulong tags);

Sixty-four more bits of the same thing (InterestClient.Tags): alliances a faction is in, roles a player holds, fronts a commander is watching — anything a policy wants to test that does not fit in SetClientTag's single byte. Server state, like the tag, and with the same timing: what the new tags no longer authorize is despawned inside this call, and what they newly reveal arrives at the client's next evaluation.

GetClientTags

public ulong GetClientTags(ulong clientId);

What SetClientTags last set for this client (0 by default).

SetClientFocusMode

public void SetClientFocusMode(ulong clientId, FocusMode mode);

How far this client's own focus hint may move its interest. The default is FocusMode.PawnClamped, so a hint is worth at most InterestSettings.HintMaxDistance of the pawn and a client with no pawn has no hint at all; FocusMode.Free is what gives a strategy camera or a spectator the run of the world. Only the server may set it — the client cannot ask for it, and cannot tell that it has it except by what it is sent.

Instance isolation is unaffected in every mode: a free focus is a wider view of the world the client is already in, never a way into another one.

Changing the mode drops the hint the gateway is holding, because a hint accepted under one mode is not an input the next one ever validated; the client's next hint (within 1/InterestSettings.HintMaxHz s) is judged afresh.

GetClientFocusMode

public FocusMode GetClientFocusMode(ulong clientId);

What SetClientFocusMode last set for this client (FocusMode.PawnClamped by default).

MarkInterestDirty

public void MarkInterestDirty(ulong clientId);

Put this client at the front of the evaluation queue: a reveal, and additive work generally (a party joined, a unit was given to the player, a fog cell was uncovered). The gateway calls it itself when the pawn, instance, carrier, focus mode or focus region changed.

The timing is honest about what it is: dirty clients jump the routine rotation but are still bounded per tick (InterestSchedule.MinDirtyPerTick, InterestSchedule.DirtyBurst), so on a busy gateway a client may wait a few ticks for its turn. That is the right trade for showing something more, and the wrong one for taking something away: for a change that tightens what may be seen call RevalidateInterest, which revokes before this call would even have run.

MarkAllInterestDirty

public void MarkAllInterestDirty();

The same for every client: a world-wide reveal. The evaluations are spread over the following ticks rather than all run on the next one — see InterestSchedule — so this is cheap to call on a live gateway, and for the same reason it is not how a revocation is applied. Use RevalidateAllInterest when a sweep can take visibility away.

RevalidateInterest

public void RevalidateInterest(ulong clientId);

Re-authorize everything this client can already see, now, and despawn whatever IInterestPolicy.Authorize no longer allows before any further traffic about it is relayed. This is the immediate half of MarkInterestDirty and the call to make whenever a change can take visibility away: a team or alliance change, a stealth roll, fog closing over a region, an access rule revoked.

It costs one authorization per entity the client already holds — no grid query, no candidate scan — and allocates nothing. It never adds anything: the client is also marked dirty, so what the same change reveals arrives at its next evaluation, within an eval interval.

Safe to call from a policy callback: a nested call falls back to marking the client dirty rather than re-entering the pass.

RevalidateAllInterest

public void RevalidateAllInterest();

RevalidateInterest for every client: a world-wide tightening (a new policy, a war declared, a fog sweep that can close as well as open). Every unauthorized replica on this gateway is gone by the time the call returns.

The cost is one authorization per replica the gateway is currently serving — about what one round of evaluation costs, concentrated into this call instead of spread over an eval interval. That is affordable for the events this is for, and it is the reason the routine rotation is still staggered: this is not a per-tick path, and nothing calls it for ordinary movement or camera updates.

InterestSetSize

public int InterestSetSize(ulong clientId);

Entities in one client's set right now (-1 when there is no such client). For tests and tooling.

TryGetPortalLinkState

public bool TryGetPortalLinkState(ulong linkId, out PortalState state, out PortalWait wait);

The state this gateway last heard of a link, or false when it has no record of it.

PortalObserves

public bool PortalObserves(ulong clientId, ulong linkId);

Whether a client may look through a link, from the observer list its worker published (docs/portals.md D21): the rule the gateway enforces without running game code. False for a link it has no record of.

PortalWindowsOf

public int PortalWindowsOf(ulong clientId);

How many windows one client looks through right now (InterestClient.PortalWindows); 0 for a client this gateway does not know.

Nested types

GatewayClientInfo

public readonly struct GatewayClientInfo

A client the gateway has admitted, as the server-side surface reports it. A snapshot taken on the gateway loop: nothing in it is a live reference.

Constructors

GatewayClientInfo
public GatewayClientInfo(ulong clientId, string identity, string name, ulong instanceId, bool isBot, bool reclaimed);
public GatewayClientInfo(ulong clientId, string identity, string name, ulong instanceId, bool isBot, bool reclaimed, IReadOnlyDictionary<string, string> claims);

Fields

ClientId
public readonly ulong ClientId;

The mesh-wide session id; the same across a reconnection with a session token.

Identity
public readonly string Identity;

The authenticated subject (PlayerIdentity), "" when there is none.

Name
public readonly string Name;
InstanceId
public readonly ulong InstanceId;

The simulation scope the client's pawn is in; 0 is the public world (and a client with no pawn yet).

IsBot
public readonly bool IsBot;
Reclaimed
public readonly bool Reclaimed;

The session was reclaimed (a reconnection), not started fresh.

Properties

Claims
public IReadOnlyDictionary<string, string> Claims { get; }

The verified token's forwarded claims (NebulaConfig.ForwardedClaims); empty, never null, for an anonymous player.

On this page