Skip to content
Draft
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
5 changes: 5 additions & 0 deletions Assets/PurrDiction/Runtime/Core/PredictedComponentID.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public GameObject GetGameObject(PredictionManager manager)
return id.gameObject;
}

internal PredictedIdentity GetIdentityIncludingDeleting(PredictionManager manager)
{
return manager.GetIdentityIncludingDeleting(this);
}

public T GetIdentity<T>(PredictionManager manager) where T : PredictedIdentity
{
return (T)manager.GetIdentity(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,4 @@ public static bool TryGetIdentity(this PredictedComponentID? id, PredictionManag
return id?.TryGetIdentity(manager, out identity) ?? false;
}
}
}
}
108 changes: 82 additions & 26 deletions Assets/PurrDiction/Runtime/Core/PredictionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ public void UnregisterPooledInstance(GameObject go)
}

readonly Dictionary<PredictedComponentID, PredictedIdentity> _instanceMap = new ();
readonly Dictionary<PredictedComponentID, PredictedIdentity> _deletingInstanceMap = new ();

public bool TryGetIdentity(PredictedComponentID id, out PredictedIdentity instance)
{
Expand All @@ -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)
Expand Down Expand Up @@ -679,8 +688,7 @@ private void OnPreTick()
}
}

for (var i = 0; i < _systemsCount; i++)
_systems[i].PostSimulate();
PostSimulateAll();

if (cachedIsServer)
FinalizeTickOnServer(cachedIsClient);
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
}
Comment on lines +1543 to 1547

@ashtonmeuser ashtonmeuser Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

After a quick test, this should never be the case (as long as users are calling PredictedHierarchy.Delete() during sim) and can therefore be removed. This entire try/finally as well as EndDeletingInstance can be removed. That said, it's not really harmful either and may help in the case of PredictedHierarchy misuse.

else
}

private List<PredictedComponentID> BeginDeletingInstance(GameObject instance)
{
var deletingIds = new List<PredictedComponentID>();

if (!instance)
return deletingIds;

var identities = ListPool<PredictedIdentity>.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<PredictedIdentity>.Destroy(identities);
return deletingIds;
}

private void EndDeletingInstance(List<PredictedComponentID> 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ private static void TriggerEvent(PredictionManager predictionManager, Physics2DE
{
if (ev.me.TryGetIdentity<PredictedRigidbody2D>(predictionManager, out var me))
{
var otherGo = ev.other.GetGameObject(predictionManager);
var other = ev.other.GetIdentityIncludingDeleting(predictionManager);
var otherGo = other ? other.gameObject : null;
if (ev.isTrigger)
{
switch (ev.type)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ private static void TriggerEvent(PredictionManager predictionManager, PhysicsEve
{
if (ev.me.TryGetIdentity<IPredictedPhysicsCallbacks>(predictionManager, out var me))
{
var otherGo = ev.other.GetGameObject(predictionManager);
var other = ev.other.GetIdentityIncludingDeleting(predictionManager);
var otherGo = other ? other.gameObject : null;
if (ev.isTrigger)
{
switch (ev.type)
Expand Down
Loading