Table of Contents

Class NetworkTransformDistanceLod

Namespace
FishMMO.Shared
Assembly
FishMMO.Shared.dll

Rate limits a FishNet.Component.Transforming.NetworkTransform's unreliable updates per observer by how far that observer is from the object, so a spectator across the zone stops paying full rate for something it can barely see while a spectator standing next to it still gets every tick.

[RequireComponent(typeof(NetworkTransform))]
public class NetworkTransformDistanceLod : NetworkBehaviour, IObserverSendFilter
Inheritance
Object
Component
Behaviour
MonoBehaviour
NetworkBehaviour
NetworkTransformDistanceLod
Implements
IObserverSendFilter
Inherited Members
NetworkBehaviour.IsSpawned
NetworkBehaviour.ComponentIndex
NetworkBehaviour.NetworkObject
NetworkBehaviour.MAXIMUM_NETWORKBEHAVIOURS
NetworkBehaviour.UNSET_NETWORKBEHAVIOUR_ID
NetworkBehaviour.ToString()
NetworkBehaviour.Reset()
NetworkBehaviour.OnValidate()
NetworkBehaviour.IsBehaviourReconciling
NetworkBehaviour.ClearReplicateCache()
NetworkBehaviour.CreateReconcile()
NetworkBehaviour.Reconcile_Reader<T>(PooledReader, ref T)
NetworkBehaviour.OnStartServerCalled
NetworkBehaviour.OnStartClientCalled
NetworkBehaviour.WritePayload(NetworkConnection, Writer)
NetworkBehaviour.ReadPayload(NetworkConnection, Reader)
NetworkBehaviour.OnStartNetwork()
NetworkBehaviour.OnStopNetwork()
NetworkBehaviour.OnOwnershipServer(NetworkConnection)
NetworkBehaviour.OnSpawnServer(NetworkConnection)
NetworkBehaviour.OnDespawnServer(NetworkConnection)
NetworkBehaviour.OnStartClient()
NetworkBehaviour.OnStopClient()
NetworkBehaviour.OnOwnershipClient(NetworkConnection)
NetworkBehaviour.ClearBuffedRpcs()
NetworkBehaviour.ExcludeOwnerFromUnbufferedObserversRpcs
NetworkBehaviour.IsClientOnly
NetworkBehaviour.IsServerOnly
NetworkBehaviour.IsHost
NetworkBehaviour.IsClient
NetworkBehaviour.IsServer
NetworkBehaviour.IsDeinitializing
NetworkBehaviour.NetworkManager
NetworkBehaviour.ServerManager
NetworkBehaviour.ClientManager
NetworkBehaviour.ObserverManager
NetworkBehaviour.TransportManager
NetworkBehaviour.TimeManager
NetworkBehaviour.SceneManager
NetworkBehaviour.PredictionManager
NetworkBehaviour.RollbackManager
NetworkBehaviour.NetworkObserver
NetworkBehaviour.IsClientInitialized
NetworkBehaviour.IsClientStarted
NetworkBehaviour.IsClientOnlyInitialized
NetworkBehaviour.IsClientOnlyStarted
NetworkBehaviour.IsServerInitialized
NetworkBehaviour.IsServerStarted
NetworkBehaviour.IsServerOnlyInitialized
NetworkBehaviour.IsServerOnlyStarted
NetworkBehaviour.IsHostInitialized
NetworkBehaviour.IsHostStarted
NetworkBehaviour.IsOffline
NetworkBehaviour.IsNetworked
NetworkBehaviour.GetIsNetworked()
NetworkBehaviour.IsManagerReconciling
NetworkBehaviour.Observers
NetworkBehaviour.IsOwner
NetworkBehaviour.IsController
NetworkBehaviour.HasAuthority
NetworkBehaviour.Owner
NetworkBehaviour.OwnerId
NetworkBehaviour.ObjectId
NetworkBehaviour.LocalConnection
NetworkBehaviour.OwnerMatches(NetworkConnection)
NetworkBehaviour.Despawn(GameObject, DespawnType?)
NetworkBehaviour.Despawn(NetworkObject, DespawnType?)
NetworkBehaviour.Despawn(DespawnType?)
NetworkBehaviour.Spawn(GameObject, NetworkConnection, Scene)
NetworkBehaviour.Spawn(NetworkObject, NetworkConnection, Scene)
NetworkBehaviour.RemoveOwnership()
NetworkBehaviour.GiveOwnership(NetworkConnection)
NetworkBehaviour.GetInstance<T>()
NetworkBehaviour.TryRegisterInstance<T>(T)
NetworkBehaviour.UnregisterInstance<T>()
NetworkBehaviour.CanLog(LoggingType)
MonoBehaviour.IsInvoking()
MonoBehaviour.CancelInvoke()
MonoBehaviour.StopCoroutine(Coroutine)
MonoBehaviour.StopAllCoroutines()
MonoBehaviour.destroyCancellationToken
MonoBehaviour.useGUILayout
MonoBehaviour.didStart
MonoBehaviour.didAwake
MonoBehaviour.runInEditMode
Behaviour.enabled
Behaviour.isActiveAndEnabled
Component.GetComponent<T>()
Component.TryGetComponent<T>(out T)
Component.GetComponentInChildren<T>()
Component.GetComponentsInChildren<T>()
Component.GetComponentInParent<T>()
Component.GetComponentsInParent<T>()
Component.GetComponents<T>()
Component.GetComponentIndex()
Component.CompareTag(TagHandle)
Component.transform
Component.transformHandle
Component.gameObject
Component.tag
Object.GetEntityId()
Object.GetInstanceID()
Object.GetHashCode()
Object.InstantiateAsync<T>(T)
Object.InstantiateAsync<T>(T, Transform)
Object.InstantiateAsync<T>(T, Vector3, Quaternion)
Object.InstantiateAsync<T>(T, Transform, Vector3, Quaternion)
Object.Instantiate(Object, Vector3, Quaternion)
Object.Instantiate(Object, Vector3, Quaternion, Transform)
Object.Instantiate(Object)
Object.Instantiate(Object, Scene)
Object.Instantiate<T>(T, InstantiateParameters)
Object.Instantiate<T>(T, Vector3, Quaternion, InstantiateParameters)
Object.Instantiate(Object, Transform)
Object.Instantiate<T>(T)
Object.Instantiate<T>(T, Vector3, Quaternion)
Object.Instantiate<T>(T, Vector3, Quaternion, Transform)
Object.Instantiate<T>(T, Transform)
Object.Destroy(Object)
Object.DestroyImmediate(Object)
Object.DontDestroyOnLoad(Object)
Object.DestroyObject(Object)
Object.FindObjectsOfType<T>()
Object.FindObjectsByType<T>(FindObjectsSortMode)
Object.FindObjectsByType<T>(FindObjectsInactive, FindObjectsSortMode)
Object.FindObjectOfType<T>()
Object.FindFirstObjectByType<T>()
Object.FindAnyObjectByType<T>()
Object.FindFirstObjectByType<T>(FindObjectsInactive)
Object.FindAnyObjectByType<T>(FindObjectsInactive)
Object.FindObjectsByType<T>()
Object.FindObjectsByType<T>(FindObjectsInactive)
Object.name
Object.hideFlags

