Skip to content

fix: remove the NetworkObjectBridge when a hybrid prefab stops being hybrid - #4211

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

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

Conversation

@NoelStephensUnity

Copy link
Copy Markdown
Member

Purpose of this PR

Removing GhostObject or NetworkObject from a hybrid prefab now leaves a clean prefab, without the hidden NetworkObjectBridge left behind.

Removing GhostObject: the pre-removal callback now removes only the NetworkObjectBridge and any GhostRigidbody, and SynchronizeTransform is enabled again. Previously it destroyed every GhostBehaviour on the prefab, including user scripts.

Removing NetworkObject: the NetworkObjectBridge now removes itself when its NetworkObject is gone.

In-scene placed hybrid instances: the GhostObject is now removed through the same path, so the bridge is removed with it.

The bridge also now sets FullTickSmoothing in place of the obsolete SingleWorldHostInterpolationSmoothing.

PR Scope:

  • Editor-time hybrid prefab authoring in NetworkObject, NetworkObjectEditor and NetworkObjectBridge.

Out of scope:

  • Removing user GhostBehaviours when GhostObject is removed (they belong to the user).
  • Undoing the N4E-side GhostRigidbody / replicate-rigidbody settings when GhostObject is removed (N4E owns those).

Jira ticket

N/A

Changelog

None (hybrid prefabs require an unreleased N4E version).

Documentation

  • No documentation changes or additions were necessary.

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

Functional Testing

Manual testing :

  • Manual testing done: add and remove GhostObject and NetworkObject on a hybrid prefab in the inspector, and toggle the Networked checkbox.

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: hybrid prefabs are NGO 3.x only.

Backports

Not needed: hybrid prefabs are NGO 3.x only.

…hybrid

Removing GhostObject from a hybrid prefab now removes only the hidden
NetworkObjectBridge and any GhostRigidbody, instead of every GhostBehaviour,
and restores SynchronizeTransform. Removing NetworkObject from a hybrid
prefab also removes the bridge. The bridge now sets FullTickSmoothing in
place of the obsolete SingleWorldHostInterpolationSmoothing.
@NoelStephensUnity
NoelStephensUnity requested a review from a team as a code owner October 22, 2026 21:43

@u-pr u-pr Bot left a comment

Copy link
Copy Markdown
Contributor

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 cleanup now targets bridge components more precisely, but component removal and serialized hybrid state need to remain consistent.

Reviewed commit 472ba7f

🤖 Helpful? 👍/👎

Comment thread com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs Outdated
Clearing HasGhost.
@NoelStephensUnity

Copy link
Copy Markdown
Member Author

@u-pr please review again

@u-pr

u-pr Bot commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

Here is a summary of findings from reviewing the updated changes:

1. GhostObject Reference Cleanup

Commit e81a81c added HasGhost = false to OnRemoveNetworkObjectBridge(), which clears the hybrid flag. However, networkObject.GhostObject is still not cleared in OnGhostObjectPreRemoval. When the GhostObject component is deleted in the Inspector, leaving networkObject.GhostObject uncleared leaves a dangling/missing component reference on the NetworkObject (which persists in serialized prefab assets). Restoring networkObject.GhostObject = null; in OnGhostObjectPreRemoval ensures the field is cleanly reset.

2. Bridge Component Detection in UnifiedValidation

In NetworkObject.UnifiedValidation:

else if (HadBridge && !HasGhost)
{
    if (NetworkObjectBridge != null)
    {
        EditorApplication.update -= OnRemoveNetworkObjectBridge;
        EditorApplication.update += OnRemoveNetworkObjectBridge;
    }
    else
    {
        HadBridge = false;
        SynchronizeTransform = true;
    }
}

If the NetworkObjectBridge field is null (such as after a domain reload or if the serialized reference was lost), but the NetworkObjectBridge component is still present on the GameObject, the check NetworkObjectBridge != null evaluates to false. This triggers the else block, resetting HadBridge = false and leaving the bridge component behind. Checking if (NetworkObjectBridge != null || GetComponent<NetworkObjectBridge>() != null) avoids orphaning the component.

