using System.Numerics; using NUnit.Framework; using Robust.Server.Containers; using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Physics; using Robust.UnitTesting.Server; namespace Robust.UnitTesting.Shared.Physics; [TestFixture] internal sealed class RecursiveUpdateTest { /// /// Check that the broadphase updates if a an entity is a child of an entity that is in a container. /// [Test] public void ContainerRecursiveUpdateTest() { var sim = RobustServerSimulation.NewSimulation().InitializeInstance(); var entManager = sim.Resolve(); var xforms = entManager.System(); var mapSystem = entManager.System(); var containers = entManager.System(); var mapId = sim.CreateMap().MapId; var grid = mapSystem.CreateGridEntity(mapId); var guid = grid.Owner; mapSystem.SetTile(grid, Vector2i.Zero, new Tile(1)); Assert.That(entManager.HasComponent(guid)); var broadphase = entManager.GetComponent(guid); var coords = new EntityCoordinates(guid, new Vector2(0.5f, 0.5f)); var broadData = new BroadphaseData(guid, false, false); var container = entManager.SpawnEntity(null, coords); var containerXform = entManager.GetComponent(container); Assert.That(broadphase.SundriesTree, Does.Contain(container)); Assert.That(containerXform.Broadphase, Is.EqualTo(broadData)); var contained = entManager.SpawnEntity(null, coords); var childA = entManager.SpawnEntity(null, MapCoordinates.Nullspace); var childB = entManager.SpawnEntity(null, MapCoordinates.Nullspace); var containedXform = entManager.GetComponent(contained); var childAXform = entManager.GetComponent(childA); var childBXform = entManager.GetComponent(childB); Assert.That(broadphase.SundriesTree, Does.Contain(contained)); Assert.That(containedXform.Broadphase, Is.EqualTo(broadData)); // Attach child A before inserting. xforms.SetCoordinates(childA, childAXform, new EntityCoordinates(contained, Vector2.Zero)); Assert.That(broadphase.SundriesTree, Does.Contain(childA)); Assert.That(childAXform.Broadphase, Is.EqualTo(broadData)); // Insert into container. var slot = containers.EnsureContainer(container, "test"); containers.Insert(contained, slot); // Attach child B after having inserted. xforms.SetCoordinates(childB, childBXform, new EntityCoordinates(contained, Vector2.Zero)); Assert.That(broadphase.SundriesTree, Does.Contain(container)); Assert.That(broadphase.SundriesTree, Does.Not.Contain(contained)); Assert.That(broadphase.SundriesTree, Does.Not.Contain(childA)); Assert.That(broadphase.SundriesTree, Does.Not.Contain(childB)); Assert.That(containerXform.Broadphase, Is.EqualTo(broadData)); Assert.That(containedXform.Broadphase, Is.EqualTo(null)); Assert.That(childAXform.Broadphase, Is.EqualTo(null)); Assert.That(childBXform.Broadphase, Is.EqualTo(null)); Assert.That(containerXform.ParentUid, Is.EqualTo(guid)); Assert.That(containedXform.ParentUid, Is.EqualTo(container)); Assert.That(childAXform.ParentUid, Is.EqualTo(contained)); Assert.That(childBXform.ParentUid, Is.EqualTo(contained)); // Check that moving the container does not re-add the contained entities to the broadphase. var newCoords = new EntityCoordinates(guid, new Vector2(0.25f, 0.25f)); xforms.SetCoordinates(container, newCoords); Assert.That(broadphase.SundriesTree, Does.Contain(container)); Assert.That(broadphase.SundriesTree, Does.Not.Contain(contained)); Assert.That(broadphase.SundriesTree, Does.Not.Contain(childA)); Assert.That(broadphase.SundriesTree, Does.Not.Contain(childB)); Assert.That(containerXform.Broadphase, Is.EqualTo(broadData)); Assert.That(containedXform.Broadphase, Is.EqualTo(null)); Assert.That(childAXform.Broadphase, Is.EqualTo(null)); Assert.That(childBXform.Broadphase, Is.EqualTo(null)); Assert.That(containerXform.ParentUid, Is.EqualTo(guid)); Assert.That(containedXform.ParentUid, Is.EqualTo(container)); Assert.That(childAXform.ParentUid, Is.EqualTo(contained)); Assert.That(childBXform.ParentUid, Is.EqualTo(contained)); // Remove from container. containers.Remove(contained, slot); Assert.That(broadphase.SundriesTree, Does.Contain(container)); Assert.That(broadphase.SundriesTree, Does.Contain(contained)); Assert.That(broadphase.SundriesTree, Does.Contain(childA)); Assert.That(broadphase.SundriesTree, Does.Contain(childB)); Assert.That(containerXform.Broadphase, Is.EqualTo(broadData)); Assert.That(containedXform.Broadphase, Is.EqualTo(broadData)); Assert.That(childAXform.Broadphase, Is.EqualTo(broadData)); Assert.That(childBXform.Broadphase, Is.EqualTo(broadData)); Assert.That(containerXform.ParentUid, Is.EqualTo(guid)); Assert.That(containedXform.ParentUid, Is.EqualTo(guid)); Assert.That(childAXform.ParentUid, Is.EqualTo(contained)); Assert.That(childBXform.ParentUid, Is.EqualTo(contained)); // Insert back into container. containers.Insert(contained, slot); Assert.That(broadphase.SundriesTree, Does.Contain(container)); Assert.That(broadphase.SundriesTree, Does.Not.Contain(contained)); Assert.That(broadphase.SundriesTree, Does.Not.Contain(childA)); Assert.That(broadphase.SundriesTree, Does.Not.Contain(childB)); Assert.That(containerXform.Broadphase, Is.EqualTo(broadData)); Assert.That(containedXform.Broadphase, Is.EqualTo(null)); Assert.That(childAXform.Broadphase, Is.EqualTo(null)); Assert.That(childBXform.Broadphase, Is.EqualTo(null)); Assert.That(containerXform.ParentUid, Is.EqualTo(guid)); Assert.That(containedXform.ParentUid, Is.EqualTo(container)); Assert.That(childAXform.ParentUid, Is.EqualTo(contained)); Assert.That(childBXform.ParentUid, Is.EqualTo(contained)); // re-remove from container, but this time WITHOUT changing parent. containers.Remove(contained, slot, reparent: false); Assert.That(broadphase.SundriesTree, Does.Contain(container)); Assert.That(broadphase.SundriesTree, Does.Contain(contained)); Assert.That(broadphase.SundriesTree, Does.Contain(childA)); Assert.That(broadphase.SundriesTree, Does.Contain(childB)); Assert.That(containerXform.Broadphase, Is.EqualTo(broadData)); Assert.That(containedXform.Broadphase, Is.EqualTo(broadData)); Assert.That(childAXform.Broadphase, Is.EqualTo(broadData)); Assert.That(childBXform.Broadphase, Is.EqualTo(broadData)); Assert.That(containerXform.ParentUid, Is.EqualTo(guid)); Assert.That(containedXform.ParentUid, Is.EqualTo(container)); Assert.That(childAXform.ParentUid, Is.EqualTo(contained)); Assert.That(childBXform.ParentUid, Is.EqualTo(contained)); } /// /// Check that the broadphase positions update when moving an entity's parent. /// [Test] public void RecursiveMoveTest() { var sim = RobustServerSimulation.NewSimulation().InitializeInstance(); var entManager = sim.Resolve(); var mapSystem = entManager.EntitySysManager.GetEntitySystem(); var transforms = entManager.EntitySysManager.GetEntitySystem(); var lookup = entManager.EntitySysManager.GetEntitySystem(); var mapId = sim.CreateMap().MapId; var map = mapSystem.GetMapOrInvalid(mapId); var mapBroadphase = entManager.GetComponent(map); var coords = new EntityCoordinates(map, new Vector2(0.5f, 0.5f)); var mapBroadData = new BroadphaseData(map, false, false); // Set up parent & child var parent = entManager.SpawnEntity(null, coords); var child = entManager.SpawnEntity(null, new EntityCoordinates(parent, Vector2.Zero)); var parentXform = entManager.GetComponent(parent); var childXform = entManager.GetComponent(child); // Check correct broadphase Assert.That(parentXform.ParentUid, Is.EqualTo(map)); Assert.That(childXform.ParentUid, Is.EqualTo(parent)); Assert.That(mapBroadphase.SundriesTree, Does.Contain(parent)); Assert.That(mapBroadphase.SundriesTree, Does.Contain(child)); Assert.That(parentXform.Broadphase, Is.EqualTo(mapBroadData)); Assert.That(childXform.Broadphase, Is.EqualTo(mapBroadData)); // Check that the entities can be found via a lookup. var box = Box2.CenteredAround(coords.Position, Vector2.One); var ents = lookup.GetEntitiesIntersecting(mapId, box); Assert.That(ents, Does.Contain(parent)); Assert.That(ents, Does.Contain(child)); // Move the parent far away. var farCoords = new EntityCoordinates(map, new Vector2(100f, 100f)); transforms.SetCoordinates(parent, farCoords); // broadphases have not changed Assert.That(parentXform.ParentUid, Is.EqualTo(map)); Assert.That(childXform.ParentUid, Is.EqualTo(parent)); Assert.That(mapBroadphase.SundriesTree, Does.Contain(parent)); Assert.That(mapBroadphase.SundriesTree, Does.Contain(child)); Assert.That(parentXform.Broadphase, Is.EqualTo(mapBroadData)); Assert.That(childXform.Broadphase, Is.EqualTo(mapBroadData)); // old lookup area no longer finds anything ents = lookup.GetEntitiesIntersecting(mapId, box); Assert.That(ents.Count, Is.EqualTo(0)); // updated lookup area still finds entities var farBox = Box2.CenteredAround(farCoords.Position, Vector2.One); ents = lookup.GetEntitiesIntersecting(mapId, farBox); Assert.That(ents, Does.Contain(parent)); Assert.That(ents, Does.Contain(child)); // Try again, but this time with a parent change. var grid = mapSystem.CreateGridEntity(mapId); var guid = grid.Owner; mapSystem.SetTile(grid, Vector2i.Zero, new Tile(1)); var gridBroadphase = entManager.GetComponent(guid); var gridBroadData = new BroadphaseData(guid, false, false); var gridCoords = new EntityCoordinates(map, new Vector2(-100f, -100f)); transforms.SetCoordinates(guid, gridCoords); // Move parent to the grid var gridLocal = new EntityCoordinates(guid, new Vector2(0.5f, 0.5f)); transforms.SetCoordinates(parent, gridLocal); // broadphases have now changed to be on the grids broadphase Assert.That(parentXform.ParentUid, Is.EqualTo(guid)); Assert.That(childXform.ParentUid, Is.EqualTo(parent)); Assert.That(gridBroadphase.SundriesTree, Does.Contain(parent)); Assert.That(gridBroadphase.SundriesTree, Does.Contain(child)); Assert.That(mapBroadphase.SundriesTree, Does.Not.Contain(parent)); Assert.That(mapBroadphase.SundriesTree, Does.Not.Contain(child)); Assert.That(parentXform.Broadphase, Is.EqualTo(gridBroadData)); Assert.That(childXform.Broadphase, Is.EqualTo(gridBroadData)); // old lookup areas no longer finds anything ents = lookup.GetEntitiesIntersecting(mapId, box); Assert.That(ents.Count, Is.EqualTo(0)); ents = lookup.GetEntitiesIntersecting(mapId, farBox); Assert.That(ents.Count, Is.EqualTo(0)); // grid lookup works var gridBox = Box2.CenteredAround(gridCoords.Position, Vector2.One); ents = lookup.GetEntitiesIntersecting(mapId, gridBox); Assert.That(ents, Does.Contain(parent)); Assert.That(ents, Does.Contain(child)); // Move grid far away var newGridCoords = new EntityCoordinates(map, new Vector2(-100f, 100f)); transforms.SetCoordinates(guid, newGridCoords); // Check lookups again ents = lookup.GetEntitiesIntersecting(mapId, box); Assert.That(ents.Count, Is.EqualTo(0)); ents = lookup.GetEntitiesIntersecting(mapId, farBox); Assert.That(ents.Count, Is.EqualTo(0)); ents = lookup.GetEntitiesIntersecting(mapId, gridBox); Assert.That(ents.Count, Is.EqualTo(0)); // grid lookup works var newGridBox = Box2.CenteredAround(newGridCoords.Position, Vector2.One); ents = lookup.GetEntitiesIntersecting(mapId, newGridBox); Assert.That(ents, Does.Contain(parent)); Assert.That(ents, Does.Contain(child)); } }