ClientInterest<T, TSource>
One client's interest set and the state machine that maintains it.
Nebula · Packages/com.1by3.nebula/Runtime/Interest/ClientInterest.cspublic 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 MemberPer-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;