Skip to content

Do not send NetworkTransform updates to an owner that discards them - #1103

Open
jimdroberts wants to merge 1 commit into
FirstGearGames:mainfrom
jimdroberts:feat/networktransform-skip-owner-discarded
Open

jimdroberts wants to merge 1 commit into
FirstGearGames:mainfrom
jimdroberts:feat/networktransform-skip-owner-discarded

Conversation

@jimdroberts

Copy link
Copy Markdown
Contributor

Problem

The server sends every NetworkTransform update to every observer, including the owner. In two configurations the owner's handler returns before doing anything:

  1. Server authoritative with SendToOwner off. ConfigureForPrediction sets 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.
  2. Client authoritative with an owner. The server relays the owner's own data back to it, and the owner discards it.

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.cs on main:

  • ObserversUpdateClientAuthoritativeTransform (L2293-2294) is a plain [ObserversRpc]. Its first statements are the owner guards: L2296 if (!_clientAuthoritative && IsOwner && !_sendToOwner) return; and L2298 if (_clientAuthoritative && IsOwner) return;. They run before _lastObserversRpcTick or any transform data is touched.
  • SendToClients calls it for the client-authoritative relay (L1783) and for the server's own state (L1828).
  • ConfigureForPrediction (L2467) sets _clientAuthoritative = false and _sendToOwner = false.

[ObserversRpc(ExcludeOwner = true)] cannot fix this because the attribute is fixed at compile time. Whether the owner discards the update depends on _clientAuthoritative and _sendToOwner, and SetSendToOwner changes the latter at runtime. NetworkTransform's other ObserversRpcs are BufferLast and must still reach the owner.

Fix

  • NetworkBehaviour.RPCs.cs: adds internal virtual bool ExcludeOwnerFromUnbufferedObserversRpcs => false. In SendObserversRpc, right after SetNetworkConnectionCache, the owner is added to the same exclusion set that ExcludeOwner fills. 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 is internal, so the public API does not change, and only NetworkTransform overrides it.
  • NetworkTransform: the override returns _clientAuthoritative || !_sendToOwner. Given IsOwner, this is exactly when one of the two guards returns. The guards stay in place as a safety net.
  • Buffered RPCs (ObserversSetSendToOwner, ObserversSetInterval, ObserversSetSynchronizedProperties) are unaffected. So is TargetUpdateTransform, which sends the spawn-time state for nested transforms. A clientHost owner is excluded as well. It already returned on IsServerInitialized.

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 edits SendObserversRpc. The owner exclusion is inserted directly after SetNetworkConnectionCache, and the filter's call goes directly before SendToClients. git merge-tree merges 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):

  • Fixed cost: 2 B link id, plus 1 B packed length of the ArraySegment. The trailing Channel parameter is not serialized, and unreliable linked RPCs carry no length.
  • Character walking and turning on flat ground: flags 1 + x 2 + z 2 + Quaternion32 4 = 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.
  • With y also changing: 14 B per tick (420 B/s at 30). Beyond ±327.66 units, where positions are floats: 20 B per tick (600 B/s at 30).

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:

  • After SetSendToOwner(false), the server stops sending to the owner immediately. Before, the owner kept applying updates until the buffered ObserversSetSendToOwner arrived, because reliable and unreliable messages are not ordered against each other. Either way, the owner then moves the object itself.
  • After an ownership change, the new owner no longer receives the few updates that can arrive before it learns it is the owner. It would discard them as soon as it did.

Unchanged and outside this PR: in development builds, SendObserversRpc counts outbound statistics as writer.Length * Observers.Count, which includes excluded connections. This already applies to ExcludeOwner and ExcludeServer.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant