Table of Contents

Class ObserverStreamingEntry

Namespace
FishMMO.Shared
Assembly
FishMMO.Shared.dll

One registered character in ObserverStreamingRegistry: the per-observer send intervals the scheduler assigned it, the range it is currently visible from, and the cached relevance inputs (combat, party, guild) used to rank it for each viewer.

public sealed class ObserverStreamingEntry : IObserverSendFilter
Inheritance
ObserverStreamingEntry
Implements
IObserverSendFilter
Inherited Members

Remarks

Doubles as the object's FishNet.Observing.IObserverSendFilter. ShouldSend(NetworkObject, NetworkConnection, Channel) runs once per observer per unreliable RPC, so it is a dictionary lookup and a modulo — nothing that allocates or walks a collection.

Two things can slow an observer down: the viewer's full-rate cap (this entry's own intervals, assigned by ObserverStreamingRegistry) and the observer's distance from the object (NetworkTransformDistanceLod on the same GameObject). They are combined by taking the larger interval, never by gating one behind the other: two independent modulo gates only coincide once in N×M ticks and would starve the observer. An engaged observer is exempt from both — except from the engaged-overflow interval, which is the bound on that exemption; see ResolveEffectiveInterval(byte, IntervalOrigin, byte, bool).

Constructors

ObserverStreamingEntry(NetworkObject, ICharacter)

public ObserverStreamingEntry(NetworkObject networkObject, ICharacter character)

Parameters

networkObject NetworkObject
character ICharacter

Fields

NoViewerDistance

Value of NearestViewerDistance when no player was in the scene.

public const float NoViewerDistance = Infinity

Field Value

float

UnclassifiedRankBucket

Rank bucket for objects with no classification; never a valid enum value.

public const int UnclassifiedRankBucket = -1

Field Value

int

Properties

AppliedRange

The range most recently applied to the distance condition.

public float AppliedRange { get; }

Property Value

float

BaseRange

The configured range from the prefab's distance condition, before density scaling.

public float BaseRange { get; }

Property Value

float

Character

The character.

public ICharacter Character { get; }

Property Value

ICharacter

Classification

What kind of entity this is, read from its ClassifiedDistanceCondition.

public ObserverClassification Classification { get; }

Property Value

ObserverClassification

Remarks

Objects whose distance condition is a plain FishNet DistanceCondition, or which have none at all, report Monster and fall back to the shared ObserverStreamingPolicy.VisibilityBudget — the behaviour of the whole system before classifications existed.

CurrentTargetObjectId

Object id of the character this one currently targets, or 0. Pinned into its visibility budget so a fight cannot despawn its own target.

public long CurrentTargetObjectId { get; }

Property Value

long

GuildID

Guild id cached for the current scheduling pass; 0 when none.

public long GuildID { get; }

Property Value

long

HasClassification

True when this object declares its own classification, and so is ranked and budgeted against its own kind rather than against the shared pool.

public bool HasClassification { get; }

Property Value

bool

HasDistanceCondition

True when this character's visibility range can be changed at runtime.

public bool HasDistanceCondition { get; }

Property Value

bool

HasDistanceLod

True when a NetworkTransformDistanceLod also shapes this object's sends.

public bool HasDistanceLod { get; }

Property Value

bool

HasViewerMeasurement

True once a scheduling pass has measured NearestViewerDistance.

public bool HasViewerMeasurement { get; }

Property Value

bool

InCombat

Combat state cached for the current scheduling pass.

public bool InCombat { get; }

Property Value

bool

IsPlayer

True for player characters, which are the only viewers the cap is computed for.

public bool IsPlayer { get; }

Property Value

bool

LimitedObserverCount

Number of viewers this entry is currently rate limited for.

public int LimitedObserverCount { get; }

Property Value

int

LongestAbilityRange

Longest range among this character's known abilities, cached for the pass. Drives how far out its own view must be tick-exact; see ObserverStreamingPolicy.ResolveEngagementRange.

public float LongestAbilityRange { get; }

Property Value

float

NearestViewerDistance

Distance to the nearest PLAYER in this object's scene as of the last scheduling pass, or NoViewerDistance when there is none.

public float NearestViewerDistance { get; }

Property Value

float

Remarks

A real proximity number, not an observer-membership one: the registry measures it before the range filter, so a character the visibility budget has evicted from every viewer still reports how far away the nearest player actually is. That distinction is what AIController needs — observer membership is a bandwidth decision, and using it as "is anybody near" let a budget eviction full-heal a monster with a player standing next to it.

Only valid when HasViewerMeasurement is true. An object registered between two passes has never been measured, and "no measurement" must never be read as "nobody is near".

NetworkObject

The character's network object.

public NetworkObject NetworkObject { get; }

Property Value

NetworkObject

PartyID

Party id cached for the current scheduling pass; 0 when none.

public long PartyID { get; }

Property Value

long

Position

World position cached for the current scheduling pass.

public Vector3 Position { get; }

Property Value

Vector3

RankBucket

The bucket this object is ranked in: its classification, or UnclassifiedRankBucket when it has none.

public int RankBucket { get; }

Property Value

int

Remarks

Distinct from Classification on purpose. An unclassified object is measured against the shared ObserverStreamingPolicy.VisibilityBudget, so counting it in the Monster bucket would push every real monster's rank up against the Monster budget while judging the unclassified object itself against a different number.

