Skip to content

fix: hybrid prefab component gating and distributed authority rejection - #4184

Open
NoelStephensUnity wants to merge 5 commits into
develop-3.x.xfrom
fix/hybrid-prefab-component-gating
Open

NoelStephensUnity wants to merge 5 commits into
develop-3.x.xfrom
fix/hybrid-prefab-component-gating

Conversation

@NoelStephensUnity

@NoelStephensUnity NoelStephensUnity commented Oct 5, 2026 •

Copy link
Copy Markdown
Member

Purpose of this PR

This PR assures the NetworkBehaviourId values on hybrid prefabs are maintained by keeping NetworkTransform and NetworkRigidbody effectively a "nop" on the instance instead of destroying them at runtime like the POC was doing. Included in this PR, hybrid prefabs are rejected when using a distributed authority network topology.

  • NetworkRigidbodyBase derived classes were originally destroyed in the POC during runtime on hybrid prefab instances. This PR keeps the components but turns them into "nop components". This fixes some edge cases:

    • The component was removed from ChildNetworkBehaviours while Destroy is deferred to the end of the frame.
    • NetworkVariable synchronization is NetworkBehaviourId relative, so any deviation from the original indices could break delta synchronization.
    • Rpcs on the runtime adjusted could be impacted the same way due to the change in NetworkBehaviour indices.
    • NetworkRigidbodyBase now early-exits on HasGhost similar to NetworkTransform. The body is made kinematic on every peer except the server at spawn, and its authored kinematic state is restored on destroy.
    • An ownership change no longer re-applies the authority kinematic rule to a hybrid prefab's body (with an owner authoritative NetworkTransform, the new owner's body would become dynamic).
  • NetworkTransform needed additional HasGhost gating within OnSynchronize, InternalOnNetworkPostSpawn, InternalOnNetworkSessionSynchronized and InternalOnNetworkObjectParentChanged. A hybrid object no longer writes a full transform state to every late joining client.

    • NetworkTransform.UnregisterRigidbody had no call sites. It is now called from the gate, so reparenting a hybrid object that has a NetworkRigidbody no longer dereferences a destroyed component.
  • [RequireComponent(typeof(Rigidbody))] and [RequireComponent(typeof(Rigidbody2D))] are unconditional again. Under UNIFIED_NETCODE they were dropped from every NetworkRigidbody in the project, hybrid or not.

  • The final suggested pattern, if a user decides to keep the hybrid prefab setting, will be to completely remove NetworkTransform and NetworkRigidbody.

  • The UNIFIED_NETCODE_DESTROY block in NetworkObject is removed (that define is never used and was a left over artifact from the POC).

  • Distributed authority does not support hybrid prefabs:

    • Before this PR, if a user were to try and start a distributed authority session and had hybrid prefab(s) registered, it would replaced the transport with UnifiedNetcodeTransport and attempt to start the session.
    • Users attempting to do this will now see an error message and the NetworkManager will shutdown before attempting to start the session.
    • Any hybrid prefab added during a distributed authority session is rejected.
  • NetworkPrefab overrides are not supported for hybrid prefabs (yet):

    • N4E instantiates the server's prefab from the ghost type, so an NGO override is never applied on clients.
    • An override with a hybrid source or target prefab is now detected as a hybrid registration.
    • Users with one registered will see an error message naming the override, and the NetworkManager will not start (in any network topology).
    • Any such override added during a session is rejected.
  • NetworkPrefabs subscribed to each NetworkPrefabsList again every time it was initialized, so a prefab added to a list during a session was registered more than once. It now keeps a single subscription, and HasGhostPrefabs is recomputed each time the prefabs are initialized.

PR Scope:

NetworkObject, NetworkTransform, NetworkRigidbodyBase, NetworkRigidbody, NetworkRigidbody2D, NetworkManager, NetworkPrefab and NetworkPrefabs. All changes are within UNIFIED_NETCODE paths except the NetworkPrefabsList subscription fix. No wire format changes.

