mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-15 06:42:25 +02:00
EntityLookup as a system (#2573)
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+12
-10
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user