mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-16 07:12:27 +02:00
* Automatically unsubscribe event bus registrations in entity system shutdown. * Fix incorrect unsubscription of local events, obsolete unsubscribe methods. That's what we got tests for. * = null instead of .Clear()
275 lines
9.6 KiB
C#
275 lines
9.6 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Maths;
|
|
using Robust.Shared.Physics;
|
|
|
|
namespace Robust.Shared.GameObjects
|
|
{
|
|
public abstract class OccluderSystem : EntitySystem
|
|
{
|
|
|
|
[Dependency] private readonly IMapManagerInternal _mapManager = default!;
|
|
|
|
private readonly Dictionary<MapId, Dictionary<GridId, DynamicTree<OccluderComponent>>> _gridTrees =
|
|
new();
|
|
|
|
private readonly List<(OccluderComponent Occluder, EntityCoordinates Coordinates)> _occluderAddQueue =
|
|
new();
|
|
|
|
private readonly List<(OccluderComponent Occluder, EntityCoordinates Coordinates)> _occluderRemoveQueue =
|
|
new();
|
|
|
|
internal bool TryGetOccluderTreeForGrid(MapId mapId, GridId gridId, [NotNullWhen(true)] out DynamicTree<OccluderComponent>? gridTree)
|
|
{
|
|
gridTree = null;
|
|
|
|
if (!_gridTrees.TryGetValue(mapId, out var grids))
|
|
return false;
|
|
|
|
if (!grids.TryGetValue(gridId, out gridTree))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
|
|
_mapManager.MapCreated += OnMapCreated;
|
|
_mapManager.MapDestroyed += OnMapDestroyed;
|
|
_mapManager.OnGridCreated += OnGridCreated;
|
|
_mapManager.OnGridRemoved += OnGridRemoved;
|
|
|
|
SubscribeLocalEvent<MoveEvent>(EntMoved);
|
|
SubscribeLocalEvent<EntParentChangedMessage>(EntParentChanged);
|
|
SubscribeLocalEvent<OccluderBoundingBoxChangedMessage>(OccluderBoundingBoxChanged);
|
|
SubscribeLocalEvent<OccluderTreeRemoveOccluderMessage>(RemoveOccluder);
|
|
}
|
|
|
|
public override void Shutdown()
|
|
{
|
|
base.Shutdown();
|
|
|
|
_mapManager.MapCreated -= OnMapCreated;
|
|
_mapManager.MapDestroyed -= OnMapDestroyed;
|
|
_mapManager.OnGridCreated -= OnGridCreated;
|
|
_mapManager.OnGridRemoved -= OnGridRemoved;
|
|
}
|
|
|
|
public override void FrameUpdate(float frameTime)
|
|
{
|
|
UpdateTrees();
|
|
}
|
|
|
|
public override void Update(float frameTime)
|
|
{
|
|
UpdateTrees();
|
|
}
|
|
|
|
private void UpdateTrees()
|
|
{
|
|
// Only care about stuff parented to a grid I think?
|
|
foreach (var (occluder, coordinates) in _occluderRemoveQueue)
|
|
{
|
|
if (coordinates.TryGetParent(EntityManager, out var parent) &&
|
|
parent.HasComponent<MapGridComponent>())
|
|
{
|
|
var gridTree = _gridTrees[parent.Transform.MapID][parent.Transform.GridID];
|
|
|
|
gridTree.Remove(occluder);
|
|
}
|
|
}
|
|
|
|
_occluderRemoveQueue.Clear();
|
|
|
|
foreach (var (occluder, coordinates) in _occluderAddQueue)
|
|
{
|
|
if (occluder.Deleted) continue;
|
|
|
|
if (coordinates.TryGetParent(EntityManager, out var parent) &&
|
|
parent.HasComponent<MapGridComponent>() || occluder.Owner.Transform.GridID == GridId.Invalid)
|
|
{
|
|
parent ??= EntityManager.GetEntity(occluder.Owner.Transform.ParentUid);
|
|
var gridTree = _gridTrees[parent.Transform.MapID][parent.Transform.GridID];
|
|
|
|
gridTree.AddOrUpdate(occluder);
|
|
}
|
|
}
|
|
|
|
_occluderAddQueue.Clear();
|
|
}
|
|
|
|
// If the Transform is removed BEFORE the Occluder,
|
|
// then the MapIdChanged code will handle and remove it (because MapId gets set to nullspace).
|
|
// Otherwise these will still have their past MapId and that's all we need..
|
|
private void RemoveOccluder(OccluderTreeRemoveOccluderMessage ev)
|
|
{
|
|
_gridTrees[ev.MapId][ev.GridId].Remove(ev.Occluder);
|
|
}
|
|
|
|
private void OccluderBoundingBoxChanged(OccluderBoundingBoxChangedMessage ev)
|
|
{
|
|
QueueUpdateOccluder(ev.Occluder, ev.Occluder.Owner.Transform.Coordinates);
|
|
}
|
|
|
|
private void EntMoved(MoveEvent ev)
|
|
{
|
|
ev.OldPosition.TryGetParent(EntityManager, out var oldParent);
|
|
ev.NewPosition.TryGetParent(EntityManager, out var newParent);
|
|
|
|
if (oldParent?.Uid != newParent?.Uid)
|
|
RemoveEntity(ev.Sender, ev.OldPosition);
|
|
|
|
AddOrUpdateEntity(ev.Sender, ev.NewPosition);
|
|
}
|
|
|
|
private void EntParentChanged(EntParentChangedMessage message)
|
|
{
|
|
if (!message.Entity.TryGetComponent(out OccluderComponent? occluder))
|
|
return;
|
|
|
|
// Really only care if it's a map or grid
|
|
if (message.OldParent != null && message.OldParent.TryGetComponent(out MapGridComponent? oldGrid))
|
|
{
|
|
var map = message.OldParent.Transform.MapID;
|
|
if (_gridTrees[map].TryGetValue(oldGrid.GridIndex, out var tree))
|
|
{
|
|
tree.Remove(occluder);
|
|
}
|
|
}
|
|
|
|
var newParent = EntityManager.GetEntity(message.Entity.Transform.ParentUid);
|
|
|
|
newParent.TryGetComponent(out MapGridComponent? newGrid);
|
|
var newGridIndex = newGrid?.GridIndex ?? GridId.Invalid;
|
|
var newMap = newParent.Transform.MapID;
|
|
|
|
if (!_gridTrees.TryGetValue(newMap, out var newMapGrids))
|
|
{
|
|
newMapGrids = new Dictionary<GridId, DynamicTree<OccluderComponent>>();
|
|
_gridTrees[newMap] = newMapGrids;
|
|
}
|
|
|
|
if (!newMapGrids.TryGetValue(newGridIndex, out var newTree))
|
|
{
|
|
newTree = new DynamicTree<OccluderComponent>(ExtractAabbFunc);
|
|
newMapGrids[newGridIndex] = newTree;
|
|
}
|
|
|
|
newTree.AddOrUpdate(occluder);
|
|
}
|
|
|
|
private void RemoveEntity(IEntity entity, EntityCoordinates coordinates)
|
|
{
|
|
if (entity.TryGetComponent(out OccluderComponent? occluder))
|
|
{
|
|
QueueRemoveOccluder(occluder, coordinates);
|
|
}
|
|
}
|
|
|
|
internal void AddOrUpdateEntity(IEntity entity, EntityCoordinates coordinates)
|
|
{
|
|
if (entity.TryGetComponent(out OccluderComponent? occluder))
|
|
{
|
|
QueueUpdateOccluder(occluder, coordinates);
|
|
}
|
|
// Do we even need the children update? Coz they be slow af and allocate a lot.
|
|
// If you do end up adding children back in then for the love of GOD check if the entity has a mapgridcomponent
|
|
}
|
|
|
|
private void OnMapDestroyed(object? sender, MapEventArgs e)
|
|
{
|
|
_gridTrees.Remove(e.Map);
|
|
}
|
|
|
|
private void OnMapCreated(object? sender, MapEventArgs e)
|
|
{
|
|
if (e.Map == MapId.Nullspace)
|
|
return;
|
|
|
|
_gridTrees[e.Map] = new Dictionary<GridId, DynamicTree<OccluderComponent>>();
|
|
}
|
|
|
|
private void OnGridRemoved(MapId mapId, GridId gridId)
|
|
{
|
|
foreach (var (_, gridIds) in _gridTrees)
|
|
{
|
|
if (gridIds.Remove(gridId))
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void OnGridCreated(MapId mapId, GridId gridId)
|
|
{
|
|
if (!_gridTrees.TryGetValue(mapId, out var gridTree))
|
|
return;
|
|
|
|
gridTree.Add(gridId, new DynamicTree<OccluderComponent>(ExtractAabbFunc));
|
|
}
|
|
|
|
private static Box2 ExtractAabbFunc(in OccluderComponent o)
|
|
{
|
|
return o.BoundingBox.Translated(o.Owner.Transform.LocalPosition);
|
|
}
|
|
|
|
private void QueueUpdateOccluder(OccluderComponent occluder, EntityCoordinates coordinates)
|
|
{
|
|
_occluderAddQueue.Add((occluder, coordinates));
|
|
}
|
|
|
|
private void QueueRemoveOccluder(OccluderComponent occluder, EntityCoordinates coordinates)
|
|
{
|
|
_occluderRemoveQueue.Add((occluder, coordinates));
|
|
}
|
|
|
|
public IEnumerable<RayCastResults> IntersectRayWithPredicate(MapId originMapId, in Ray ray, float maxLength,
|
|
Func<IEntity, bool>? predicate = null, bool returnOnFirstHit = true)
|
|
{
|
|
var list = new List<RayCastResults>();
|
|
var worldBox = new Box2();
|
|
|
|
foreach (var gridId in _mapManager.FindGridIdsIntersecting(originMapId, worldBox, true))
|
|
{
|
|
var gridTree = _gridTrees[originMapId][gridId];
|
|
|
|
gridTree.QueryRay(ref list,
|
|
(ref List<RayCastResults> state, in OccluderComponent value, in Vector2 point, float distFromOrigin) =>
|
|
{
|
|
if (distFromOrigin > maxLength)
|
|
return true;
|
|
|
|
if (!value.Enabled)
|
|
return true;
|
|
|
|
if (predicate != null && predicate.Invoke(value.Owner))
|
|
return true;
|
|
|
|
var result = new RayCastResults(distFromOrigin, point, value.Owner);
|
|
state.Add(result);
|
|
return !returnOnFirstHit;
|
|
}, ray);
|
|
}
|
|
|
|
return list;
|
|
}
|
|
}
|
|
|
|
internal readonly struct OccluderTreeRemoveOccluderMessage
|
|
{
|
|
public readonly OccluderComponent Occluder;
|
|
public readonly MapId MapId;
|
|
public readonly GridId GridId;
|
|
|
|
public OccluderTreeRemoveOccluderMessage(OccluderComponent occluder, MapId mapId, GridId gridId)
|
|
{
|
|
Occluder = occluder;
|
|
MapId = mapId;
|
|
GridId = gridId;
|
|
}
|
|
}
|
|
}
|