Remarks

Why per observer and not SetInterval(byte). The transform's own interval is one value for the whole object, changed through a buffered observers RPC, so the best it could do was follow the nearest observer — useless in a crowd, where somebody is always close to everything. It also fought the per-observer streaming filter: with the transform sending every Nth tick and the filter passing every Mth tick per observer, an observer only heard from the object when both gates coincided, which for coprime intervals happens once in N×M ticks. This component therefore never touches the transform's interval (it stays at 1) and instead answers, per observer and per send, through FishNet's FishNet.Observing.IObserverSendFilter hook. Skipping an unreliable update is indistinguishable from packet loss to the receiver, and reliable sends (the settle after a stop, a teleport) are never filtered, so every observer still converges on the true pose.

Composition with the observer cap. Characters registered with ObserverStreamingRegistry already have a per-observer filter, ObserverStreamingEntry, that rate limits everything beyond a viewer's full-rate cap. That entry reads this component and applies the larger of the two intervals, so the two never multiply and never starve anyone. Objects without an entry — world items, static interactables — install this component as the object's filter directly.

Owner. The owner never counts. Its own character is at distance zero from itself and would have pinned every object at full rate; more to the point a server-authoritative transform with SendToOwner off does not send to its owner at all (FishNet.Object.NetworkBehaviour.ExcludeOwnerFromUnbufferedObserversRpcs).

