Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1164,6 +1164,17 @@
/// <inheritdoc />
public override void OnNetworkDespawn()
{
#if UNIFIED_NETCODE
// The ghost replicates the kinematic state it reads when spawned, so a pooled server instance needs its authored state back.
if (NetworkObject.HasGhost)
{
if (m_LocalNetworkManager.IsServer)
{
SetIsKinematic(m_OriginalKinematicState);
}
return;

Check warning on line 1175 in com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs#L1170-L1175

Added lines #L1170 - L1175 were not covered by tests

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? 👍/👎

}
#endif
if (UseRigidBodyForMotion && HasAuthority)
{
DetachFromFixedJoint();
Expand Down
6 changes: 4 additions & 2 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4241,9 +4241,11 @@
{
Debug.Log($"[{nameof(NetworkObject)}] GhostBridge {name} detected and instantiated.");
}
if (GhostObject.WasInitialized && NetworkObjectBridge.NetworkObjectId.Value != 0)
var networkObjectId = NetworkObjectBridge.NetworkObjectId.Value;

Check warning on line 4244 in com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs#L4244

Added line #L4244 was not covered by tests
// A prefab handler instance is registered by HybridPrefabHandlerSpawnSystem before its Start runs.
if (GhostObject.WasInitialized && networkObjectId != 0 && !IsSpawned && !NetworkManager.SpawnManager.GhostSpawnManager.IsGhostPendingSpawn(networkObjectId))

Check warning on line 4246 in com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs#L4246

Added line #L4246 was not covered by tests
{
NetworkManager.SpawnManager.GhostSpawnManager.RegisterGhostBridge(NetworkObjectBridge.NetworkObjectId.Value, this);
NetworkManager.SpawnManager.GhostSpawnManager.RegisterGhostBridge(networkObjectId, this);

Check warning on line 4248 in com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs#L4248

Added line #L4248 was not covered by tests
}
}
#endif
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#if UNIFIED_NETCODE
using Unity.Collections;
using Unity.Entities;
using Unity.Netcode.Components;
using Unity.Transforms;
using UnityEngine;

namespace Unity.Netcode
{
/// <summary>
/// Gives a client the instance of a hybrid prefab from its <see cref="INetworkPrefabInstanceHandler"/> instead of letting N4E instantiate it.
/// </summary>
/// <remarks>
/// The ghost arrives before NGO's spawn message, so the handler receives <see cref="NetworkManager.ServerClientId"/> as the owner.
/// </remarks>
[UpdateInGroup(typeof(GhostSimulationSystemGroup))]
[UpdateAfter(typeof(GhostUpdateSystem))]
[UpdateBefore(typeof(GhostGameObjectSpawnSystem))]
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
internal partial class HybridPrefabHandlerSpawnSystem : SystemBase
{
private EntityQuery m_PendingSpawnQuery;
private EntityQuery m_HybridNetworkManagerQuery;

protected override void OnCreate()
{
if (World.IsHost())
{
Enabled = false;
return;

Check warning on line 30 in com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs#L26-L30

Added lines #L26 - L30 were not covered by tests
}
m_PendingSpawnQuery = GetEntityQuery(ComponentType.ReadOnly<PendingClientGameObjectSpawn>(), ComponentType.ReadOnly<GhostInstance>());
m_HybridNetworkManagerQuery = GetEntityQuery(ComponentType.ReadOnly<HybridNetworkManager>());
RequireForUpdate(m_PendingSpawnQuery);
RequireForUpdate(m_HybridNetworkManagerQuery);
}

Check warning on line 36 in com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs#L32-L36

Added lines #L32 - L36 were not covered by tests

protected override void OnUpdate()
{
var networkManager = m_HybridNetworkManagerQuery.GetSingleton<HybridNetworkManager>().NetworkManager.Value;
if (!networkManager || networkManager.ShutdownInProgress)
{
return;

Check warning on line 43 in com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs#L40-L43

Added lines #L40 - L43 were not covered by tests
}

using var pendingEntities = m_PendingSpawnQuery.ToEntityArray(Allocator.Temp);
using var ghostInstances = m_PendingSpawnQuery.ToComponentDataArray<GhostInstance>(Allocator.Temp);
var prefabs = EntityManager.GetBuffer<GhostCollectionPrefab>(SystemAPI.GetSingletonEntity<GhostCollection>()).ToNativeArray(Allocator.Temp);

Check warning on line 48 in com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs#L46-L48

Added lines #L46 - L48 were not covered by tests

for (int i = 0; i < pendingEntities.Length; i++)
{
var entity = pendingEntities[i];
var prefabEntity = prefabs[ghostInstances[i].ghostType].GhostPrefab;
var prefabId = EntityManager.GetComponentData<GhostGameObjectLink>(prefabEntity).AssociatedGameObject;
var prefab = Resources.EntityIdToObject(prefabId) as GameObject;
if (!prefab || !prefab.TryGetComponent<NetworkObject>(out var prefabNetworkObject) || !networkManager.PrefabHandler.ContainsHandler(prefabNetworkObject.GlobalObjectIdHash))
{
continue;

Check warning on line 58 in com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs#L50-L58

Added lines #L50 - L58 were not covered by tests
}

var localTransform = EntityManager.GetComponentData<LocalTransform>(entity);
var networkObject = networkManager.PrefabHandler.HandleNetworkPrefabSpawn(prefabNetworkObject.GlobalObjectIdHash, NetworkManager.ServerClientId, localTransform.Position, localTransform.Rotation);
if (!networkObject || !networkObject.HasGhost || networkObject.GhostObject.IsSpawned)
{
Debug.LogError($"[{nameof(INetworkPrefabInstanceHandler)}] The handler for {prefab.name} did not return an unspawned instance of it. N4E instantiates this ghost instead.");
continue;

Check warning on line 66 in com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs#L61-L66

Added lines #L61 - L66 were not covered by tests
}

var gameObject = networkObject.gameObject;
var link = new GhostGameObjectLink(gameObject.GetEntityId(), gameObject.transform.GetEntityId());

Check warning on line 70 in com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs#L69-L70

Added lines #L69 - L70 were not covered by tests
// Released by GhostObject.Despawn in HybridPrefabHandlerDespawnSystem.
var entityLink = GhostEntityMapping.AcquireEntityReferenceGameObject(link.AssociatedGameObject, link.AssociatedTransform, prefabId, World.Unmanaged, entity);
EntityManager.SetComponentData(entity, link);
EntityManager.RemoveComponent<PendingClientGameObjectSpawn>(entity);
networkObject.GhostObject.ClientRuntimeInitialize(entityLink);

Check warning on line 75 in com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs#L72-L75

Added lines #L72 - L75 were not covered by tests

// A reused instance does not run Start again, so it registers here.
networkManager.SpawnManager.GhostSpawnManager.RegisterGhostBridge(networkObject.NetworkObjectBridge.NetworkObjectId.Value, networkObject);

Check warning on line 78 in com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs#L78

Added line #L78 was not covered by tests
}
}
}

/// <summary>
/// Returns a client's hybrid instance to its <see cref="INetworkPrefabInstanceHandler"/> when N4E despawns its ghost, instead of letting N4E destroy it.
/// </summary>
[UpdateInGroup(typeof(GhostSimulationSystemGroup))]
[UpdateAfter(typeof(GhostDespawnSystem))]
[UpdateAfter(typeof(PredictedGhostDespawnSystem))]
[UpdateAfter(typeof(GhostReceiveSystem))]
[UpdateBefore(typeof(GhostGameObjectDespawnManagedSystem))]
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
internal partial class HybridPrefabHandlerDespawnSystem : SystemBase
{
private EntityQuery m_DespawnTrackingQuery;
private EntityQuery m_HybridNetworkManagerQuery;

protected override void OnCreate()
{
if (World.IsHost())
{
Enabled = false;
return;

Check warning on line 102 in com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs#L98-L102

Added lines #L98 - L102 were not covered by tests
}
m_DespawnTrackingQuery = GetEntityQuery(typeof(GameObjectDespawnTracking));
m_HybridNetworkManagerQuery = GetEntityQuery(ComponentType.ReadOnly<HybridNetworkManager>());
RequireForUpdate(m_DespawnTrackingQuery);
RequireForUpdate(m_HybridNetworkManagerQuery);
}

Check warning on line 108 in com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs#L104-L108

Added lines #L104 - L108 were not covered by tests

protected override void OnUpdate()
{
var networkManager = m_HybridNetworkManagerQuery.GetSingleton<HybridNetworkManager>().NetworkManager.Value;
if (!networkManager)
{
return;

Check warning on line 115 in com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs#L112-L115

Added lines #L112 - L115 were not covered by tests
}

var trackingEntity = m_DespawnTrackingQuery.GetSingletonEntity();

Check warning on line 118 in com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs#L118

Added line #L118 was not covered by tests
// Copied out, as GhostGameObjectDespawnManagedSystem does, because of a DynamicBuffer safety handle bug.
var despawns = EntityManager.GetBuffer<GameObjectDespawnTracking>(trackingEntity, isReadOnly: true).ToNativeArray(Allocator.Temp);
if (despawns.Length == 0)
{
return;

Check warning on line 123 in com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs#L120-L123

Added lines #L120 - L123 were not covered by tests
}

EntityManager.CompleteDependencyBeforeRW<SpawnedGhostEntityMap>();
var spawnedGhostMap = SystemAPI.GetSingletonRW<SpawnedGhostEntityMap>().ValueRO.SpawnedGhostMapRW;
var spawnManager = networkManager.SpawnManager;
var handled = new NativeList<int>(despawns.Length, Allocator.Temp);

Check warning on line 129 in com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs#L126-L129

Added lines #L126 - L129 were not covered by tests

for (int i = 0; i < despawns.Length; i++)
{
var despawn = despawns[i].oneDespawn;
var gameObjectId = EntityManager.GetComponentData<GhostGameObjectLink>(despawn.entity).AssociatedGameObject;
var gameObject = Resources.EntityIdToObject(gameObjectId) as GameObject;
if (!gameObject || !gameObject.TryGetComponent<NetworkObject>(out var networkObject) || !networkObject.HasGhost || !networkManager.PrefabHandler.ContainsHandler(networkObject))
{
continue;

Check warning on line 138 in com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs#L131-L138

Added lines #L131 - L138 were not covered by tests
}

if (spawnedGhostMap.TryGetValue(despawn.ghost, out var mapped) && mapped == despawn.entity)
{
spawnedGhostMap.Remove(despawn.ghost);
}

Check warning on line 144 in com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs#L141-L144

Added lines #L141 - L144 were not covered by tests

var networkObjectId = networkObject.NetworkObjectId;

Check warning on line 146 in com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs#L146

Added line #L146 was not covered by tests

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? 👍/👎

// The ghost can despawn before NGO's destroy message arrives, the same case NetworkObject.OnDestroy handles.
if (networkObject.IsSpawned && spawnManager.SpawnedObjects.TryGetValue(networkObjectId, out var spawned) && spawned == networkObject)
{
spawnManager.OnDespawnObject(networkObject, false);
}
spawnManager.GhostSpawnManager.MarkNetworkObjectAsDestroying(networkObjectId);

Check warning on line 152 in com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs#L148-L152

Added lines #L148 - L152 were not covered by tests

NetworkSpawnManager.DespawnGhostForPrefabHandler(networkObject);
networkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObject);
handled.Add(i);
}

Check warning on line 157 in com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs#L154-L157

Added lines #L154 - L157 were not covered by tests

if (handled.Length == 0)
{
return;

Check warning on line 161 in com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs#L159-L161

Added lines #L159 - L161 were not covered by tests
}
var buffer = EntityManager.GetBuffer<GameObjectDespawnTracking>(trackingEntity);
for (int i = handled.Length - 1; i >= 0; i--)
{
buffer.RemoveAt(handled[i]);

Check warning on line 166 in com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs#L163-L166

Added lines #L163 - L166 were not covered by tests
}
}
}
}
#endif

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,19 @@
entitiesNetcode.m_ActiveWorld = previousWorld;
}
}

