Repository navigation
Do not send NetworkTransform updates to an owner that discards them - #1103
Open
jimdroberts wants to merge 1 commit into
Open
jimdroberts wants to merge 1 commit into
jimdroberts wants to merge 1 commit into
Conversation
ObserversUpdateClientAuthoritativeTransform returns early on the owner when the transform is client authoritative, or server authoritative with SendToOwner off, as it is for predicted objects. The server still sent it to the owner every tick. NetworkBehaviour gains an internal virtual, ExcludeOwnerFromUnbufferedObserversRpcs, which SendObserversRpc uses to add the owner to the same exclusion list ExcludeOwner feeds. NetworkTransform returns true in the two cases the receive guard drops. Buffered RPCs, such as SendToOwner and interval changes, still reach the owner.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The server sends every NetworkTransform update to every observer, including the owner. In two configurations the owner's handler returns before doing anything:
ConfigureForPredictionsets this for predicted objects without state forwarding, and it is a common manual setup for owner-simulated characters. The owner receives a full update stream and discards every packet.The owner is the one client that observes its object all the time, so this costs one discarded packet per tick per owned moving object.
Cause
Runtime/Generated/Component/NetworkTransform/NetworkTransform.cson main:ObserversUpdateClientAuthoritativeTransform(L2293-2294) is a plain[ObserversRpc]. Its first statements are the owner guards: L2296if (!_clientAuthoritative && IsOwner && !_sendToOwner) return;and L2298if (_clientAuthoritative && IsOwner) return;. They run before_lastObserversRpcTickor any transform data is touched.SendToClientscalls it for the client-authoritative relay (L1783) and for the server's own state (L1828).ConfigureForPrediction(L2467) sets_clientAuthoritative = falseand_sendToOwner = false.[ObserversRpc(ExcludeOwner = true)]cannot fix this because the attribute is fixed at compile time. Whether the owner discards the update depends on_clientAuthoritativeand_sendToOwner, andSetSendToOwnerchanges the latter at runtime. NetworkTransform's other ObserversRpcs areBufferLastand must still reach the owner.Fix
NetworkBehaviour.RPCs.cs: addsinternal virtual bool ExcludeOwnerFromUnbufferedObserversRpcs => false. InSendObserversRpc, right afterSetNetworkConnectionCache, the owner is added to the same exclusion set thatExcludeOwnerfills. This happens only when the RPC is not buffered, the owner is not already excluded, the owner is valid, and the property is true. Nothing else on the send path changes. The property isinternal, so the public API does not change, and only NetworkTransform overrides it.NetworkTransform: the override returns_clientAuthoritative || !_sendToOwner. GivenIsOwner, this is exactly when one of the two guards returns. The guards stay in place as a safety net.ObserversSetSendToOwner,ObserversSetInterval,ObserversSetSynchronizedProperties) are unaffected. So isTargetUpdateTransform, which sends the spawn-time state for nested transforms. A clientHost owner is excluded as well. It already returned onIsServerInitialized.No define. Nothing changes for observers, and the owner ends in the same state whether a packet is discarded or never sent, because the guards are the RPC's first statements. A server with this change works with clients without it. Since nothing behaved incorrectly before, this is a bandwidth change, not a bug fix.
This PR is independent of feat/networktransform-position-scale, which also edits
NetworkTransform.cs(different lines). It is also independent of feat/observer-send-filter, which also editsSendObserversRpc. The owner exclusion is inserted directly afterSetNetworkConnectionCache, and the filter's call goes directly beforeSendToClients.git merge-treemerges this branch cleanly with both and with every other open PR branch of mine.Evidence
Compiled with Unity 6000.4's Roslyn (FishNet.Runtime, GameKit.Dependencies, both codegen assemblies): 0 errors and the same warnings as main, alone and merged with feat/networktransform-position-scale.
Bytes saved, computed from the packet layout (not a capture), for a release build using an RpcLink (the default while links are available):
ArraySegment. The trailingChannelparameter is not serialized, and unreliable linked RPCs carry no length.Quaternion324 = 9 B of transform data, so 12 B per tick. That is 360 B/s per owned moving object at tick rate 30, or 720 B/s at 60.These are server egress per owner, excluding per-datagram overhead. 500 owners walking on flat ground would cost about 180 KB/s.
Behavior change
The owner no longer receives packets it discarded. The steady state is the same. Two short windows differ:
SetSendToOwner(false), the server stops sending to the owner immediately. Before, the owner kept applying updates until the bufferedObserversSetSendToOwnerarrived, because reliable and unreliable messages are not ordered against each other. Either way, the owner then moves the object itself.Unchanged and outside this PR: in development builds,
SendObserversRpccounts outbound statistics aswriter.Length * Observers.Count, which includes excluded connections. This already applies toExcludeOwnerandExcludeServer.