Repository navigation
feat: object pooling for hybrid prefab instances #4247
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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
|
||
| } | ||
| 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
|
||
|
|
||
| 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
|
||
| } | ||
|
|
||
| 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
|
||
|
|
||
| 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
|
||
| } | ||
|
|
||
| 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
|
||
| } | ||
|
|
||
| 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
|
||
| // 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
|
||
|
|
||
| // A reused instance does not run Start again, so it registers here. | ||
| networkManager.SpawnManager.GhostSpawnManager.RegisterGhostBridge(networkObject.NetworkObjectBridge.NetworkObjectId.Value, networkObject); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// <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
|
||
| } | ||
| 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
|
||
|
|
||
| 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
|
||
| } | ||
|
|
||
| var trackingEntity = m_DespawnTrackingQuery.GetSingletonEntity(); | ||
| // 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
|
||
| } | ||
|
|
||
| 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
|
||
|
|
||
| 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
|
||
| } | ||
|
|
||
| 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
|
||
|
|
||
| var networkObjectId = networkObject.NetworkObjectId; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If a ghost despawns before NGO processes its spawn message, the delayed message can spawn the instance after it has returned to the pool. 🤖 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
|
||
|
|
||
| NetworkSpawnManager.DespawnGhostForPrefabHandler(networkObject); | ||
| networkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObject); | ||
| handled.Add(i); | ||
| } | ||
|
Check warning on line 157 in com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs
|
||
|
|
||
| if (handled.Length == 0) | ||
| { | ||
| return; | ||
|
Check warning on line 161 in com.unity.netcode.gameobjects/Runtime/Spawning/HybridPrefabHandlerSystems.cs
|
||
| } | ||
| 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
|
||
| } | ||
| } | ||
| } | ||
| } | ||
| #endif | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A server hybrid body pooled after
AttachToFixedJointremains constrained to its previous target when spawned again.AttachToFixedJointpermits dynamic hybrid bodies withUseRigidBodyForMotionenabled. The new return bypassesDetachFromFixedJointandNetworkRigidbodyConnections.Clear(). Consequently,FixedJoint.connectedBodyandParentBodysurvive 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? 👍/👎