Nebula
Interest

InterestIndex<T>

Region → dense list of items, with swap-remove and O(1) rebucketing.

generated from the sources
public sealed class InterestIndex<T>

Region → dense list of items, with swap-remove and O(1) rebucketing. Used by the gateway for its entity records and by the worker for its authoritative entities, so both sides answer "what is near this focus" the same way.

Carried entities are bucketed in their root carrier's region and move with it: a ship and everything riding in it enter and leave a client's set as one unit at any speed. Bucketing a passenger by its own resolved position would let a seat straddle a region edge and pop separately from its ship.

Placement follows the root carrier too, not only the region: an item's own Add/AddWide/AddGlobal call is remembered as its own placement and is what it reverts to when it is no longer carried, but while it rides in something it sits exactly where its root carrier sits. An always-relevant passenger of an ordinary ship is therefore a region item in the ship's region, not a global one — otherwise the subtree would not enter and leave as one unit, and every gateway in the mesh would be sent a crate because of the crate's own prefab settings.

Steady state allocates nothing: bucket lists are pooled and reused, and a region is removed from the map (its list returned to the pool) only when it empties, which is also what keeps RegionCount meaningful for the subscriber-mask work on the worker.

Properties

Count

public int Count { get; }

RegionCount

public int RegionCount { get; }

Regions holding at least one item.

Regions

public Dictionary<ulong, List<Entry>>.KeyCollection Regions { get; }

Every non-empty region. Enumerating this is allocation free.

WideCount

public int WideCount { get; }

GlobalCount

public int GlobalCount { get; }

Wide

public Bucket Wide { get; }

Entities whose own relevance radius exceeds the scan radius; tested against each focus directly.

Global

public Bucket Global { get; }

Entities that are in every set.

Methods

Region

public Bucket Region(ulong region);

Items of one region (empty when nothing is there). Allocation free.

Contains

public bool Contains(ulong id);

TryGetValue

public bool TryGetValue(ulong id, out T value);

TryGetPlacement

public bool TryGetPlacement(ulong id, out InterestPlacement placement, out ulong region);

Where the item sits, and in which region when it is bucketed in one.

TryGetOwnPlacement

public bool TryGetOwnPlacement(ulong id, out InterestPlacement placement, out ulong region);

What the item asked for itself, ignoring anything it rides in: what TryGetPlacement would say once it is no longer carried. For diagnostics and for a caller that has to tell the two apart.

CarrierOf

public ulong CarrierOf(ulong id);

The item's direct carrier, or 0 when it is not carried.

RootOf

public ulong RootOf(ulong id);

The outermost carrier this item rides in — the one whose placement the whole subtree takes. An item that is not carried is its own root, and so is one whose carrier is not in the index (yet): that is as far up as this index can see, and it is what the item is placed by until the carrier arrives.

DepthOf

public int DepthOf(ulong id);

How many carriers this item rides inside: 0 for something standing in the world, 1 inside a ship, 2 inside a crate inside that ship. The chain is acyclic (SetCarrier refuses anything else), so the index's own size is the only bound needed and no valid tree is ever cut short — which is what lets publication order a subtree of any depth.

HasCarried

public bool HasCarried(ulong id);

Whether anything is riding in this item, so a caller can skip the subtree work entirely. True for an item that is not in the index too: a passenger can name a carrier that has not arrived yet, and that pending subtree is exactly what has to be republished the moment the carrier does arrive.

CollectCarried

public int CollectCarried(ulong id, List<ulong> result);

Append everything id carries, to any depth, to result, each carrier before what it carries. That order is the one publication needs: a spawn walks it forwards so a container always arrives before its contents, a forget backwards so contents leave before their container. Returns how many ids were appended.

The whole subtree is collected at any width and any depth. There is no cap, because a partly walked tree is a partly rebucketed ship — some of the crates published to the gateways at the destination and the rest left addressed to the ones at the origin — and nothing downstream could tell. What makes that safe is that the links are acyclic: SetCarrier refuses a link that would close a cycle, so a walk cannot revisit an item and the subtree is at most the rest of the index. That count is the only bound here, and it stops a corrupted link set rather than a large valid one.

The carrier itself need not be in the index: children are remembered from the moment they name it, so this is also how the subtree waiting for a missing ship is walked — which is what a publisher needs to announce the transition the ship's arrival causes.

Allocation free: the caller's list is also the walk frontier, and nothing else is touched.

Add

public void Add(ulong id, ulong region, in T value);

Add or update an item in a region. This is the item's own placement: while it rides in something it stays wherever its root carrier is, and this is what it comes back to when it gets off.

AddWide

public void AddWide(ulong id, in T value);

Add or update an item in the wide list (its own relevance radius decides who hears about it).

AddGlobal

public void AddGlobal(ulong id, in T value);

Add or update an always-relevant item.

Move

public bool Move(ulong id, ulong region);

Rebucket an item whose region key changed. Its carried subtree moves with it. Returns false for an unknown item, for a carried one (which is bucketed with its carrier and must not move by itself), and for one whose own placement is wide or global (its reach, not a bucket, decides who hears about it).

SetValue

public bool SetValue(ulong id, in T value);

Replace the record kept for an item, leaving it where it is.

SetCarrier

public CarrierLink SetCarrier(ulong id, ulong carrier);

Link a carried item to the item carrying it (0 detaches). The child — and everything riding in the child — is placed where the root carrier sits at once, and follows it from then on. The carrier need not be in the index yet; the link is remembered either way, and the subtree is placed the moment the carrier is added or moves. Getting off puts the item back on its own placement: a wide or global item returns to its list, and a region one stays in the bucket it was put down in until the next Move says otherwise.

A link that would close a cycle is refused and reported (CarrierLink.Cycle): see the return values for what each outcome means.

Remove

public bool Remove(ulong id);

Clear

public void Clear();

Nested types

Entry

public struct Entry

One item in a bucket: the entity's net id and whatever record the owner of the index keeps.

Fields

Id
public ulong Id;
Value
public T Value;

Bucket

public readonly struct Bucket

Allocation-free view over one bucket.

Properties

Count
public int Count { get; }
this[]
public Entry this[int i] => _list[i];

Methods

GetEnumerator
public Enumerator GetEnumerator();

Nested types

Enumerator
public struct Enumerator

Stores the values used by enumerator.

Properties

Current
public Entry Current { get; }

Methods

MoveNext
public bool MoveNext();

On this page