diff --git a/Content.IntegrationTests/PoolManager.cs b/Content.IntegrationTests/PoolManager.cs index 165c54681d..3bb55ef6fb 100644 --- a/Content.IntegrationTests/PoolManager.cs +++ b/Content.IntegrationTests/PoolManager.cs @@ -766,6 +766,7 @@ public sealed class PairTracker : IAsyncDisposable { Pair.Client.Dispose(); Pair.Server.Dispose(); + await PoolManager.ReallyBeIdle(Pair); var returnTime2 = cleanWatch.Elapsed; await TestContext.Out.WriteLineAsync($"{nameof(CleanReturnAsync)}: Clean disposed in {returnTime2.TotalMilliseconds} ms"); return; diff --git a/Content.IntegrationTests/Tests/EntityTest.cs b/Content.IntegrationTests/Tests/EntityTest.cs index 5966446dd4..9f967b33df 100644 --- a/Content.IntegrationTests/Tests/EntityTest.cs +++ b/Content.IntegrationTests/Tests/EntityTest.cs @@ -6,6 +6,7 @@ using Content.Shared.CCVar; using Content.Shared.Coordinates; using NUnit.Framework; using Robust.Shared.GameObjects; +using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Maths; @@ -18,79 +19,89 @@ namespace Content.IntegrationTests.Tests public sealed class EntityTest { [Test] - public async Task SpawnTest() + public async Task SpawnAndDeleteAllEntitiesOnDifferentMaps() { - //TODO: Run this test in a for loop, and figure out why garbage is ending up in the Entities list on cleanup. - //If this gets fixed, see also UninitializedSaveTest. - await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, Dirty = true, Destructive = true}); + await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, Destructive = true}); var server = pairTracker.Pair.Server; - var mapManager = server.ResolveDependency(); - var entityMan = server.ResolveDependency(); - var prototypeMan = server.ResolveDependency(); - var tileDefinitionManager = server.ResolveDependency(); + IEntityManager entityMan = null; - var prototypes = new List(); - IMapGrid grid = default; - EntityUid testEntity; - - //Build up test environment await server.WaitPost(() => { - // Create a one tile grid to stave off the grid 0 monsters - var mapId = mapManager.CreateMap(); - - mapManager.AddUninitializedMap(mapId); - - grid = mapManager.CreateGrid(mapId); - - var tileDefinition = tileDefinitionManager["UnderPlating"]; - var tile = new Tile(tileDefinition.TileId); - var coordinates = grid.ToCoordinates(); - - grid.SetTile(coordinates, tile); - - mapManager.DoMapInitialize(mapId); + entityMan = IoCManager.Resolve(); + var mapManager = IoCManager.Resolve(); + var prototypeMan = IoCManager.Resolve(); + var protoIds = prototypeMan + .EnumeratePrototypes() + .Where(p=>!p.Abstract) + .Select(p => p.ID) + .ToList(); + foreach (var protoId in protoIds) + { + var mapId = mapManager.CreateMap(); + var grid = mapManager.CreateGrid(mapId); + var coord = new EntityCoordinates(grid.GridEntityId, 0, 0); + entityMan.SpawnEntity(protoId, coord); + } }); await server.WaitRunTicks(5); - await server.WaitAssertion(() => + await server.WaitPost(() => { - var testLocation = grid.ToCoordinates(); - - //Generate list of non-abstract prototypes to test - foreach (var prototype in prototypeMan.EnumeratePrototypes()) + var entityMetas = entityMan.EntityQuery().ToList(); + foreach (var meta in entityMetas) { - if (prototype.Abstract) - { - continue; - } - - prototypes.Add(prototype); + if(!entityMan.Deleted(meta.Owner)) + entityMan.DeleteEntity(meta.Owner); } - Assert.Multiple(() => - { - //Iterate list of prototypes to spawn - foreach (var prototype in prototypes) - { - Assert.DoesNotThrow(() => - { - Logger.LogS(LogLevel.Debug, "EntityTest", $"Testing: {prototype.ID}"); - testEntity = entityMan.SpawnEntity(prototype.ID, testLocation); - server.RunTicks(1); - if(!entityMan.Deleted(testEntity)) - entityMan.DeleteEntity(testEntity); - }, "Entity '{0}' threw an exception.", - prototype.ID); - } - }); + Assert.That(entityMan.EntityCount, Is.Zero); }); - await pairTracker.CleanReturnAsync(); } + [Test] + public async Task SpawnAndDeleteAllEntitiesInTheSameSpot() + { + await using var pairTracker = await PoolManager.GetServerClient(new PoolSettings{NoClient = true, Destructive = true}); + var server = pairTracker.Pair.Server; + + IEntityManager entityMan = null; + + await server.WaitPost(() => + { + entityMan = IoCManager.Resolve(); + var mapManager = IoCManager.Resolve(); + + var prototypeMan = IoCManager.Resolve(); + var protoIds = prototypeMan + .EnumeratePrototypes() + .Where(p=>!p.Abstract) + .Select(p => p.ID) + .ToList(); + var mapId = mapManager.CreateMap(); + var grid = mapManager.CreateGrid(mapId); + var coord = new EntityCoordinates(grid.GridEntityId, 0, 0); + foreach (var protoId in protoIds) + { + entityMan.SpawnEntity(protoId, coord); + } + }); + await server.WaitRunTicks(5); + await server.WaitPost(() => + { + var entityMetas = entityMan.EntityQuery().ToList(); + foreach (var meta in entityMetas) + { + if(!entityMan.Deleted(meta.Owner)) + entityMan.DeleteEntity(meta.Owner); + } + + Assert.That(entityMan.EntityCount, Is.Zero); + }); + await pairTracker.CleanReturnAsync(); + } [Test] public async Task AllComponentsOneToOneDeleteTest() { diff --git a/Content.Server/AI/Pathfinding/Accessible/AiReachableSystem.cs b/Content.Server/AI/Pathfinding/Accessible/AiReachableSystem.cs index 86a08855d6..a9bd15991d 100644 --- a/Content.Server/AI/Pathfinding/Accessible/AiReachableSystem.cs +++ b/Content.Server/AI/Pathfinding/Accessible/AiReachableSystem.cs @@ -445,10 +445,11 @@ namespace Content.Server.AI.Pathfinding.Accessible var parentChunk = node.ParentChunk; // No guarantee the node even has a region yet (if we're doing neighbor lookups) - if (!_regions[parentChunk.GridId].TryGetValue(parentChunk, out var regions)) - { + if (!_regions.TryGetValue(parentChunk.GridId, out var chunk)) + return null; + + if (!chunk.TryGetValue(parentChunk, out var regions)) return null; - } foreach (var region in regions) { diff --git a/Content.Server/Explosion/EntitySystems/TriggerSystem.TimedCollide.cs b/Content.Server/Explosion/EntitySystems/TriggerSystem.TimedCollide.cs index 54b13f76d1..3251702469 100644 --- a/Content.Server/Explosion/EntitySystems/TriggerSystem.TimedCollide.cs +++ b/Content.Server/Explosion/EntitySystems/TriggerSystem.TimedCollide.cs @@ -19,6 +19,8 @@ public sealed partial class TriggerSystem //Ensures the entity trigger will have an active component EnsureComp(uid); var otherUID = args.OtherFixture.Body.Owner; + if (component.Colliding.ContainsKey(otherUID)) + return; component.Colliding.Add(otherUID, 0); } diff --git a/Content.Server/Station/Systems/StationSystem.cs b/Content.Server/Station/Systems/StationSystem.cs index 5a6c6625c0..290c4ce703 100644 --- a/Content.Server/Station/Systems/StationSystem.cs +++ b/Content.Server/Station/Systems/StationSystem.cs @@ -119,7 +119,8 @@ public sealed class StationSystem : EntitySystem private void OnStationDeleted(EntityUid uid, StationDataComponent component, ComponentShutdown args) { if (_stations.Contains(uid) && // Was not deleted via DeleteStation() - _gameTicker.RunLevel == GameRunLevel.InRound) // And not due to a round restart + _gameTicker.RunLevel == GameRunLevel.InRound && // And not due to a round restart + _gameTicker.LobbyEnabled) // If there isn't a lobby, this is probably sandbox, single player, or a test { // printing a stack trace, rather than throwing an exception so that entity deletion continues as normal. Logger.Error($"Station entity {ToPrettyString(uid)} is getting deleted mid-round. Trace: {Environment.StackTrace}"); diff --git a/Content.Shared/Body/Components/SharedBodyComponent.cs b/Content.Shared/Body/Components/SharedBodyComponent.cs index ca1c2ed81f..27bc6b0686 100644 --- a/Content.Shared/Body/Components/SharedBodyComponent.cs +++ b/Content.Shared/Body/Components/SharedBodyComponent.cs @@ -395,7 +395,8 @@ namespace Content.Shared.Body.Components var gibs = new HashSet(); foreach (var part in SlotParts.Keys) { - if (!metaQuery.HasComponent(part.Owner)) + if (!metaQuery.TryGetComponent(part.Owner, out var meta) || + meta.EntityLifeStage >= EntityLifeStage.Terminating) { SlotParts.Remove(part); continue; diff --git a/Resources/Prototypes/Entities/Structures/Power/debug_power.yml b/Resources/Prototypes/Entities/Structures/Power/debug_power.yml index e264e2d75e..0dd5233c71 100644 --- a/Resources/Prototypes/Entities/Structures/Power/debug_power.yml +++ b/Resources/Prototypes/Entities/Structures/Power/debug_power.yml @@ -94,6 +94,7 @@ tags: - Debug - type: Clickable + - type: PowerNetworkBattery - type: InteractionOutline - type: Physics - type: Fixtures