Out of scope:

  • Supporting NetworkPrefab overrides for hybrid prefabs (needs N4E support for instantiating a different prefab per client).
  • NetworkRigidbody integration coverage (NetworkRigidbodyTest never compiles because the test assembly is missing the physics2d versionDefine. This will be fixed in a separate PR using the fix/rigidbody-tests-never-compiled branch).
  • As an example, pre-existing issues noticed but not changed here:
    • The transport replaced by UnifiedNetcodeTransport is not restored after a hybrid session ends.
    • NetworkClient.SetRole leaves IsServer set when it rejects a distributed authority StartServer, so destroying that NetworkManager throws (plain NGO, both develop lines; will be fixed in a separate PR).

Jira ticket

N/A

Changelog

  • Fixed: Issue where a prefab added to a NetworkPrefabsList while a session was running was registered more than once and logged a duplicate GlobalObjectIdHash error.

Documentation

  • No documentation changes or additions were necessary (NGO hybrid prefab documentation does not exist yet. "Distributed authority does not support hybrid prefabs" should be clearly stated when this is put together).

Testing & QA (How your changes can be verified during release Playtest)

Fixture Passed Failed
UnifiedHybridPrefabBehaviourIdTests (UnifiedHost) 4 0
UnifiedHybridPrefabValidationTests (UnifiedHost) 5 0
Full unified pass (UNIFIED_TESTS=true, all runtime tests) 241 0 (non-unified fixtures skipped by design, plus 4 pre-existing ignores)
Non-unified regression: DAHost, ownership, prefab, NetworkTransform, NetworkRigidbody and NetworkManagerStartExceptionTests fixtures 4186 0 (43 skipped: hybrid-only fixtures in the non-unified run, plus pre-existing ignores)

Functional Testing

Manual testing :

  • Manual testing done

Automated tests:

  • Covered by existing automated tests
  • Covered by new automated tests

Does the change require QA team to:

  • Review automated tests?
  • Execute manual tests?
  • Provide feedback about the PR?

Up-port

Not needed. This PR targets develop-3.x.x.

Backports

The hybrid prefab changes are not needed on develop-2.0.0 (hybrid prefabs exist on develop-3.x.x only). The NetworkPrefabsList subscription fix applies to develop-2.0.0 as well, and a port will follow.

A hybrid prefab's NetworkRigidbodyBase derivatives were destroyed at runtime so a user could
try hybrid mode without first stripping components from the prefab. Removing an entry from
ChildNetworkBehaviours is not safe: InitializeChildNetworkBehaviours rebuilds unconditionally
and has five call sites, two of them the public GetNetworkBehaviourOrderIndex and
GetNetworkBehaviourAtOrderIndex, while Destroy is deferred to end of frame. A rebuild in a
later frame therefore renumbers every behaviour after the removed one, and the NetworkVariable
half of object synchronization is positional with no id, so a one-entry disagreement corrupts
the whole buffer for that object.

NetworkTransform was already handled by early-returning on HasGhost rather than being
destroyed - the removal behind UNIFIED_NETCODE_DESTROY was dead, that define exists nowhere.
This extends the same treatment to NetworkRigidbodyBase and closes four NetworkTransform paths
that had no gate: OnSynchronize, InternalOnNetworkPostSpawn,
InternalOnNetworkSessionSynchronized and InternalOnNetworkObjectParentChanged. The first of
those means a hybrid object no longer writes a full teleport state to every joining client.

The rigidbody is now forced kinematic on non-server peers rather than destroyed, restored from
m_OriginalKinematicState on destroy as the class already provided for. That decision is made at
spawn, not during Awake, because Initialize runs from Awake where an in-scene placed instance
has no session to ask whether it is the server. UnregisterRigidbody had no call sites and is
now called from the gate, which is what keeps a second Initialize from leaving a registration
pointing at a component that no longer drives anything.

