EntityLookup as a system (#2573)

This commit is contained in:
metalgearsloth
2022-03-03 21:17:01 +11:00
committed by GitHub
parent 70c1e8680f
commit 33251222cd
38 changed files with 1163 additions and 1060 deletions
-3
View File
@@ -26,7 +26,6 @@ namespace Robust.Client
[Dependency] private readonly IPlayerManager _playMan = default!;
[Dependency] private readonly INetConfigurationManager _configManager = default!;
[Dependency] private readonly IClientEntityManager _entityManager = default!;
[Dependency] private readonly IEntityLookup _entityLookup = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IDiscordRichPresence _discord = default!;
[Dependency] private readonly IGameTiming _timing = default!;
@@ -213,7 +212,6 @@ namespace Robust.Client
{
_entityManager.Startup();
_mapManager.Startup();
_entityLookup.Startup();
_timing.ResetSimTime();
_timing.Paused = false;
@@ -224,7 +222,6 @@ namespace Robust.Client
IoCManager.Resolve<INetConfigurationManager>().FlushMessages();
_gameStates.Reset();
_playMan.Shutdown();
_entityLookup.Shutdown();
_entityManager.Shutdown();
_mapManager.Shutdown();
_discord.ClearPresence();
-2
View File
@@ -47,7 +47,6 @@ namespace Robust.Client
IoCManager.Register<IMapManagerInternal, NetworkedMapManager>();
IoCManager.Register<INetworkedMapManager, NetworkedMapManager>();
IoCManager.Register<IEntityManager, ClientEntityManager>();
IoCManager.Register<IEntityLookup, EntityLookup>();
IoCManager.Register<IReflectionManager, ClientReflectionManager>();
IoCManager.Register<IConsoleHost, ClientConsoleHost>();
IoCManager.Register<IClientConsoleHost, ClientConsoleHost>();
@@ -72,7 +71,6 @@ namespace Robust.Client
IoCManager.Register<IStateManager, StateManager>();
IoCManager.Register<IUserInterfaceManager, UserInterfaceManager>();
IoCManager.Register<IUserInterfaceManagerInternal, UserInterfaceManager>();
IoCManager.Register<IDebugDrawing, DebugDrawing>();
IoCManager.Register<ILightManager, LightManager>();
IoCManager.Register<IDiscordRichPresence, DiscordRichPresence>();
IoCManager.Register<IMidiManager, MidiManager>();
+1 -1
View File
@@ -168,7 +168,7 @@ namespace Robust.Client.Console.Commands
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var mgr = IoCManager.Resolve<IDebugDrawing>();
var mgr = EntitySystem.Get<DebugDrawingSystem>();
mgr.DebugPositions = !mgr.DebugPositions;
}
}
@@ -6,17 +6,21 @@ using Robust.Shared.Maths;
namespace Robust.Client.Debugging
{
/// <inheritdoc />
public sealed class DebugDrawing : IDebugDrawing
/// <summary>
/// A collection of visual debug overlays for the client game.
/// </summary>
public sealed class DebugDrawingSystem : EntitySystem
{
[Dependency] private readonly IOverlayManager _overlayManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
private bool _debugPositions;
/// <inheritdoc />
/// <summary>
/// Toggles the visual overlay of the local origin for each entity on screen.
/// </summary>
public bool DebugPositions
{
get => _debugPositions;
@@ -42,13 +46,13 @@ namespace Robust.Client.Debugging
private sealed class EntityPositionOverlay : Overlay
{
private readonly IEntityLookup _lookup;
private readonly EntityLookupSystem _lookup;
private readonly IEyeManager _eyeManager;
private readonly IEntityManager _entityManager;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityPositionOverlay(IEntityLookup lookup, IEyeManager eyeManager, IEntityManager entityManager)
public EntityPositionOverlay(EntityLookupSystem lookup, IEyeManager eyeManager, IEntityManager entityManager)
{
_lookup = lookup;
_eyeManager = eyeManager;
@@ -61,13 +65,11 @@ namespace Robust.Client.Debugging
var worldHandle = (DrawingHandleWorld) args.DrawingHandle;
var viewport = _eyeManager.GetWorldViewport();
var xformQuery = _entityManager.GetEntityQuery<TransformComponent>();
foreach (var entity in _lookup.GetEntitiesIntersecting(_eyeManager.CurrentMap, viewport))
{
var transform = _entityManager.GetComponent<TransformComponent>(entity);
var center = transform.WorldPosition;
var worldRotation = transform.WorldRotation;
var (center, worldRotation) = xformQuery.GetComponent(entity).GetWorldPositionRotation();
var xLine = worldRotation.RotateVec(Vector2.UnitX);
var yLine = worldRotation.RotateVec(Vector2.UnitY);
-13
View File
@@ -1,13 +0,0 @@
namespace Robust.Client.Debugging
{
/// <summary>
/// A collection of visual debug overlays for the client game.
/// </summary>
public interface IDebugDrawing
{
/// <summary>
/// Toggles the visual overlay of the local origin for each entity on screen.
/// </summary>
bool DebugPositions { get; set; }
}
}
@@ -50,7 +50,6 @@ namespace Robust.Client
[Dependency] private readonly IClientConsoleHost _console = default!;
[Dependency] private readonly ITimerManager _timerManager = default!;
[Dependency] private readonly IClientEntityManager _entityManager = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly IPlacementManager _placementManager = default!;
[Dependency] private readonly IClientGameStateManager _gameStateManager = default!;
[Dependency] private readonly IOverlayManagerInternal _overlayManager = default!;
@@ -471,7 +470,6 @@ namespace Robust.Client
// The last real tick is the current tick! This way we won't be in "prediction" mode.
_gameTiming.LastRealTick = _gameTiming.CurTick;
_entityManager.TickUpdate(frameEventArgs.DeltaSeconds, noPredictions: false);
_lookup.Update();
}
_modLoader.BroadcastUpdate(ModUpdateLevel.PostEngine, frameEventArgs);
@@ -576,7 +574,6 @@ namespace Robust.Client
_networkManager.Shutdown("Client shutting down");
_midiManager.Shutdown();
IoCManager.Resolve<IEntityLookup>().Shutdown();
_entityManager.Shutdown();
_clyde.Shutdown();
_clydeAudio.Shutdown();
@@ -75,7 +75,7 @@ namespace Robust.Client.GameObjects
public Vector2 Scale
{
get => scale;
set
set
{
scale = value;
UpdateLocalMatrix();
@@ -1539,7 +1539,7 @@ namespace Robust.Client.GameObjects
// Need relative angle on screen for determining the sprite rsi direction.
Angle relativeRotation = NoRotation
? Angle.Zero
: worldRotation + eye.Rotation;
: worldRotation + eye.Rotation;
// we need to calculate bounding box taking into account all nested layers
// because layers can have offsets, scale or rotation, we need to calculate a new BB
@@ -1967,7 +1967,7 @@ namespace Robust.Client.GameObjects
// rotate 90 degrees:
RSIDirection.East or RSIDirection.West => Box2.CenteredAround(Offset, (textureSize.Y, textureSize.X)),
// rotated 45 degrees (any 45 degree rotated rectangle has a square bounding box with sides of length (x+y)/sqrt(2) )
_ => Box2.CenteredAround(Offset, Vector2.One * (textureSize.X + textureSize.Y) / MathF.Sqrt(2))
_ => Box2.CenteredAround(Offset, Vector2.One * (textureSize.X + textureSize.Y) / MathF.Sqrt(2))
};
return _scale == Vector2.One ? box : box.Scale(_scale);
@@ -25,7 +25,7 @@ namespace Robust.Client.GameObjects
if (_enabled)
{
_lightOverlay = new DebugLightOverlay(
IoCManager.Resolve<IEntityLookup>(),
EntitySystem.Get<EntityLookupSystem>(),
IoCManager.Resolve<IEyeManager>(),
IoCManager.Resolve<IMapManager>(),
Get<RenderingTreeSystem>());
@@ -44,7 +44,7 @@ namespace Robust.Client.GameObjects
private sealed class DebugLightOverlay : Overlay
{
private IEntityLookup _lookup;
private EntityLookupSystem _lookup;
private IEyeManager _eyeManager;
private IMapManager _mapManager;
@@ -52,7 +52,7 @@ namespace Robust.Client.GameObjects
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public DebugLightOverlay(IEntityLookup lookup, IEyeManager eyeManager, IMapManager mapManager, RenderingTreeSystem tree)
public DebugLightOverlay(EntityLookupSystem lookup, IEyeManager eyeManager, IMapManager mapManager, RenderingTreeSystem tree)
{
_lookup = lookup;
_eyeManager = eyeManager;
@@ -72,7 +72,7 @@ namespace Robust.Client.GameObjects
{
foreach (var light in tree.LightTree)
{
var aabb = _lookup.GetWorldAabbFromEntity(light.Owner);
var aabb = _lookup.GetWorldAABB(light.Owner);
if (!aabb.Intersects(worldBounds)) continue;
args.WorldHandle.DrawRect(aabb, Color.Green.WithAlpha(0.1f));
@@ -45,7 +45,6 @@ namespace Robust.Client.GameStates
[Dependency] private readonly IComponentFactory _compFactory = default!;
[Dependency] private readonly IClientEntityManagerInternal _entities = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly IPlayerManager _players = default!;
[Dependency] private readonly IClientNetManager _network = default!;
[Dependency] private readonly IBaseClient _client = default!;
@@ -332,8 +331,6 @@ namespace Robust.Client.GameStates
}
_entities.TickUpdate((float) _timing.TickPeriod.TotalSeconds, noPredictions: !IsPredictionEnabled);
_lookup.Update();
}
private void ResetPredictedEntities(GameTick curTick)
@@ -31,7 +31,7 @@ namespace Robust.Client.Placement.Modes
var mapId = MouseCoords.GetMapId(pManager.EntityManager);
var snapToEntities = IoCManager.Resolve<IEntityLookup>().GetEntitiesInRange(MouseCoords, SnapToRange)
var snapToEntities = EntitySystem.Get<EntityLookupSystem>().GetEntitiesInRange(MouseCoords, SnapToRange)
.Where(entity => pManager.EntityManager.GetComponent<MetaDataComponent>(entity).EntityPrototype == pManager.CurrentPrototype && pManager.EntityManager.GetComponent<TransformComponent>(entity).MapID == mapId)
.OrderBy(entity => (pManager.EntityManager.GetComponent<TransformComponent>(entity).WorldPosition - MouseCoords.ToMapPos(pManager.EntityManager)).LengthSquared)
.ToList();
@@ -53,7 +53,7 @@ namespace Robust.Client.Placement.Modes
var topRight = new Vector2(CurrentTile.X + 0.99f, CurrentTile.Y + 0.99f);
var box = new Box2(bottomLeft, topRight);
return !IoCManager.Resolve<IEntityLookup>().AnyEntitiesIntersecting(map, box);
return !EntitySystem.Get<EntityLookupSystem>().AnyEntitiesIntersecting(map, box);
}
}
}
+1
View File
@@ -53,6 +53,7 @@
<Compile Update="UserInterface\CustomControls\DefaultWindow.xaml.cs">
<DependentUpon>DefaultWindow.xaml</DependentUpon>
</Compile>
<Compile Remove="Debugging\IDebugDrawing.cs" />
</ItemGroup>
<Import Project="..\MSBuild\Robust.Engine.targets" />
<PropertyGroup>
+4 -9
View File
@@ -66,18 +66,17 @@ namespace Robust.Server
[Dependency] private readonly IConfigurationManagerInternal _config = default!;
[Dependency] private readonly IServerEntityManager _entityManager = default!;
[Dependency] private readonly IEntityLookup _lookup = default!;
[Dependency] private readonly ILogManager _log = default!;
[Dependency] private readonly IRobustSerializer _serializer = default!;
[Dependency] private readonly IGameTiming _time = default!;
[Dependency] private readonly IResourceManagerInternal _resources = default!;
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly ITimerManager timerManager = default!;
[Dependency] private readonly ITimerManager _timerManager = default!;
[Dependency] private readonly IServerGameStateManager _stateManager = default!;
[Dependency] private readonly IServerNetManager _network = default!;
[Dependency] private readonly ISystemConsoleManager _systemConsole = default!;
[Dependency] private readonly ITaskManager _taskManager = default!;
[Dependency] private readonly IRuntimeLog runtimeLog = default!;
[Dependency] private readonly IRuntimeLog _runtimeLog = default!;
[Dependency] private readonly IModLoaderInternal _modLoader = default!;
[Dependency] private readonly IWatchdogApi _watchdogApi = default!;
[Dependency] private readonly HubManager _hubManager = default!;
@@ -344,7 +343,6 @@ namespace Robust.Server
_consoleHost.Initialize();
_entityManager.Startup();
_mapManager.Startup();
IoCManager.Resolve<IEntityLookup>().Startup();
_stateManager.Initialize();
var reg = _entityManager.ComponentFactory.GetRegistration<TransformComponent>();
@@ -603,7 +601,6 @@ namespace Robust.Server
_network.Shutdown($"Server shutting down: {_shutdownReason}");
// shutdown entities
IoCManager.Resolve<IEntityLookup>().Shutdown();
_entityManager.Cleanup();
if (_config.GetCVar(CVars.LogRuntimeLog))
@@ -614,7 +611,7 @@ namespace Robust.Server
Directory.CreateDirectory(relPath);
var pathToWrite = Path.Combine(relPath,
"Runtime-" + DateTime.Now.ToString("yyyy-MM-dd-THH-mm-ss") + ".txt");
File.WriteAllText(pathToWrite, runtimeLog.Display(), EncodingHelpers.UTF8);
File.WriteAllText(pathToWrite, _runtimeLog.Display(), EncodingHelpers.UTF8);
}
AppDomain.CurrentDomain.ProcessExit -= ProcessExiting;
@@ -660,7 +657,7 @@ namespace Robust.Server
using (TickUsage.WithLabels("Timers").NewTimer())
{
_consoleHost.CommandBufferExecute();
timerManager.UpdateTimers(frameEventArgs);
_timerManager.UpdateTimers(frameEventArgs);
}
using (TickUsage.WithLabels("AsyncTasks").NewTimer())
@@ -671,8 +668,6 @@ namespace Robust.Server
// Pass Histogram into the IEntityManager.Update so it can do more granular measuring.
_entityManager.TickUpdate(frameEventArgs.DeltaSeconds, noPredictions: false, TickUsage);
_lookup.Update();
using (TickUsage.WithLabels("PostEngine").NewTimer())
{
_modLoader.BroadcastUpdate(ModUpdateLevel.PostEngine, frameEventArgs);
@@ -311,7 +311,7 @@ namespace Robust.Server.Bql
public override IEnumerable<EntityUid> DoSelection(IEnumerable<EntityUid> input, IReadOnlyList<object> arguments, bool isInverted, IEntityManager entityManager)
{
var radius = (float)(double)arguments[0];
var entityLookup = IoCManager.Resolve<IEntityLookup>();
var entityLookup = EntitySystem.Get<EntityLookupSystem>();
// TODO: Make this a foreach and reduce LINQ chain because it'll allocate a LOT
//BUG: GetEntitiesInRange effectively uses manhattan distance. This is not intended, near is supposed to be circular.
+1 -1
View File
@@ -209,7 +209,7 @@ namespace Robust.Server.Placement
{
EntityCoordinates start = msg.EntityCoordinates;
Vector2 rectSize = msg.RectSize;
foreach (EntityUid entity in IoCManager.Resolve<IEntityLookup>().GetEntitiesIntersecting(start.GetMapId(_entityManager),
foreach (EntityUid entity in EntitySystem.Get<EntityLookupSystem>().GetEntitiesIntersecting(start.GetMapId(_entityManager),
new Box2(start.Position, start.Position + rectSize)))
{
if (_entityManager.Deleted(entity) || _entityManager.HasComponent<IMapGridComponent>(entity) || _entityManager.HasComponent<ActorComponent>(entity))
-1
View File
@@ -50,7 +50,6 @@ namespace Robust.Server
IoCManager.Register<IMapManagerInternal, NetworkedMapManager>();
IoCManager.Register<INetworkedMapManager, NetworkedMapManager>();
IoCManager.Register<IEntityManager, ServerEntityManager>();
IoCManager.Register<IEntityLookup, EntityLookup>();
IoCManager.Register<IEntityNetworkManager, ServerEntityManager>();
IoCManager.Register<IServerEntityNetworkManager, ServerEntityManager>();
IoCManager.Register<IMapLoader, MapLoader>();
@@ -209,6 +209,43 @@ namespace Robust.Shared.Containers
return false;
}
/// <summary>
/// Gets the top-most container in the hierarchy for this entity, if it exists.
/// </summary>
public bool TryGetOuterContainer(EntityUid uid, TransformComponent xform, [NotNullWhen(true)] out IContainer? container)
{
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
return TryGetOuterContainer(uid, xform, out container, xformQuery);
}
public bool TryGetOuterContainer(EntityUid uid, TransformComponent xform,
[NotNullWhen(true)] out IContainer? container, EntityQuery<TransformComponent> xformQuery)
{
container = null;
if (!uid.IsValid())
return false;
var conQuery = EntityManager.GetEntityQuery<ContainerManagerComponent>();
var child = uid;
var parent = xform.ParentUid;
while (parent.IsValid())
{
if (conQuery.TryGetComponent(parent, out var conManager) &&
conManager.TryGetContainer(child, out var parentContainer))
{
container = parentContainer;
}
var parentXform = xformQuery.GetComponent(parent);
child = parent;
parent = parentXform.ParentUid;
}
return container != null;
}
#endregion
// Eject entities from their parent container if the parent change is done by the transform only.
@@ -1,9 +1,10 @@
using Robust.Shared.Maths;
using Robust.Shared.Physics;
namespace Robust.Shared.GameObjects
{
public interface ILookupWorldBox2Component
{
Box2 GetWorldAABB(Vector2? worldPos = null, Angle? worldRot = null);
Box2 GetAABB(Transform transform);
}
}
@@ -266,12 +266,8 @@ namespace Robust.Shared.GameObjects
Dirty(_entMan);
}
public Box2 GetWorldAABB(Vector2? worldPos = null, Angle? worldRot = null)
public Box2 GetAABB(Transform transform)
{
worldPos ??= _entMan.GetComponent<TransformComponent>(Owner).WorldPosition;
worldRot ??= _entMan.GetComponent<TransformComponent>(Owner).WorldRotation;
var transform = new Transform(worldPos.Value, (float) worldRot.Value.Theta);
var bounds = new Box2(transform.Position, transform.Position);
foreach (var fixture in _entMan.GetComponent<FixturesComponent>(Owner).Fixtures.Values)
@@ -286,13 +282,24 @@ namespace Robust.Shared.GameObjects
return bounds;
}
public Box2 GetWorldAABB(Vector2 worldPos, Angle worldRot, EntityQuery<FixturesComponent> fixtures)
[Obsolete("Use the GetWorldAABB on EntityLookupSystem")]
public Box2 GetWorldAABB(Vector2? worldPos = null, Angle? worldRot = null)
{
var transform = new Transform(worldPos, (float) worldRot.Theta);
if (worldPos == null && worldRot == null)
{
(worldPos, worldRot) = _entMan.GetComponent<TransformComponent>(Owner).GetWorldPositionRotation();
}
else
{
worldPos ??= _entMan.GetComponent<TransformComponent>(Owner).WorldPosition;
worldRot ??= _entMan.GetComponent<TransformComponent>(Owner).WorldRotation;
}
var transform = new Transform(worldPos.Value, (float) worldRot.Value.Theta);
var bounds = new Box2(transform.Position, transform.Position);
foreach (var fixture in fixtures.GetComponent(Owner).Fixtures.Values)
foreach (var fixture in _entMan.GetComponent<FixturesComponent>(Owner).Fixtures.Values)
{
for (var i = 0; i < fixture.Shape.ChildCount; i++)
{
@@ -78,7 +78,7 @@ namespace Robust.Shared.GameObjects
if (result)
{
xform.Parent = _entMan.GetComponent<TransformComponent>(Owner);
xform.ParentUid = Owner;
// anchor snapping
xform.LocalPosition = Grid.GridTileToLocal(tileIndices).Position;
@@ -140,7 +140,7 @@ namespace Robust.Shared.GameObjects
if (!DeferUpdates)
{
RebuildMatrices();
var rotateEvent = new RotateEvent(Owner, oldRotation, _localRotation);
var rotateEvent = new RotateEvent(Owner, oldRotation, _localRotation, this);
_entMan.EventBus.RaiseLocalEvent(Owner, ref rotateEvent);
}
else
@@ -210,7 +210,11 @@ namespace Robust.Shared.GameObjects
public EntityUid ParentUid
{
get => _parent;
set => Parent = _entMan.GetComponent<TransformComponent>(value);
set
{
if (value == _parent) return;
Parent = _entMan.GetComponent<TransformComponent>(value);
}
}
/// <summary>
@@ -618,7 +622,7 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Run MoveEvent, RotateEvent, and UpdateEntityTree updates.
/// </summary>
public void RunDeferred(Box2 worldAABB)
public void RunDeferred()
{
// if we resolved to (close enough) to the OG position then no update.
if ((_oldCoords == null || _oldCoords.Equals(Coordinates)) &&
@@ -631,14 +635,14 @@ namespace Robust.Shared.GameObjects
if (_oldCoords != null)
{
var moveEvent = new MoveEvent(Owner, _oldCoords.Value, Coordinates, this, worldAABB);
var moveEvent = new MoveEvent(Owner, _oldCoords.Value, Coordinates, this);
_entMan.EventBus.RaiseLocalEvent(Owner, ref moveEvent);
_oldCoords = null;
}
if (_oldLocalRotation != null)
{
var rotateEvent = new RotateEvent(Owner, _oldLocalRotation.Value, _localRotation, worldAABB);
var rotateEvent = new RotateEvent(Owner, _oldLocalRotation.Value, _localRotation, this);
_entMan.EventBus.RaiseLocalEvent(Owner, ref rotateEvent);
_oldLocalRotation = null;
}
@@ -1137,24 +1141,18 @@ namespace Robust.Shared.GameObjects
[ByRefEvent]
public readonly struct MoveEvent
{
public MoveEvent(EntityUid sender, EntityCoordinates oldPos, EntityCoordinates newPos, TransformComponent component, Box2? worldAABB = null)
public MoveEvent(EntityUid sender, EntityCoordinates oldPos, EntityCoordinates newPos, TransformComponent component)
{
Sender = sender;
OldPosition = oldPos;
NewPosition = newPos;
Component = component;
WorldAABB = worldAABB;
}
public readonly EntityUid Sender;
public readonly EntityCoordinates OldPosition;
public readonly EntityCoordinates NewPosition;
public readonly TransformComponent Component;
/// <summary>
/// New AABB of the entity.
/// </summary>
public readonly Box2? WorldAABB;
}
/// <summary>
@@ -1163,22 +1161,18 @@ namespace Robust.Shared.GameObjects
[ByRefEvent]
public readonly struct RotateEvent
{
public RotateEvent(EntityUid sender, Angle oldRotation, Angle newRotation, Box2? worldAABB = null)
public RotateEvent(EntityUid sender, Angle oldRotation, Angle newRotation, TransformComponent xform)
{
Sender = sender;
OldRotation = oldRotation;
NewRotation = newRotation;
WorldAABB = worldAABB;
Component = xform;
}
public readonly EntityUid Sender;
public readonly Angle OldRotation;
public readonly Angle NewRotation;
/// <summary>
/// New AABB of the entity.
/// </summary>
public readonly Box2? WorldAABB;
public readonly TransformComponent Component;
}
public struct TransformChildrenEnumerator : IDisposable
+2 -1
View File
@@ -282,7 +282,8 @@ namespace Robust.Shared.GameObjects
var transform = GetComponent<TransformComponent>(uid);
metadata.EntityLifeStage = EntityLifeStage.Terminating;
EventBus.RaiseLocalEvent(uid, new EntityTerminatingEvent(), false);
var ev = new EntityTerminatingEvent(uid);
EventBus.RaiseLocalEvent(uid, ref ev, false);
// DeleteEntity modifies our _children collection, we must cache the collection to iterate properly
foreach (var child in transform._children.ToArray())
@@ -4,7 +4,7 @@
/// Raised when an entity parent is changed.
/// </summary>
[ByRefEvent]
public sealed class EntParentChangedMessage : EntityEventArgs
public readonly struct EntParentChangedMessage
{
/// <summary>
/// Entity that was adopted. The transform component has a property with the new parent.
@@ -3,5 +3,14 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// The children of this entity are about to be deleted.
/// </summary>
public sealed class EntityTerminatingEvent : EntityEventArgs { }
[ByRefEvent]
public struct EntityTerminatingEvent
{
public EntityUid Owner;
public EntityTerminatingEvent(EntityUid uid)
{
Owner = uid;
}
}
}
@@ -0,0 +1,476 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects;
public sealed partial class EntityLookupSystem
{
// TODO: Need to nuke most of the below and cleanup when entitylookup gets optimised some more (physics + containers).
private LookupsEnumerator GetLookupsIntersecting(MapId mapId, Box2 worldAABB)
{
_mapManager.FindGridsIntersectingEnumerator(mapId, worldAABB, out var gridEnumerator, true);
return new LookupsEnumerator(EntityManager, _mapManager, mapId, gridEnumerator);
}
private struct LookupsEnumerator
{
private IEntityManager EntityManager;
private IMapManager _mapManager;
private MapId _mapId;
private FindGridsEnumerator _enumerator;
private bool _final;
public LookupsEnumerator(IEntityManager entityManager, IMapManager mapManager, MapId mapId, FindGridsEnumerator enumerator)
{
EntityManager = entityManager;
_mapManager = mapManager;
_mapId = mapId;
_enumerator = enumerator;
_final = false;
}
public bool MoveNext([NotNullWhen(true)] out EntityLookupComponent? component)
{
if (!_enumerator.MoveNext(out var grid))
{
if (_final || _mapId == MapId.Nullspace)
{
component = null;
return false;
}
_final = true;
EntityUid mapUid = _mapManager.GetMapEntityIdOrThrow(_mapId);
component = EntityManager.GetComponent<EntityLookupComponent>(mapUid);
return true;
}
// TODO: Recursive and all that.
component = EntityManager.GetComponent<EntityLookupComponent>(grid.GridEntityId);
return true;
}
}
private IEnumerable<EntityUid> GetAnchored(MapId mapId, Box2 worldAABB, LookupFlags flags)
{
if ((flags & LookupFlags.IncludeAnchored) == 0x0) yield break;
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
foreach (var uid in grid.GetAnchoredEntities(worldAABB))
{
if (!EntityManager.EntityExists(uid)) continue;
yield return uid;
}
}
}
private IEnumerable<EntityUid> GetAnchored(MapId mapId, Box2Rotated worldBounds, LookupFlags flags)
{
if ((flags & LookupFlags.IncludeAnchored) == 0x0) yield break;
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds))
{
foreach (var uid in grid.GetAnchoredEntities(worldBounds))
{
if (!EntityManager.EntityExists(uid)) continue;
yield return uid;
}
}
}
/// <inheritdoc />
public bool AnyEntitiesIntersecting(MapId mapId, Box2 box, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var found = false;
var enumerator = GetLookupsIntersecting(mapId, box);
while (enumerator.MoveNext(out var lookup))
{
var offsetBox = EntityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.TransformBox(box);
lookup.Tree.QueryAabb(ref found, (ref bool found, in EntityUid ent) =>
{
if (EntityManager.Deleted(ent))
return true;
found = true;
return false;
}, offsetBox, (flags & LookupFlags.Approximate) != 0x0);
}
if (!found)
{
foreach (var _ in GetAnchored(mapId, box, flags))
{
return true;
}
}
return found;
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public void FastEntitiesIntersecting(in MapId mapId, ref Box2 worldAABB, EntityUidQueryCallback callback, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var enumerator = GetLookupsIntersecting(mapId, worldAABB);
while (enumerator.MoveNext(out var lookup))
{
var offsetBox = EntityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.TransformBox(worldAABB);
lookup.Tree._b2Tree.FastQuery(ref offsetBox, (ref EntityUid data) => callback(data));
}
if ((flags & LookupFlags.IncludeAnchored) != 0x0)
{
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
foreach (var uid in grid.GetAnchoredEntities(worldAABB))
{
if (!EntityManager.EntityExists(uid)) continue;
callback(uid);
}
}
}
}
/// <inheritdoc />
public void FastEntitiesIntersecting(EntityLookupComponent lookup, ref Box2 localAABB, EntityUidQueryCallback callback)
{
lookup.Tree._b2Tree.FastQuery(ref localAABB, (ref EntityUid data) => callback(data));
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, LookupFlags flags = LookupFlags.IncludeAnchored)
{
if (mapId == MapId.Nullspace) return Enumerable.Empty<EntityUid>();
var list = new List<EntityUid>();
var enumerator = GetLookupsIntersecting(mapId, worldAABB);
while (enumerator.MoveNext(out var lookup))
{
var offsetBox = EntityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.TransformBox(worldAABB);
lookup.Tree.QueryAabb(ref list, (ref List<EntityUid> list, in EntityUid ent) =>
{
if (!EntityManager.Deleted(ent))
{
list.Add(ent);
}
return true;
}, offsetBox, (flags & LookupFlags.Approximate) != 0x0);
}
foreach (var ent in GetAnchored(mapId, worldAABB, flags))
{
list.Add(ent);
}
return list;
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = LookupFlags.IncludeAnchored)
{
if (mapId == MapId.Nullspace) return Enumerable.Empty<EntityUid>();
var list = new List<EntityUid>();
var worldAABB = worldBounds.CalcBoundingBox();
var enumerator = GetLookupsIntersecting(mapId, worldAABB);
while (enumerator.MoveNext(out var lookup))
{
var offsetBox = EntityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.TransformBox(worldBounds);
lookup.Tree.QueryAabb(ref list, (ref List<EntityUid> list, in EntityUid ent) =>
{
if (!EntityManager.Deleted(ent))
{
list.Add(ent);
}
return true;
}, offsetBox, (flags & LookupFlags.Approximate) != 0x0);
}
foreach (var ent in GetAnchored(mapId, worldBounds, flags))
{
list.Add(ent);
}
return list;
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesIntersecting(MapId mapId, Vector2 position, LookupFlags flags = LookupFlags.IncludeAnchored)
{
if (mapId == MapId.Nullspace) return Enumerable.Empty<EntityUid>();
var aabb = new Box2(position, position).Enlarged(PointEnlargeRange);
var list = new List<EntityUid>();
var state = (list, position);
var enumerator = GetLookupsIntersecting(mapId, aabb);
while (enumerator.MoveNext(out var lookup))
{
var localPoint = EntityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.Transform(position);
lookup.Tree.QueryPoint(ref state, (ref (List<EntityUid> list, Vector2 position) state, in EntityUid ent) =>
{
if (Intersecting(ent, state.position))
{
state.list.Add(ent);
}
return true;
}, localPoint, (flags & LookupFlags.Approximate) != 0x0);
}
if ((flags & LookupFlags.IncludeAnchored) != 0x0 &&
_mapManager.TryFindGridAt(mapId, position, out var grid) &&
grid.TryGetTileRef(position, out var tile))
{
foreach (var uid in grid.GetAnchoredEntities(tile.GridIndices))
{
if (!EntityManager.EntityExists(uid)) continue;
state.list.Add(uid);
}
}
return list;
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesIntersecting(MapCoordinates position, LookupFlags flags = LookupFlags.IncludeAnchored)
{
return GetEntitiesIntersecting(position.MapId, position.Position, flags);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesIntersecting(EntityCoordinates position, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var mapPos = position.ToMap(EntityManager);
return GetEntitiesIntersecting(mapPos.MapId, mapPos.Position, flags);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesIntersecting(EntityUid entity, float enlarged = 0f, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var worldAABB = GetWorldAABB(entity);
var xform = EntityManager.GetComponent<TransformComponent>(entity);
var (worldPos, worldRot) = xform.GetWorldPositionRotation();
var enumerator = GetLookupsIntersecting(xform.MapID, worldAABB);
var list = new List<EntityUid>();
while (enumerator.MoveNext(out var lookup))
{
// To get the tightest bounds possible we'll re-calculate it for each lookup.
var localBounds = GetLookupBounds(entity, lookup, worldPos, worldRot, enlarged);
lookup.Tree.QueryAabb(ref list, (ref List<EntityUid> list, in EntityUid ent) =>
{
if (!EntityManager.Deleted(ent))
{
list.Add(ent);
}
return true;
}, localBounds, (flags & LookupFlags.Approximate) != 0x0);
}
foreach (var ent in GetAnchored(xform.MapID, worldAABB, flags))
{
list.Add(ent);
}
return list;
}
private Box2 GetLookupBounds(EntityUid uid, EntityLookupComponent lookup, Vector2 worldPos, Angle worldRot, float enlarged)
{
var (_, lookupRot, lookupInvWorldMatrix) = EntityManager.GetComponent<TransformComponent>(lookup.Owner).GetWorldPositionRotationInvMatrix();
var localPos = lookupInvWorldMatrix.Transform(worldPos);
var localRot = worldRot - lookupRot;
if (EntityManager.TryGetComponent(uid, out FixturesComponent? manager))
{
var transform = new Transform(localPos, localRot);
Box2? aabb = null;
foreach (var (_, fixture) in manager.Fixtures)
{
if (!fixture.Hard) continue;
for (var i = 0; i < fixture.Shape.ChildCount; i++)
{
aabb = aabb?.Union(fixture.Shape.ComputeAABB(transform, i)) ?? fixture.Shape.ComputeAABB(transform, i);
}
}
if (aabb != null)
{
return aabb.Value.Enlarged(enlarged);
}
}
// So IsEmpty checks don't get triggered
return new Box2(localPos - float.Epsilon, localPos + float.Epsilon);
}
/// <inheritdoc />
public bool IsIntersecting(EntityUid entityOne, EntityUid entityTwo)
{
var position = EntityManager.GetComponent<TransformComponent>(entityOne).MapPosition.Position;
return Intersecting(entityTwo, position);
}
private bool Intersecting(EntityUid entity, Vector2 mapPosition)
{
if (EntityManager.TryGetComponent(entity, out IPhysBody? component))
{
if (component.GetWorldAABB().Contains(mapPosition))
return true;
}
else
{
var transform = EntityManager.GetComponent<TransformComponent>(entity);
var entPos = transform.WorldPosition;
if (MathHelper.CloseToPercent(entPos.X, mapPosition.X)
&& MathHelper.CloseToPercent(entPos.Y, mapPosition.Y))
{
return true;
}
}
return false;
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesInRange(EntityCoordinates position, float range, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var mapCoordinates = position.ToMap(EntityManager);
var mapPosition = mapCoordinates.Position;
var aabb = new Box2(mapPosition - new Vector2(range, range),
mapPosition + new Vector2(range, range));
return GetEntitiesIntersecting(mapCoordinates.MapId, aabb, flags);
// TODO: Use a circle shape here mate
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesInRange(MapId mapId, Box2 box, float range, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var aabb = box.Enlarged(range);
return GetEntitiesIntersecting(mapId, aabb, flags);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesInRange(MapId mapId, Vector2 point, float range, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var aabb = new Box2(point, point).Enlarged(range);
return GetEntitiesIntersecting(mapId, aabb, flags);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesInRange(EntityUid entity, float range, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var worldAABB = GetWorldAABB(entity);
return GetEntitiesInRange(EntityManager.GetComponent<TransformComponent>(entity).MapID, worldAABB, range, flags);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesInArc(EntityCoordinates coordinates, float range, Angle direction,
float arcWidth, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var position = coordinates.ToMap(EntityManager).Position;
foreach (var entity in GetEntitiesInRange(coordinates, range * 2, flags))
{
var angle = new Angle(EntityManager.GetComponent<TransformComponent>(entity).WorldPosition - position);
if (angle.Degrees < direction.Degrees + arcWidth / 2 &&
angle.Degrees > direction.Degrees - arcWidth / 2)
yield return entity;
}
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesInMap(MapId mapId, LookupFlags flags = LookupFlags.IncludeAnchored)
{
DebugTools.Assert((flags & LookupFlags.Approximate) == 0x0);
foreach (EntityLookupComponent comp in EntityManager.EntityQuery<EntityLookupComponent>(true))
{
if (EntityManager.GetComponent<TransformComponent>(comp.Owner).MapID != mapId) continue;
foreach (var entity in comp.Tree)
{
if (EntityManager.Deleted(entity)) continue;
yield return entity;
}
}
if ((flags & LookupFlags.IncludeAnchored) == 0x0) yield break;
foreach (var grid in _mapManager.GetAllMapGrids(mapId))
{
foreach (var tile in grid.GetAllTiles())
{
foreach (var uid in grid.GetAnchoredEntities(tile.GridIndices))
{
if (!EntityManager.EntityExists(uid)) continue;
yield return uid;
}
}
}
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesAt(MapId mapId, Vector2 position, LookupFlags flags = LookupFlags.IncludeAnchored)
{
if (mapId == MapId.Nullspace) return Enumerable.Empty<EntityUid>();
var list = new List<EntityUid>();
var state = (list, position);
var aabb = new Box2(position, position).Enlarged(PointEnlargeRange);
var enumerator = GetLookupsIntersecting(mapId, aabb);
while (enumerator.MoveNext(out var lookup))
{
var offsetPos = EntityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.Transform(position);
lookup.Tree.QueryPoint(ref state, (ref (List<EntityUid> list, Vector2 position) state, in EntityUid ent) =>
{
state.list.Add(ent);
return true;
}, offsetPos, (flags & LookupFlags.Approximate) != 0x0);
}
if ((flags & LookupFlags.IncludeAnchored) != 0x0)
{
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, aabb))
{
foreach (var uid in grid.GetAnchoredEntities(aabb))
{
if (!EntityManager.EntityExists(uid)) continue;
list.Add(uid);
}
}
}
return list;
}
}
@@ -6,7 +6,7 @@ using Robust.Shared.Maths;
namespace Robust.Shared.GameObjects;
public sealed partial class EntityLookup
public sealed partial class EntityLookupSystem
{
#region Grid Methods
@@ -21,7 +21,7 @@ public sealed partial class EntityLookup
// Technically this doesn't consider anything overlapping from outside the grid but is this an issue?
if (!_mapManager.TryGetGrid(gridId, out var grid)) return Enumerable.Empty<EntityUid>();
var lookup = _entityManager.GetComponent<EntityLookupComponent>(grid.GridEntityId);
var lookup = EntityManager.GetComponent<EntityLookupComponent>(grid.GridEntityId);
var results = new HashSet<EntityUid>();
var tileSize = grid.TileSize;
@@ -32,13 +32,13 @@ public sealed partial class EntityLookup
lookup.Tree._b2Tree.FastQuery(ref aabb, (ref EntityUid data) =>
{
if (_entityManager.Deleted(data)) return;
if (EntityManager.Deleted(data)) return;
results.Add(data);
});
foreach (var ent in grid.GetAnchoredEntities(index))
{
if (_entityManager.Deleted(ent)) continue;
if (EntityManager.Deleted(ent)) continue;
results.Add(ent);
}
}
@@ -51,7 +51,7 @@ public sealed partial class EntityLookup
// Technically this doesn't consider anything overlapping from outside the grid but is this an issue?
if (!_mapManager.TryGetGrid(gridId, out var grid)) return Enumerable.Empty<EntityUid>();
var lookup = _entityManager.GetComponent<EntityLookupComponent>(grid.GridEntityId);
var lookup = EntityManager.GetComponent<EntityLookupComponent>(grid.GridEntityId);
var tileSize = grid.TileSize;
var aabb = GetLocalBounds(gridIndices, tileSize);
@@ -59,13 +59,13 @@ public sealed partial class EntityLookup
lookup.Tree._b2Tree.FastQuery(ref aabb, (ref EntityUid data) =>
{
if (_entityManager.Deleted(data)) return;
if (EntityManager.Deleted(data)) return;
results.Add(data);
});
foreach (var ent in grid.GetAnchoredEntities(gridIndices))
{
if (_entityManager.Deleted(ent)) continue;
if (EntityManager.Deleted(ent)) continue;
results.Add(ent);
}
@@ -98,7 +98,7 @@ public sealed partial class EntityLookup
if (worldMatrix == null || angle == null)
{
var gridXform = _entityManager.GetComponent<TransformComponent>(grid.GridEntityId);
var gridXform = EntityManager.GetComponent<TransformComponent>(grid.GridEntityId);
var (_, wAng, wMat) = gridXform.GetWorldPositionRotationMatrix();
worldMatrix = wMat;
angle = wAng;
@@ -1,890 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
using Robust.Shared.Configuration;
using Robust.Shared.Containers;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects
{
[Flags]
public enum LookupFlags : byte
{
None = 0,
Approximate = 1 << 0,
IncludeAnchored = 1 << 1,
// IncludeGrids = 1 << 2,
}
// TODO: Nuke IEntityLookup and just make a system
public interface IEntityLookup
{
// Not an EntitySystem given _entityManager has a dependency on it which means it's just easier to IoC it for tests.
void Startup();
void Shutdown();
void Update();
bool AnyEntitiesIntersecting(MapId mapId, Box2 box, LookupFlags flags = LookupFlags.IncludeAnchored);
IEnumerable<EntityUid> GetEntitiesInMap(MapId mapId, LookupFlags flags = LookupFlags.IncludeAnchored);
IEnumerable<EntityUid> GetEntitiesAt(MapId mapId, Vector2 position, LookupFlags flags = LookupFlags.IncludeAnchored);
IEnumerable<EntityUid> GetEntitiesInArc(EntityCoordinates coordinates, float range, Angle direction,
float arcWidth, LookupFlags flags = LookupFlags.IncludeAnchored);
IEnumerable<EntityUid> GetEntitiesIntersecting(GridId gridId, IEnumerable<Vector2i> gridIndices);
IEnumerable<EntityUid> GetEntitiesIntersecting(GridId gridId, Vector2i gridIndices);
IEnumerable<EntityUid> GetEntitiesIntersecting(TileRef tileRef);
IEnumerable<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, LookupFlags flags = LookupFlags.IncludeAnchored);
IEnumerable<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2Rotated worldAABB, LookupFlags flags = LookupFlags.IncludeAnchored);
IEnumerable<EntityUid> GetEntitiesIntersecting(EntityUid entity, float enlarged = 0f, LookupFlags flags = LookupFlags.IncludeAnchored);
IEnumerable<EntityUid> GetEntitiesIntersecting(MapCoordinates position, LookupFlags flags = LookupFlags.IncludeAnchored);
IEnumerable<EntityUid> GetEntitiesIntersecting(EntityCoordinates position, LookupFlags flags = LookupFlags.IncludeAnchored);
IEnumerable<EntityUid> GetEntitiesIntersecting(MapId mapId, Vector2 position, LookupFlags flags = LookupFlags.IncludeAnchored);
void FastEntitiesIntersecting(in MapId mapId, ref Box2 worldAABB, EntityUidQueryCallback callback, LookupFlags flags = LookupFlags.IncludeAnchored);
void FastEntitiesIntersecting(EntityLookupComponent lookup, ref Box2 localAABB, EntityUidQueryCallback callback);
IEnumerable<EntityUid> GetEntitiesInRange(EntityCoordinates position, float range, LookupFlags flags = LookupFlags.IncludeAnchored);
IEnumerable<EntityUid> GetEntitiesInRange(EntityUid entity, float range, LookupFlags flags = LookupFlags.IncludeAnchored);
IEnumerable<EntityUid> GetEntitiesInRange(MapId mapId, Vector2 point, float range, LookupFlags flags = LookupFlags.IncludeAnchored);
IEnumerable<EntityUid> GetEntitiesInRange(MapId mapId, Box2 box, float range, LookupFlags flags = LookupFlags.IncludeAnchored);
bool IsIntersecting(EntityUid entityOne, EntityUid entityTwo);
bool UpdateEntityTree(EntityUid entity, TransformComponent xform, Box2? worldAABB = null);
void RemoveFromEntityTrees(EntityUid entity);
Box2 GetWorldAabbFromEntity(in EntityUid ent, TransformComponent? xform = null);
Box2 GetLocalBounds(TileRef tileRef, ushort tileSize);
Box2 GetLocalBounds(Vector2i gridIndices, ushort tileSize);
Box2Rotated GetWorldBounds(TileRef tileRef, Matrix3? worldMatrix = null, Angle? angle = null);
}
[UsedImplicitly]
public sealed partial class EntityLookup : IEntityLookup, IEntityEventSubscriber
{
private readonly IEntityManager _entityManager;
private readonly IMapManager _mapManager;
private const int GrowthRate = 256;
private const float PointEnlargeRange = .00001f / 2;
// Using stacks so we always use latest data (given we only run it once per entity).
private readonly Stack<MoveEvent> _moveQueue = new();
private readonly Stack<RotateEvent> _rotateQueue = new();
private readonly Queue<EntParentChangedMessage> _parentChangeQueue = new();
/// <summary>
/// Like RenderTree we need to enlarge our lookup range for EntityLookupComponent as an entity is only ever on
/// 1 EntityLookupComponent at a time (hence it may overlap without another lookup).
/// </summary>
private float _lookupEnlargementRange;
/// <summary>
/// Move and rotate events generate the same update so no point duplicating work in the same tick.
/// </summary>
private readonly HashSet<EntityUid> _handledThisTick = new();
// TODO: Should combine all of the methods that check for IPhysBody and just use the one GetWorldAabbFromEntity method
// TODO: Combine GridTileLookupSystem and entity anchoring together someday.
// Queries are a bit of spaghet rn but ideally you'd just have:
// A) The fast tile-based one
// B) The physics-only one (given physics needs it to be fast af)
// C) A generic use one that covers anything not caught in the above.
public bool Started { get; private set; } = false;
public EntityLookup(IEntityManager entityManager, IMapManager mapManager)
{
_entityManager = entityManager;
_mapManager = mapManager;
}
public void Startup()
{
if (Started)
{
throw new InvalidOperationException("Startup() called multiple times.");
}
var configManager = IoCManager.Resolve<IConfigurationManager>();
configManager.OnValueChanged(CVars.LookupEnlargementRange, value => _lookupEnlargementRange = value, true);
var eventBus = _entityManager.EventBus;
eventBus.SubscribeEvent(EventSource.Local, this, (ref MoveEvent ev) => _moveQueue.Push(ev));
eventBus.SubscribeEvent(EventSource.Local, this, (ref RotateEvent ev) => _rotateQueue.Push(ev));
eventBus.SubscribeEvent(EventSource.Local, this, (ref EntParentChangedMessage ev) => _parentChangeQueue.Enqueue(ev));
eventBus.SubscribeEvent<AnchorStateChangedEvent>(EventSource.Local, this, HandleAnchored);
eventBus.SubscribeLocalEvent<EntityLookupComponent, ComponentAdd>(OnLookupAdd);
eventBus.SubscribeLocalEvent<EntityLookupComponent, ComponentShutdown>(OnLookupShutdown);
eventBus.SubscribeEvent<GridInitializeEvent>(EventSource.Local, this, OnGridInit);
_entityManager.EntityDeleted += OnEntityDeleted;
_entityManager.EntityInitialized += OnEntityInit;
_mapManager.MapCreated += OnMapCreated;
Started = true;
}
public void Shutdown()
{
// If we haven't even started up, there's nothing to clean up then.
if (!Started)
return;
_moveQueue.Clear();
_rotateQueue.Clear();
_handledThisTick.Clear();
_parentChangeQueue.Clear();
_entityManager.EntityDeleted -= OnEntityDeleted;
_entityManager.EntityInitialized -= OnEntityInit;
_mapManager.MapCreated -= OnMapCreated;
Started = false;
}
private void HandleAnchored(ref AnchorStateChangedEvent @event)
{
// 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 (@event.Anchored)
{
RemoveFromEntityTrees(@event.Entity);
}
else if (_entityManager.TryGetComponent(@event.Entity, out MetaDataComponent? meta) && meta.EntityLifeStage < EntityLifeStage.Terminating)
{
var xform = _entityManager.GetComponent<TransformComponent>(@event.Entity);
UpdateEntityTree(@event.Entity, xform);
}
// else -> the entity is terminating. We can ignore this un-anchor event, as this entity will be removed by the tree via OnEntityDeleted.
}
private void OnLookupShutdown(EntityUid uid, EntityLookupComponent component, ComponentShutdown args)
{
component.Tree.Clear();
}
private void OnGridInit(GridInitializeEvent ev)
{
_entityManager.EnsureComponent<EntityLookupComponent>(ev.EntityUid);
}
private void OnLookupAdd(EntityUid uid, EntityLookupComponent 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<EntityUid>(
GetRelativeAABBFromEntity,
capacity: capacity,
growthFunc: x => x == GrowthRate ? GrowthRate * 8 : x * 2
);
}
private Box2 GetRelativeAABBFromEntity(in EntityUid entity)
{
// TODO: Should feed in AABB to lookup so it's not enlarged unnecessarily
var aabb = GetWorldAABB(entity);
var tree = GetLookup(entity);
if (tree == null)
return aabb;
return _entityManager.GetComponent<TransformComponent>(tree.Owner).InvWorldMatrix.TransformBox(aabb);
}
private void OnEntityDeleted(object? sender, EntityUid uid)
{
RemoveFromEntityTrees(uid);
}
private void OnEntityInit(object? sender, EntityUid uid)
{
var xform = _entityManager.GetComponent<TransformComponent>(uid);
if (xform.Anchored) return;
UpdateEntityTree(uid, xform);
}
private void OnMapCreated(object? sender, MapEventArgs eventArgs)
{
if (eventArgs.Map == MapId.Nullspace) return;
_mapManager.GetMapEntityId(eventArgs.Map).EnsureComponent<EntityLookupComponent>();
}
public void Update()
{
// Acruid said he'd deal with Update being called around I_entityManager later.
// Could be more efficient but essentially nuke their old lookup and add to new lookup if applicable.
while (_parentChangeQueue.TryDequeue(out var mapChangeEvent))
{
_handledThisTick.Add(mapChangeEvent.Entity);
RemoveFromEntityTrees(mapChangeEvent.Entity);
if (_entityManager.Deleted(mapChangeEvent.Entity)) continue;
var xform = _entityManager.GetComponent<TransformComponent>(mapChangeEvent.Entity);
if (xform.Anchored) continue;
UpdateEntityTree(mapChangeEvent.Entity, xform, GetWorldAabbFromEntity(mapChangeEvent.Entity));
}
while (_moveQueue.TryPop(out var moveEvent))
{
if (!_handledThisTick.Add(moveEvent.Sender) ||
_entityManager.Deleted(moveEvent.Sender)) continue;
var xform = _entityManager.GetComponent<TransformComponent>(moveEvent.Sender);
if (xform.Anchored) continue;
DebugTools.Assert(!xform.Anchored);
UpdateEntityTree(moveEvent.Sender, xform, moveEvent.WorldAABB);
}
while (_rotateQueue.TryPop(out var rotateEvent))
{
if (!_handledThisTick.Add(rotateEvent.Sender) ||
_entityManager.Deleted(rotateEvent.Sender)) continue;
var xform = _entityManager.GetComponent<TransformComponent>(rotateEvent.Sender);
if (xform.Anchored) continue;
DebugTools.Assert(!xform.Anchored);
UpdateEntityTree(rotateEvent.Sender, xform, rotateEvent.WorldAABB);
}
_handledThisTick.Clear();
}
#region Spatial Queries
private LookupsEnumerator GetLookupsIntersecting(MapId mapId, Box2 worldAABB)
{
_mapManager.FindGridsIntersectingEnumerator(mapId, worldAABB, out var gridEnumerator, true);
return new LookupsEnumerator(_entityManager, _mapManager, mapId, gridEnumerator);
}
private struct LookupsEnumerator
{
private IEntityManager _entityManager;
private IMapManager _mapManager;
private MapId _mapId;
private FindGridsEnumerator _enumerator;
private bool _final;
public LookupsEnumerator(IEntityManager entityManager, IMapManager mapManager, MapId mapId, FindGridsEnumerator enumerator)
{
_entityManager = entityManager;
_mapManager = mapManager;
_mapId = mapId;
_enumerator = enumerator;
_final = false;
}
public bool MoveNext([NotNullWhen(true)] out EntityLookupComponent? component)
{
if (!_enumerator.MoveNext(out var grid))
{
if (_final || _mapId == MapId.Nullspace)
{
component = null;
return false;
}
_final = true;
EntityUid mapUid = _mapManager.GetMapEntityIdOrThrow(_mapId);
component = _entityManager.GetComponent<EntityLookupComponent>(mapUid);
return true;
}
// TODO: Recursive and all that.
component = _entityManager.GetComponent<EntityLookupComponent>(grid.GridEntityId);
return true;
}
}
private IEnumerable<EntityUid> GetAnchored(MapId mapId, Box2 worldAABB, LookupFlags flags)
{
if ((flags & LookupFlags.IncludeAnchored) == 0x0) yield break;
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
foreach (var uid in grid.GetAnchoredEntities(worldAABB))
{
if (!_entityManager.EntityExists(uid)) continue;
yield return uid;
}
}
}
private IEnumerable<EntityUid> GetAnchored(MapId mapId, Box2Rotated worldBounds, LookupFlags flags)
{
if ((flags & LookupFlags.IncludeAnchored) == 0x0) yield break;
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldBounds))
{
foreach (var uid in grid.GetAnchoredEntities(worldBounds))
{
if (!_entityManager.EntityExists(uid)) continue;
yield return uid;
}
}
}
/// <inheritdoc />
public bool AnyEntitiesIntersecting(MapId mapId, Box2 box, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var found = false;
var enumerator = GetLookupsIntersecting(mapId, box);
while (enumerator.MoveNext(out var lookup))
{
var offsetBox = _entityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.TransformBox(box);
lookup.Tree.QueryAabb(ref found, (ref bool found, in EntityUid ent) =>
{
if (_entityManager.Deleted(ent))
return true;
found = true;
return false;
}, offsetBox, (flags & LookupFlags.Approximate) != 0x0);
}
if (!found)
{
foreach (var _ in GetAnchored(mapId, box, flags))
{
return true;
}
}
return found;
}
/// <inheritdoc />
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public void FastEntitiesIntersecting(in MapId mapId, ref Box2 worldAABB, EntityUidQueryCallback callback, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var enumerator = GetLookupsIntersecting(mapId, worldAABB);
while (enumerator.MoveNext(out var lookup))
{
var offsetBox = _entityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.TransformBox(worldAABB);
lookup.Tree._b2Tree.FastQuery(ref offsetBox, (ref EntityUid data) => callback(data));
}
if ((flags & LookupFlags.IncludeAnchored) != 0x0)
{
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, worldAABB))
{
foreach (var uid in grid.GetAnchoredEntities(worldAABB))
{
if (!_entityManager.EntityExists(uid)) continue;
callback(uid);
}
}
}
}
/// <inheritdoc />
public void FastEntitiesIntersecting(EntityLookupComponent lookup, ref Box2 localAABB, EntityUidQueryCallback callback)
{
lookup.Tree._b2Tree.FastQuery(ref localAABB, (ref EntityUid data) => callback(data));
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, LookupFlags flags = LookupFlags.IncludeAnchored)
{
if (mapId == MapId.Nullspace) return Enumerable.Empty<EntityUid>();
var list = new List<EntityUid>();
var enumerator = GetLookupsIntersecting(mapId, worldAABB);
while (enumerator.MoveNext(out var lookup))
{
var offsetBox = _entityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.TransformBox(worldAABB);
lookup.Tree.QueryAabb(ref list, (ref List<EntityUid> list, in EntityUid ent) =>
{
if (!_entityManager.Deleted(ent))
{
list.Add(ent);
}
return true;
}, offsetBox, (flags & LookupFlags.Approximate) != 0x0);
}
foreach (var ent in GetAnchored(mapId, worldAABB, flags))
{
list.Add(ent);
}
return list;
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesIntersecting(MapId mapId, Box2Rotated worldBounds, LookupFlags flags = LookupFlags.IncludeAnchored)
{
if (mapId == MapId.Nullspace) return Enumerable.Empty<EntityUid>();
var list = new List<EntityUid>();
var worldAABB = worldBounds.CalcBoundingBox();
var enumerator = GetLookupsIntersecting(mapId, worldAABB);
while (enumerator.MoveNext(out var lookup))
{
var offsetBox = _entityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.TransformBox(worldBounds);
lookup.Tree.QueryAabb(ref list, (ref List<EntityUid> list, in EntityUid ent) =>
{
if (!_entityManager.Deleted(ent))
{
list.Add(ent);
}
return true;
}, offsetBox, (flags & LookupFlags.Approximate) != 0x0);
}
foreach (var ent in GetAnchored(mapId, worldBounds, flags))
{
list.Add(ent);
}
return list;
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesIntersecting(MapId mapId, Vector2 position, LookupFlags flags = LookupFlags.IncludeAnchored)
{
if (mapId == MapId.Nullspace) return Enumerable.Empty<EntityUid>();
var aabb = new Box2(position, position).Enlarged(PointEnlargeRange);
var list = new List<EntityUid>();
var state = (list, position);
var enumerator = GetLookupsIntersecting(mapId, aabb);
while (enumerator.MoveNext(out var lookup))
{
var localPoint = _entityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.Transform(position);
lookup.Tree.QueryPoint(ref state, (ref (List<EntityUid> list, Vector2 position) state, in EntityUid ent) =>
{
if (Intersecting(ent, state.position))
{
state.list.Add(ent);
}
return true;
}, localPoint, (flags & LookupFlags.Approximate) != 0x0);
}
if ((flags & LookupFlags.IncludeAnchored) != 0x0 &&
_mapManager.TryFindGridAt(mapId, position, out var grid) &&
grid.TryGetTileRef(position, out var tile))
{
foreach (var uid in grid.GetAnchoredEntities(tile.GridIndices))
{
if (!_entityManager.EntityExists(uid)) continue;
state.list.Add(uid);
}
}
return list;
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesIntersecting(MapCoordinates position, LookupFlags flags = LookupFlags.IncludeAnchored)
{
return GetEntitiesIntersecting(position.MapId, position.Position, flags);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesIntersecting(EntityCoordinates position, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var mapPos = position.ToMap(_entityManager);
return GetEntitiesIntersecting(mapPos.MapId, mapPos.Position, flags);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesIntersecting(EntityUid entity, float enlarged = 0f, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var worldAABB = GetWorldAabbFromEntity(entity);
var xform = _entityManager.GetComponent<TransformComponent>(entity);
var (worldPos, worldRot) = xform.GetWorldPositionRotation();
var enumerator = GetLookupsIntersecting(xform.MapID, worldAABB);
var list = new List<EntityUid>();
while (enumerator.MoveNext(out var lookup))
{
// To get the tightest bounds possible we'll re-calculate it for each lookup.
var localBounds = GetLookupBounds(entity, lookup, worldPos, worldRot, enlarged);
lookup.Tree.QueryAabb(ref list, (ref List<EntityUid> list, in EntityUid ent) =>
{
if (!_entityManager.Deleted(ent))
{
list.Add(ent);
}
return true;
}, localBounds, (flags & LookupFlags.Approximate) != 0x0);
}
foreach (var ent in GetAnchored(xform.MapID, worldAABB, flags))
{
list.Add(ent);
}
return list;
}
private Box2 GetLookupBounds(EntityUid uid, EntityLookupComponent lookup, Vector2 worldPos, Angle worldRot, float enlarged)
{
var (_, lookupRot, lookupInvWorldMatrix) = _entityManager.GetComponent<TransformComponent>(lookup.Owner).GetWorldPositionRotationInvMatrix();
var localPos = lookupInvWorldMatrix.Transform(worldPos);
var localRot = worldRot - lookupRot;
if (_entityManager.TryGetComponent(uid, out FixturesComponent? manager))
{
var transform = new Transform(localPos, localRot);
Box2? aabb = null;
foreach (var (_, fixture) in manager.Fixtures)
{
if (!fixture.Hard) continue;
for (var i = 0; i < fixture.Shape.ChildCount; i++)
{
aabb = aabb?.Union(fixture.Shape.ComputeAABB(transform, i)) ?? fixture.Shape.ComputeAABB(transform, i);
}
}
if (aabb != null)
{
return aabb.Value.Enlarged(enlarged);
}
}
// So IsEmpty checks don't get triggered
return new Box2(localPos - float.Epsilon, localPos + float.Epsilon);
}
/// <inheritdoc />
public bool IsIntersecting(EntityUid entityOne, EntityUid entityTwo)
{
var position = _entityManager.GetComponent<TransformComponent>(entityOne).MapPosition.Position;
return Intersecting(entityTwo, position);
}
private bool Intersecting(EntityUid entity, Vector2 mapPosition)
{
if (_entityManager.TryGetComponent(entity, out IPhysBody? component))
{
if (component.GetWorldAABB().Contains(mapPosition))
return true;
}
else
{
var transform = _entityManager.GetComponent<TransformComponent>(entity);
var entPos = transform.WorldPosition;
if (MathHelper.CloseToPercent(entPos.X, mapPosition.X)
&& MathHelper.CloseToPercent(entPos.Y, mapPosition.Y))
{
return true;
}
}
return false;
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesInRange(EntityCoordinates position, float range, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var mapCoordinates = position.ToMap(_entityManager);
var mapPosition = mapCoordinates.Position;
var aabb = new Box2(mapPosition - new Vector2(range, range),
mapPosition + new Vector2(range, range));
return GetEntitiesIntersecting(mapCoordinates.MapId, aabb, flags);
// TODO: Use a circle shape here mate
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesInRange(MapId mapId, Box2 box, float range, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var aabb = box.Enlarged(range);
return GetEntitiesIntersecting(mapId, aabb, flags);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesInRange(MapId mapId, Vector2 point, float range, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var aabb = new Box2(point, point).Enlarged(range);
return GetEntitiesIntersecting(mapId, aabb, flags);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesInRange(EntityUid entity, float range, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var worldAABB = GetWorldAabbFromEntity(entity);
return GetEntitiesInRange(_entityManager.GetComponent<TransformComponent>(entity).MapID, worldAABB, range, flags);
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesInArc(EntityCoordinates coordinates, float range, Angle direction,
float arcWidth, LookupFlags flags = LookupFlags.IncludeAnchored)
{
var position = coordinates.ToMap(_entityManager).Position;
foreach (var entity in GetEntitiesInRange(coordinates, range * 2, flags))
{
var angle = new Angle(_entityManager.GetComponent<TransformComponent>(entity).WorldPosition - position);
if (angle.Degrees < direction.Degrees + arcWidth / 2 &&
angle.Degrees > direction.Degrees - arcWidth / 2)
yield return entity;
}
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesInMap(MapId mapId, LookupFlags flags = LookupFlags.IncludeAnchored)
{
DebugTools.Assert((flags & LookupFlags.Approximate) == 0x0);
foreach (EntityLookupComponent comp in _entityManager.EntityQuery<EntityLookupComponent>(true))
{
if (_entityManager.GetComponent<TransformComponent>(comp.Owner).MapID != mapId) continue;
foreach (var entity in comp.Tree)
{
if (_entityManager.Deleted(entity)) continue;
yield return entity;
}
}
if ((flags & LookupFlags.IncludeAnchored) == 0x0) yield break;
foreach (var grid in _mapManager.GetAllMapGrids(mapId))
{
foreach (var tile in grid.GetAllTiles())
{
foreach (var uid in grid.GetAnchoredEntities(tile.GridIndices))
{
if (!_entityManager.EntityExists(uid)) continue;
yield return uid;
}
}
}
}
/// <inheritdoc />
public IEnumerable<EntityUid> GetEntitiesAt(MapId mapId, Vector2 position, LookupFlags flags = LookupFlags.IncludeAnchored)
{
if (mapId == MapId.Nullspace) return Enumerable.Empty<EntityUid>();
var list = new List<EntityUid>();
var state = (list, position);
var aabb = new Box2(position, position).Enlarged(PointEnlargeRange);
var enumerator = GetLookupsIntersecting(mapId, aabb);
while (enumerator.MoveNext(out var lookup))
{
var offsetPos = _entityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.Transform(position);
lookup.Tree.QueryPoint(ref state, (ref (List<EntityUid> list, Vector2 position) state, in EntityUid ent) =>
{
state.list.Add(ent);
return true;
}, offsetPos, (flags & LookupFlags.Approximate) != 0x0);
}
if ((flags & LookupFlags.IncludeAnchored) != 0x0)
{
foreach (var grid in _mapManager.FindGridsIntersecting(mapId, aabb))
{
foreach (var uid in grid.GetAnchoredEntities(aabb))
{
if (!_entityManager.EntityExists(uid)) continue;
list.Add(uid);
}
}
}
return list;
}
#endregion
#region Entity DynamicTree
private EntityLookupComponent? GetLookup(EntityUid entity)
{
// TODO: This should just be passed in when we cleanup EntityLookup a bit.
var xforms = _entityManager.GetEntityQuery<TransformComponent>();
var xform = xforms.GetComponent(entity);
if (xform.MapID == MapId.Nullspace)
return null;
var lookups = _entityManager.GetEntityQuery<EntityLookupComponent>();
var parent = xform.ParentUid;
// if it's map return null. Grids should return the map's broadphase.
if (lookups.HasComponent(entity) &&
!parent.IsValid())
{
return null;
}
while (parent.IsValid())
{
if (lookups.TryGetComponent(parent, out var comp)) return comp;
parent = xforms.GetComponent(parent).ParentUid;
}
return null;
}
/// <inheritdoc />
public bool UpdateEntityTree(EntityUid entity, TransformComponent xform, Box2? worldAABB = null)
{
DebugTools.Assert(!_entityManager.Deleted(entity));
var lookup = GetLookup(entity);
if (lookup == null)
{
RemoveFromEntityTrees(entity);
return true;
}
// Temp PVS guard for when we clear dynamictree for now.
worldAABB ??= GetWorldAabbFromEntity(entity, xform);
var center = worldAABB.Value.Center;
DebugTools.Assert(!float.IsNaN(center.X) && !float.IsNaN(center.Y));
var aabb = _entityManager.GetComponent<TransformComponent>(lookup.Owner).InvWorldMatrix.TransformBox(worldAABB.Value);
// for debugging
var necessary = 0;
if (lookup.Tree.AddOrUpdate(entity, aabb))
{
++necessary;
}
if (!_entityManager.HasComponent<EntityLookupComponent>(entity))
{
DebugTools.Assert(!_entityManager.HasComponent<IMapGridComponent>(entity));
var children = xform.ChildEnumerator;
while (children.MoveNext(out var child))
{
if (!_handledThisTick.Add(child.Value)) continue;
var childXform = _entityManager.GetComponent<TransformComponent>(child.Value);
if (UpdateEntityTree(child.Value, childXform))
{
++necessary;
}
}
}
return necessary > 0;
}
/// <inheritdoc />
public void RemoveFromEntityTrees(EntityUid entity)
{
// TODO: Need to fix ordering issues and then we can just directly remove it from the tree
// rather than this O(n) legacy garbage.
// Also we can't early returns because somehow it gets added to multiple trees!!!
foreach (var lookup in _entityManager.EntityQuery<EntityLookupComponent>(true))
{
lookup.Tree.Remove(entity);
}
}
public Box2 GetWorldAabbFromEntity(in EntityUid ent, TransformComponent? xform = null)
{
return GetWorldAABB(ent, xform);
}
private Box2 GetWorldAABB(in EntityUid ent, TransformComponent? xform = null)
{
Vector2 pos;
xform ??= _entityManager.GetComponent<TransformComponent>(ent);
if ((!_entityManager.EntityExists(ent) ? EntityLifeStage.Deleted : _entityManager.GetComponent<MetaDataComponent>(ent).EntityLifeStage) >= EntityLifeStage.Deleted)
{
pos = xform.WorldPosition;
return new Box2(pos, pos);
}
// MOCKS WHY
if (ent.TryGetContainer(out var container, _entityManager))
{
return GetWorldAABB(container.Owner);
}
pos = xform.WorldPosition;
return _entityManager.TryGetComponent(ent, out ILookupWorldBox2Component? lookup) ?
lookup.GetWorldAABB(pos) :
new Box2(pos, pos);
}
#endregion
}
}
@@ -0,0 +1,463 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using JetBrains.Annotations;
using Robust.Shared.Configuration;
using Robust.Shared.Containers;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Utility;
namespace Robust.Shared.GameObjects
{
[Flags]
public enum LookupFlags : byte
{
None = 0,
Approximate = 1 << 0,
IncludeAnchored = 1 << 1,
// IncludeGrids = 1 << 2,
}
public sealed partial class EntityLookupSystem : EntitySystem
{
[IoC.Dependency] private readonly IMapManager _mapManager = default!;
[IoC.Dependency] private readonly SharedContainerSystem _container = default!;
[IoC.Dependency] private readonly SharedTransformSystem _transform = default!;
private const int GrowthRate = 256;
private const float PointEnlargeRange = .00001f / 2;
/// <summary>
/// Like RenderTree we need to enlarge our lookup range for EntityLookupComponent as an entity is only ever on
/// 1 EntityLookupComponent at a time (hence it may overlap without another lookup).
/// </summary>
private float _lookupEnlargementRange;
public override void Initialize()
{
base.Initialize();
var configManager = IoCManager.Resolve<IConfigurationManager>();
configManager.OnValueChanged(CVars.LookupEnlargementRange, value => _lookupEnlargementRange = value, true);
SubscribeLocalEvent<MoveEvent>(OnMove);
SubscribeLocalEvent<RotateEvent>(OnRotate);
SubscribeLocalEvent<EntParentChangedMessage>(OnParentChange);
SubscribeLocalEvent<AnchorStateChangedEvent>(OnAnchored);
SubscribeLocalEvent<UpdateLookupBoundsEvent>(OnBoundsUpdate);
SubscribeLocalEvent<EntityLookupComponent, ComponentAdd>(OnLookupAdd);
SubscribeLocalEvent<EntityLookupComponent, ComponentShutdown>(OnLookupShutdown);
SubscribeLocalEvent<GridInitializeEvent>(OnGridInit);
SubscribeLocalEvent<EntityTerminatingEvent>(OnTerminate);
EntityManager.EntityInitialized += OnEntityInit;
_mapManager.MapCreated += OnMapCreated;
}
public override void Shutdown()
{
base.Shutdown();
EntityManager.EntityInitialized -= OnEntityInit;
_mapManager.MapCreated -= OnMapCreated;
}
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 (EntityManager.TryGetComponent(args.Entity, out MetaDataComponent? meta) && meta.EntityLifeStage < EntityLifeStage.Terminating)
{
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var xform = xformQuery.GetComponent(args.Entity);
var lookup = GetLookup(args.Entity, xform, xformQuery);
if (lookup == null)
throw new InvalidOperationException();
var lookupXform = xformQuery.GetComponent(lookup.Owner);
var coordinates = _transform.GetMoverCoordinates(xform.Coordinates, 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) - _transform.GetWorldRotation(lookupXform), xform, xformQuery);
AddToEntityTree(lookup, xform, aabb, xformQuery);
}
// 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)
{
component.Tree.Clear();
}
private void OnGridInit(GridInitializeEvent ev)
{
EntityManager.EnsureComponent<EntityLookupComponent>(ev.EntityUid);
}
private void OnLookupAdd(EntityUid uid, EntityLookupComponent 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<EntityUid>(
GetTreeAABB,
capacity: capacity,
growthFunc: x => x == GrowthRate ? GrowthRate * 8 : x * 2
);
}
private void OnMapCreated(object? sender, MapEventArgs eventArgs)
{
if (eventArgs.Map == MapId.Nullspace) return;
EntityManager.EnsureComponent<EntityLookupComponent>(_mapManager.GetMapEntityId(eventArgs.Map));
}
private Box2 GetTreeAABB(in EntityUid entity)
{
// TODO: Should feed in AABB to lookup so it's not enlarged unnecessarily
var aabb = GetWorldAABB(entity);
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var tree = GetLookup(entity, xformQuery);
if (tree == null)
return aabb;
return xformQuery.GetComponent(tree.Owner).InvWorldMatrix.TransformBox(aabb);
}
#endregion
#region Entity events
private void OnTerminate(ref EntityTerminatingEvent args)
{
RemoveFromEntityTree(args.Owner, false);
}
private void OnEntityInit(object? sender, EntityUid uid)
{
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
if (!xformQuery.TryGetComponent(uid, out var xform) ||
xform.Anchored ||
_mapManager.IsMap(uid) ||
_mapManager.IsGrid(uid)) return;
var lookup = GetLookup(uid, xform, xformQuery);
// If nullspace or the likes.
if (lookup == null) return;
var lookupXform = xformQuery.GetComponent(lookup.Owner);
var coordinates = _transform.GetMoverCoordinates(xform.Coordinates, xformQuery);
DebugTools.Assert(coordinates.EntityId == lookup.Owner);
// If we're contained then LocalRotation should be 0 anyway.
var aabb = GetAABB(uid, coordinates.Position, _transform.GetWorldRotation(xform) - _transform.GetWorldRotation(lookupXform), xform, xformQuery);
// Any child entities should be handled by their own OnEntityInit
AddToEntityTree(lookup, xform, aabb, xformQuery, false);
}
private void OnMove(ref MoveEvent args)
{
UpdatePosition(args.Sender, args.Component);
}
private void OnRotate(ref RotateEvent args)
{
UpdatePosition(args.Sender, args.Component);
}
private void UpdatePosition(EntityUid uid, TransformComponent xform)
{
// Even if the entity is contained it may have children that aren't so we still need to update.
if (!CanMoveUpdate(uid, xform)) return;
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var lookup = GetLookup(uid, xform, xformQuery);
if (lookup == null) return;
var lookupXform = xformQuery.GetComponent(lookup.Owner);
var coordinates = _transform.GetMoverCoordinates(xform.Coordinates, xformQuery);
var aabb = GetAABB(uid, coordinates.Position, _transform.GetWorldRotation(xform) - _transform.GetWorldRotation(lookupXform), xformQuery.GetComponent(uid), xformQuery);
AddToEntityTree(lookup, xform, aabb, xformQuery);
}
private bool CanMoveUpdate(EntityUid uid, TransformComponent xform)
{
return !_mapManager.IsMap(uid) &&
!_mapManager.IsGrid(uid) &&
!_container.IsEntityInContainer(uid, xform);
}
private void OnParentChange(ref EntParentChangedMessage args)
{
if (_mapManager.IsMap(args.Entity) ||
_mapManager.IsGrid(args.Entity) ||
EntityManager.GetComponent<MetaDataComponent>(args.Entity).EntityLifeStage < EntityLifeStage.Initialized) return;
EntityLookupComponent? oldLookup = null;
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var xform = xformQuery.GetComponent(args.Entity);
if (args.OldParent != null)
{
oldLookup = GetLookup(args.OldParent.Value, xformQuery);
}
var newLookup = GetLookup(args.Entity, xform, xformQuery);
// If parent is the same then no need to do anything as position should stay the same.
if (oldLookup == newLookup) return;
RemoveFromEntityTree(oldLookup, xform, xformQuery);
if (newLookup != null)
AddToEntityTree(newLookup, xform, xformQuery);
}
private void OnBoundsUpdate(ref UpdateLookupBoundsEvent ev)
{
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var xform = xformQuery.GetComponent(ev.Uid);
if (xform.Anchored || _container.IsEntityInContainer(ev.Uid, xform)) return;
var lookup = GetLookup(ev.Uid, xform, xformQuery);
if (lookup == null) return;
var lookupXform = xformQuery.GetComponent(lookup.Owner);
var coordinates = _transform.GetMoverCoordinates(xform.Coordinates, xformQuery);
// If we're contained then LocalRotation should be 0 anyway.
var aabb = GetAABB(xform.Owner, coordinates.Position, _transform.GetWorldRotation(xform) - _transform.GetWorldRotation(lookupXform), xform, xformQuery);
// TODO: Only container children need updating so could manually do this slightly better.
AddToEntityTree(lookup, xform, aabb, xformQuery);
}
private void AddToEntityTree(
EntityLookupComponent lookup,
TransformComponent xform,
EntityQuery<TransformComponent> xformQuery,
bool recursive = true,
bool contained = false)
{
var lookupXform = xformQuery.GetComponent(lookup.Owner);
var coordinates = _transform.GetMoverCoordinates(xform.Coordinates, xformQuery);
// If we're contained then LocalRotation should be 0 anyway.
var aabb = GetAABB(xform.Owner, coordinates.Position, _transform.GetWorldRotation(xform) - _transform.GetWorldRotation(lookupXform), xform, xformQuery);
AddToEntityTree(lookup, xform, aabb, xformQuery, recursive, contained);
}
private void AddToEntityTree(
EntityLookupComponent? lookup,
TransformComponent xform,
Box2 aabb,
EntityQuery<TransformComponent> xformQuery,
bool recursive = true,
bool contained = false)
{
// 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);
var childEnumerator = xform.ChildEnumerator;
if (xform.ChildCount == 0 || !recursive) return;
// TODO: Pass this down instead son.
var lookupXform = xformQuery.GetComponent(lookup.Owner);
// TODO: Just don't store contained stuff, it's way too expensive for updates and makes the tree much bigger.
// Recursively update children.
if (contained)
{
// Just re-use the topmost AABB.
while (childEnumerator.MoveNext(out var child))
{
AddToEntityTree(lookup, xformQuery.GetComponent(child.Value), aabb, xformQuery, contained: true);
}
}
// If they're in a container then it just uses the parent's AABB.
else if (EntityManager.TryGetComponent<ContainerManagerComponent>(xform.Owner, out var conManager))
{
while (childEnumerator.MoveNext(out var child))
{
if (conManager.ContainsEntity(child.Value))
{
AddToEntityTree(lookup, xformQuery.GetComponent(child.Value), aabb, xformQuery, contained: true);
}
else
{
var coordinates = _transform.GetMoverCoordinates(xform.Coordinates, xformQuery);
var childXform = xformQuery.GetComponent(child.Value);
// TODO: If we have 0 position and not contained can optimise these further, but future problem.
var childAABB = GetAABBNoContainer(child.Value, coordinates.Position, childXform.WorldRotation - lookupXform.WorldRotation);
AddToEntityTree(lookup, childXform, childAABB, xformQuery);
}
}
}
else
{
while (childEnumerator.MoveNext(out var child))
{
var coordinates = _transform.GetMoverCoordinates(xform.Coordinates, xformQuery);
var childXform = xformQuery.GetComponent(child.Value);
// TODO: If we have 0 position and not contained can optimise these further, but future problem.
var childAABB = GetAABBNoContainer(child.Value, coordinates.Position, childXform.WorldRotation - lookupXform.WorldRotation);
AddToEntityTree(lookup, childXform, childAABB, xformQuery);
}
}
}
private void RemoveFromEntityTree(EntityUid uid, bool recursive = true)
{
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
var xform = xformQuery.GetComponent(uid);
var lookup = GetLookup(uid, xform, xformQuery);
RemoveFromEntityTree(lookup, xform, xformQuery, recursive);
}
/// <summary>
/// Recursively iterates through this entity's children and removes them from the entitylookupcomponent.
/// </summary>
private void RemoveFromEntityTree(EntityLookupComponent? lookup, TransformComponent xform, EntityQuery<TransformComponent> xformQuery, bool recursive = true)
{
// TODO: Move this out of the loop
if (lookup == null) return;
lookup.Tree.Remove(xform.Owner);
if (!recursive) return;
var childEnumerator = xform.ChildEnumerator;
while (childEnumerator.MoveNext(out var child))
{
RemoveFromEntityTree(lookup, xformQuery.GetComponent(child.Value), xformQuery);
}
}
#endregion
private EntityLookupComponent? GetLookup(EntityUid entity, EntityQuery<TransformComponent> xformQuery)
{
var xform = xformQuery.GetComponent(entity);
return GetLookup(entity, xform, xformQuery);
}
private EntityLookupComponent? GetLookup(EntityUid uid, TransformComponent xform, EntityQuery<TransformComponent> xformQuery)
{
if (xform.MapID == MapId.Nullspace)
return null;
var parent = xform.ParentUid;
var lookupQuery = EntityManager.GetEntityQuery<EntityLookupComponent>();
// If we're querying a map / grid just return it directly.
if (lookupQuery.TryGetComponent(uid, out var lookup))
{
return lookup;
}
while (parent.IsValid())
{
if (lookupQuery.TryGetComponent(parent, out var comp)) return comp;
parent = xformQuery.GetComponent(parent).ParentUid;
}
return null;
}
#region Bounds
/// <summary>
/// Get the AABB of an entity with the supplied position and angle. Tries to consider if the entity is in a container.
/// </summary>
private Box2 GetAABB(EntityUid uid, Vector2 position, Angle angle, TransformComponent xform, EntityQuery<TransformComponent> xformQuery)
{
// If we're in a container then we just use the container's bounds.
if (_container.TryGetOuterContainer(uid, xform, out var container, xformQuery))
{
return GetAABBNoContainer(container.Owner, position, angle);
}
return GetAABBNoContainer(uid, position, angle);
}
/// <summary>
/// Get the AABB of an entity with the supplied position and angle without considering containers.
/// </summary>
private Box2 GetAABBNoContainer(EntityUid uid, Vector2 position, Angle angle)
{
// DebugTools.Assert(!_container.IsEntityInContainer(uid, xform));
Box2 localAABB;
var transform = new Transform(position, angle);
if (EntityManager.TryGetComponent<ILookupWorldBox2Component>(uid, out var worldLookup))
{
localAABB = worldLookup.GetAABB(transform);
}
else
{
localAABB = new Box2Rotated(new Box2(transform.Position, transform.Position), transform.Quaternion2D.Angle, transform.Position).CalcBoundingBox();
}
return localAABB;
}
public Box2 GetWorldAABB(EntityUid uid, TransformComponent? xform = null)
{
var xformQuery = EntityManager.GetEntityQuery<TransformComponent>();
xform ??= xformQuery.GetComponent(uid);
var (worldPos, worldRot) = xform.GetWorldPositionRotation();
return GetAABB(uid, worldPos, worldRot, xform, xformQuery);
}
#endregion
}
}
/// <summary>
/// Flags this entity for an update to their lookup bounds.
/// </summary>
[ByRefEvent]
public readonly struct UpdateLookupBoundsEvent
{
public readonly EntityUid Uid;
public UpdateLookupBoundsEvent(EntityUid uid)
{
Uid = uid;
}
}
@@ -15,7 +15,6 @@ namespace Robust.Shared.GameObjects
{
public abstract class SharedGridFixtureSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly FixtureSystem _fixtures = default!;
private bool _enabled;
@@ -11,7 +11,7 @@ public abstract partial class SharedTransformSystem
[Pure]
public Matrix3 GetWorldMatrix(EntityUid uid)
{
return Comp<TransformComponent>(uid).WorldMatrix;
return Transform(uid).WorldMatrix;
}
// Temporary until it's moved here
@@ -31,6 +31,31 @@ public abstract partial class SharedTransformSystem
#endregion
#region World Rotation
[Pure]
public Angle GetWorldRotation(EntityUid uid)
{
return Transform(uid).WorldRotation;
}
// Temporary until it's moved here
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Angle GetWorldRotation(TransformComponent component)
{
return component.WorldRotation;
}
[Pure]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Angle GetWorldRotation(EntityUid uid, EntityQuery<TransformComponent> xformQuery)
{
return GetWorldRotation(xformQuery.GetComponent(uid));
}
#endregion
#region Inverse World Matrix
[Pure]
@@ -10,7 +10,7 @@ namespace Robust.Shared.GameObjects
public abstract partial class SharedTransformSystem : EntitySystem
{
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IEntityLookup _entityLookup = default!;
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
private readonly Queue<MoveEvent> _gridMoves = new();
private readonly Queue<MoveEvent> _otherMoves = new();
@@ -124,5 +124,41 @@ namespace Robust.Shared.GameObjects
var gridPos = Transform(grid.GridEntityId).InvWorldMatrix.Transform(xform.WorldPosition);
return new EntityCoordinates(grid.GridEntityId, gridPos);
}
public EntityCoordinates GetMoverCoordinates(EntityCoordinates coordinates, EntityQuery<TransformComponent> xformQuery)
{
// GridID isn't ready during EntityInit so YAY
IMapGrid? grid = null;
var ent = coordinates.EntityId;
while (ent.IsValid())
{
if (_mapManager.TryGetGrid(ent, out grid))
break;
ent = xformQuery.GetComponent(ent).ParentUid;
}
// If they're parented directly to the map or grid then just return the coordinates.
if (grid == null)
{
var mapPos = coordinates.ToMap(EntityManager);
var mapUid = _mapManager.GetMapEntityId(mapPos.MapId);
// Parented directly to the map.
if (coordinates.EntityId == mapUid)
return coordinates;
return new EntityCoordinates(mapUid, mapPos.Position);
}
// Parented directly to the grid
if (grid.GridEntityId == coordinates.EntityId)
return coordinates;
// Parented to grid so convert their pos back to the grid.
var gridPos = Transform(grid.GridEntityId).InvWorldMatrix.Transform(coordinates.ToMapPos(EntityManager));
return new EntityCoordinates(grid.GridEntityId, gridPos);
}
}
}
@@ -478,7 +478,7 @@ stored in a single array since multiple arrays lead to multiple misses.
}
}
internal void UpdateBodies(List<(TransformComponent Transform, PhysicsComponent Body)> deferredUpdates)
internal void UpdateBodies(HashSet<TransformComponent> deferredUpdates)
{
foreach (var (joint, error) in _brokenJoints)
{
@@ -530,7 +530,7 @@ stored in a single array since multiple arrays lead to multiple misses.
// changes then this is immediately invalidated.
if (transform.UpdatesDeferred)
{
deferredUpdates.Add((transform, body));
deferredUpdates.Add(transform);
}
}
@@ -71,7 +71,7 @@ namespace Robust.Shared.Physics.Dynamics
// TODO: Given physics bodies are a common thing to be listening for on moveevents it's probably beneficial to have 2 versions; one that includes the entity
// and one that includes the body
private List<(TransformComponent Transform, PhysicsComponent Body)> _deferredUpdates = new();
private HashSet<TransformComponent> _deferredUpdates = new();
/// <summary>
/// All bodies present on this map.
@@ -258,14 +258,10 @@ namespace Robust.Shared.Physics.Dynamics
/// </summary>
public void ProcessQueue()
{
var xforms = _entityManager.GetEntityQuery<TransformComponent>();
var fixtures = _entityManager.GetEntityQuery<FixturesComponent>();
// We'll store the WorldAABB on the MoveEvent given a lot of stuff ends up re-calculating it.
foreach (var (transform, physics) in _deferredUpdates)
foreach (var xform in _deferredUpdates)
{
var worldAABB = _physics.GetWorldAABB(physics, transform, xforms, fixtures);
transform.RunDeferred(worldAABB);
xform.RunDeferred();
}
_deferredUpdates.Clear();
+1 -5
View File
@@ -6,7 +6,6 @@ using NUnit.Framework;
using Robust.Server.Containers;
using Robust.Server.GameObjects;
using Robust.Server.Physics;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.Containers;
using Robust.Shared.ContentPack;
@@ -18,7 +17,6 @@ using Robust.Shared.Physics.Dynamics;
using Robust.Shared.Prototypes;
using Robust.Shared.Reflection;
using Robust.Shared.Utility;
using GridFixtureSystem = Robust.Client.GameObjects.GridFixtureSystem;
namespace Robust.UnitTesting
{
@@ -80,6 +78,7 @@ namespace Robust.UnitTesting
// Required systems
systems.LoadExtraSystemType<ContainerSystem>();
systems.LoadExtraSystemType<TransformSystem>();
systems.LoadExtraSystemType<EntityLookupSystem>();
var entMan = IoCManager.Resolve<IEntityManager>();
var mapMan = IoCManager.Resolve<IMapManager>();
@@ -90,9 +89,6 @@ namespace Robust.UnitTesting
mapMan.Initialize();
systems.Initialize();
// TODO: Make this a system and it should be covered off by the above.
IoCManager.Resolve<IEntityLookup>().Startup();
IoCManager.Resolve<IReflectionManager>().LoadAssemblies(assemblies);
var modLoader = IoCManager.Resolve<TestingModLoader>();
@@ -2,6 +2,7 @@ using System.IO;
using System.Reflection;
using Moq;
using NUnit.Framework;
using Robust.Server.Containers;
using Robust.Server.GameObjects;
using Robust.Server.Physics;
using Robust.Shared.GameObjects;
@@ -473,28 +474,10 @@ namespace Robust.UnitTesting.Server.GameObjects.Components
Assert.That(node3Trans.WorldPosition, new ApproxEqualityConstraint(new Vector2(15, 15)));
}
[Test]
public void TestMapIdInitOrder()
{
// Tests that if a child initializes before its parent, MapID still gets initialized correctly.
// Set private _parent field via reflection here.
// This basically simulates the field getting set in ExposeData(), with way less test boilerplate.
var field = typeof(TransformComponent).GetField("_parent", BindingFlags.NonPublic | BindingFlags.Instance)!;
var parent = EntityManager.CreateEntityUninitialized("mapDummy");
var child1 = EntityManager.CreateEntityUninitialized("dummy");
var child2 = EntityManager.CreateEntityUninitialized("dummy");
field.SetValue(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(child1), parent);
field.SetValue(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(child2), child1);
EntityManager.FinishEntityInitialization(child2);
EntityManager.FinishEntityInitialization(child1);
EntityManager.FinishEntityInitialization(parent);
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(child2).MapID, Is.EqualTo(new MapId(123)));
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(child1).MapID, Is.EqualTo(new MapId(123)));
Assert.That(IoCManager.Resolve<IEntityManager>().GetComponent<TransformComponent>(parent).MapID, Is.EqualTo(new MapId(123)));
}
/*
* There used to be a TestMapInitOrder test here. The problem is that the actual game will probably explode if
* you start initialising children before parents and the test only worked because of specific setup being done
* to prevent this in its use case.
*/
}
}
@@ -66,7 +66,7 @@ entities:
{
// For some reason RobustUnitTest doesn't discover PVSSystem but this does here so ?
var syssy = IoCManager.Resolve<IEntitySystemManager>();
syssy.Clear();
syssy.Shutdown();
syssy.Initialize();
var compFactory = IoCManager.Resolve<IComponentFactory>();
@@ -200,7 +200,6 @@ namespace Robust.UnitTesting.Server
container.Register<IEntityManager, EntityManager>();
container.Register<IMapManager, MapManager>();
container.Register<ISerializationManager, SerializationManager>();
container.Register<IEntityLookup, EntityLookup>();
container.Register<IPrototypeManager, PrototypeManager>();
container.Register<IComponentFactory, ComponentFactory>();
container.Register<IEntitySystemManager, EntitySystemManager>();
@@ -257,6 +256,7 @@ namespace Robust.UnitTesting.Server
entitySystemMan.LoadExtraSystemType<FixtureSystem>();
entitySystemMan.LoadExtraSystemType<GridFixtureSystem>();
entitySystemMan.LoadExtraSystemType<TransformSystem>();
entitySystemMan.LoadExtraSystemType<EntityLookupSystem>();
_systemDelegate?.Invoke(entitySystemMan);
@@ -265,7 +265,6 @@ namespace Robust.UnitTesting.Server
entityMan.Startup();
mapManager.Startup();
IoCManager.Resolve<IEntityLookup>().Startup();
container.Resolve<ISerializationManager>().Initialize();
+29 -31
View File
@@ -5,54 +5,52 @@ using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.UnitTesting.Server;
namespace Robust.UnitTesting.Shared
{
[TestFixture, TestOf(typeof(IEntityLookup))]
public sealed class EntityLookupTest : RobustIntegrationTest
[TestFixture, TestOf(typeof(EntityLookupSystem))]
public sealed class EntityLookupTest
{
/// <summary>
/// Is the entity correctly removed / added to EntityLookup when anchored
/// </summary>
[Test]
public async Task TestAnchoring()
public void TestAnchoring()
{
var server = StartServer();
await server.WaitIdleAsync();
var sim = RobustServerSimulation.NewSimulation();
// sim.RegisterEntitySystems(m => m.LoadExtraSystemType<EntityLookupSystem>());
var server = sim.InitializeInstance();
var lookup = server.ResolveDependency<IEntityLookup>();
var entManager = server.ResolveDependency<IEntityManager>();
var mapManager = server.ResolveDependency<IMapManager>();
var lookup = server.Resolve<IEntitySystemManager>().GetEntitySystem<EntityLookupSystem>();
var entManager = server.Resolve<IEntityManager>();
var mapManager = server.Resolve<IMapManager>();
await server.WaitAssertion(() =>
{
var mapId = mapManager.CreateMap();
var grid = mapManager.CreateGrid(mapId);
var mapId = mapManager.CreateMap();
var grid = mapManager.CreateGrid(mapId);
var theMapSpotBeingUsed = new Box2(Vector2.Zero, Vector2.One);
grid.SetTile(new Vector2i(), new Tile(1));
var theMapSpotBeingUsed = new Box2(Vector2.Zero, Vector2.One);
grid.SetTile(new Vector2i(), new Tile(1));
lookup.Update();
Assert.That(lookup.GetEntitiesIntersecting(mapId, theMapSpotBeingUsed).ToList().Count, Is.EqualTo(1));
Assert.That(lookup.GetEntitiesIntersecting(mapId, theMapSpotBeingUsed).ToList().Count, Is.EqualTo(0));
// Setup and check it actually worked
var dummy = entManager.SpawnEntity(null, new MapCoordinates(Vector2.Zero, mapId));
lookup.Update();
Assert.That(lookup.GetEntitiesIntersecting(mapId, theMapSpotBeingUsed).ToList().Count, Is.EqualTo(2));
// Setup and check it actually worked
var dummy = entManager.SpawnEntity(null, new MapCoordinates(Vector2.Zero, mapId));
Assert.That(lookup.GetEntitiesIntersecting(mapId, theMapSpotBeingUsed).ToList().Count, Is.EqualTo(1));
var xform = entManager.GetComponent<TransformComponent>(dummy);
var xform = entManager.GetComponent<TransformComponent>(dummy);
// When anchoring should still only be 1 entity.
xform.Anchored = true;
Assert.That(xform.Anchored);
lookup.Update();
Assert.That(lookup.GetEntitiesIntersecting(mapId, theMapSpotBeingUsed).ToList().Count, Is.EqualTo(2));
// When anchoring it should still get returned.
xform.Anchored = true;
Assert.That(xform.Anchored);
Assert.That(lookup.GetEntitiesIntersecting(mapId, theMapSpotBeingUsed).ToList().Count, Is.EqualTo(1));
// Even when unanchored should still be there
xform.Anchored = false;
lookup.Update();
Assert.That(lookup.GetEntitiesIntersecting(mapId, theMapSpotBeingUsed).ToList().Count, Is.EqualTo(2));
});
xform.Anchored = false;
Assert.That(lookup.GetEntitiesIntersecting(mapId, theMapSpotBeingUsed).ToList().Count, Is.EqualTo(1));
entManager.DeleteEntity(dummy);
mapManager.DeleteGrid(grid.Index);
mapManager.DeleteMap(mapId);
}
}
}