From ae2f3fe70ce97652dec2e77feaa2b8316a3b0e60 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Thu, 27 Oct 2022 23:36:01 +1100 Subject: [PATCH] Remove EntityLookupComponent and de-dupe AABBs (#3367) Co-authored-by: metalgearsloth --- .../EntitySystems/DebugEntityLookupSystem.cs | 21 +- Robust.Server/Maps/MapLoader.cs | 2 +- Robust.Server/Physics/GridFixtureSystem.cs | 2 +- .../Components/EntityLookupComponent.cs | 10 - .../Transform/TransformComponent.cs | 1 + .../Systems/EntityLookup.Queries.cs | 380 ++++++++----- .../EntityLookupSystem.ComponentQueries.cs | 48 +- .../GameObjects/Systems/EntityLookupSystem.cs | 450 +++++++++++---- .../Systems/SharedTransformSystem.cs | 2 +- Robust.Shared/Physics/BroadphaseComponent.cs | 15 +- .../Components/PhysicsComponent.Physics.cs | 36 -- .../Physics/Dynamics/ContactManager.cs | 7 +- .../Physics/Systems/FixtureSystem.cs | 30 +- .../Physics/Systems/SharedBroadphaseSystem.cs | 529 ++---------------- .../Systems/SharedPhysicsSystem.Components.cs | 9 +- .../Systems/SharedPhysicsSystem.Queries.cs | 134 +++-- .../Physics/Systems/SharedPhysicsSystem.cs | 20 +- Robust.UnitTesting/RobustUnitTest.cs | 10 - .../GameObjects/Components/Transform_Test.cs | 2 +- .../Server/RobustServerSimulation.cs | 1 - .../Shared/EntityLookup_Test.cs | 37 -- .../Shared/Physics/Broadphase_Test.cs | 39 +- 22 files changed, 837 insertions(+), 948 deletions(-) delete mode 100644 Robust.Shared/GameObjects/Components/EntityLookupComponent.cs diff --git a/Robust.Client/GameObjects/EntitySystems/DebugEntityLookupSystem.cs b/Robust.Client/GameObjects/EntitySystems/DebugEntityLookupSystem.cs index 05ef929af..5e4ddcff3 100644 --- a/Robust.Client/GameObjects/EntitySystems/DebugEntityLookupSystem.cs +++ b/Robust.Client/GameObjects/EntitySystems/DebugEntityLookupSystem.cs @@ -6,6 +6,7 @@ using Robust.Shared.Enums; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Maths; +using Robust.Shared.Physics.Dynamics; using Robust.Shared.Utility; using Color = Robust.Shared.Maths.Color; @@ -79,11 +80,23 @@ public sealed class EntityLookupOverlay : Overlay var lookupAABB = invMatrix.TransformBox(args.WorldBounds); var ents = new List(); - // Gonna allocate a lot but debug overlay sooo - lookup.Tree._b2Tree.FastQuery(ref lookupAABB, (ref EntityUid data) => + lookup.DynamicTree.QueryAabb(ref ents, static (ref List state, in FixtureProxy value) => { - ents.Add(data); - }); + state.Add(value.Fixture.Body.Owner); + return true; + }, lookupAABB); + + lookup.StaticTree.QueryAabb(ref ents, static (ref List state, in FixtureProxy value) => + { + state.Add(value.Fixture.Body.Owner); + return true; + }, lookupAABB); + + lookup.SundriesTree.QueryAabb(ref ents, static (ref List state, in EntityUid value) => + { + state.Add(value); + return true; + }, lookupAABB); foreach (var ent in ents) { diff --git a/Robust.Server/Maps/MapLoader.cs b/Robust.Server/Maps/MapLoader.cs index 1d23c2a89..9dbdb3b43 100644 --- a/Robust.Server/Maps/MapLoader.cs +++ b/Robust.Server/Maps/MapLoader.cs @@ -646,7 +646,7 @@ namespace Robust.Server.Maps // is bad for slothcoin because a bunch of components are only added // to the grid during its initialisation hence you get exceptions // hence this 1 snowflake thing. - _serverEntityManager.EnsureComponent(grid.GridEntityId); + _serverEntityManager.EnsureComponent(grid.GridEntityId); } } diff --git a/Robust.Server/Physics/GridFixtureSystem.cs b/Robust.Server/Physics/GridFixtureSystem.cs index 44b023347..39a09ba05 100644 --- a/Robust.Server/Physics/GridFixtureSystem.cs +++ b/Robust.Server/Physics/GridFixtureSystem.cs @@ -310,7 +310,7 @@ namespace Robust.Server.Physics var tilePos = offset + tile; var bounds = _lookup.GetLocalBounds(tilePos, mapGrid.TileSize); - foreach (var ent in _lookup.GetEntitiesIntersecting(mapGrid.GridEntityId, tilePos, LookupFlags.None)) + foreach (var ent in _lookup.GetEntitiesIntersecting(mapGrid.GridEntityId, tilePos, LookupFlags.Dynamic | LookupFlags.Sundries)) { // Consider centre of entity position maybe? var entXform = xformQuery.GetComponent(ent); diff --git a/Robust.Shared/GameObjects/Components/EntityLookupComponent.cs b/Robust.Shared/GameObjects/Components/EntityLookupComponent.cs deleted file mode 100644 index f1880e032..000000000 --- a/Robust.Shared/GameObjects/Components/EntityLookupComponent.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Robust.Shared.Physics; - -namespace Robust.Shared.GameObjects -{ - [RegisterComponent] - public sealed class EntityLookupComponent : Component - { - public DynamicTree Tree = default!; - } -} diff --git a/Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs b/Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs index 3f6b28394..d95addeb7 100644 --- a/Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs +++ b/Robust.Shared/GameObjects/Components/Transform/TransformComponent.cs @@ -190,6 +190,7 @@ namespace Robust.Shared.GameObjects /// Reference to the transform of the container of this object if it exists, can be nested several times. /// [ViewVariables] + [Obsolete("Use ParentUid and query the parent TransformComponent")] public TransformComponent? Parent { get => !_parent.IsValid() ? null : _entMan.GetComponent(_parent); diff --git a/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs b/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs index fd02e8ca0..836a89e6d 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookup.Queries.cs @@ -6,6 +6,7 @@ using Robust.Shared.Containers; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Physics; +using Robust.Shared.Physics.Dynamics; using Robust.Shared.Utility; namespace Robust.Shared.GameObjects; @@ -25,19 +26,42 @@ public sealed partial class EntityLookupSystem HashSet intersecting, Box2 worldAABB, LookupFlags flags, - EntityQuery lookupQuery, + EntityQuery lookupQuery, EntityQuery xformQuery) { var lookup = lookupQuery.GetComponent(lookupUid); var invMatrix = _transform.GetInvWorldMatrix(lookupUid, xformQuery); var localAABB = invMatrix.TransformBox(worldAABB); - lookup.Tree.QueryAabb(ref intersecting, - static (ref HashSet intersecting, in EntityUid value) => + if ((flags & LookupFlags.Dynamic) != 0x0) { - intersecting.Add(value); - return true; - }, localAABB, (flags & LookupFlags.Approximate) != 0x0); + lookup.DynamicTree.QueryAabb(ref intersecting, + static (ref HashSet state, in FixtureProxy value) => + { + state.Add(value.Fixture.Body.Owner); + return true; + }, localAABB, (flags & LookupFlags.Approximate) != 0x0); + } + + if ((flags & (LookupFlags.Static | LookupFlags.Anchored)) != 0x0) + { + lookup.StaticTree.QueryAabb(ref intersecting, + static (ref HashSet state, in FixtureProxy value) => + { + state.Add(value.Fixture.Body.Owner); + return true; + }, localAABB, (flags & LookupFlags.Approximate) != 0x0); + } + + if ((flags & LookupFlags.Sundries) != 0x0) + { + lookup.SundriesTree.QueryAabb(ref intersecting, + static (ref HashSet state, in EntityUid value) => + { + state.Add(value); + return true; + }, localAABB, (flags & LookupFlags.Approximate) != 0x0); + } } private void AddEntitiesIntersecting( @@ -45,25 +69,49 @@ public sealed partial class EntityLookupSystem HashSet intersecting, Box2Rotated worldBounds, LookupFlags flags, - EntityQuery lookupQuery, + EntityQuery lookupQuery, EntityQuery xformQuery) { var lookup = lookupQuery.GetComponent(lookupUid); var invMatrix = _transform.GetInvWorldMatrix(lookupUid, xformQuery); + // We don't just use CalcBoundingBox because the transformed bounds might be tighter. var localAABB = invMatrix.TransformBox(worldBounds); - lookup.Tree.QueryAabb(ref intersecting, - static (ref HashSet intersecting, in EntityUid value) => + if ((flags & LookupFlags.Dynamic) != 0x0) { - intersecting.Add(value); - return true; - }, localAABB, (flags & LookupFlags.Approximate) != 0x0); + lookup.DynamicTree.QueryAabb(ref intersecting, + static (ref HashSet state, in FixtureProxy value) => + { + state.Add(value.Fixture.Body.Owner); + return true; + }, localAABB, (flags & LookupFlags.Approximate) != 0x0); + } + + if ((flags & (LookupFlags.Static | LookupFlags.Anchored)) != 0x0) + { + lookup.StaticTree.QueryAabb(ref intersecting, + static (ref HashSet state, in FixtureProxy value) => + { + state.Add(value.Fixture.Body.Owner); + return true; + }, localAABB, (flags & LookupFlags.Approximate) != 0x0); + } + + if ((flags & LookupFlags.Sundries) != 0x0) + { + lookup.SundriesTree.QueryAabb(ref intersecting, + static (ref HashSet state, in EntityUid value) => + { + state.Add(value); + return true; + }, localAABB, (flags & LookupFlags.Approximate) != 0x0); + } } private bool AnyEntitiesIntersecting(EntityUid lookupUid, Box2 worldAABB, LookupFlags flags, - EntityQuery lookupQuery, + EntityQuery lookupQuery, EntityQuery xformQuery, EntityUid? ignored = null) { @@ -71,38 +119,52 @@ public sealed partial class EntityLookupSystem var localAABB = xformQuery.GetComponent(lookupUid).InvWorldMatrix.TransformBox(worldAABB); var state = (ignored, found: false); - lookup.Tree.QueryAabb(ref state, static (ref (EntityUid? ignored, bool found) tuple, in EntityUid value) => + if ((flags & LookupFlags.Dynamic) != 0x0) { - if (tuple.ignored == value) - return true; + lookup.DynamicTree.QueryAabb(ref state, (ref (EntityUid? ignored, bool found) tuple, in FixtureProxy value) => + { + if (tuple.ignored == value.Fixture.Body.Owner) + return true; - tuple.found = true; - return false; - }, localAABB, (flags & LookupFlags.Approximate) != 0x0); + tuple.found = true; + return false; + }, localAABB, (flags & LookupFlags.Approximate) != 0x0); + } + + if ((flags & LookupFlags.Dynamic) != 0x0) + { + lookup.StaticTree.QueryAabb(ref state, (ref (EntityUid? ignored, bool found) tuple, in FixtureProxy value) => + { + if (tuple.ignored == value.Fixture.Body.Owner) + return true; + + tuple.found = true; + return false; + }, localAABB, (flags & LookupFlags.Approximate) != 0x0); + } + + if ((flags & LookupFlags.Sundries) != 0x0) + { + lookup.SundriesTree.QueryAabb(ref state, static (ref (EntityUid? ignored, bool found) tuple, in EntityUid value) => + { + if (tuple.ignored == value) + return true; + + tuple.found = true; + return false; + }, localAABB, (flags & LookupFlags.Approximate) != 0x0); + } if (state.found) return true; - if (!_mapManager.TryGetGrid(lookupUid, out var grid)) return false; - - // TODO: Need a method that takes in local AABB. - - if ((flags & LookupFlags.Anchored) != 0x0) - { - foreach (var uid in grid.GetAnchoredEntities(worldAABB)) - { - if (uid == ignored) continue; - return true; - } - } - return false; } private bool AnyEntitiesIntersecting(EntityUid lookupUid, Box2Rotated worldBounds, LookupFlags flags, - EntityQuery lookupQuery, + EntityQuery lookupQuery, EntityQuery xformQuery, EntityUid? ignored = null) { @@ -110,30 +172,45 @@ public sealed partial class EntityLookupSystem var localAABB = xformQuery.GetComponent(lookupUid).InvWorldMatrix.TransformBox(worldBounds); var state = (ignored, found: false); - lookup.Tree.QueryAabb(ref state, static (ref (EntityUid? ignored, bool found) tuple, in EntityUid value) => + if ((flags & LookupFlags.Dynamic) != 0x0) { - if (tuple.ignored == value) - return true; + lookup.DynamicTree.QueryAabb(ref state, (ref (EntityUid? ignored, bool found) tuple, in FixtureProxy value) => + { + if (tuple.ignored == value.Fixture.Body.Owner) + return true; - tuple.found = true; - return false; - }, localAABB, (flags & LookupFlags.Approximate) != 0x0); + tuple.found = true; + return false; + }, localAABB, (flags & LookupFlags.Approximate) != 0x0); + } + + if ((flags & (LookupFlags.Static | LookupFlags.Anchored)) != 0x0) + { + lookup.StaticTree.QueryAabb(ref state, (ref (EntityUid? ignored, bool found) tuple, in FixtureProxy value) => + { + if (tuple.ignored == value.Fixture.Body.Owner) + return true; + + tuple.found = true; + return false; + }, localAABB, (flags & LookupFlags.Approximate) != 0x0); + } + + if ((flags & LookupFlags.Sundries) != 0x0) + { + lookup.SundriesTree.QueryAabb(ref state, static (ref (EntityUid? ignored, bool found) tuple, in EntityUid value) => + { + if (tuple.ignored == value) + return true; + + tuple.found = true; + return false; + }, localAABB, (flags & LookupFlags.Approximate) != 0x0); + } if (state.found) return true; - if (_mapManager.TryGetGrid(lookupUid, out var grid)) - { - // TODO: Need a method that takes in local AABB. - if ((flags & LookupFlags.Anchored) != 0x0) - { - foreach (var _ in grid.GetAnchoredEntities(worldBounds)) - { - return true; - } - } - } - return state.found; } @@ -150,7 +227,8 @@ public sealed partial class EntityLookupSystem private void AddContained(HashSet intersecting, LookupFlags flags, EntityQuery xformQuery) { - if ((flags & LookupFlags.Contained) == 0x0 || intersecting.Count == 0) return; + if ((flags & LookupFlags.Contained) == 0x0 || intersecting.Count == 0) + return; var conQuery = GetEntityQuery(); var toAdd = new ValueList(); @@ -217,7 +295,7 @@ public sealed partial class EntityLookupSystem { if (mapId == MapId.Nullspace) return false; - var lookupQuery = GetEntityQuery(); + var lookupQuery = GetEntityQuery(); var xformQuery = GetEntityQuery(); // Don't need to check contained entities as they have the same bounds as the parent. @@ -235,7 +313,7 @@ public sealed partial class EntityLookupSystem { if (mapId == MapId.Nullspace) return new HashSet(); - var lookupQuery = GetEntityQuery(); + var lookupQuery = GetEntityQuery(); var xformQuery = GetEntityQuery(); var intersecting = new HashSet(); @@ -269,7 +347,7 @@ public sealed partial class EntityLookupSystem public bool AnyEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = DefaultFlags) { - var lookupQuery = GetEntityQuery(); + var lookupQuery = GetEntityQuery(); var xformQuery = GetEntityQuery(); // Don't need to check contained entities as they have the same bounds as the parent. @@ -287,7 +365,7 @@ public sealed partial class EntityLookupSystem { if (mapId == MapId.Nullspace) return new HashSet(); - var lookupQuery = GetEntityQuery(); + var lookupQuery = GetEntityQuery(); var xformQuery = GetEntityQuery(); var intersecting = new HashSet(); @@ -296,15 +374,6 @@ public sealed partial class EntityLookupSystem foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds.CalcBoundingBox())) { AddEntitiesIntersecting(grid.GridEntityId, intersecting, worldBounds, flags, lookupQuery, xformQuery); - - if ((flags & LookupFlags.Anchored) != 0x0) - { - foreach (var uid in grid.GetAnchoredEntities(worldBounds)) - { - if (Deleted(uid)) continue; - intersecting.Add(uid); - } - } } // Get map entities @@ -328,7 +397,7 @@ public sealed partial class EntityLookupSystem if (mapID == MapId.Nullspace) return false; - var lookupQuery = GetEntityQuery(); + var lookupQuery = GetEntityQuery(); var xformQuery = GetEntityQuery(); foreach (var grid in _mapManager.FindGridsIntersecting(mapID, worldAABB)) @@ -348,7 +417,7 @@ public sealed partial class EntityLookupSystem if (mapPos.MapId == MapId.Nullspace) return false; var worldAABB = new Box2(mapPos.Position - range, mapPos.Position + range); - var lookupQuery = GetEntityQuery(); + var lookupQuery = GetEntityQuery(); var xformQuery = GetEntityQuery(); foreach (var grid in _mapManager.FindGridsIntersecting(mapPos.MapId, worldAABB)) @@ -484,7 +553,7 @@ public sealed partial class EntityLookupSystem // Technically this doesn't consider anything overlapping from outside the grid but is this an issue? if (!_mapManager.TryGetGrid(gridId, out var grid)) return new HashSet(); - var lookup = Comp(grid.GridEntityId); + var lookup = Comp(grid.GridEntityId); var intersecting = new HashSet(); var tileSize = grid.TileSize; @@ -493,18 +562,31 @@ public sealed partial class EntityLookupSystem { var aabb = GetLocalBounds(index, tileSize); - lookup.Tree.QueryAabb(ref intersecting, static (ref HashSet intersecting, in EntityUid value) => + if ((flags & LookupFlags.Dynamic) != 0x0) { - intersecting.Add(value); - return true; - }, aabb, (flags & LookupFlags.Approximate) != 0x0); - - if ((flags & LookupFlags.Anchored) != 0x0) - { - foreach (var ent in grid.GetAnchoredEntities(index)) + lookup.DynamicTree.QueryAabb(ref intersecting, static (ref HashSet state, in FixtureProxy value) => { - intersecting.Add(ent); - } + state.Add(value.Fixture.Body.Owner); + return true; + }, aabb, (flags & LookupFlags.Approximate) != 0x0); + } + + if ((flags & (LookupFlags.Static | LookupFlags.Anchored)) != 0x0) + { + lookup.StaticTree.QueryAabb(ref intersecting, static (ref HashSet state, in FixtureProxy value) => + { + state.Add(value.Fixture.Body.Owner); + return true; + }, aabb, (flags & LookupFlags.Approximate) != 0x0); + } + + if ((flags & LookupFlags.Sundries) != 0x0) + { + lookup.SundriesTree.QueryAabb(ref intersecting, static (ref HashSet intersecting, in EntityUid value) => + { + intersecting.Add(value); + return true; + }, aabb, (flags & LookupFlags.Approximate) != 0x0); } } @@ -519,26 +601,41 @@ public sealed partial class EntityLookupSystem // Technically this doesn't consider anything overlapping from outside the grid but is this an issue? if (!_mapManager.TryGetGrid(gridId, out var grid)) return new HashSet(); - var lookup = Comp(grid.GridEntityId); + var lookup = Comp(grid.GridEntityId); var tileSize = grid.TileSize; var aabb = GetLocalBounds(gridIndices, tileSize); var intersecting = new HashSet(); - var state = (lookup.Tree._b2Tree, intersecting); + var state = (lookup.SundriesTree._b2Tree, intersecting); - lookup.Tree._b2Tree.Query(ref state, static (ref (B2DynamicTree _b2Tree, HashSet intersecting) tuple, DynamicTree.Proxy proxy) => + if ((flags & LookupFlags.Dynamic) != 0x0) { - tuple.intersecting.Add(tuple._b2Tree.GetUserData(proxy)); - return true; - }, aabb); + lookup.DynamicTree.QueryAabb(ref state, + static (ref (B2DynamicTree _b2Tree, HashSet intersecting) tuple, in FixtureProxy value) => + { + tuple.intersecting.Add(value.Fixture.Body.Owner); + return true; + }, aabb, (flags & LookupFlags.Approximate) != 0x0); + } - if ((flags & LookupFlags.Anchored) != 0x0) + if ((flags & (LookupFlags.Static | LookupFlags.Anchored)) != 0x0) { - foreach (var ent in grid.GetAnchoredEntities(gridIndices)) + lookup.StaticTree.QueryAabb(ref state, + static (ref (B2DynamicTree _b2Tree, HashSet intersecting) tuple, in FixtureProxy value) => + { + tuple.intersecting.Add(value.Fixture.Body.Owner); + return true; + }, aabb, (flags & LookupFlags.Approximate) != 0x0); + } + + if ((flags & LookupFlags.Sundries) != 0x0) + { + lookup.SundriesTree._b2Tree.Query(ref state, static (ref (B2DynamicTree _b2Tree, HashSet intersecting) tuple, DynamicTree.Proxy proxy) => { - intersecting.Add(ent); - } + tuple.intersecting.Add(tuple._b2Tree.GetUserData(proxy)); + return true; + }, aabb); } var xformQuery = GetEntityQuery(); @@ -551,21 +648,13 @@ public sealed partial class EntityLookupSystem { if (!_mapManager.TryGetGrid(gridId, out var grid)) return new HashSet(); - var lookupQuery = GetEntityQuery(); + var lookupQuery = GetEntityQuery(); var xformQuery = GetEntityQuery(); var intersecting = new HashSet(); AddEntitiesIntersecting(gridId, intersecting, worldAABB, flags, lookupQuery, xformQuery); - - if ((flags & LookupFlags.Anchored) != 0x0) - { - foreach (var uid in grid.GetAnchoredEntities(worldAABB)) - { - intersecting.Add(uid); - } - } - AddContained(intersecting, flags, xformQuery); + return intersecting; } @@ -573,22 +662,13 @@ public sealed partial class EntityLookupSystem { if (!_mapManager.TryGetGrid(gridId, out var grid)) return new HashSet(); - var lookupQuery = GetEntityQuery(); + var lookupQuery = GetEntityQuery(); var xformQuery = GetEntityQuery(); var intersecting = new HashSet(); AddEntitiesIntersecting(grid.GridEntityId, intersecting, worldBounds, flags, lookupQuery, xformQuery); - - if ((flags & LookupFlags.Anchored) != 0x0) - { - foreach (var uid in grid.GetAnchoredEntities(worldBounds)) - { - if (Deleted(uid)) continue; - intersecting.Add(uid); - } - } - AddContained(intersecting, flags, xformQuery); + return intersecting; } @@ -602,24 +682,37 @@ public sealed partial class EntityLookupSystem #region Lookup Query - public HashSet GetEntitiesIntersecting(EntityLookupComponent component, ref Box2 worldAABB, LookupFlags flags = DefaultFlags) + public HashSet GetEntitiesIntersecting(BroadphaseComponent component, ref Box2 worldAABB, LookupFlags flags = DefaultFlags) { var intersecting = new HashSet(); var xformQuery = GetEntityQuery(); var localAABB = xformQuery.GetComponent(component.Owner).InvWorldMatrix.TransformBox(worldAABB); - component.Tree.QueryAabb(ref intersecting, static (ref HashSet intersecting, in EntityUid value) => + if ((flags & LookupFlags.Dynamic) != 0x0) { - intersecting.Add(value); - return true; - }, localAABB, (flags & LookupFlags.Approximate) != 0x0); - - if ((flags & LookupFlags.Anchored) != 0x0 && _mapManager.TryGetGrid(component.Owner, out var grid)) - { - foreach (var uid in grid.GetLocalAnchoredEntities(localAABB)) + component.DynamicTree.QueryAabb(ref intersecting, static (ref HashSet intersecting, in FixtureProxy value) => { - intersecting.Add(uid); - } + intersecting.Add(value.Fixture.Body.Owner); + return true; + }, localAABB, (flags & LookupFlags.Approximate) != 0x0); + } + + if ((flags & (LookupFlags.Static | LookupFlags.Anchored)) != 0x0) + { + component.StaticTree.QueryAabb(ref intersecting, static (ref HashSet intersecting, in FixtureProxy value) => + { + intersecting.Add(value.Fixture.Body.Owner); + return true; + }, localAABB, (flags & LookupFlags.Approximate) != 0x0); + } + + if ((flags & LookupFlags.Sundries) != 0x0) + { + component.SundriesTree.QueryAabb(ref intersecting, static (ref HashSet intersecting, in EntityUid value) => + { + intersecting.Add(value); + return true; + }, localAABB, (flags & LookupFlags.Approximate) != 0x0); } AddContained(intersecting, flags, xformQuery); @@ -627,22 +720,35 @@ public sealed partial class EntityLookupSystem return intersecting; } - public HashSet GetLocalEntitiesIntersecting(EntityLookupComponent component, Box2 localAABB, LookupFlags flags = DefaultFlags) + public HashSet GetLocalEntitiesIntersecting(BroadphaseComponent component, Box2 localAABB, LookupFlags flags = DefaultFlags) { var intersecting = new HashSet(); - component.Tree.QueryAabb(ref intersecting, static (ref HashSet intersecting, in EntityUid value) => + if ((flags & LookupFlags.Dynamic) != 0x0) { - intersecting.Add(value); - return true; - }, localAABB, (flags & LookupFlags.Approximate) != 0x0); - - if ((flags & LookupFlags.Anchored) != 0x0 && _mapManager.TryGetGrid(component.Owner, out var grid)) - { - foreach (var uid in grid.GetLocalAnchoredEntities(localAABB)) + component.DynamicTree.QueryAabb(ref intersecting, static (ref HashSet intersecting, in FixtureProxy value) => { - intersecting.Add(uid); - } + intersecting.Add(value.Fixture.Body.Owner); + return true; + }, localAABB, (flags & LookupFlags.Approximate) != 0x0); + } + + if ((flags & (LookupFlags.Static | LookupFlags.Anchored)) != 0x0) + { + component.StaticTree.QueryAabb(ref intersecting, static (ref HashSet intersecting, in FixtureProxy value) => + { + intersecting.Add(value.Fixture.Body.Owner); + return true; + }, localAABB, (flags & LookupFlags.Approximate) != 0x0); + } + + if ((flags & LookupFlags.Sundries) != 0x0) + { + component.SundriesTree.QueryAabb(ref intersecting, static (ref HashSet intersecting, in EntityUid value) => + { + intersecting.Add(value); + return true; + }, localAABB, (flags & LookupFlags.Approximate) != 0x0); } AddContained(intersecting, flags, GetEntityQuery()); @@ -655,13 +761,13 @@ public sealed partial class EntityLookupSystem #region Lookups /// - /// Gets the relevant that intersects the specified area. + /// Gets the relevant that intersects the specified area. /// - public IEnumerable FindLookupsIntersecting(MapId mapId, Box2 worldAABB) + public IEnumerable FindLookupsIntersecting(MapId mapId, Box2 worldAABB) { if (mapId == MapId.Nullspace) yield break; - var lookupQuery = GetEntityQuery(); + var lookupQuery = GetEntityQuery(); yield return lookupQuery.GetComponent(_mapManager.GetMapEntityId(mapId)); @@ -672,13 +778,13 @@ public sealed partial class EntityLookupSystem } /// - /// Gets the relevant that intersects the specified area. + /// Gets the relevant that intersects the specified area. /// - public IEnumerable FindLookupsIntersecting(MapId mapId, Box2Rotated worldBounds) + public IEnumerable FindLookupsIntersecting(MapId mapId, Box2Rotated worldBounds) { if (mapId == MapId.Nullspace) yield break; - var lookupQuery = GetEntityQuery(); + var lookupQuery = GetEntityQuery(); yield return lookupQuery.GetComponent(_mapManager.GetMapEntityId(mapId)); diff --git a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs index 8bacb0497..7b885bbf8 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.ComponentQueries.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using Robust.Shared.Containers; using Robust.Shared.Map; using Robust.Shared.Maths; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Dynamics; using Robust.Shared.Utility; namespace Robust.Shared.GameObjects; @@ -15,7 +17,7 @@ public sealed partial class EntityLookupSystem HashSet intersecting, Box2 worldAABB, LookupFlags flags, - EntityQuery lookupQuery, + EntityQuery lookupQuery, EntityQuery xformQuery, EntityQuery query) where T : Component { @@ -24,7 +26,33 @@ public sealed partial class EntityLookupSystem var localAABB = invMatrix.TransformBox(worldAABB); var state = (intersecting, query); - lookup.Tree.QueryAabb(ref state, (ref (HashSet intersecting, EntityQuery query) tuple, in EntityUid value) => + if ((flags & LookupFlags.Dynamic) != 0x0) + { + lookup.DynamicTree.QueryAabb(ref state, static (ref (HashSet intersecting, EntityQuery query) tuple, in FixtureProxy value) => + { + if (!tuple.query.TryGetComponent(value.Fixture.Body.Owner, out var comp)) + return true; + + tuple.intersecting.Add(comp); + return true; + }, localAABB, (flags & LookupFlags.Approximate) != 0x0); + } + + if ((flags & (LookupFlags.Static | LookupFlags.Anchored)) != 0x0) + { + lookup.StaticTree.QueryAabb(ref state, static (ref (HashSet intersecting, EntityQuery query) tuple, in FixtureProxy value) => + { + if (!tuple.query.TryGetComponent(value.Fixture.Body.Owner, out var comp)) + return true; + + tuple.intersecting.Add(comp); + return true; + }, localAABB, (flags & LookupFlags.Approximate) != 0x0); + } + + if ((flags & LookupFlags.Sundries) != 0x0) + { + lookup.SundriesTree.QueryAabb(ref state, static (ref (HashSet intersecting, EntityQuery query) tuple, in EntityUid value) => { if (!tuple.query.TryGetComponent(value, out var comp)) return true; @@ -32,6 +60,7 @@ public sealed partial class EntityLookupSystem tuple.intersecting.Add(comp); return true; }, localAABB, (flags & LookupFlags.Approximate) != 0x0); + } } private void RecursiveAdd(EntityUid uid, List toAdd, EntityQuery xformQuery, EntityQuery query) where T : Component @@ -96,7 +125,7 @@ public sealed partial class EntityLookupSystem // Like .Queries but works with components #region Box2 - public HashSet GetComponentsIntersecting(MapId mapId, Box2 worldAABB, LookupFlags flags = DefaultFlags) where T : Component + public HashSet GetComponentsIntersecting(MapId mapId, Box2 worldAABB, LookupFlags flags = DefaultFlags) where T : Component { if (mapId == MapId.Nullspace) return new HashSet(); @@ -107,27 +136,18 @@ public sealed partial class EntityLookupSystem { foreach (var (comp, xform) in EntityQuery(true)) { - if (xform.MapID != mapId || (xform.Anchored && (flags & LookupFlags.Anchored) == 0x0) || !worldAABB.Contains(_transform.GetWorldPosition(comp.Owner, xformQuery))) continue; + if (xform.MapID != mapId || !worldAABB.Contains(_transform.GetWorldPosition(comp.Owner, xformQuery))) continue; intersecting.Add(comp); } } else { var query = GetEntityQuery(); - var lookupQuery = GetEntityQuery(); + var lookupQuery = GetEntityQuery(); // Get grid entities foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB)) { AddComponentsIntersecting(grid.GridEntityId, intersecting, worldAABB, flags, lookupQuery, xformQuery, query); - - if ((flags & LookupFlags.Anchored) != 0x0) - { - foreach (var uid in grid.GetAnchoredEntities(worldAABB)) - { - if (!query.TryGetComponent(uid, out var comp)) continue; - intersecting.Add(comp); - } - } } // Get map entities diff --git a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs index 05a223593..583ff4b3b 100644 --- a/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs +++ b/Robust.Shared/GameObjects/Systems/EntityLookupSystem.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using JetBrains.Annotations; using Robust.Shared.Configuration; using Robust.Shared.Containers; @@ -8,6 +9,11 @@ using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Physics; +using Robust.Shared.Physics.BroadPhase; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Dynamics; +using Robust.Shared.Physics.Events; +using Robust.Shared.Physics.Systems; using Robust.Shared.Utility; namespace Robust.Shared.GameObjects @@ -22,16 +28,33 @@ namespace Robust.Shared.GameObjects /// Approximate = 1 << 0, + /// + /// Should we query dynamic physics bodies. + /// + Dynamic = 1 << 1, + + /// + /// Should we query static physics bodies. + /// + Static = 1 << 2, + + /// + /// Should we query non-collidable physics bodies. + /// + Sundries = 1 << 3, + /// /// Also return entities from an anchoring query. /// - Anchored = 1 << 1, + [Obsolete("Use Static")] + Anchored = 1 << 4, /// /// Include entities that are currently in containers. /// - Contained = 1 << 2, - // IncludeGrids = 1 << 2, + Contained = 1 << 5, + + Uncontained = Dynamic | Static | Sundries, } public sealed partial class EntityLookupSystem : EntitySystem @@ -43,7 +66,7 @@ namespace Robust.Shared.GameObjects /// /// Returns all non-grid entities. Consider using your own flags if you wish for a faster query. /// - public const LookupFlags DefaultFlags = LookupFlags.Contained | LookupFlags.Anchored; + public const LookupFlags DefaultFlags = LookupFlags.Contained | LookupFlags.Dynamic | LookupFlags.Static | LookupFlags.Sundries; private const int GrowthRate = 256; @@ -61,18 +84,19 @@ namespace Robust.Shared.GameObjects var configManager = IoCManager.Resolve(); configManager.OnValueChanged(CVars.LookupEnlargementRange, value => _lookupEnlargementRange = value, true); + SubscribeLocalEvent(OnBroadphaseAdd); + SubscribeLocalEvent(OnGridAdd); + SubscribeLocalEvent(OnMapChange); + SubscribeLocalEvent(OnMove); SubscribeLocalEvent(OnParentChange); - SubscribeLocalEvent(OnAnchored); SubscribeLocalEvent(OnContainerInsert); SubscribeLocalEvent(OnContainerRemove); - SubscribeLocalEvent(OnLookupAdd); - SubscribeLocalEvent(OnLookupShutdown); - SubscribeLocalEvent(OnGridAdd); + SubscribeLocalEvent(OnBodyTypeChange); + SubscribeLocalEvent(OnPhysicsUpdate); EntityManager.EntityInitialized += OnEntityInit; - SubscribeLocalEvent(OnMapCreated); } public override void Shutdown() @@ -92,10 +116,11 @@ namespace Robust.Shared.GameObjects var xformQuery = GetEntityQuery(); - if (!xformQuery.Resolve(uid, ref xform) || xform.Anchored) + if (!xformQuery.Resolve(uid, ref xform)) return; - var lookup = GetLookup(uid, xform, xformQuery); + var broadQuery = GetEntityQuery(); + var lookup = GetBroadphase(uid, xform, broadQuery, xformQuery); if (lookup == null) return; @@ -108,76 +133,28 @@ namespace Robust.Shared.GameObjects AddToEntityTree(lookup, xform, aabb, xformQuery, lookupRotation); } - private void OnAnchored(ref AnchorStateChangedEvent args) - { - // This event needs to be handled immediately as anchoring is handled immediately - // and any callers may potentially get duplicate entities that just changed state. - if (args.Anchored) - { - RemoveFromEntityTree(args.Entity); - } - else if (!args.Detaching && - TryComp(args.Entity, out MetaDataComponent? meta) && - meta.EntityLifeStage < EntityLifeStage.Terminating) - { - var xformQuery = GetEntityQuery(); - var xform = xformQuery.GetComponent(args.Entity); - var lookup = GetLookup(args.Entity, xform, xformQuery); - - if (lookup == null) - throw new InvalidOperationException(); - - var coordinates = _transform.GetMoverCoordinates(xform.Coordinates, xformQuery); - var lookupRotation = _transform.GetWorldRotation(lookup.Owner, xformQuery); - DebugTools.Assert(coordinates.EntityId == lookup.Owner); - - // If we're contained then LocalRotation should be 0 anyway. - var aabb = GetAABB(args.Entity, coordinates.Position, _transform.GetWorldRotation(xform, xformQuery) - lookupRotation, xform, xformQuery); - AddToEntityTree(lookup, xform, aabb, xformQuery, lookupRotation); - } - // else -> the entity is terminating. We can ignore this un-anchor event, as this entity will be removed by the tree via OnEntityDeleted. - } - #region DynamicTree - private void OnLookupShutdown(EntityUid uid, EntityLookupComponent component, ComponentShutdown args) + private void OnMapChange(MapChangedEvent ev) { - component.Tree.Clear(); + if (ev.Created && ev.Map != MapId.Nullspace) + { + EnsureComp(_mapManager.GetMapEntityId(ev.Map)); + } } private void OnGridAdd(GridAddEvent ev) { - EntityManager.EnsureComponent(ev.EntityUid); + // Must be done before initialization as that's when broadphase data starts getting set. + EnsureComp(ev.EntityUid); } - private void OnLookupAdd(EntityUid uid, EntityLookupComponent component, ComponentAdd args) + private void OnBroadphaseAdd(EntityUid uid, BroadphaseComponent component, ComponentAdd args) { - int capacity; - - if (EntityManager.TryGetComponent(uid, out TransformComponent? xform)) - { - capacity = (int) Math.Min(256, Math.Ceiling(xform.ChildCount / (float) GrowthRate) * GrowthRate); - } - else - { - capacity = 256; - } - - component.Tree = new DynamicTree( - (in EntityUid e) => GetTreeAABB(e, component.Owner), - capacity: capacity, - growthFunc: x => x == GrowthRate ? GrowthRate * 8 : x * 2 - ); - } - - private void OnMapCreated(MapChangedEvent eventArgs) - { - if(eventArgs.Destroyed) - return; - - if (eventArgs.Map == MapId.Nullspace) return; - - EntityManager.EnsureComponent(_mapManager.GetMapEntityId(eventArgs.Map)); + component.DynamicTree = new DynamicTreeBroadPhase(); + component.StaticTree = new DynamicTreeBroadPhase(); + component.SundriesTree = new DynamicTree( + (in EntityUid value) => GetTreeAABB(value, component.Owner)); } private Box2 GetTreeAABB(EntityUid entity, EntityUid tree) @@ -202,22 +179,233 @@ namespace Robust.Shared.GameObjects return treeXform.InvWorldMatrix.TransformBox(GetWorldAABB(entity, xform)); } + internal void CreateProxies(Fixture fixture, Vector2 worldPos, Angle worldRot) + { + // TODO: Grids on broadphasecomponent + if (_mapManager.IsGrid(fixture.Body.Owner)) + return; + + var xformQuery = GetEntityQuery(); + var broadQuery = GetEntityQuery(); + var xform = xformQuery.GetComponent(fixture.Body.Owner); + var broadphase = GetBroadphase(fixture.Body.Owner, xformQuery.GetComponent(fixture.Body.Owner), broadQuery, xformQuery); + + if (broadphase == null || xform.MapUid == null) + { + throw new InvalidOperationException(); + } + + var mapTransform = new Transform(worldPos, worldRot); + var (_, broadWorldRot, _, broadInvMatrix) = xformQuery.GetComponent(broadphase.Owner).GetWorldPositionRotationMatrixWithInv(); + var broadphaseTransform = new Transform(broadInvMatrix.Transform(mapTransform.Position), mapTransform.Quaternion2D.Angle - broadWorldRot); + var moveBuffer = Comp(xform.MapUid.Value).MoveBuffer; + var tree = fixture.Body.BodyType == BodyType.Static ? broadphase.StaticTree : broadphase.DynamicTree; + DebugTools.Assert(fixture.ProxyCount == 0); + + AddOrMoveProxies(fixture, tree, broadphaseTransform, mapTransform, moveBuffer); + } + + internal void DestroyProxies(Fixture fixture, TransformComponent xform) + { + if (_mapManager.IsGrid(fixture.Body.Owner)) + return; + + if (fixture.ProxyCount == 0) + { + Logger.Warning($"Tried to destroy fixture {fixture.ID} on {ToPrettyString(fixture.Body.Owner)} that already has no proxies?"); + return; + } + + var xformQuery = GetEntityQuery(); + var broadQuery = GetEntityQuery(); + var broadphase = GetBroadphase(fixture.Body.Owner, xformQuery.GetComponent(fixture.Body.Owner), broadQuery, xformQuery); + + if (broadphase == null || xform.MapUid == null) + { + throw new InvalidOperationException(); + } + + var tree = fixture.Body.BodyType == BodyType.Static ? broadphase.StaticTree : broadphase.DynamicTree; + var moveBuffer = Comp(xform.MapUid.Value).MoveBuffer; + DestroyProxies(fixture, tree, moveBuffer); + } + #endregion #region Entity events + + private void OnPhysicsUpdate(ref CollisionChangeEvent ev) + { + if (HasComp(ev.Body.Owner)) + return; + + var broadQuery = GetEntityQuery(); + var xformQuery = GetEntityQuery(); + var xform = xformQuery.GetComponent(ev.Body.Owner); + var broadphase = GetBroadphase(ev.Body.Owner, xform, broadQuery, xformQuery); + + if (broadphase == null) + return; + + if (ev.CanCollide) + { + RemoveSundriesTree(ev.Body.Owner, broadphase); + AddBroadTree(ev.Body, broadphase, ev.Body.BodyType, xform: xform); + } + else + { + RemoveBroadTree(ev.Body, broadphase, ev.Body.BodyType); + AddSundriesTree(ev.Body.Owner, broadphase); + } + } + + private void OnBodyTypeChange(EntityUid uid, PhysicsComponent component, ref PhysicsBodyTypeChangedEvent args) + { + if (!component.CanCollide || HasComp(uid)) + return; + + var broadphase = GetBroadphase(Transform(uid)); + + if (broadphase == null) + return; + + if (args.Old != BodyType.Static && args.New != BodyType.Static) + return; + + RemoveBroadTree(component, broadphase, args.Old); + AddBroadTree(component, broadphase, component.BodyType); + } + + private void RemoveBroadTree(PhysicsComponent body, BroadphaseComponent lookup, BodyType bodyType, FixturesComponent? manager = null) + { + if (!Resolve(body.Owner, ref manager)) + return; + + if (!TryComp(lookup.Owner, out var lookupXform) || lookupXform.MapUid == null) + { + throw new InvalidOperationException(); + } + + var tree = bodyType == BodyType.Static ? lookup.StaticTree : lookup.DynamicTree; + var moveBuffer = Comp(lookupXform.MapUid.Value).MoveBuffer; + + foreach (var (_, fixture) in manager.Fixtures) + { + DestroyProxies(fixture, tree, moveBuffer); + } + } + + private void DestroyProxies(Fixture fixture, IBroadPhase tree, Dictionary moveBuffer) + { + for (var i = 0; i < fixture.ProxyCount; i++) + { + var proxy = fixture.Proxies[i]; + tree.RemoveProxy(proxy.ProxyId); + moveBuffer.Remove(proxy); + } + + fixture.ProxyCount = 0; + fixture.Proxies = Array.Empty(); + } + + private void AddBroadTree(PhysicsComponent body, BroadphaseComponent lookup, BodyType bodyType, FixturesComponent? manager = null, TransformComponent? xform = null) + { + if (!Resolve(body.Owner, ref manager, ref xform)) + return; + + var tree = bodyType == BodyType.Static ? lookup.StaticTree : lookup.DynamicTree; + var xformQuery = GetEntityQuery(); + + if (!TryComp(lookup.Owner, out var lookupXform) || lookupXform.MapUid == null) + { + throw new InvalidOperationException(); + } + + var (worldPos, worldRot) = _transform.GetWorldPositionRotation(xform, xformQuery); + var mapTransform = new Transform(worldPos, worldRot); + var (_, broadWorldRot, _, broadInvMatrix) = xformQuery.GetComponent(lookup.Owner).GetWorldPositionRotationMatrixWithInv(); + var broadphaseTransform = new Transform(broadInvMatrix.Transform(mapTransform.Position), mapTransform.Quaternion2D.Angle - broadWorldRot); + var moveBuffer = Comp(lookupXform.MapUid.Value).MoveBuffer; + + foreach (var (_, fixture) in manager.Fixtures) + { + AddOrMoveProxies(fixture, tree, broadphaseTransform, mapTransform, moveBuffer); + } + } + + private void AddOrMoveProxies( + Fixture fixture, + IBroadPhase tree, + Transform broadphaseTransform, + Transform mapTransform, + Dictionary moveBuffer) + { + DebugTools.Assert(fixture.Body.CanCollide); + + // Moving + if (fixture.ProxyCount > 0) + { + for (var i = 0; i < fixture.ProxyCount; i++) + { + var bounds = fixture.Shape.ComputeAABB(broadphaseTransform, i); + var proxy = fixture.Proxies[i]; + tree.MoveProxy(proxy.ProxyId, bounds, Vector2.Zero); + proxy.AABB = bounds; + moveBuffer[proxy] = fixture.Shape.ComputeAABB(mapTransform, i); + } + + return; + } + + var count = fixture.Shape.ChildCount; + var proxies = new FixtureProxy[count]; + + for (var i = 0; i < count; i++) + { + var bounds = fixture.Shape.ComputeAABB(broadphaseTransform, i); + var proxy = new FixtureProxy(bounds, fixture, i); + proxy.ProxyId = tree.AddProxy(ref proxy); + proxy.AABB = bounds; + proxies[i] = proxy; + moveBuffer[proxy] = fixture.Shape.ComputeAABB(mapTransform, i); + } + + fixture.Proxies = proxies; + fixture.ProxyCount = count; + } + + private void AddSundriesTree(EntityUid uid, BroadphaseComponent lookup) + { + var tree = lookup.SundriesTree; + tree.Add(uid); + } + + private void RemoveSundriesTree(EntityUid uid, BroadphaseComponent lookup) + { + var tree = lookup.SundriesTree; + tree.Remove(uid); + } + private void OnEntityInit(EntityUid uid) { - if (_container.IsEntityInContainer(uid)) return; + if (_container.IsEntityInContainer(uid)) + return; var xformQuery = GetEntityQuery(); - if (!xformQuery.TryGetComponent(uid, out var xform) || - xform.Anchored) return; + if (!xformQuery.TryGetComponent(uid, out var xform)) + { + return; + } if (_mapManager.IsMap(uid) || - _mapManager.IsGrid(uid)) return; + _mapManager.IsGrid(uid)) + { + return; + } - var lookup = GetLookup(uid, xform, xformQuery); + var broadQuery = GetEntityQuery(); + var lookup = GetBroadphase(uid, xform, broadQuery, xformQuery); // If nullspace or the likes. if (lookup == null) return; @@ -243,8 +431,9 @@ namespace Robust.Shared.GameObjects // Even if the entity is contained it may have children that aren't so we still need to update. if (!CanMoveUpdate(uid)) return; + var broadQuery = GetEntityQuery(); var xformQuery = GetEntityQuery(); - var lookup = GetLookup(uid, xform, xformQuery); + var lookup = GetBroadphase(uid, xform, broadQuery, xformQuery); if (lookup == null) return; @@ -282,18 +471,26 @@ namespace Robust.Shared.GameObjects if (meta.EntityLifeStage < EntityLifeStage.Initialized || _mapManager.IsGrid(args.Entity) || - _mapManager.IsMap(args.Entity)) return; - - var xformQuery = GetEntityQuery(); - var xform = args.Transform; - EntityLookupComponent? oldLookup = null; - - if (args.OldMapId != MapId.Nullspace && args.OldParent != null) + _mapManager.IsMap(args.Entity)) { - oldLookup = GetLookup(args.OldParent.Value, xformQuery); + return; } - var newLookup = GetLookup(args.Entity, xform, xformQuery); + var xformQuery = GetEntityQuery(); + var broadQuery = GetEntityQuery(); + var xform = args.Transform; + BroadphaseComponent? oldLookup = null; + + if (args.OldMapId != MapId.Nullspace && xformQuery.TryGetComponent(args.OldParent, out var parentXform)) + { + // If the old parent has a broadphase return that, otherwise return the parent's broadphase. + if (!broadQuery.TryGetComponent(args.OldParent.Value, out oldLookup)) + { + oldLookup = GetBroadphase(args.OldParent.Value, parentXform, broadQuery, xformQuery); + } + } + + var newLookup = GetBroadphase(args.Entity, xform, broadQuery, xformQuery); // If parent is the same then no need to do anything as position should stay the same. if (oldLookup == newLookup) return; @@ -306,9 +503,10 @@ namespace Robust.Shared.GameObjects private void OnContainerRemove(EntRemovedFromContainerMessage ev) { + var broadQuery = GetEntityQuery(); var xformQuery = GetEntityQuery(); var xform = xformQuery.GetComponent(ev.Entity); - var lookup = GetLookup(ev.Entity, xform, xformQuery); + var lookup = GetBroadphase(ev.Entity, xform, broadQuery, xformQuery); if (lookup == null) return; @@ -322,13 +520,14 @@ namespace Robust.Shared.GameObjects if (ev.OldParent == EntityUid.Invalid || !xformQuery.TryGetComponent(ev.OldParent, out var oldXform)) return; - var lookup = GetLookup(ev.OldParent, oldXform, xformQuery); + var broadQuery = GetEntityQuery(); + var lookup = GetBroadphase(ev.OldParent, oldXform, broadQuery, xformQuery); RemoveFromEntityTree(lookup, xformQuery.GetComponent(ev.Entity), xformQuery); } private void AddToEntityTree( - EntityLookupComponent lookup, + BroadphaseComponent lookup, TransformComponent xform, EntityQuery xformQuery, Angle lookupRotation, @@ -341,7 +540,7 @@ namespace Robust.Shared.GameObjects } private void AddToEntityTree( - EntityLookupComponent? lookup, + BroadphaseComponent? lookup, TransformComponent xform, Box2 aabb, EntityQuery xformQuery, @@ -351,8 +550,7 @@ namespace Robust.Shared.GameObjects // If entity is in nullspace then no point keeping track of data structure. if (lookup == null) return; - if (!xform.Anchored) - lookup.Tree.AddOrUpdate(xform.Owner, aabb); + AddTree(xform.Owner, lookup, aabb, xform: xform); var childEnumerator = xform.ChildEnumerator; @@ -386,23 +584,37 @@ namespace Robust.Shared.GameObjects } } - private void RemoveFromEntityTree(EntityUid uid, bool recursive = true) + private void AddTree(EntityUid uid, BroadphaseComponent broadphase, Box2 aabb, PhysicsComponent? body = null, TransformComponent? xform = null) { - var xformQuery = GetEntityQuery(); - var xform = xformQuery.GetComponent(uid); - var lookup = GetLookup(uid, xform, xformQuery); - RemoveFromEntityTree(lookup, xform, xformQuery, recursive); + if (!Resolve(uid, ref body, false) || !body.CanCollide) + { + broadphase.SundriesTree.AddOrUpdate(uid, aabb); + return; + } + + AddBroadTree(body, broadphase, body.BodyType, xform: xform); + } + + private void RemoveTree(EntityUid uid, BroadphaseComponent broadphase, PhysicsComponent? body = null) + { + if (!Resolve(uid, ref body, false) || !body.CanCollide) + { + broadphase.SundriesTree.Remove(uid); + return; + } + + RemoveBroadTree(body, broadphase, body.BodyType); } /// /// Recursively iterates through this entity's children and removes them from the entitylookupcomponent. /// - private void RemoveFromEntityTree(EntityLookupComponent? lookup, TransformComponent xform, EntityQuery xformQuery, bool recursive = true) + private void RemoveFromEntityTree(BroadphaseComponent? lookup, TransformComponent xform, EntityQuery xformQuery, bool recursive = true) { // TODO: Move this out of the loop if (lookup == null) return; - lookup.Tree.Remove(xform.Owner); + RemoveTree(xform.Owner, lookup); if (!recursive) return; @@ -414,37 +626,47 @@ namespace Robust.Shared.GameObjects } } - #endregion - - private EntityLookupComponent? GetLookup(EntityUid entity, EntityQuery xformQuery) + /// + /// Attempt to get the relevant broadphase for this entity. + /// Can return null if it's the map entity. + /// + private BroadphaseComponent? GetBroadphase(TransformComponent xform) { - var xform = xformQuery.GetComponent(entity); - return GetLookup(entity, xform, xformQuery); + if (xform.MapID == MapId.Nullspace) return null; + + var broadQuery = GetEntityQuery(); + var xformQuery = GetEntityQuery(); + return GetBroadphase(xform.Owner, xform, broadQuery, xformQuery); } - private EntityLookupComponent? GetLookup(EntityUid uid, TransformComponent xform, EntityQuery xformQuery) + public BroadphaseComponent? GetBroadphase(EntityUid uid) { - if (xform.MapID == MapId.Nullspace) - return null; + var broadQuery = GetEntityQuery(); + var xformQuery = GetEntityQuery(); + return GetBroadphase(uid, xformQuery.GetComponent(uid), broadQuery, xformQuery); + } + + public BroadphaseComponent? GetBroadphase(EntityUid uid, TransformComponent xform, EntityQuery broadQuery, EntityQuery xformQuery) + { + if (xform.MapID == MapId.Nullspace) return null; var parent = xform.ParentUid; - var lookupQuery = GetEntityQuery(); - // If we're querying a map / grid just return it directly. - if (lookupQuery.TryGetComponent(uid, out var lookup)) - { - return lookup; - } + // if it's map (or in null-space) return null. Grids should return the map's broadphase. while (parent.IsValid()) { - if (lookupQuery.TryGetComponent(parent, out var comp)) return comp; + if (broadQuery.TryGetComponent(parent, out var comp)) + return comp; + parent = xformQuery.GetComponent(parent).ParentUid; } return null; } + #endregion + #region Bounds /// diff --git a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.cs b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.cs index 79bc4f520..6fd0c6b07 100644 --- a/Robust.Shared/GameObjects/Systems/SharedTransformSystem.cs +++ b/Robust.Shared/GameObjects/Systems/SharedTransformSystem.cs @@ -78,7 +78,7 @@ namespace Robust.Shared.GameObjects var mapTransform = Transform(_mapManager.GetMapEntityId(grid.ParentMapId)); var aabb = _entityLookup.GetLocalBounds(tileIndices, grid.TileSize); - foreach (var entity in _entityLookup.GetEntitiesIntersecting(gridId, tileIndices, LookupFlags.Anchored).ToList()) + foreach (var entity in _entityLookup.GetEntitiesIntersecting(gridId, tileIndices, LookupFlags.Uncontained).ToList()) { // If a tile is being removed due to an explosion or somesuch, some entities are likely being deleted. // Avoid unnecessary entity updates. diff --git a/Robust.Shared/Physics/BroadphaseComponent.cs b/Robust.Shared/Physics/BroadphaseComponent.cs index 312f9e055..31ed5ec01 100644 --- a/Robust.Shared/Physics/BroadphaseComponent.cs +++ b/Robust.Shared/Physics/BroadphaseComponent.cs @@ -8,6 +8,19 @@ namespace Robust.Shared.Physics [RegisterComponent] public sealed class BroadphaseComponent : Component { - internal IBroadPhase Tree = default!; + /// + /// Stores all non-static bodies. + /// + public IBroadPhase DynamicTree = default!; + + /// + /// Stores all static bodies. + /// + public IBroadPhase StaticTree = default!; + + /// + /// Stores all entities not in another tree. + /// + public DynamicTree SundriesTree = default!; } } diff --git a/Robust.Shared/Physics/Components/PhysicsComponent.Physics.cs b/Robust.Shared/Physics/Components/PhysicsComponent.Physics.cs index d3f1b797f..bd34e425e 100644 --- a/Robust.Shared/Physics/Components/PhysicsComponent.Physics.cs +++ b/Robust.Shared/Physics/Components/PhysicsComponent.Physics.cs @@ -24,17 +24,13 @@ using System; using System.Collections.Generic; -using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.IoC; using Robust.Shared.Maths; -using Robust.Shared.Physics.Dynamics; using Robust.Shared.Physics.Dynamics.Contacts; -using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Systems; using Robust.Shared.Serialization.Manager.Attributes; -using Robust.Shared.Utility; using Robust.Shared.ViewVariables; namespace Robust.Shared.Physics.Components @@ -54,38 +50,6 @@ namespace Robust.Shared.Physics.Components /// public bool Island { get; set; } - [ViewVariables] - internal BroadphaseComponent? Broadphase { get; set; } - - /// - /// Debugging VV - /// - [ViewVariables] - private Box2? _broadphaseAABB - { - get - { - Box2? aabb = null; - - if (Broadphase == null) - { - return aabb; - } - - var tree = Broadphase.Tree; - - foreach (var (_, fixture) in IoCManager.Resolve().GetComponent(Owner).Fixtures) - { - foreach (var proxy in fixture.Proxies) - { - aabb = aabb?.Union(tree.GetProxy(proxy.ProxyId)!.AABB) ?? tree.GetProxy(proxy.ProxyId)!.AABB; - } - } - - return aabb; - } - } - /// /// Store the body's index within the island so we can lookup its data. /// Key is Island's ID and value is our index. diff --git a/Robust.Shared/Physics/Dynamics/ContactManager.cs b/Robust.Shared/Physics/Dynamics/ContactManager.cs index 271f182d5..6c3792325 100644 --- a/Robust.Shared/Physics/Dynamics/ContactManager.cs +++ b/Robust.Shared/Physics/Dynamics/ContactManager.cs @@ -51,6 +51,7 @@ namespace Robust.Shared.Physics.Dynamics { [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IPhysicsManager _physicsManager = default!; + private EntityLookupSystem _lookup = default!; private SharedPhysicsSystem _physics = default!; private SharedTransformSystem _transform = default!; @@ -163,6 +164,7 @@ namespace Robust.Shared.Physics.Dynamics public void Initialize() { IoCManager.InjectDependencies(this); + _lookup = _entityManager.EntitySysManager.GetEntitySystem(); _physics = _entityManager.EntitySysManager.GetEntitySystem(); _transform = _entityManager.EntitySysManager.GetEntitySystem(); var configManager = IoCManager.Resolve(); @@ -431,8 +433,9 @@ namespace Robust.Shared.Physics.Dynamics var proxyA = fixtureA.Proxies[indexA]; var proxyB = fixtureB.Proxies[indexB]; - var broadphaseA = bodyA.Broadphase; - var broadphaseB = bodyB.Broadphase; + var broadQuery = _entityManager.GetEntityQuery(); + var broadphaseA = _lookup.GetBroadphase(bodyA.Owner, xformQuery.GetComponent(bodyA.Owner), broadQuery, xformQuery); + var broadphaseB = _lookup.GetBroadphase(bodyB.Owner, xformQuery.GetComponent(bodyB.Owner), broadQuery, xformQuery); var overlap = false; // We can have cross-broadphase proxies hence need to change them to worldspace diff --git a/Robust.Shared/Physics/Systems/FixtureSystem.cs b/Robust.Shared/Physics/Systems/FixtureSystem.cs index 533fd8707..9591a4f9c 100644 --- a/Robust.Shared/Physics/Systems/FixtureSystem.cs +++ b/Robust.Shared/Physics/Systems/FixtureSystem.cs @@ -1,7 +1,6 @@ using System; using System.Linq; using Robust.Shared.Collections; -using Robust.Shared.Containers; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.IoC; @@ -19,8 +18,7 @@ namespace Robust.Shared.Physics.Systems /// public sealed partial class FixtureSystem : EntitySystem { - [Dependency] private readonly SharedContainerSystem _containers = default!; - [Dependency] private readonly SharedBroadphaseSystem _broadphaseSystem = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; public override void Initialize() @@ -51,7 +49,6 @@ namespace Robust.Shared.Physics.Systems // Can't just get physicscomp on shutdown as it may be touched completely independently. _physics.DestroyContacts(body, xform.MapID, xform); - _broadphaseSystem.RemoveBody(body, component); // TODO im 99% sure _broadphaseSystem.RemoveBody(body, component) gets triggered by this as well, so is this even needed? _physics.SetCanCollide(body, false); @@ -87,14 +84,10 @@ namespace Robust.Shared.Physics.Systems manager.Fixtures.Add(fixture.ID, fixture); fixture.Body = body; - // TODO: Assert world locked - // Broadphase should be set in the future TM - // Should only happen for nullspace / initializing entities - if (body.Broadphase != null) + if (body.CanCollide) { var (worldPos, worldRot) = xform.GetWorldPositionRotation(); - - _broadphaseSystem.CreateProxies(fixture, worldPos, worldRot); + _lookup.CreateProxies(fixture, worldPos, worldRot); } // Supposed to be wrapped in density but eh @@ -213,9 +206,9 @@ namespace Robust.Shared.Physics.Systems } } - if (body.Broadphase != null) + if (body.CanCollide) { - _broadphaseSystem.DestroyProxies(body.Broadphase, fixture, xform.MapID); + _lookup.DestroyProxies(fixture, xform); } if (updates) @@ -262,20 +255,7 @@ namespace Robust.Shared.Physics.Systems // Make sure all the right stuff is set on the body FixtureUpdate(component, body, false); - - if (body.CanCollide) - { - DebugTools.Assert(!_containers.IsEntityInContainer(uid)); - _broadphaseSystem.AddBody(body, component); - } } - /* TODO: Literally only AllComponentsOneToOneDeleteTest fails on this so fuck it this is what we get. - else - { - Logger.ErrorS("physics", $"Didn't find a {nameof(PhysicsComponuid)}" - } - */ - } private void OnGetState(EntityUid uid, FixturesComponent component, ref ComponentGetState args) diff --git a/Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs b/Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs index e9ea51f62..c0903149b 100644 --- a/Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs +++ b/Robust.Shared/Physics/Systems/SharedBroadphaseSystem.cs @@ -7,11 +7,9 @@ using Robust.Shared.IoC; using Robust.Shared.Log; using Robust.Shared.Map; using Robust.Shared.Maths; -using Robust.Shared.Physics.BroadPhase; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Dynamics; using Robust.Shared.Physics.Dynamics.Contacts; -using Robust.Shared.Physics.Events; using Robust.Shared.Utility; namespace Robust.Shared.Physics.Systems @@ -19,7 +17,7 @@ namespace Robust.Shared.Physics.Systems public abstract class SharedBroadphaseSystem : EntitySystem { [Dependency] private readonly IMapManagerInternal _mapManager = default!; - [Dependency] private readonly SharedTransformSystem _xformSys = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly SharedPhysicsSystem _physicsSystem = default!; private ISawmill _logger = default!; @@ -54,21 +52,8 @@ namespace Robust.Shared.Physics.Systems UpdatesAfter.Add(typeof(SharedTransformSystem)); - SubscribeLocalEvent(OnBroadphaseAdd); - SubscribeLocalEvent(OnGridAdd); - - SubscribeLocalEvent(OnPhysicsUpdate); - - SubscribeLocalEvent(OnMove); - var configManager = IoCManager.Resolve(); configManager.OnValueChanged(CVars.BroadphaseExpand, SetBroadphaseExpand, true); - - SubscribeLocalEvent(ev => - { - if (ev.Created) - OnMapCreated(ev); - }); } public override void Shutdown() @@ -108,23 +93,8 @@ namespace Robust.Shared.Physics.Systems var enlargedAABB = worldAABB.Enlarged(_broadphaseExpand); var state = (moveBuffer, gridMoveBuffer); - // Easier to just not go over each proxy as we already unioned the fixture's worldaabb. - mapBroadphase.Tree.QueryAabb(ref state, static (ref ( - Dictionary moveBuffer, - Dictionary gridMoveBuffer) tuple, - in FixtureProxy value) => - { - // 99% of the time it's just going to be the broadphase (for now the grid) itself. - // hence this body check makes this run significantly better. - // Also check if it's not already on the movebuffer. - if (tuple.moveBuffer.ContainsKey(value)) - return true; - - // To avoid updating during iteration. - // Don't need to transform as it's already in map terms. - tuple.gridMoveBuffer[value] = value.AABB; - return true; - }, enlargedAABB, true); + QueryMapBroadphase(mapBroadphase.DynamicTree, ref state, enlargedAABB); + QueryMapBroadphase(mapBroadphase.StaticTree, ref state, enlargedAABB); } foreach (var (proxy, worldAABB) in gridMoveBuffer) @@ -133,6 +103,29 @@ namespace Robust.Shared.Physics.Systems } } + private void QueryMapBroadphase(IBroadPhase broadPhase, + ref (Dictionary, Dictionary) state, + Box2 enlargedAABB) + { + // Easier to just not go over each proxy as we already unioned the fixture's worldaabb. + broadPhase.QueryAabb(ref state, static (ref ( + Dictionary moveBuffer, + Dictionary gridMoveBuffer) tuple, + in FixtureProxy value) => + { + // 99% of the time it's just going to be the broadphase (for now the grid) itself. + // hence this body check makes this run significantly better. + // Also check if it's not already on the movebuffer. + if (tuple.moveBuffer.ContainsKey(value)) + return true; + + // To avoid updating during iteration. + // Don't need to transform as it's already in map terms. + tuple.gridMoveBuffer[value] = value.AABB; + return true; + }, enlargedAABB, true); + } + [Obsolete("Use the overload with SharedPhysicsMapComponent")] internal void FindNewContacts(MapId mapId) { @@ -319,11 +312,14 @@ namespace Robust.Shared.Physics.Systems var proxyBody = proxy.Fixture.Body; // Broadphase can't intersect with entities on itself so skip. - if (proxyBody.Owner == broadphase) return; + if (proxyBody.Owner == broadphase || !xformQuery.TryGetComponent(proxyBody.Owner, out var xform)) + { + return; + } // Logger.DebugS("physics", $"Checking proxy for {proxy.Fixture.Body.Owner} on {broadphase.Owner}"); Box2 aabb; - var proxyBroad = proxyBody.Broadphase; + var proxyBroad = _lookup.GetBroadphase(proxy.Fixture.Body.Owner, xform, broadphaseQuery, xformQuery); if (proxyBroad == null) { @@ -353,7 +349,17 @@ namespace Robust.Shared.Physics.Systems var state = (proxyPairs, pairBuffer, proxy); - broadphaseComp.Tree.QueryAabb(ref state, static ( + QueryBroadphase(broadphaseComp.DynamicTree, ref state, aabb); + + if ((proxy.Fixture.Body.BodyType & BodyType.Static) != 0x0) + return; + + QueryBroadphase(broadphaseComp.StaticTree, ref state, aabb); + } + + private void QueryBroadphase(IBroadPhase broadPhase, ref (HashSet, Dictionary>, FixtureProxy) state, Box2 aabb) + { + broadPhase.QueryAabb(ref state, static ( ref (HashSet proxyPairs, Dictionary> pairBuffer, FixtureProxy proxy) tuple, in FixtureProxy other) => { @@ -381,181 +387,13 @@ namespace Robust.Shared.Physics.Systems }, aabb, true); } - /// - /// If our broadphase has changed then remove us from our old one and add to our new one. - /// - internal void UpdateBroadphase(EntityUid uid, MapId oldMapId, TransformComponent? xform = null) - { - if (!Resolve(uid, ref xform)) - return; - - var bodyQuery = GetEntityQuery(); - var fixturesQuery = GetEntityQuery(); - var xformQuery = GetEntityQuery(); - var broadQuery = GetEntityQuery(); - - var newBroadphase = GetBroadphase(xform, broadQuery, xformQuery); - Dictionary? oldMoveBuffer = null; - - if (TryComp(_mapManager.GetMapEntityId(oldMapId), out var physicsMap)) - { - oldMoveBuffer = physicsMap.MoveBuffer; - } - - RecursiveBroadphaseUpdate(xform, bodyQuery, fixturesQuery, xformQuery, broadQuery, newBroadphase, oldMoveBuffer); - } - - private void RecursiveBroadphaseUpdate( - TransformComponent xform, - EntityQuery bodyQuery, - EntityQuery fixturesQuery, - EntityQuery xformQuery, - EntityQuery broadQuery, - BroadphaseComponent? newBroadphase, - Dictionary? oldMoveBuffer) - { - var uid = xform.Owner; - var childEnumerator = xform.ChildEnumerator; - - if (bodyQuery.TryGetComponent(uid, out var body) && - body._canCollide && - fixturesQuery.TryGetComponent(uid, out var manager)) - { - // TODO while iterating down through children, evaluate world position & rotation and pass into this function - UpdateBodyBroadphase(body, manager, xform, newBroadphase, xformQuery, oldMoveBuffer); - } - - if (xform.MapID != MapId.Nullspace && broadQuery.TryGetComponent(uid, out var parentBroad)) - newBroadphase = parentBroad; - - while (childEnumerator.MoveNext(out var child)) - { - if (xformQuery.TryGetComponent(child, out var childXform)) - RecursiveBroadphaseUpdate(childXform, bodyQuery, fixturesQuery, xformQuery, broadQuery, newBroadphase, oldMoveBuffer); - } - } - - internal void UpdateBodyBroadphase( - PhysicsComponent body, - FixturesComponent manager, - TransformComponent xform, - BroadphaseComponent? newBroadphase, - EntityQuery xformQuery, - Dictionary? oldMoveBuffer) - { - if (body.Broadphase == newBroadphase) - return; - - DestroyProxies(body, manager, oldMoveBuffer); - body.Broadphase = newBroadphase; - - if (newBroadphase == null) - return; - - // TODO optimize map moving. Seeing as we iterate downwards through children, world position/rotation can be - // tracked, instead of re-calculated each time by iterating upwards though parents. But for deletions, - // newBroadphase is null anyways, so this only matters for things like shuttles moving across maps. - var (worldPos, worldRot) = _xformSys.GetWorldPositionRotation(xform, xformQuery); - - foreach (var (_, fixture) in manager.Fixtures) - { - // TODO pass in broadphaseXform - CreateProxies(fixture, worldPos, worldRot); - } - } - - /// - /// Remove all of our fixtures from the broadphase. - /// - private void DestroyProxies(PhysicsComponent body, FixturesComponent manager) - { - if (body.Broadphase == null) - return; - - if (TryComp(body.Owner, out var xform) && - TryComp(xform.MapUid, out var map)) - { - DestroyProxies(body, manager, map.MoveBuffer); - } - } - - private void DestroyProxies(PhysicsComponent body, FixturesComponent manager, Dictionary? moveBuffer) - { - if (body.Broadphase == null) - return; - - foreach (var (_, fixture) in manager.Fixtures) - { - var proxyCount = fixture.ProxyCount; - for (var i = 0; i < proxyCount; i++) - { - var proxy = fixture.Proxies[i]; - body.Broadphase.Tree.RemoveProxy(proxy.ProxyId); - proxy.ProxyId = DynamicTree.Proxy.Free; - moveBuffer?.Remove(proxy); - } - - fixture.ProxyCount = 0; - } - - body.Broadphase = null; - } - - private void OnPhysicsUpdate(ref CollisionChangeEvent ev) - { - var lifestage = ev.Body.LifeStage; - - // Oh god kill it with fire. - if (lifestage is < ComponentLifeStage.Initialized or > ComponentLifeStage.Running) return; - - if (ev.CanCollide) - { - AddBody(ev.Body); - } - else - { - RemoveBody(ev.Body); - } - } - - public void AddBody(PhysicsComponent body, FixturesComponent? manager = null) - { - // TODO: Good idea? Ehhhhhhhhhhhh - // The problem is there's some fuckery with events while an entity is initializing. - // Can probably just bypass this by doing stuff in Update / FrameUpdate again but future problem - // Also grids are special-cased due to their high fixture count. - if (body.Broadphase != null || - _mapManager.IsGrid(body.Owner)) return; - - if (!Resolve(body.Owner, ref manager)) - { - return; - } - - // TODO: This should do an embed check... somehow... unfortunately we can't just awaken all pairs - // because it makes stacks unstable... - CreateProxies(body, manager); - } - - internal void RemoveBody(PhysicsComponent body, FixturesComponent? manager = null) - { - // Not on any broadphase anyway. - if (body.Broadphase == null) return; - - // TODO: Would reaaalllyy like for this to not be false in future - if (!Resolve(body.Owner, ref manager, false)) - { - return; - } - - DestroyProxies(body, manager); - } - public void RegenerateContacts(PhysicsComponent body) { _physicsSystem.DestroyContacts(body); + var broadQuery = GetEntityQuery(); + var xformQuery = GetEntityQuery(); - var broadphase = body.Broadphase; + var broadphase = _lookup.GetBroadphase(body.Owner, xformQuery.GetComponent(body.Owner), broadQuery, xformQuery); if (broadphase != null) { @@ -568,31 +406,6 @@ namespace Robust.Shared.Physics.Systems } } - public void Refilter(Fixture fixture) - { - // TODO: Call this method whenever collisionmask / collisionlayer changes - // TODO: This should never becalled when body is null. - DebugTools.Assert(fixture.Body != null); - if (fixture.Body == null) - { - return; - } - - var body = fixture.Body; - - foreach (var (_, contact) in fixture.Contacts) - { - contact.Flags |= ContactFlags.Filter; - } - - var broadphase = body.Broadphase; - - // If nullspace or whatever ignore it. - if (broadphase == null) return; - - TouchProxies(Transform(fixture.Body.Owner).MapID, broadphase, fixture); - } - private void TouchProxies(MapId mapId, BroadphaseComponent broadphase, Fixture fixture) { var broadphasePos = Transform(broadphase.Owner).WorldMatrix; @@ -603,84 +416,6 @@ namespace Robust.Shared.Physics.Systems } } - private void OnMove(EntityUid uid, PhysicsComponent component, ref MoveEvent args) - { - if (!component.CanCollide - || args.Component.GridUid == uid - || !TryComp(uid, out FixturesComponent? manager)) - return; - - var (worldPos, worldRot) = _xformSys.GetWorldPositionRotation(args.Component, GetEntityQuery()); - SynchronizeFixtures(component, worldPos, (float)worldRot, manager); - } - - private void SynchronizeFixtures(PhysicsComponent body, Vector2 worldPos, float worldRot, FixturesComponent manager) - { - // Logger.DebugS("physics", $"Synchronizing fixtures for {body.Owner}"); - // Don't cache this as controllers may change it freely before we run physics! - var xf = new Transform(worldPos, worldRot); - - if (body.Awake) - { - // TODO: SWEPT HERE - // Check if we need to use the normal synchronize which also supports TOI - // Otherwise, use the slightly faster one. - - // For now we'll just use the normal one as no TOI support - foreach (var (_, fixture) in manager.Fixtures) - { - if (fixture.ProxyCount == 0) continue; - - // SynchronizezTOI(fixture, xf1, xf2); - - Synchronize(fixture, xf); - } - } - else - { - foreach (var (_, fixture) in manager.Fixtures) - { - if (fixture.ProxyCount == 0) continue; - - Synchronize(fixture, xf); - } - } - } - - /// - /// A more efficient Synchronize for 1 transform. - /// - private void Synchronize(Fixture fixture, Transform transform1) - { - // tl;dr update our bounding boxes stored in broadphase. - var broadphase = fixture.Body.Broadphase!; - var proxyCount = fixture.ProxyCount; - - var broadphaseXform = EntityManager.GetComponent(broadphase.Owner); - - var broadphaseMapId = broadphaseXform.MapID; - var (broadphaseWorldPos, broadphaseWorldRot, broadphaseInvMatrix) = broadphaseXform.GetWorldPositionRotationInvMatrix(); - - var relativePos1 = new Transform( - broadphaseInvMatrix.Transform(transform1.Position), - transform1.Quaternion2D.Angle - broadphaseWorldRot); - - for (var i = 0; i < proxyCount; i++) - { - var proxy = fixture.Proxies[i]; - var bounds = fixture.Shape.ComputeAABB(relativePos1, i); - proxy.AABB = bounds; - var displacement = Vector2.Zero; - broadphase.Tree.MoveProxy(proxy.ProxyId, bounds, displacement); - - var worldAABB = new Box2Rotated(bounds, broadphaseWorldRot, Vector2.Zero) - .CalcBoundingBox() - .Translated(broadphaseWorldPos); - - AddToMoveBuffer(broadphaseMapId, proxy, worldAABB); - } - } - private void AddToMoveBuffer(MapId mapId, FixtureProxy proxy, Box2 aabb) { if (!TryComp(_mapManager.GetMapEntityId(mapId), out var physicsMap)) @@ -691,174 +426,29 @@ namespace Robust.Shared.Physics.Systems physicsMap.MoveBuffer[proxy] = aabb; } - /// - /// Get broadphase proxies from the body's fixtures and add them to the relevant broadphase. - /// - private void CreateProxies(PhysicsComponent body, FixturesComponent? manager = null, TransformComponent? xform = null) + public void Refilter(Fixture fixture) { - if (!Resolve(body.Owner, ref manager, ref xform) || - xform.MapID == MapId.Nullspace) return; - - var (worldPos, worldRot) = xform.GetWorldPositionRotation(); - - // Outside of PVS (TODO Remove when PVS is better) - if (float.IsNaN(worldPos.X) || float.IsNaN(worldPos.Y)) + // TODO: Call this method whenever collisionmask / collisionlayer changes + // TODO: This should never becalled when body is null. + DebugTools.Assert(fixture.Body != null); + if (fixture.Body == null) { return; } - var broadphase = GetBroadphase(xform); - - if (broadphase == null) + foreach (var (_, contact) in fixture.Contacts) { - throw new InvalidBroadphaseException($"Unable to find broadphase for {body.Owner}"); + contact.Flags |= ContactFlags.Filter; } - if (body.Broadphase != null) - { - throw new InvalidBroadphaseException($"{body.Owner} already has proxies on a broadphase?"); - } - - body.Broadphase = broadphase; - - foreach (var (_, fixture) in manager.Fixtures) - { - CreateProxies(fixture, worldPos, worldRot); - } - // Logger.DebugS("physics", $"Created proxies for {body.Owner} on {broadphase.Owner}"); - } - - /// - /// Create the proxies for this fixture on the body's broadphase. - /// - internal void CreateProxies(Fixture fixture, Vector2 worldPos, Angle worldRot) - { - // Ideally we would always just defer this until Update / FrameUpdate but that will have to wait for a future - // PR for my own sanity. - - DebugTools.Assert(fixture.ProxyCount == 0); - DebugTools.Assert(EntityManager.GetComponent(fixture.Body.Owner).MapID != MapId.Nullspace); - - var proxyCount = fixture.Shape.ChildCount; - - if (proxyCount == 0) return; - - var broadphase = fixture.Body.Broadphase; - - if (broadphase == null) - { - throw new InvalidBroadphaseException($"Unable to find broadphase for create on {fixture.Body.Owner}"); - } - - fixture.ProxyCount = proxyCount; - var proxies = fixture.Proxies; - - Array.Resize(ref proxies, proxyCount); - fixture.Proxies = proxies; - - var broadphaseXform = EntityManager.GetComponent(broadphase.Owner); - - var (broadphaseWorldPosition, broadphaseWorldRotation, broadphaseInvMatrix) = broadphaseXform.GetWorldPositionRotationInvMatrix(); - - var localPos = broadphaseInvMatrix.Transform(worldPos); - - var transform = new Transform(localPos, worldRot - broadphaseWorldRotation); - var mapId = broadphaseXform.MapID; - - for (var i = 0; i < proxyCount; i++) - { - var bounds = fixture.Shape.ComputeAABB(transform, i); - var proxy = new FixtureProxy(bounds, fixture, i); - DebugTools.Assert(fixture.Body.CanCollide); - proxy.ProxyId = broadphase.Tree.AddProxy(ref proxy); - fixture.Proxies[i] = proxy; - - var worldAABB = new Box2Rotated(bounds, broadphaseWorldRotation, Vector2.Zero) - .CalcBoundingBox() - .Translated(broadphaseWorldPosition); - - AddToMoveBuffer(mapId, proxy, worldAABB); - } - } - - /// - /// Destroy the proxies for this fixture on the broadphase. - /// - internal void DestroyProxies(BroadphaseComponent broadphase, Fixture fixture, MapId map) - { - if (broadphase == null) - { - throw new InvalidBroadphaseException($"Unable to find broadphase for destroy on {fixture.Body}"); - } - - DebugTools.Assert(Transform(broadphase.Owner).MapID == map); - - var proxyCount = fixture.ProxyCount; - TryComp(_mapManager.GetMapEntityId(map), out var physicsMap); - var moveBuffer = physicsMap?.MoveBuffer; - - for (var i = 0; i < proxyCount; i++) - { - var proxy = fixture.Proxies[i]; - broadphase.Tree.RemoveProxy(proxy.ProxyId); - proxy.ProxyId = DynamicTree.Proxy.Free; - moveBuffer?.Remove(proxy); - } - - fixture.ProxyCount = 0; - } - - #region Broadphase management - - private void OnMapCreated(MapChangedEvent e) - { - if (e.Map == MapId.Nullspace) return; - - EntityManager.EnsureComponent(_mapManager.GetMapEntityId(e.Map)); - } - - private void OnGridAdd(GridAddEvent ev) - { - // Must be done before initialization as that's when broadphase data starts getting set. - EnsureComp(ev.EntityUid); - } - - private void OnBroadphaseAdd(EntityUid uid, BroadphaseComponent component, ComponentAdd args) - { - var capacity = (int) Math.Max(MinimumBroadphaseCapacity, Math.Ceiling(EntityManager.GetComponent(component.Owner).ChildCount / (float) MinimumBroadphaseCapacity) * MinimumBroadphaseCapacity); - component.Tree = new DynamicTreeBroadPhase(capacity); - } - - #endregion - - /// - /// Attempt to get the relevant broadphase for this entity. - /// Can return null if it's the map entity. - /// - private BroadphaseComponent? GetBroadphase(TransformComponent xform) - { - if (xform.MapID == MapId.Nullspace) return null; - var broadQuery = GetEntityQuery(); var xformQuery = GetEntityQuery(); - return GetBroadphase(xform, broadQuery, xformQuery); - } + var broadphase = _lookup.GetBroadphase(fixture.Body.Owner, xformQuery.GetComponent(fixture.Body.Owner), broadQuery, xformQuery); - public BroadphaseComponent? GetBroadphase(TransformComponent xform, EntityQuery broadQuery, EntityQuery xformQuery) - { - if (xform.MapID == MapId.Nullspace) return null; + // If nullspace or whatever ignore it. + if (broadphase == null) return; - var parent = xform.ParentUid; - - // if it's map (or in null-space) return null. Grids should return the map's broadphase. - - while (parent.IsValid()) - { - if (broadQuery.TryGetComponent(parent, out var comp)) return comp; - parent = xformQuery.GetComponent(parent).ParentUid; - } - - return null; + TouchProxies(Transform(fixture.Body.Owner).MapID, broadphase, fixture); } // TODO: The below is slow and should just query the map's broadphase directly. The problem is that @@ -892,12 +482,5 @@ namespace Robust.Shared.Physics.Systems } } } - - private sealed class InvalidBroadphaseException : Exception - { - public InvalidBroadphaseException() {} - - public InvalidBroadphaseException(string message) : base(message) {} - } } } diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs index 4436a011d..4f1fd0a2e 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Components.cs @@ -46,12 +46,13 @@ public partial class SharedPhysicsSystem { var xform = Transform(uid); - if (component.CanCollide && _containerSystem.IsEntityInContainer(uid)) + + if (component.CanCollide && (_containerSystem.IsEntityInContainer(uid) || xform.MapID == MapId.Nullspace)) { SetCanCollide(component, false, false); } - if (component._canCollide && xform.MapID != MapId.Nullspace) + if (component._canCollide) { if (component.BodyType != BodyType.Static) { @@ -424,10 +425,12 @@ public partial class SharedPhysicsSystem if (value && _containerSystem.IsEntityOrParentInContainer(body.Owner)) return false; + // Need to do this before SetAwake to avoid double-changing it. + body._canCollide = value; + if (!value) SetAwake(body, false); - body._canCollide = value; var ev = new CollisionChangeEvent(body, value); RaiseLocalEvent(ref ev); diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs index a668185cf..9b41fd9a0 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.Queries.cs @@ -25,6 +25,7 @@ namespace Robust.Shared.Physics.Systems /// /// /// 0 -> 1.0f based on WorldAABB overlap + [Obsolete] public float IntersectionPercent(PhysicsComponent bodyA, PhysicsComponent bodyB) { // TODO: Use actual shapes and not just the AABB? @@ -47,7 +48,20 @@ namespace Robust.Shared.Physics.Systems { var gridCollider = EntityManager.GetComponent(broadphase.Owner).InvWorldMatrix.TransformBox(collider); - broadphase.Tree.QueryAabb(ref state, (ref (Box2 collider, MapId map, bool found) state, in FixtureProxy proxy) => + broadphase.StaticTree.QueryAabb(ref state, (ref (Box2 collider, MapId map, bool found) state, in FixtureProxy proxy) => + { + if (proxy.Fixture.CollisionLayer == 0x0) + return true; + + if (proxy.AABB.Intersects(gridCollider)) + { + state.found = true; + return false; + } + return true; + }, gridCollider, approximate); + + broadphase.DynamicTree.QueryAabb(ref state, (ref (Box2 collider, MapId map, bool found) state, in FixtureProxy proxy) => { if (proxy.Fixture.CollisionLayer == 0x0) return true; @@ -64,39 +78,6 @@ namespace Robust.Shared.Physics.Systems return state.found; } - public IEnumerable GetCollidingEntities(PhysicsComponent body, bool approximate = true) - { - var broadphase = body.Broadphase; - if (broadphase == null || !EntityManager.TryGetComponent(body.Owner, out FixturesComponent? manager)) - { - return Array.Empty(); - } - - var entities = new List(); - - var state = (body, entities); - - foreach (var (_, fixture) in manager.Fixtures) - { - foreach (var proxy in fixture.Proxies) - { - broadphase.Tree.QueryAabb(ref state, - (ref (PhysicsComponent body, List entities) state, - in FixtureProxy other) => - { - if (other.Fixture.Body.Deleted || other.Fixture.Body == body) return true; - if ((proxy.Fixture.CollisionMask & other.Fixture.CollisionLayer) == 0x0) return true; - if (!ShouldCollide(body, other.Fixture.Body)) return true; - - state.entities.Add(other.Fixture.Body); - return true; - }, proxy.AABB, approximate); - } - } - - return entities; - } - /// /// Get all the entities whose fixtures intersect the fixtures of the given entity. Basically a variant of /// that allows the user to specify @@ -111,7 +92,14 @@ namespace Robust.Shared.Physics.Systems { var entities = new HashSet(); - if (!Resolve(uid, ref body, ref fixtureComp, false) || body.Broadphase == null) + if (!Resolve(uid, ref body, ref fixtureComp, false)) + return entities; + + var broadQuery = GetEntityQuery(); + var xformQuery = GetEntityQuery(); + var broadphase = _lookup.GetBroadphase(uid, xformQuery.GetComponent(uid), broadQuery, xformQuery); + + if (broadphase == null) return entities; var state = (body, entities); @@ -120,7 +108,18 @@ namespace Robust.Shared.Physics.Systems { foreach (var proxy in fixture.Proxies) { - body.Broadphase.Tree.QueryAabb(ref state, + broadphase.StaticTree.QueryAabb(ref state, + (ref (PhysicsComponent body, HashSet entities) state, + in FixtureProxy other) => + { + if (other.Fixture.Body.Deleted || other.Fixture.Body == body) return true; + if ((collisionMask & other.Fixture.CollisionLayer) == 0x0) return true; + + state.entities.Add(other.Fixture.Body.Owner); + return true; + }, proxy.AABB, approximate); + + broadphase.DynamicTree.QueryAabb(ref state, (ref (PhysicsComponent body, HashSet entities) state, in FixtureProxy other) => { @@ -149,7 +148,12 @@ namespace Robust.Shared.Physics.Systems { var gridAABB = EntityManager.GetComponent(broadphase.Owner).InvWorldMatrix.TransformBox(worldAABB); - foreach (var proxy in broadphase.Tree.QueryAabb(gridAABB, false)) + foreach (var proxy in broadphase.StaticTree.QueryAabb(gridAABB, false)) + { + bodies.Add(proxy.Fixture.Body); + } + + foreach (var proxy in broadphase.DynamicTree.QueryAabb(gridAABB, false)) { bodies.Add(proxy.Fixture.Body); } @@ -171,7 +175,12 @@ namespace Robust.Shared.Physics.Systems { var gridAABB = EntityManager.GetComponent(broadphase.Owner).InvWorldMatrix.TransformBox(worldBounds); - foreach (var proxy in broadphase.Tree.QueryAabb(gridAABB, false)) + foreach (var proxy in broadphase.StaticTree.QueryAabb(gridAABB, false)) + { + bodies.Add(proxy.Fixture.Body); + } + + foreach (var proxy in broadphase.DynamicTree.QueryAabb(gridAABB, false)) { bodies.Add(proxy.Fixture.Body); } @@ -274,7 +283,33 @@ namespace Robust.Shared.Physics.Systems var gridRay = new CollisionRay(position, direction, ray.CollisionMask); - broadphase.Tree.QueryRay((in FixtureProxy proxy, in Vector2 point, float distFromOrigin) => + broadphase.StaticTree.QueryRay((in FixtureProxy proxy, in Vector2 point, float distFromOrigin) => + { + if (returnOnFirstHit && results.Count > 0) + return true; + + if (distFromOrigin > maxLength) + return true; + + if ((proxy.Fixture.CollisionLayer & ray.CollisionMask) == 0x0) + return true; + + if (!proxy.Fixture.Body.Hard) + return true; + + if (predicate.Invoke(proxy.Fixture.Body.Owner, state) == true) + return true; + + // TODO: Shape raycast here + + // Need to convert it back to world-space. + var result = new RayCastResults(distFromOrigin, matrix.Transform(point), proxy.Fixture.Body.Owner); + results.Add(result); + _sharedDebugRaySystem.ReceiveLocalRayFromAnyThread(new DebugRayData(ray, maxLength, result)); + return true; + }, gridRay); + + broadphase.DynamicTree.QueryRay((in FixtureProxy proxy, in Vector2 point, float distFromOrigin) => { if (returnOnFirstHit && results.Count > 0) return true; @@ -353,7 +388,26 @@ namespace Robust.Shared.Physics.Systems var gridRay = new CollisionRay(position, direction, ray.CollisionMask); - broadphase.Tree.QueryRay((in FixtureProxy proxy, in Vector2 point, float distFromOrigin) => + broadphase.StaticTree.QueryRay((in FixtureProxy proxy, in Vector2 point, float distFromOrigin) => + { + if (distFromOrigin > maxLength || proxy.Fixture.Body.Owner == ignoredEnt) + return true; + + if (!proxy.Fixture.Hard) + return true; + + if ((proxy.Fixture.CollisionLayer & ray.CollisionMask) == 0x0) + return true; + + if (new Ray(point + gridRay.Direction * proxy.AABB.Size.Length * 2, -gridRay.Direction).Intersects( + proxy.AABB, out _, out var exitPoint)) + { + penetration += (point - exitPoint).Length; + } + return true; + }, gridRay); + + broadphase.DynamicTree.QueryRay((in FixtureProxy proxy, in Vector2 point, float distFromOrigin) => { if (distFromOrigin > maxLength || proxy.Fixture.Body.Owner == ignoredEnt) return true; diff --git a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs index 5ef9fbea2..0d1f2daad 100644 --- a/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs +++ b/Robust.Shared/Physics/Systems/SharedPhysicsSystem.cs @@ -45,6 +45,7 @@ namespace Robust.Shared.Physics.Systems }); [Dependency] private readonly SharedBroadphaseSystem _broadphase = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly SharedJointSystem _joints = default!; [Dependency] private readonly SharedGridTraversalSystem _traversal = default!; [Dependency] protected readonly IMapManager MapManager = default!; @@ -167,8 +168,6 @@ namespace Robust.Shared.Physics.Systems if (args.OldMapId != xform.MapID) return; - _broadphase.UpdateBroadphase(uid, args.OldMapId, xform: xform); - if (body != null) HandleParentChangeVelocity(uid, body, ref args, xform); } @@ -194,9 +193,7 @@ namespace Robust.Shared.Physics.Systems oldMoveBuffer = oldMap.MoveBuffer; } - var newBroadphase = _broadphase.GetBroadphase(xform, broadQuery, xformQuery); - - RecursiveMapUpdate(xform, body, newMapId, newBroadphase, newMap, oldMap, oldMoveBuffer, bodyQuery, xformQuery, fixturesQuery, jointQuery, broadQuery); + RecursiveMapUpdate(xform, body, newMapId, newMap, oldMap, oldMoveBuffer, bodyQuery, xformQuery, fixturesQuery, jointQuery, broadQuery); } /// @@ -206,7 +203,6 @@ namespace Robust.Shared.Physics.Systems TransformComponent xform, PhysicsComponent? body, MapId newMapId, - BroadphaseComponent? newBroadphase, SharedPhysicsMapComponent? newMap, SharedPhysicsMapComponent? oldMap, Dictionary? oldMoveBuffer, @@ -236,28 +232,18 @@ namespace Robust.Shared.Physics.Systems if (oldMap != null) DestroyContacts(body, oldMap); // This can modify body.Awake DebugTools.Assert(body.Contacts.Count == 0); - - // TODO: When we cull sharedphysicsmapcomponent we can probably remove this grid check. - if (!MapManager.IsGrid(uid) && fixturesQuery.TryGetComponent(uid, out var fixtures) && body._canCollide) - { - // TODO If not deleting, update world position+rotation while iterating through children and pass into UpdateBodyBroadphase - _broadphase.UpdateBodyBroadphase(body, fixtures, xform, newBroadphase, xformQuery, oldMoveBuffer); - } } if (jointQuery.TryGetComponent(uid, out var joint)) _joints.ClearJoints(uid, joint); - if (newMapId != MapId.Nullspace && broadQuery.TryGetComponent(uid, out var parentBroadphase)) - newBroadphase = parentBroadphase; - var childEnumerator = xform.ChildEnumerator; while (childEnumerator.MoveNext(out var child)) { if (xformQuery.TryGetComponent(child, out var childXform)) { bodyQuery.TryGetComponent(child, out var childBody); - RecursiveMapUpdate(childXform, childBody, newMapId, newBroadphase, newMap, oldMap, oldMoveBuffer, bodyQuery, xformQuery, fixturesQuery, jointQuery, broadQuery); + RecursiveMapUpdate(childXform, childBody, newMapId, newMap, oldMap, oldMoveBuffer, bodyQuery, xformQuery, fixturesQuery, jointQuery, broadQuery); } } diff --git a/Robust.UnitTesting/RobustUnitTest.cs b/Robust.UnitTesting/RobustUnitTest.cs index eae74e0fd..ce7ab5bf9 100644 --- a/Robust.UnitTesting/RobustUnitTest.cs +++ b/Robust.UnitTesting/RobustUnitTest.cs @@ -109,11 +109,6 @@ namespace Robust.UnitTesting compFactory.RegisterClass(); } - if (!compFactory.AllRegisteredTypes.Contains(typeof(EntityLookupComponent))) - { - compFactory.RegisterClass(); - } - if (!compFactory.AllRegisteredTypes.Contains(typeof(SharedPhysicsMapComponent))) { compFactory.RegisterClass(); @@ -129,11 +124,6 @@ namespace Robust.UnitTesting compFactory.RegisterClass(); } - if (!compFactory.AllRegisteredTypes.Contains(typeof(EntityLookupComponent))) - { - compFactory.RegisterClass(); - } - // So by default EntityManager does its own EntitySystemManager initialize during Startup. // We want to bypass this and load our own systems hence we will manually initialize it here. entMan.Initialize(); diff --git a/Robust.UnitTesting/Server/GameObjects/Components/Transform_Test.cs b/Robust.UnitTesting/Server/GameObjects/Components/Transform_Test.cs index 105072c50..9b08302de 100644 --- a/Robust.UnitTesting/Server/GameObjects/Components/Transform_Test.cs +++ b/Robust.UnitTesting/Server/GameObjects/Components/Transform_Test.cs @@ -40,7 +40,7 @@ namespace Robust.UnitTesting.Server.GameObjects.Components - type: Map index: 123 # Due to the map getting initialised last this seemed easiest to fix the test while removing the mocks. - - type: EntityLookup + - type: Broadphase "; private MapId MapA; diff --git a/Robust.UnitTesting/Server/RobustServerSimulation.cs b/Robust.UnitTesting/Server/RobustServerSimulation.cs index 367a3ce00..e8c8409e8 100644 --- a/Robust.UnitTesting/Server/RobustServerSimulation.cs +++ b/Robust.UnitTesting/Server/RobustServerSimulation.cs @@ -249,7 +249,6 @@ namespace Robust.UnitTesting.Server compFactory.RegisterClass(); compFactory.RegisterClass(); compFactory.RegisterClass(); - compFactory.RegisterClass(); compFactory.RegisterClass(); compFactory.RegisterClass(); compFactory.RegisterClass(); diff --git a/Robust.UnitTesting/Shared/EntityLookup_Test.cs b/Robust.UnitTesting/Shared/EntityLookup_Test.cs index e04191d52..65e8ced5d 100644 --- a/Robust.UnitTesting/Shared/EntityLookup_Test.cs +++ b/Robust.UnitTesting/Shared/EntityLookup_Test.cs @@ -1,8 +1,6 @@ using System.Linq; -using System.Threading.Tasks; using NUnit.Framework; using Robust.Shared.GameObjects; -using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.UnitTesting.Server; @@ -31,41 +29,6 @@ namespace Robust.UnitTesting.Shared mapManager.DeleteMap(mapId); } - /// - /// If we don't pass an anchor flag do we still return an anchored entity - /// - [Test] - public void TestNoAnchorReturn() - { - var sim = RobustServerSimulation.NewSimulation(); - var server = sim.InitializeInstance(); - - var lookup = server.Resolve().GetEntitySystem(); - var entManager = server.Resolve(); - var mapManager = server.Resolve(); - - var mapId = mapManager.CreateMap(); - var grid = mapManager.CreateGrid(mapId); - - var theMapSpotBeingUsed = new Box2(Vector2.Zero, Vector2.One); - grid.SetTile(new Vector2i(), new Tile(1)); - - // Setup and check it actually worked - var dummy = entManager.SpawnEntity(null, new MapCoordinates(Vector2.Zero, mapId)); - var xform = entManager.GetComponent(dummy); - - // When anchoring it should still get returned. - xform.Anchored = true; - Assert.That(xform.Anchored); - Assert.That(lookup.GetEntitiesIntersecting(mapId, theMapSpotBeingUsed).ToList(), Has.Count.EqualTo(1)); - - Assert.That(lookup.GetEntitiesIntersecting(mapId, theMapSpotBeingUsed, LookupFlags.None).ToList(), Is.Empty); - - entManager.DeleteEntity(dummy); - mapManager.DeleteGrid(grid.GridEntityId); - mapManager.DeleteMap(mapId); - } - /// /// Is the entity correctly removed / added to EntityLookup when anchored /// diff --git a/Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs b/Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs index 7b61c145e..fd8d24b22 100644 --- a/Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs +++ b/Robust.UnitTesting/Shared/Physics/Broadphase_Test.cs @@ -36,13 +36,13 @@ public sealed class Broadphase_Test var mapBroadphase2 = entManager.GetComponent(mapManager.GetMapEntityId(mapId2)); entManager.TickUpdate(0.016f, false); #pragma warning disable NUnit2046 - Assert.That(mapBroadphase1.Tree.Count, Is.EqualTo(0)); + Assert.That(mapBroadphase1.DynamicTree.Count, Is.EqualTo(0)); #pragma warning restore NUnit2046 xform.Coordinates = new EntityCoordinates(mapManager.GetMapEntityId(mapId2), Vector2.Zero); entManager.TickUpdate(0.016f, false); #pragma warning disable NUnit2046 - Assert.That(mapBroadphase2.Tree.Count, Is.EqualTo(0)); + Assert.That(mapBroadphase2.DynamicTree.Count, Is.EqualTo(0)); #pragma warning restore NUnit2046 } @@ -56,6 +56,7 @@ public sealed class Broadphase_Test var entManager = sim.Resolve(); var mapManager = sim.Resolve(); var physicsSystem = sim.Resolve().GetEntitySystem(); + var lookup = sim.Resolve().GetEntitySystem(); var mapId = mapManager.CreateMap(); var grid = mapManager.CreateGrid(mapId); @@ -67,11 +68,9 @@ public sealed class Broadphase_Test Assert.That(entManager.EntityQuery(true).Count(), Is.EqualTo(2)); var parent = entManager.SpawnEntity(null, new EntityCoordinates(grid.GridEntityId, new Vector2(0.5f, 0.5f))); - var parentBody = entManager.AddComponent(parent); var child1 = entManager.SpawnEntity(null, new EntityCoordinates(parent, Vector2.Zero)); var child1Xform = entManager.GetComponent(child1); - var child1Body = entManager.AddComponent(child1); // Have a non-collidable child and check it doesn't get added too. var child2 = entManager.SpawnEntity(null, new EntityCoordinates(child1, Vector2.Zero)); @@ -79,19 +78,18 @@ public sealed class Broadphase_Test var child2Body = entManager.AddComponent(child2); physicsSystem.SetCanCollide(child2Body, false); Assert.That(!child2Body.CanCollide); - Assert.That(child2Body.Broadphase, Is.EqualTo(null)); Assert.That(child1Xform.ParentUid, Is.EqualTo(parent)); Assert.That(child2Xform.ParentUid, Is.EqualTo(child1)); - Assert.That(parentBody.Broadphase, Is.EqualTo(gridBroadphase)); - Assert.That(child1Body.Broadphase, Is.EqualTo(gridBroadphase)); + Assert.That(lookup.GetBroadphase(parent), Is.EqualTo(gridBroadphase)); + Assert.That(lookup.GetBroadphase(child1), Is.EqualTo(gridBroadphase)); // They should get deparented to the map and updated to the map's broadphase instead. grid.SetTile(Vector2i.Zero, Tile.Empty); - Assert.That(parentBody.Broadphase, Is.EqualTo(mapBroadphase)); - Assert.That(child1Body.Broadphase, Is.EqualTo(mapBroadphase)); - Assert.That(child2Body.Broadphase, Is.EqualTo(null)); + Assert.That(lookup.GetBroadphase(parent), Is.EqualTo(mapBroadphase)); + Assert.That(lookup.GetBroadphase(child1), Is.EqualTo(mapBroadphase)); + Assert.That(lookup.GetBroadphase(child2Body.Owner), Is.EqualTo(mapBroadphase)); } /// @@ -104,11 +102,12 @@ public sealed class Broadphase_Test var entManager = sim.Resolve(); var xformSystem = sim.Resolve().GetEntitySystem(); var physSystem = sim.Resolve().GetEntitySystem(); + var lookup = sim.Resolve().GetEntitySystem(); var mapManager = sim.Resolve(); var mapId = mapManager.CreateMap(); var mapUid = mapManager.GetMapEntityId(mapId); - var mapBroapdhase = entManager.GetComponent(mapUid); + var mapBroadphase = entManager.GetComponent(mapUid); Assert.That(entManager.EntityQuery(true).Count(), Is.EqualTo(1)); @@ -130,22 +129,22 @@ public sealed class Broadphase_Test Assert.That(child1Xform.ParentUid, Is.EqualTo(parent)); Assert.That(child2Xform.ParentUid, Is.EqualTo(child1)); - Assert.That(parentBody.Broadphase, Is.EqualTo(mapBroapdhase)); - Assert.That(child1Body.Broadphase, Is.EqualTo(mapBroapdhase)); - Assert.That(child2Body.Broadphase, Is.EqualTo(null)); + Assert.That(lookup.GetBroadphase(parentBody.Owner), Is.EqualTo(mapBroadphase)); + Assert.That(lookup.GetBroadphase(child1Body.Owner), Is.EqualTo(mapBroadphase)); + Assert.That(lookup.GetBroadphase(child2Body.Owner), Is.EqualTo(mapBroadphase)); // They should get deparented to the map and updated to the map's broadphase instead. xformSystem.DetachParentToNull(parentXform); - Assert.That(parentBody.Broadphase, Is.EqualTo(null)); - Assert.That(child1Body.Broadphase, Is.EqualTo(null)); - Assert.That(child2Body.Broadphase, Is.EqualTo(null)); + Assert.That(lookup.GetBroadphase(parentBody.Owner), Is.EqualTo(null)); + Assert.That(lookup.GetBroadphase(child1Body.Owner), Is.EqualTo(null)); + Assert.That(lookup.GetBroadphase(child2Body.Owner), Is.EqualTo(null)); // Can't assert CanCollide because they may still want to be valid when coming out of nullspace. // Check it goes back to normal parentXform.AttachParent(mapUid); - Assert.That(parentBody.Broadphase, Is.EqualTo(mapBroapdhase)); - Assert.That(child1Body.Broadphase, Is.EqualTo(mapBroapdhase)); - Assert.That(child2Body.Broadphase, Is.EqualTo(null)); + Assert.That(lookup.GetBroadphase(parentBody.Owner), Is.EqualTo(mapBroadphase)); + Assert.That(lookup.GetBroadphase(child1Body.Owner), Is.EqualTo(mapBroadphase)); + Assert.That(lookup.GetBroadphase(child2Body.Owner), Is.EqualTo(mapBroadphase)); } }