Skip to content

feat: object pooling for hybrid prefab instances - #4247

Closed
NoelStephensUnity wants to merge 2 commits into
develop-3.x.xfrom
feat/hybrid-prefab-pooling
Closed

NoelStephensUnity wants to merge 2 commits into
develop-3.x.xfrom
feat/hybrid-prefab-pooling

Conversation

@NoelStephensUnity

@NoelStephensUnity NoelStephensUnity commented Nov 9, 2026 •

Copy link
Copy Markdown
Member

Purpose of this PR

Hybrid (NetworkObject + GhostObject) prefabs can now be pooled with an INetworkPrefabInstanceHandler on both the server and clients, under UnifiedHost and UnifiedServer. Before this, the server pooled its instances, but every respawned Rigidbody came back kinematic, and clients ignored the handler entirely.

  • Server: a despawned hybrid instance kept its ghost and the kinematic state NetworkRigidbody sets on despawn. The ghost then replicated that kinematic state when the instance was spawned again.
  • Clients: N4E instantiates a client's ghost GameObject itself and always destroys it on despawn, so the handler's Instantiate and Destroy were never called.

PR Scope:

  • NetworkRigidbodyBase: a hybrid instance on the server returns to its authored kinematic state on despawn.
  • NetworkSpawnManager: a hybrid instance handed to a prefab handler has its ghost despawned first (including at shutdown), so the pooled GameObject is not linked to a ghost or a disposed world. Spawning it again spawns a new ghost.
  • New client systems in HybridPrefabHandlerSystems: for a prefab with a registered handler, the client takes the instance from the handler instead of letting N4E instantiate it, and returns it to the handler instead of letting N4E destroy it.
  • NetworkObject.InitGhost: a handler instance is not registered twice.
  • New HybridPrefabHandlerPoolingTests (UnifiedHost and UnifiedServer): two spawn/despawn cycles in which the server and the client both reuse their pooled instances, and the server body stays dynamic.

Out of scope:

  • The client handler receives the server's client id as ownerClientId. (The ghost arrives before NGO's spawn message, so the owner is not known yet.)
  • Hybrid child objects nested under a pooled hybrid instance are not detached when the parent returns to its pool.

Jira ticket

N/A

Changelog

  • Added: Support for pooled hybrid prefabs configured for delayed spawning.

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 (ngo-examples StressTest with pooling enabled, host and client)

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?

If any boxes above are checked the QA team will be automatically added as a PR reviewer.

Up-port

N/A (hybrid mode exists on develop-3.x.x only).

Backports

N/A (hybrid mode exists on develop-3.x.x only).

Server: a hybrid instance handed to an INetworkPrefabInstanceHandler now has
its ghost despawned (GhostObject.Despawn), so reusing it spawns a new ghost.
NetworkRigidbodyBase restores the authored kinematic state on despawn; the
ghost captured the despawn-time kinematic state on respawn and replicated it.

Client: N4E instantiates and destroys client ghost GameObjects itself, so two
NGO systems run ahead of GhostGameObjectSpawnSystem and
GhostGameObjectDespawnManagedSystem and route prefabs with a registered
handler through it instead.
Two spawn/despawn cycles on UnifiedHost and UnifiedServer: the client takes
and returns instances through its handler, the server reuses its pooled
instances, and the server body stays dynamic after a pooled respawn.
@NoelStephensUnity
NoelStephensUnity requested a review from a team as a code owner November 9, 2026 21:35

@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 change adds hybrid prefab pooling across server and client lifecycles, but retained instances are not fully cleaned up before reuse.

Reviewed commit 7b25256

🤖 Helpful? 👍/👎

spawnedGhostMap.Remove(despawn.ghost);
}

var networkObjectId = networkObject.NetworkObjectId;

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

If a ghost despawns before NGO processes its spawn message, the delayed message can spawn the instance after it has returned to the pool. HybridPrefabHandlerSpawnSystem registers the instance using NetworkObjectBridge.NetworkObjectId.Value. NetworkObject.NetworkObjectId is assigned later by SetupOnSpawn, so networkObjectId here is zero or the previous spawn's ID. MarkNetworkObjectAsDestroying therefore removes the wrong entry, leaving TryGetGhostNetworkObjectForSpawn able to return the still-live pooled instance. Capture the current bridge ID before despawning the ghost. Use that ID to clear the pending registration.

🤖 Helpful? 👍/👎

{
SetIsKinematic(m_OriginalKinematicState);
}
return;

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

A server hybrid body pooled after AttachToFixedJoint remains constrained to its previous target when spawned again. AttachToFixedJoint permits dynamic hybrid bodies with UseRigidBodyForMotion enabled. The new return bypasses DetachFromFixedJoint and NetworkRigidbodyConnections.Clear(). Consequently, FixedJoint.connectedBody and ParentBody survive when the handler merely deactivates the instance, as the new pooling test does. Keep the joint cleanup before the hybrid-specific return. Restore the authored kinematic state after that cleanup.

🤖 Helpful? 👍/👎

@codecov-github-com

codecov-github-com Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Codecov Report

Attention: Patch coverage is 6.36364% with 103 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...cts/Runtime/Spawning/HybridPrefabHandlerSystems.cs 0.00% 91 Missing ⚠️
...objects/Runtime/Components/NetworkRigidBodyBase.cs 14.28% 6 Missing ⚠️
....netcode.gameobjects/Runtime/Core/NetworkObject.cs 0.00% 3 Missing ⚠️
...ameobjects/Runtime/Spawning/NetworkSpawnManager.cs 66.66% 3 Missing ⚠️
@@                Coverage Diff                @@
##           develop-3.x.x    #4247      +/-   ##
=================================================
- Coverage          76.73%   76.47%   -0.27%     
=================================================
  Files                159      160       +1     
  Lines              28101    28209     +108     
=================================================
+ Hits               21564    21572       +8     
- Misses              6537     6637     +100     
Flag Coverage Δ
NGOv2_project_testproject_ubuntu 75.51% <6.36%> (-0.28%) ⬇️

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

Files with missing lines Coverage Δ
....netcode.gameobjects/Runtime/Core/NetworkObject.cs 71.40% <0.00%> (-0.04%) ⬇️
...ameobjects/Runtime/Spawning/NetworkSpawnManager.cs 70.75% <66.66%> (-0.03%) ⬇️
...objects/Runtime/Components/NetworkRigidBodyBase.cs 61.58% <14.28%> (-0.62%) ⬇️
...cts/Runtime/Spawning/HybridPrefabHandlerSystems.cs 0.00% <0.00%> (ø)

... and 1 file with indirect coverage changes

Components Coverage Δ
com.unity.netcode.gameobjects 76.47% <6.36%> (-0.27%) ⬇️

ℹ️ Need help interpreting these results?

@NoelStephensUnity

Copy link
Copy Markdown
Member Author

Waiting for Emma's object pool work.

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