From f724ff64f385cf78a873f72e8c47b82214a0d811 Mon Sep 17 00:00:00 2001 From: Ashton Meuser Date: Thu, 11 Jun 2026 19:48:13 -0700 Subject: [PATCH 1/2] Cache deleting identities --- .../Runtime/Core/PredictedComponentID.cs | 2 +- .../Core/PredictedComponentIDExtensions.cs | 2 +- .../Runtime/Core/PredictionManager.cs | 108 +++++++++++++----- 3 files changed, 84 insertions(+), 28 deletions(-) diff --git a/Assets/PurrDiction/Runtime/Core/PredictedComponentID.cs b/Assets/PurrDiction/Runtime/Core/PredictedComponentID.cs index b8372805..96f9b5db 100644 --- a/Assets/PurrDiction/Runtime/Core/PredictedComponentID.cs +++ b/Assets/PurrDiction/Runtime/Core/PredictedComponentID.cs @@ -22,7 +22,7 @@ public bool TryGetGameObject(PredictionManager manager, out GameObject gameObjec public GameObject GetGameObject(PredictionManager manager) { - var id = manager.GetIdentity(this); + var id = manager.GetIdentityIncludingDeleting(this); if (!id) return null; return id.gameObject; } diff --git a/Assets/PurrDiction/Runtime/Core/PredictedComponentIDExtensions.cs b/Assets/PurrDiction/Runtime/Core/PredictedComponentIDExtensions.cs index e6e68a73..9e974ea9 100644 --- a/Assets/PurrDiction/Runtime/Core/PredictedComponentIDExtensions.cs +++ b/Assets/PurrDiction/Runtime/Core/PredictedComponentIDExtensions.cs @@ -34,4 +34,4 @@ public static bool TryGetIdentity(this PredictedComponentID? id, PredictionManag return id?.TryGetIdentity(manager, out identity) ?? false; } } -} \ No newline at end of file +} diff --git a/Assets/PurrDiction/Runtime/Core/PredictionManager.cs b/Assets/PurrDiction/Runtime/Core/PredictionManager.cs index 612cdd6d..bb291f41 100644 --- a/Assets/PurrDiction/Runtime/Core/PredictionManager.cs +++ b/Assets/PurrDiction/Runtime/Core/PredictionManager.cs @@ -388,6 +388,7 @@ public void UnregisterPooledInstance(GameObject go) } readonly Dictionary _instanceMap = new (); + readonly Dictionary _deletingInstanceMap = new (); public bool TryGetIdentity(PredictedComponentID id, out PredictedIdentity instance) { @@ -399,6 +400,14 @@ public PredictedIdentity GetIdentity(PredictedComponentID id) return _instanceMap.GetValueOrDefault(id); } + internal PredictedIdentity GetIdentityIncludingDeleting(PredictedComponentID id) + { + if (_instanceMap.TryGetValue(id, out var identity)) + return identity; + + return _deletingInstanceMap.GetValueOrDefault(id); + } + private void RegisterInstance(PredictedIdentity system, PredictedObjectID objectId, uint componentId, PlayerID? owner) { if (!isSpawned) @@ -679,8 +688,7 @@ private void OnPreTick() } } - for (var i = 0; i < _systemsCount; i++) - _systems[i].PostSimulate(); + PostSimulateAll(); if (cachedIsServer) FinalizeTickOnServer(cachedIsClient); @@ -1176,8 +1184,7 @@ private void SimulateFrameInPlace(ulong verifiedTick) lateSimulateMarker.Dispose(); } - for (var i = 0; i < _systemsCount; i++) - _systems[i].PostSimulate(); + PostSimulateAll(); for (var j = 0; j < _systemsCount; j++) _systems[j].GetLatestUnityState(); @@ -1258,8 +1265,7 @@ private void SimulateFrame(ulong verifiedTick, bool saveState) } } - for (var i = 0; i < _systemsCount; i++) - _systems[i].PostSimulate(); + PostSimulateAll(); for (var j = 0; j < _systemsCount; j++) _systems[j].GetLatestUnityState(); @@ -1502,34 +1508,84 @@ internal GameObject InternalCreate(GameObject prefab, Vector3 position, Quaterni internal void InternalDelete(PackedInt prefabId, GameObject instance) { - int pid = prefabId; - - if (!_predictedPrefabs || pid < 0 || pid >= _predictedPrefabs.prefabs.Count) + var deletingIds = BeginDeletingInstance(instance); + try { - UnregisterInstance(instance, false, true); - UnityProxy.DestroyImmediateDirectly(instance); - return; - } + int pid = prefabId; - var prefabsInfo = _predictedPrefabs.prefabs[pid]; + if (!_predictedPrefabs || pid < 0 || pid >= _predictedPrefabs.prefabs.Count) + { + UnregisterInstance(instance, false, true); + UnityProxy.DestroyImmediateDirectly(instance); + return; + } - if (!prefabsInfo.pooled) - { - UnregisterInstance(instance, false, true); - UnityProxy.DestroyImmediateDirectly(instance); - return; - } + var prefabsInfo = _predictedPrefabs.prefabs[pid]; - if (_pools != null && _pools.TryGetPool(prefabsInfo.prefab, out var pool)) + if (!prefabsInfo.pooled) + { + UnregisterInstance(instance, false, true); + UnityProxy.DestroyImmediateDirectly(instance); + return; + } + + if (_pools != null && _pools.TryGetPool(prefabsInfo.prefab, out var pool)) + { + UnregisterPooledInstance(instance); + pool.Delete(instance); + } + else + { + UnregisterInstance(instance, false, true); + UnityProxy.DestroyImmediateDirectly(instance); + } + } + finally { - UnregisterPooledInstance(instance); - pool.Delete(instance); + if (!isSimulating) + EndDeletingInstance(deletingIds); } - else + } + + private List BeginDeletingInstance(GameObject instance) + { + var deletingIds = new List(); + + if (!instance) + return deletingIds; + + var identities = ListPool.Instantiate(); + instance.GetComponentsInChildren(true, identities); + + for (int i = 0; i < identities.Count; i++) { - UnregisterInstance(instance, false, true); - UnityProxy.DestroyImmediateDirectly(instance); + var identity = identities[i]; + var id = identity.id; + _deletingInstanceMap[id] = identity; + deletingIds.Add(id); } + + ListPool.Destroy(identities); + return deletingIds; + } + + private void EndDeletingInstance(List deletingIds) + { + for (int i = 0; i < deletingIds.Count; i++) + _deletingInstanceMap.Remove(deletingIds[i]); + } + + private void ClearDeletingInstanceMap() + { + _deletingInstanceMap.Clear(); + } + + private void PostSimulateAll() + { + for (var i = 0; i < _systemsCount; i++) + _systems[i].PostSimulate(); + + ClearDeletingInstanceMap(); } public void SetOwnership(PredictedObjectID? root, PlayerID? player) From ac303fca3aabca56eb3cb678b98120063cd550d4 Mon Sep 17 00:00:00 2001 From: Ashton Meuser Date: Thu, 11 Jun 2026 20:38:28 -0700 Subject: [PATCH 2/2] Always use deleting cache for identity resolution --- .../PurrDiction/Runtime/Core/PredictedComponentID.cs | 2 +- Assets/PurrDiction/Runtime/Core/PredictionManager.cs | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/Assets/PurrDiction/Runtime/Core/PredictedComponentID.cs b/Assets/PurrDiction/Runtime/Core/PredictedComponentID.cs index 96f9b5db..b8372805 100644 --- a/Assets/PurrDiction/Runtime/Core/PredictedComponentID.cs +++ b/Assets/PurrDiction/Runtime/Core/PredictedComponentID.cs @@ -22,7 +22,7 @@ public bool TryGetGameObject(PredictionManager manager, out GameObject gameObjec public GameObject GetGameObject(PredictionManager manager) { - var id = manager.GetIdentityIncludingDeleting(this); + var id = manager.GetIdentity(this); if (!id) return null; return id.gameObject; } diff --git a/Assets/PurrDiction/Runtime/Core/PredictionManager.cs b/Assets/PurrDiction/Runtime/Core/PredictionManager.cs index bb291f41..0f06bc2d 100644 --- a/Assets/PurrDiction/Runtime/Core/PredictionManager.cs +++ b/Assets/PurrDiction/Runtime/Core/PredictionManager.cs @@ -392,15 +392,13 @@ public void UnregisterPooledInstance(GameObject go) public bool TryGetIdentity(PredictedComponentID id, out PredictedIdentity instance) { - return _instanceMap.TryGetValue(id, out instance); - } + if (_instanceMap.TryGetValue(id, out instance)) + return true; - public PredictedIdentity GetIdentity(PredictedComponentID id) - { - return _instanceMap.GetValueOrDefault(id); + return _deletingInstanceMap.TryGetValue(id, out instance); } - internal PredictedIdentity GetIdentityIncludingDeleting(PredictedComponentID id) + public PredictedIdentity GetIdentity(PredictedComponentID id) { if (_instanceMap.TryGetValue(id, out var identity)) return identity;