using System.Threading.Tasks; using NUnit.Framework; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Reflection; namespace Robust.UnitTesting.Shared.GameObjects; internal sealed partial class DeferredEntityDeletionTest : RobustIntegrationTest { // This test ensures that deferred deletion can be used while handling events without issue, and that deleting an // entity after deferring component removal doesn't cause any issues. [Test] public async Task TestDeferredEntityDeletion() { var options = new ServerIntegrationOptions(); options.Pool = false; options.BeforeRegisterComponents += () => { var fact = IoCManager.Resolve(); fact.RegisterClass(); fact.RegisterClass(); }; options.BeforeStart += () => { var sysMan = IoCManager.Resolve(); sysMan.LoadExtraSystemType(); sysMan.LoadExtraSystemType(); }; var server = StartServer(options); await server.WaitIdleAsync(); EntityUid uid1 = default, uid2 = default, uid3 = default, uid4 = default; DeferredDeletionTestComponent comp1 = default!, comp2 = default!, comp3 = default!, comp4 = default!; IEntityManager entMan = default!; await server.WaitAssertion(() => { var mapMan = IoCManager.Resolve(); entMan = IoCManager.Resolve(); var sys = entMan.EntitySysManager.GetEntitySystem(); uid1 = entMan.SpawnEntity(null, MapCoordinates.Nullspace); uid2 = entMan.SpawnEntity(null, MapCoordinates.Nullspace); uid3 = entMan.SpawnEntity(null, MapCoordinates.Nullspace); uid4 = entMan.SpawnEntity(null, MapCoordinates.Nullspace); comp1 = entMan.AddComponent(uid1); comp2 = entMan.AddComponent(uid2); comp3 = entMan.AddComponent(uid3); comp4 = entMan.AddComponent(uid4); entMan.AddComponent(uid1); entMan.AddComponent(uid2); entMan.AddComponent(uid3); entMan.AddComponent(uid4); }); await server.WaitRunTicks(1); // first: test that deferring deletion while handling events doesn't cause issues await server.WaitAssertion(() => { Assert.That(comp1.Running); var ev = new DeferredDeletionTestEvent(); entMan.EventBus.RaiseLocalEvent(uid1, ev); Assert.That(comp1.LifeStage == ComponentLifeStage.Stopped); }); await server.WaitRunTicks(1); Assert.That(comp1.LifeStage == ComponentLifeStage.Deleted); // next check that entity deletion doesn't cause issues: await server.WaitAssertion(() => { var ev = new DeferredDeletionTestEvent(); entMan.EventBus.RaiseLocalEvent(uid2, ev); entMan.EventBus.RaiseLocalEvent(uid3, ev); entMan.EventBus.RaiseLocalEvent(uid4, ev); entMan.DeleteEntity(uid2); entMan.QueueDeleteEntity(uid3); entMan.TryQueueDeleteEntity(uid4); Assert.That(entMan.Deleted(uid2)); Assert.That(!entMan.Deleted(uid3)); Assert.That(!entMan.Deleted(uid4)); Assert.That(comp2.LifeStage == ComponentLifeStage.Deleted); Assert.That(comp3.LifeStage == ComponentLifeStage.Stopped); Assert.That(comp4.LifeStage == ComponentLifeStage.Stopped); }); await server.WaitRunTicks(1); Assert.That(comp3.LifeStage == ComponentLifeStage.Deleted); Assert.That(comp4.LifeStage == ComponentLifeStage.Deleted); Assert.That(entMan.Deleted(uid3)); Assert.That(entMan.Deleted(uid4)); await server.WaitIdleAsync(); } [Reflect(false)] private sealed class DeferredDeletionTestSystem : EntitySystem { public override void Initialize() { SubscribeLocalEvent(OnTestEvent); } private void OnTestEvent(EntityUid uid, DeferredDeletionTestComponent component, DeferredDeletionTestEvent args) { // remove both this component, and some other component that this entity has that also subscribes to this event. RemCompDeferred(uid); RemCompDeferred(uid); } } [Reflect(false)] private sealed class OtherDeferredDeletionTestSystem : EntitySystem { public override void Initialize() => SubscribeLocalEvent(OnTestEvent); private void OnTestEvent(EntityUid uid, OtherDeferredDeletionTestComponent component, DeferredDeletionTestEvent args) { // remove both this component, and some other component that this entity has that also subscribes to this event. RemCompDeferred(uid); RemCompDeferred(uid); } } [RegisterComponent] [Reflect(false)] private sealed partial class DeferredDeletionTestComponent : Component { } [Reflect(false)] private sealed partial class OtherDeferredDeletionTestComponent : Component { } private sealed class DeferredDeletionTestEvent { } }