Make recursive delete detach parents before children (#3008)

This commit is contained in:
Leon Friedrich
2022-07-17 08:05:10 +12:00
committed by GitHub
parent 5146b1b3c6
commit a38cbc7188
11 changed files with 286 additions and 210 deletions

View File

@@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Prometheus;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Profiling;
using Robust.Shared.Prototypes;
@@ -273,7 +274,7 @@ namespace Robust.Shared.GameObjects
/// <param name="e">Entity to remove</param>
public virtual void DeleteEntity(EntityUid e)
{
// Some UIs get dispose after entity-manager has shut down and already deleted all entities.
// Some UIs get disposed after entity-manager has shut down and already deleted all entities.
if (!Started)
return;
@@ -289,31 +290,61 @@ namespace Robust.Shared.GameObjects
return;
if (meta.EntityLifeStage == EntityLifeStage.Terminating)
{
var msg = $"Called Delete on an entity already being deleted. Entity: {ToPrettyString(meta.Owner)}";
#if !EXCEPTION_TOLERANCE
throw new InvalidOperationException("Called Delete on an entity already being deleted.");
throw new InvalidOperationException(msg);
#else
return;
Logger.Error(msg);
#endif
}
// Notify all entities they are being terminated prior to detaching & deleting
RecursiveFlagEntityTermination(meta, metaQuery, xformQuery, xformSys);
// Then actually delete them
RecursiveDeleteEntity(meta, metaQuery, xformQuery, xformSys);
}
private void RecursiveFlagEntityTermination(MetaDataComponent metadata, EntityQuery<MetaDataComponent> metaQuery, EntityQuery<TransformComponent> xformQuery, SharedTransformSystem xformSys)
{
var transform = xformQuery.GetComponent(metadata.Owner);
metadata.EntityLifeStage = EntityLifeStage.Terminating;
var ev = new EntityTerminatingEvent(metadata.Owner);
// TODO: consider making this a meta-data flag?
// veeeery few entities make use of this event.
EventBus.RaiseLocalEvent(metadata.Owner, ref ev, false);
foreach (var child in transform._children)
{
if (!metaQuery.TryGetComponent(child, out var childMeta) || childMeta.EntityDeleted)
{
Logger.Error($"A deleted entity was still the transform child of another entity. Parent: {ToPrettyString(metadata.Owner)}.");
transform._children.Remove(child);
continue;
}
RecursiveFlagEntityTermination(childMeta, metaQuery, xformQuery, xformSys);
}
}
private void RecursiveDeleteEntity(MetaDataComponent metadata, EntityQuery<MetaDataComponent> metaQuery, EntityQuery<TransformComponent> xformQuery, SharedTransformSystem xformSys)
{
var transform = xformQuery.GetComponent(metadata.Owner);
metadata.EntityLifeStage = EntityLifeStage.Terminating;
var ev = new EntityTerminatingEvent(metadata.Owner);
EventBus.RaiseLocalEvent(metadata.Owner, ref ev, false);
// DeleteEntity modifies our _children collection, we must cache the collection to iterate properly
foreach (var child in transform._children.ToArray())
// Detach the base entity to null before iterating over children
// This also ensures that the entity-lookup updates don't have to be re-run for every child (which recurses up the transform hierarchy).
if (transform.ParentUid != EntityUid.Invalid)
xformSys.DetachParentToNull(transform, xformQuery, metaQuery);
foreach (var child in transform._children)
{
if (!metaQuery.TryGetComponent(child, out var childMeta) || childMeta.EntityDeleted)
continue; //TODO: Why was this still a child if it was already deleted?
// Recursion Alert
RecursiveDeleteEntity(childMeta, metaQuery, xformQuery, xformSys);
RecursiveDeleteEntity(metaQuery.GetComponent(child), metaQuery, xformQuery, xformSys);
}
DebugTools.Assert(transform._children.Count == 0, $"Failed to delete all children of entity: {ToPrettyString(metadata.Owner)}");
// Shut down all components.
foreach (var component in InSafeOrder(_entCompIndex[metadata.Owner]))
{
@@ -321,13 +352,6 @@ namespace Robust.Shared.GameObjects
component.LifeShutdown(this);
}
// map does not have a parent node, everything else needs to be detached
if (transform.ParentUid != EntityUid.Invalid)
{
// Detach from my parent, if any
xformSys.DetachParentToNull(transform, xformQuery, metaQuery);
}
// Dispose all my components, in a safe order so transform is available
DisposeComponents(metadata.Owner);