Nebula
Interest

ClientInterest<T, TSource>

One client's interest set and the state machine that maintains it.

generated from the sources
public sealed class ClientInterest<T, TSource> where TSource : struct, IInterestSource<T>

One client's interest set and the state machine that maintains it. Evaluation walks only the regions the client's foci cover, so its cost follows what is near the client rather than the size of the world, and the set is stable: an entity enters at the relevance radius and leaves only once it has been more than InterestSettings.ExitMargin further out for InterestSettings.LingerSeconds. That hysteresis is what stops an entity walking a boundary from spawning and despawning several times a second on every nearby client.

Enter and leave events come out in an order the client can act on: carriers before their contents on the way in (a passenger's spawn names its ship's container) and contents before carriers on the way out.

Constructors

ClientInterest

public ClientInterest(TSource source);

Fields

Client

public InterestClient Client;

Who this set belongs to; handed to the policy's authorization on every test.

Settings

public InterestSettings Settings = InterestSettings.Default;

Grid

public InterestGrid Grid = InterestGrid.Resolve(InterestSettings.Default);

The grid the region scan uses. Must be the grid the index was bucketed with.

ScopeSalt

public ulong ScopeSalt;

This client's scope salt (RegionKeys.SaltOf), applied to every region the scan collects. 0 — the public world — leaves the keys exactly what they were. It must be the salt the index was bucketed with, or the scan looks in another world's buckets (docs/scope-frames.md D7).

Clock

public Func<double> Clock;

Where the evaluation gets "now" from; tests and the standalone gateway supply their own.

Properties

Count

public int Count { get; }

Ids

public Dictionary<ulong, Member>.KeyCollection Ids { get; }

ScannedRegions

public int ScannedRegions { get; }

Regions the last evaluation scanned; the gateway subscribes exactly these (plus its margin).

Foci

public IReadOnlyList<InterestFocus> Foci { get; }

Methods

Contains

public bool Contains(ulong netId);

SetFoci

public void SetFoci(IReadOnlyList<InterestFocus> foci);

SetAlways

public void SetAlways(IReadOnlyList<ulong> ids);

Entities that are in the set whatever the distance: the client's own pawn, what it owns, and the policy's explicit extras. They are still authorized — an extra is not a way around a security filter.

AddAlways

public void AddAlways(ulong netId);

RemoveAlways

public void RemoveAlways(ulong netId);

Clear

public void Clear();

Forget the whole set without emitting leaves (the client is gone).

Remove

public bool Remove(ulong netId, List<ulong> left);

Take an entity out of the set now: it was despawned, its worker died, or the gateway forgot it. Appends to left when the client had it.

Revalidate

public void Revalidate(InterestIndex<T> index, List<ulong> left);

Re-run authorization over the set exactly as it stands and drop whatever no longer passes, appending the losses to left. Nothing is added: this is the revocation half of an evaluation, for the moment something the security filter depends on was tightened — a new policy, a changed team, a fog sweep — and the answer must not wait for the client's turn in the rotation.

It costs one IInterestSource<T>.Authorize per entity the client already holds and makes no grid query at all, so it is strictly cheaper than the evaluation it precedes. The additive half — what the change newly reveals — is left to that evaluation, because revealing late is a latency bug and revoking late is a security one.

Evaluate

public void Evaluate(InterestIndex<T> index, List<ulong> entered, List<ulong> left);
public void Evaluate(InterestIndex<T> index, double now, List<ulong> entered, List<ulong> left);

Bring the set up to date against the index and append what changed. Both lists may be null when the caller only wants the set maintained (a re-evaluation after a policy change, say).

ConsiderOne

public void ConsiderOne(InterestIndex<T> index, ulong id, double now, List<ulong> entered, List<ulong> left);

Test one entity against this client and nothing else. This is what an arrival costs: an entity that spawns or rebuckets into a region is tested against the clients whose focus regions cover that region, instead of waiting for their next full evaluation (which would show it up to an eval interval late) or re-scanning every client (which would be the O(clients x entities) loop interest replaces).

Nested types

Member

public struct Member

Per-entity leave state. Public only because Ids hands out the dictionary's keys.

Fields

OutsideSince
public double OutsideSince;

When the entity first went beyond the exit radius; the linger is measured from it.

Outside
public bool Outside;

On this page