Class NetworkTransformDistanceLod
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
-
ObjectComponentBehaviourMonoBehaviourNetworkBehaviourNetworkTransformDistanceLod
- Implements
-
IObserverSendFilter
- Inherited Members
-
NetworkBehaviour.IsSpawnedNetworkBehaviour.ComponentIndexNetworkBehaviour.NetworkObjectNetworkBehaviour.MAXIMUM_NETWORKBEHAVIOURSNetworkBehaviour.UNSET_NETWORKBEHAVIOUR_IDNetworkBehaviour.ToString()NetworkBehaviour.Reset()NetworkBehaviour.OnValidate()NetworkBehaviour.IsBehaviourReconcilingNetworkBehaviour.ClearReplicateCache()NetworkBehaviour.CreateReconcile()NetworkBehaviour.Reconcile_Reader<T>(PooledReader, ref T)NetworkBehaviour.OnStartServerCalledNetworkBehaviour.OnStartClientCalledNetworkBehaviour.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.ExcludeOwnerFromUnbufferedObserversRpcsNetworkBehaviour.IsClientOnlyNetworkBehaviour.IsServerOnlyNetworkBehaviour.IsHostNetworkBehaviour.IsClientNetworkBehaviour.IsServerNetworkBehaviour.IsDeinitializingNetworkBehaviour.NetworkManagerNetworkBehaviour.ServerManagerNetworkBehaviour.ClientManagerNetworkBehaviour.ObserverManagerNetworkBehaviour.TransportManagerNetworkBehaviour.TimeManagerNetworkBehaviour.SceneManagerNetworkBehaviour.PredictionManagerNetworkBehaviour.RollbackManagerNetworkBehaviour.NetworkObserverNetworkBehaviour.IsClientInitializedNetworkBehaviour.IsClientStartedNetworkBehaviour.IsClientOnlyInitializedNetworkBehaviour.IsClientOnlyStartedNetworkBehaviour.IsServerInitializedNetworkBehaviour.IsServerStartedNetworkBehaviour.IsServerOnlyInitializedNetworkBehaviour.IsServerOnlyStartedNetworkBehaviour.IsHostInitializedNetworkBehaviour.IsHostStartedNetworkBehaviour.IsOfflineNetworkBehaviour.IsNetworkedNetworkBehaviour.GetIsNetworked()NetworkBehaviour.IsManagerReconcilingNetworkBehaviour.ObserversNetworkBehaviour.IsOwnerNetworkBehaviour.IsControllerNetworkBehaviour.HasAuthorityNetworkBehaviour.OwnerNetworkBehaviour.OwnerIdNetworkBehaviour.ObjectIdNetworkBehaviour.LocalConnectionNetworkBehaviour.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.destroyCancellationTokenMonoBehaviour.useGUILayoutMonoBehaviour.didStartMonoBehaviour.didAwakeMonoBehaviour.runInEditModeBehaviour.enabledBehaviour.isActiveAndEnabledComponent.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.transformComponent.transformHandleComponent.gameObjectComponent.tagObject.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.nameObject.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
LimitedObserverCount
Number of observers currently held below full rate by distance.
public int LimitedObserverCount { get; }
Property Value
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
connectionNetworkConnection
Returns
GetInterval(NetworkConnection)
Send interval currently applied to an observer by distance; 1 when unlimited.
public byte GetInterval(NetworkConnection connection)
Parameters
connectionNetworkConnection
Returns
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
Returns
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
connectionNetworkConnection
Returns
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
bandsBand[]Ascending bands; must have at least one element.
hysteresisfloatFraction each held band's edge is widened by.
sqrDistancefloatSquared distance from the object to the observer.
currentBandintBand 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
networkObjectNetworkObjectObject sending the RPC.
connectionNetworkConnectionObserver the RPC would be sent to.
channelChannelChannel the RPC is being sent on. Only Unreliable is ever filtered.
Returns
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.