Hysteresis. Bands are evaluated on a slow timer and each observer holds its band until it clears the edge by FishMMO.Shared.NetworkTransformDistanceLod.hysteresis, so an observer hovering on a boundary sees a steady sample spacing rather than one that flips every evaluation. Changing an observer's interval is free on the wire now (it is a dictionary write, not an RPC), but an interpolator fed at an abruptly changing rate can hitch, which is what the margin protects.

Properties

IntervalScale

Multiplier applied to the selected band. See FishMMO.Shared.NetworkTransformDistanceLod.intervalScale.

public int IntervalScale { get; set; }

Property Value

int

LimitedObserverCount

Number of observers currently held below full rate by distance.

public int LimitedObserverCount { get; }

Property Value

int

Methods

Evaluate()

Re-bands every current observer. Public so a server can force a pass after a bulk change (a teleport, a load boundary) and so tests can drive it.

public void Evaluate()

GetBand(NetworkConnection)

Band an observer is currently held in, or -1 when it is not tracked.

public int GetBand(NetworkConnection connection)

Parameters

connection NetworkConnection

Returns

int

GetInterval(NetworkConnection)

Send interval currently applied to an observer by distance; 1 when unlimited.

public byte GetInterval(NetworkConnection connection)

Parameters

connection NetworkConnection

Returns

byte

IntervalForBand(Band[], int, int)

Interval for a band after scaling, clamped to the byte range; 1 for no band.

public static byte IntervalForBand(NetworkTransformDistanceLod.Band[] bands, int band, int scale)

Parameters

bands Band[]
band int
scale int

Returns

byte

IsEngaged(NetworkConnection)

True while this object is close enough to connection for that client to attack it, so no throttle of any kind may apply.

public bool IsEngaged(NetworkConnection connection)

Parameters

connection NetworkConnection

Returns

bool

Remarks

Read by ObserverStreamingEntry as well, because the per-observer CAP throttles independently of distance and would otherwise reintroduce the gap this closes.

OnStartServer()

Called on the server after initializing this object. SyncTypes modified before or during this method will be sent to clients in the spawn message.

public override void OnStartServer()

OnStopServer()

Called on the server before deinitializing this object.

public override void OnStopServer()

ResolveBand(Band[], float, float, int)

Picks the band for one observer's squared distance, holding currentBand until the distance clears that band's edge by hysteresis.

public static int ResolveBand(NetworkTransformDistanceLod.Band[] bands, float hysteresis, float sqrDistance, int currentBand)

Parameters

bands Band[]

Ascending bands; must have at least one element.

hysteresis float

Fraction each held band's edge is widened by.

sqrDistance float

Squared distance from the object to the observer.

currentBand int

Band the observer is currently held in, or -1 for none.

Returns

int

Band index. Distances beyond the last band clamp to it.

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

Remarks

Used directly only when the object has no ObserverStreamingEntry; the entry otherwise folds GetInterval(NetworkConnection) into its own decision. Reliable sends are never declined — the reliable settle after a stop must reach every observer.