3. Null Safety and Unregistration Order for EditorApplication.update Callbacks

In OnRemoveNetworkObjectBridge, OnAddNetworkObjectBridge, and OnRemoveGhostObject, unregistering (EditorApplication.update -= ...) occurs at the end of each method:

  • If the target NetworkObject or GameObject is destroyed between being scheduled and the update tick running, accessing gameObject throws a MissingReferenceException.
  • Because the exception occurs before reaching the unsubscription line, the delegate remains subscribed and will throw MissingReferenceException repeatedly on every editor update frame.

Unregistering at the start of each callback and checking if (this == null) return; ensures that callbacks are cleanly removed even if the object was destroyed in the meantime.

🤖 Helpful? 👍/👎

Comment thread com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs Outdated
Comment thread com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
Comment thread com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs Outdated
Comment on lines +48 to +51
if (this != null)
{
DestroyImmediate(this, true);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Fun fact (I just tested this), DestroyImmediate will handle the null internally so we can simplify this even further!

Suggested change
if (this != null)
{
DestroyImmediate(this, true);
}
DestroyImmediate(this, true);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Applied, thanks.

…allbacks

The GhostObject removal hook now re-runs UnifiedValidation on the next editor tick, so GhostObject and
HasGhost match the components on the prefab stage root, and the bridge comes back when the hook fired
for a different GhostBehaviour. The deferred add/remove callbacks unsubscribe first and return when
their NetworkObject was destroyed.
@NoelStephensUnity
NoelStephensUnity marked this pull request as draft October 23, 2026 19:24
@NoelStephensUnity
NoelStephensUnity marked this pull request as ready for review October 23, 2026 19:24

@u-pr u-pr Bot left a comment

Copy link
Copy Markdown
Contributor

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 cleanup and delayed validation address the hybrid-state lifecycle, but rebuilding the bridge can overwrite a user-authored prediction setting.

Reviewed commit a182f27

🤖 Helpful? 👍/👎

{
if (networkObject)
{
networkObject.UnifiedValidation();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1

Removing a user GhostBehaviour from a predicted hybrid prefab resets SupportedGhostModes to Interpolated. OnGhostObjectPreRemoval also fires for other GhostBehaviour components, as the new comment notes. OnRemoveNetworkObjectBridge destroys the existing bridge even when GhostObject remains. The delayed UnifiedValidation then schedules a replacement bridge. NetworkObjectBridge.Reset explicitly sets SupportedGhostModes to Interpolated, overwriting the user's prediction setting. Preserve and restore SupportedGhostModes when rebuilding the bridge after an unrelated component removal.

🤖 Helpful? 👍/👎

…modes

UnifiedValidation removes a leftover NetworkObjectBridge, so the GhostObjectEditor pre-removal callback is no longer needed. It also fired when the inspection component was removed, and the rebuilt bridge reset SupportedGhostModes. The bridge no longer forces SupportedGhostModes to Interpolated.
@codecov-github-com

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

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 1.78571% with 55 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
....netcode.gameobjects/Runtime/Core/NetworkObject.cs 2.00% 49 Missing ⚠️
.../Runtime/Components/Helpers/NetworkObjectBridge.cs 0.00% 6 Missing ⚠️
@@                Coverage Diff                @@
##           develop-3.x.x    #4211      +/-   ##
=================================================
- Coverage          76.03%   75.92%   -0.11%     
=================================================
  Files                159      159              
  Lines              27344    27382      +38     
=================================================
  Hits               20791    20791              
- Misses              6553     6591      +38     
Flag Coverage Δ
NGOv2_project_testproject_ubuntu 74.94% <1.78%> (-0.15%) ⬇️

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

Files with missing lines Coverage Δ
.../Runtime/Components/Helpers/NetworkObjectBridge.cs 0.00% <0.00%> (ø)
....netcode.gameobjects/Runtime/Core/NetworkObject.cs 72.24% <2.00%> (-1.58%) ⬇️
Components Coverage Δ
com.unity.netcode.gameobjects 75.92% <1.78%> (-0.11%) ⬇️

ℹ️ 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.

2 participants