[RequireComponent(typeof(Rigidbody))] is unconditional again. Behind #if !UNIFIED_NETCODE it
was stripped from every NetworkRigidbody in the project the moment N4E was installed, hybrid or
not, after which NetworkRigidbodyBase dereferences a null rigidbody in Awake.
Distributed authority does not support hybrid prefabs. A start with any GhostObject prefab
registered now fails in CanStart with an error naming each prefab, before Initialize replaces
the transport with UnifiedNetcodeTransport. A hybrid prefab added during a distributed
authority session is refused, so HasGhostPrefabs cannot switch the running session into
hybrid mode and stop its send queue.
@NoelStephensUnity
NoelStephensUnity requested a review from a team as a code owner October 5, 2026 15:02
@u-pr

u-pr Bot commented Oct 5, 2026

Copy link
Copy Markdown

⚠️ Some settings were ignored in '.pr_agent.toml (repository root)' repo settings

Check the configuration's TOML syntax, permitted settings, and size limit. See the error below.


Error message:
Ignored prohibited settings: github_app.handle_pr_actions. Permitted settings were applied.

Configuration content:
# This is the configuration for the u-pr bot
# https://github.cds.internal.unity3d.com/unity/u-pr
# For configuration of this file:
# https://developer.portal.internal.unity.com/catalog/default/component/u-pr/docs/configuration/automatic_runs/

[github_app]
handle_pr_actions = ['opened', 'ready_for_review']  # PR events that auto-run pr_commands
pr_commands       = ["/harness-review"]             # what runs on those events
reviewer_commands = ["/harness-review"]             # what runs when @u-pr is added as reviewer

@u-pr u-pr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Harness Review

Needs changes

The change keeps hybrid components present to preserve behaviour IDs and adds distributed authority prefab checks, but the checks and rigidbody gating miss several reachable paths.

Reviewed commit 03ba4e1

🤖 Helpful? 👍/👎

Comment thread com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs Outdated
Comment thread com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs Outdated
Comment thread com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs Outdated
Comment thread com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs Outdated
- NetworkRigidbodyBase no longer re-applies the authority kinematic rule on an ownership change for a hybrid prefab.
- NetworkPrefabs recomputes HasGhostPrefabs on initialize, and keeps a single subscription to each NetworkPrefabsList
  (a prefab added to a list during a session was registered more than once).
- RejectGhostPrefabs is set in Initialize, after SetRole accepts the start, so a failed start cannot leave it set.
- A NetworkPrefab override with a hybrid source or target prefab is detected as a hybrid registration. N4E
  instantiates the server's prefab from the ghost type, so overrides are not supported for hybrid prefabs yet: a start
  with one registered fails in any topology, and one added during a session is rejected.
@codecov-github-com

codecov-github-com Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Codecov Report

All modified and coverable lines are covered by tests ✅

@@              Coverage Diff               @@
##           develop-3.x.x    #4184   +/-   ##
==============================================
  Coverage          78.19%   78.20%           
==============================================
  Files                153      153           
  Lines              26272    26276    +4     
==============================================
+ Hits               20544    20548    +4     
  Misses              5728     5728           
Flag Coverage Δ
NGOv2_project_testproject_ubuntu 76.64% <100.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...objects/Runtime/Components/NetworkRigidBodyBase.cs 41.35% <100.00%> (+0.24%) ⬆️
...gameobjects/Runtime/Components/NetworkRigidbody.cs 100.00% <ø> (ø)
...meobjects/Runtime/Components/NetworkRigidbody2D.cs 0.00% <ø> (ø)
...gameobjects/Runtime/Components/NetworkTransform.cs 88.75% <ø> (ø)
...gameobjects/Runtime/Configuration/NetworkPrefab.cs 69.15% <ø> (ø)
...ameobjects/Runtime/Configuration/NetworkPrefabs.cs 81.81% <100.00%> (+0.17%) ⬆️
...netcode.gameobjects/Runtime/Core/NetworkManager.cs 79.97% <ø> (ø)
....netcode.gameobjects/Runtime/Core/NetworkObject.cs 75.31% <ø> (ø)
Components Coverage Δ
com.unity.netcode.gameobjects 78.20% <100.00%> (+<0.01%) ⬆️

ℹ️ Need help interpreting these results?

This branch has not been deployed

No deployments
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