/// <summary>
/// Despawns the ghost of a hybrid instance handed to an <see cref="INetworkPrefabInstanceHandler"/>, which may keep the
/// GameObject for reuse. <see cref="SpawnGhost"/> spawns a new ghost when the instance is spawned again.
/// </summary>
internal static void DespawnGhostForPrefabHandler(NetworkObject networkObject)
{
var ghostObject = networkObject.GhostObject;
if (networkObject.HasGhost && ghostObject != null && ghostObject.IsSpawned && ghostObject.DelaySpawning)
{
ghostObject.Despawn();
}

Check warning on line 1011 in com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs

View check run for this annotation

Codecov GitHub.com / codecov/patch

com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs#L1009-L1011

Added lines #L1009 - L1011 were not covered by tests
}
#endif

/// <summary>
Expand Down Expand Up @@ -1601,6 +1614,10 @@
if (NetworkManager.PrefabHandler.ContainsHandler(networkObject))
{
OnDespawnObject(networkObject, false);
#if UNIFIED_NETCODE
// The world is disposed after this, so a pooled instance must not keep a ghost linked to it.
DespawnGhostForPrefabHandler(networkObject);
#endif
// Leave destruction up to the handler
NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObject);
}
Expand Down Expand Up @@ -1972,6 +1989,9 @@
{
if (NetworkManager.PrefabHandler.ContainsHandler(networkObject))
{
#if UNIFIED_NETCODE
DespawnGhostForPrefabHandler(networkObject);
#endif
NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObject);
}
else
Expand Down
Loading
Loading