VisibilityBudget

How many objects of this kind one viewer may observe. 0 means unlimited.

public int VisibilityBudget { get; }

Property Value

int

Remarks

Per classification, which is what stops one crowd squeezing out another. Under a single shared budget the failures were not hypothetical: forty players in a town square evicted the banker they were queueing for, because a stationary service NPC scored below every living thing near it and nothing distinguished the two costs.

A Titan is authored at 0 — unlimited — which is what "always visible when in the scene" means here: its range is the only thing that ever hides it.

Methods

ApplyRange(float)

Applies a density-scaled range to the distance condition when it differs from the applied one by at least RangeChangeThreshold. The timed observer rebuild picks the new distance up on its next cycle.

public bool ApplyRange(float range)

Parameters

range float

Returns

bool

True when the range was changed.

ClearIntervals()

Forgets every per-observer interval; every observer is back at full rate.

public void ClearIntervals()

GetEffectiveInterval(NetworkConnection)

Send interval an observer actually receives, composing the cap interval, the distance LOD interval and the engagement exemption.

public byte GetEffectiveInterval(NetworkConnection connection)

Parameters

connection NetworkConnection

Returns

byte

GetInterval(NetworkConnection)

Send interval assigned to an observer by the viewer cap; 1 when unlimited.

public byte GetInterval(NetworkConnection connection)

Parameters

connection NetworkConnection

Returns

byte

GetIntervalOrigin(NetworkConnection)

Which policy assigned this observer's interval; None when none did.

public ObserverStreamingEntry.IntervalOrigin GetIntervalOrigin(NetworkConnection connection)

Parameters

connection NetworkConnection

Returns

ObserverStreamingEntry.IntervalOrigin

IsFoughtBy(long)

True when otherCharacterID has contributed to this character's current combat — that is, when the two are actually fighting each other.

public bool IsFoughtBy(long otherCharacterID)

Parameters

otherCharacterID long

The other character's id.

Returns

bool

Remarks

Read live rather than cached with the rest of the pass state: it is a question about a PAIR, so there is nothing to cache on one entry, and the underlying lookup is a dictionary probe on a set holding a handful of attackers.

RefreshForPass()

Refreshes the cached relevance inputs. Called once per entry per scheduling pass so the O(viewers × entries) ranking below reads fields, not behaviours.

public void RefreshForPass()

ResolveEffectiveInterval(byte, IntervalOrigin, byte, bool)

The composition rule, as a pure function so it is testable without a NetworkManager.

public static byte ResolveEffectiveInterval(byte capInterval, ObserverStreamingEntry.IntervalOrigin capOrigin, byte lodInterval, bool engaged)

Parameters

capInterval byte

Interval the viewer cap assigned; 1 for none.

capOrigin ObserverStreamingEntry.IntervalOrigin

Which policy assigned capInterval.

lodInterval byte

Interval the distance LOD assigned; 1 for none.

engaged bool

True when the observer is inside its own engagement radius of this object.

Returns

byte

Remarks

Truth table:

not engaged, any origin → max(cap, lod). The two policies compose by taking the LARGER interval, never by gating one behind the other: two independent modulo gates coincide once in N×M ticks and would starve the observer.
engaged, origin None or RelevanceCap → 1. This is the exemption: the cap is scored by relevance and knows nothing about distance, so without it a character standing next to its attacker could be throttled to every 2nd tick and lag compensation would rewind to a pose that was never rendered.
engaged, origin EngagedOverflow → the cap interval. That interval IS the bound on the exemption — assigned precisely because the viewer already has EngagedFullRateBudget engaged characters at full rate — so exempting it cancelled the budget that assigned it. While the origin was not recorded, EngagedFullRateBudget and EngagedOverflowInterval had no runtime effect at all: the registry assigned the overflow interval and this method discarded it on the same engagement test the registry had just used, so thirty players inside 40 m were still thirty full-rate streams and LastPassLimitedPairs counted throttles that never happened.

The LOD interval is not composed into the engaged rows because the LOD exempts engaged observers itself (NetworkTransformDistanceLod.BandObserver writes an interval of 1 for them), so it is already 1 in both of them.

SetInterval(NetworkConnection, byte)

Sets the send interval for one observer as a RELEVANCE CAP interval. An interval of 1 removes the limit.

public void SetInterval(NetworkConnection connection, byte interval)

Parameters

connection NetworkConnection
interval byte

SetInterval(NetworkConnection, byte, IntervalOrigin)

Sets the send interval for one observer and records which policy assigned it. An interval of 1, or an origin of None, removes the limit.

public void SetInterval(NetworkConnection connection, byte interval, ObserverStreamingEntry.IntervalOrigin origin)

Parameters

connection NetworkConnection
interval byte
origin ObserverStreamingEntry.IntervalOrigin

ShouldSend(NetworkObject, NetworkConnection, Channel)

Returns true to send this RPC to connection, false to skip it.

public bool ShouldSend(NetworkObject networkObject, NetworkConnection connection, Channel channel)

Parameters

networkObject NetworkObject

Object sending the RPC.

connection NetworkConnection

Observer the RPC would be sent to.

channel Channel

Channel the RPC is being sent on. Only Unreliable is ever filtered.

Returns

bool