Class ObserverStreamingEntry
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
networkObjectNetworkObjectcharacterICharacter
Fields
NoViewerDistance
Value of NearestViewerDistance when no player was in the scene.
public const float NoViewerDistance = Infinity
Field Value
UnclassifiedRankBucket
Rank bucket for objects with no classification; never a valid enum value.
public const int UnclassifiedRankBucket = -1
Field Value
Properties
AppliedRange
The range most recently applied to the distance condition.
public float AppliedRange { get; }
Property Value
BaseRange
The configured range from the prefab's distance condition, before density scaling.
public float BaseRange { get; }
Property Value
Character
The character.
public ICharacter Character { get; }
Property Value
Classification
What kind of entity this is, read from its ClassifiedDistanceCondition.
public ObserverClassification Classification { get; }
Property Value
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
GuildID
Guild id cached for the current scheduling pass; 0 when none.
public long GuildID { get; }
Property Value
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
HasDistanceCondition
True when this character's visibility range can be changed at runtime.
public bool HasDistanceCondition { get; }
Property Value
HasDistanceLod
True when a NetworkTransformDistanceLod also shapes this object's sends.
public bool HasDistanceLod { get; }
Property Value
HasViewerMeasurement
True once a scheduling pass has measured NearestViewerDistance.
public bool HasViewerMeasurement { get; }
Property Value
InCombat
Combat state cached for the current scheduling pass.
public bool InCombat { get; }
Property Value
IsPlayer
True for player characters, which are the only viewers the cap is computed for.
public bool IsPlayer { get; }
Property Value
LimitedObserverCount
Number of viewers this entry is currently rate limited for.
public int LimitedObserverCount { get; }
Property Value
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
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
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
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
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
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
rangefloat
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
connectionNetworkConnection
Returns
GetInterval(NetworkConnection)
Send interval assigned to an observer by the viewer cap; 1 when unlimited.
public byte GetInterval(NetworkConnection connection)
Parameters
connectionNetworkConnection
Returns
GetIntervalOrigin(NetworkConnection)
Which policy assigned this observer's interval; None when none did.
public ObserverStreamingEntry.IntervalOrigin GetIntervalOrigin(NetworkConnection connection)
Parameters
connectionNetworkConnection
Returns
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
otherCharacterIDlongThe other character's id.
Returns
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
capIntervalbyteInterval the viewer cap assigned; 1 for none.
capOriginObserverStreamingEntry.IntervalOriginWhich policy assigned
capInterval.lodIntervalbyteInterval the distance LOD assigned; 1 for none.
engagedboolTrue when the observer is inside its own engagement radius of this object.
Returns
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
connectionNetworkConnectionintervalbyte
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
connectionNetworkConnectionintervalbyteoriginObserverStreamingEntry.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
networkObjectNetworkObjectObject sending the RPC.
connectionNetworkConnectionObserver the RPC would be sent to.
channelChannelChannel the RPC is being sent on. Only Unreliable is ever filtered.