Files
RobustToolbox/Robust.Server/GameObjects/ServerEntityManager.cs
Tyler Young faff0797bf Make Lidgren Use Spans & Shared Pool (#1140)
* make lidgren use spans everywhere where it can

convert custom pooling to shared array pool impl

add unit tests for read/write

add native socket extensions to socket so we can legit pass spans for SendTo/ReceiveFrom

bump version in lidgren csproj

replace some random "% 8" w/ "& 7"

more minor nullability hacks to fix static analysis complaints

made receiving packets use span

minor native sockets refactor to use pinvoke

add read/write constrained/prealloc'd bit stream impl to lidgren and update usages

fixed missing stream cleanup

remove outstanding stream cleanup since it refs buffer thru the class, can't read some other buf

apply suggestions from code review

remove unsafe cruft

* add tests to gh actions

* make stats use interpolation in tostring and remove m_bytesAllocated since it's all in the shared pool now

* this pr still open so fuck it

stats, human readability, faster BitsToHold methods

* add api compatible version of ReadBytes

* rename ReadOnlyStreamWrapper -> ReadOnlyWrapperStream

rename WriteOnlyStreamWrapper -> WriteOnlyWrapperStream

add AppendViaStream, AppenderStream impl

add and update documentation on read/write bytes methods

also fix some goofs
2020-06-24 04:09:20 +02:00

949 lines
33 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Prometheus;
using Robust.Server.GameObjects.Components;
using Robust.Server.GameObjects.Components.Container;
using Robust.Server.Interfaces.GameObjects;
using Robust.Server.Interfaces.Player;
using Robust.Server.Interfaces.Timing;
using Robust.Shared.GameObjects;
using Robust.Shared.GameObjects.Components.Transform;
using Robust.Shared.Interfaces.Configuration;
using Robust.Shared.Interfaces.GameObjects;
using Robust.Shared.Interfaces.GameObjects.Components;
using Robust.Shared.Interfaces.Map;
using Robust.Shared.IoC;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Robust.Server.GameObjects
{
/// <summary>
/// Manager for entities -- controls things like template loading and instantiation
/// </summary>
public sealed class ServerEntityManager : EntityManager, IServerEntityManagerInternal
{
private static readonly Gauge EntitiesCount = Metrics.CreateGauge(
"robust_entities_count",
"Amount of alive entities.");
private const float MinimumMotionForMovers = 1 / 128f;
#region IEntityManager Members
[Dependency] private readonly IMapManager _mapManager = default!;
[Dependency] private readonly IPauseManager _pauseManager = default!;
[Dependency] private readonly IConfigurationManager _configurationManager = default!;
private float? _maxUpdateRangeCache;
public float MaxUpdateRange => _maxUpdateRangeCache
??= _configurationManager.GetCVar<float>("net.maxupdaterange");
private int _nextServerEntityUid = (int) EntityUid.FirstUid;
private readonly List<(GameTick tick, EntityUid uid)> _deletionHistory = new List<(GameTick, EntityUid)>();
public override void Update()
{
base.Update();
_maxUpdateRangeCache = null;
}
/// <inheritdoc />
public override IEntity CreateEntityUninitialized(string? prototypeName)
{
return CreateEntityServer(prototypeName);
}
/// <inheritdoc />
public override IEntity CreateEntityUninitialized(string? prototypeName, GridCoordinates coordinates)
{
var newEntity = CreateEntityServer(prototypeName);
if (coordinates.GridID != GridId.Invalid)
{
var gridEntityId = _mapManager.GetGrid(coordinates.GridID).GridEntityId;
newEntity.Transform.AttachParent(GetEntity(gridEntityId));
newEntity.Transform.LocalPosition = coordinates.Position;
}
return newEntity;
}
/// <inheritdoc />
public override IEntity CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates)
{
var newEntity = CreateEntityServer(prototypeName);
newEntity.Transform.AttachParent(_mapManager.GetMapEntity(coordinates.MapId));
newEntity.Transform.WorldPosition = coordinates.Position;
return newEntity;
}
private Entity CreateEntityServer(string? prototypeName)
{
var entity = CreateEntity(prototypeName);
if (prototypeName != null)
{
var prototype = PrototypeManager.Index<EntityPrototype>(prototypeName);
// At this point in time, all data configure on the entity *should* be purely from the prototype.
// As such, we can reset the modified ticks to Zero,
// which indicates "not different from client's own deserialization".
// So the initial data for the component or even the creation doesn't have to be sent over the wire.
foreach (var component in ComponentManager.GetNetComponents(entity.Uid))
{
// Make sure to ONLY get components that are defined in the prototype.
// Others could be instantiated directly by AddComponent (e.g. ContainerManager).
// And those aren't guaranteed to exist on the client, so don't clear them.
if (prototype.Components.ContainsKey(component.Name))
{
((Component) component).ClearTicks();
}
}
}
return entity;
}
/// <inheritdoc />
public override IEntity SpawnEntity(string? protoName, GridCoordinates coordinates)
{
if (coordinates.GridID == GridId.Invalid)
throw new InvalidOperationException($"Tried to spawn entity {protoName} onto invalid grid.");
var entity = CreateEntityUninitialized(protoName, coordinates);
InitializeAndStartEntity((Entity) entity);
var grid = _mapManager.GetGrid(coordinates.GridID);
if (_pauseManager.IsMapInitialized(grid.ParentMapId))
{
entity.RunMapInit();
}
return entity;
}
/// <inheritdoc />
public override IEntity SpawnEntity(string? protoName, MapCoordinates coordinates)
{
var entity = CreateEntityUninitialized(protoName, coordinates);
InitializeAndStartEntity((Entity) entity);
return entity;
}
/// <inheritdoc />
public override IEntity SpawnEntityNoMapInit(string? protoName, GridCoordinates coordinates)
{
var newEnt = CreateEntityUninitialized(protoName, coordinates);
InitializeAndStartEntity((Entity) newEnt);
return newEnt;
}
/// <inheritdoc />
public List<EntityState>? GetEntityStates(GameTick fromTick)
{
var stateEntities = new List<EntityState>();
foreach (var entity in AllEntities)
{
if (entity.Deleted)
{
continue;
}
DebugTools.Assert(entity.Initialized);
if (entity.LastModifiedTick <= fromTick)
continue;
stateEntities.Add(GetEntityState(ComponentManager, entity.Uid, fromTick));
}
// no point sending an empty collection
return stateEntities.Count == 0 ? default : stateEntities;
}
private readonly Dictionary<IPlayerSession, ISet<EntityUid>> _seenMovers
= new Dictionary<IPlayerSession, ISet<EntityUid>>();
private ISet<EntityUid> GetSeenMovers(IPlayerSession player)
{
if (!_seenMovers.TryGetValue(player, out var movers))
{
movers = new SortedSet<EntityUid>();
_seenMovers.Add(player, movers);
}
return movers;
}
private readonly Dictionary<IPlayerSession, Dictionary<EntityUid, GameTick>> _playerLastSeen
= new Dictionary<IPlayerSession, Dictionary<EntityUid, GameTick>>();
private static readonly Vector2 Vector2NaN = new Vector2(float.NaN, float.NaN);
private Dictionary<EntityUid, GameTick> GetLastSeen(IPlayerSession player)
{
if (!_playerLastSeen.TryGetValue(player, out var lastSeen))
{
lastSeen = new Dictionary<EntityUid, GameTick>();
_playerLastSeen.Add(player, lastSeen);
}
return lastSeen;
}
private GameTick GetLastSeenTick(IPlayerSession player, EntityUid uid)
{
var lastSeen = GetLastSeen(player);
if (!lastSeen.TryGetValue(uid, out var tick))
{
tick = GameTick.First;
}
return tick;
}
private GameTick UpdateLastSeenTick(IPlayerSession player, EntityUid uid, GameTick newTick)
{
var lastSeen = GetLastSeen(player);
if (!lastSeen.TryGetValue(uid, out var oldTick))
{
oldTick = GameTick.First;
}
lastSeen[uid] = newTick;
return oldTick;
}
private IEnumerable<EntityUid> GetLastSeenAfter(IPlayerSession player, GameTick fromTick)
{
var lastSeen = GetLastSeen(player);
foreach (var (uid, tick) in lastSeen)
{
if (tick > fromTick)
{
yield return uid;
}
}
}
private IEnumerable<EntityUid> GetLastSeenOn(IPlayerSession player, GameTick fromTick)
{
var lastSeen = GetLastSeen(player);
foreach (var (uid, tick) in lastSeen)
{
if (tick == fromTick)
{
yield return uid;
}
}
}
private void SetLastSeenTick(IPlayerSession player, EntityUid uid, GameTick tick)
{
var lastSeen = GetLastSeen(player);
lastSeen[uid] = tick;
}
private void ClearLastSeenTick(IPlayerSession player, EntityUid uid)
{
var lastSeen = GetLastSeen(player);
lastSeen.Remove(uid);
}
public void DropPlayerState(IPlayerSession player)
{
_playerLastSeen.Remove(player);
}
private void IncludeRelatives(IEnumerable<IEntity> children, HashSet<IEntity> set)
{
foreach (var child in children)
{
var ent = child!;
do
{
if (set.Add(ent))
{
AddContainedRecursive(ent, set);
ent = ent.Transform.Parent?.Owner!;
}
else
{
// Already processed this entity once.
break;
}
} while (ent != null && !ent.Deleted);
}
}
private static void AddContainedRecursive(IEntity ent, HashSet<IEntity> set)
{
if (!ent.TryGetComponent(out ContainerManagerComponent contMgr))
{
return;
}
foreach (var container in contMgr.GetAllContainers())
{
foreach (var contEnt in container.ContainedEntities)
{
set.Add(contEnt);
AddContainedRecursive(contEnt, set);
}
}
}
private readonly struct PlayerSeenEntityStatesResources
{
public readonly HashSet<EntityUid> IncludedEnts;
public readonly List<EntityState> EntityStates;
public readonly HashSet<EntityUid> NeededEnts;
public readonly HashSet<IEntity> Relatives;
public PlayerSeenEntityStatesResources(bool memes = false)
{
IncludedEnts = new HashSet<EntityUid>();
EntityStates = new List<EntityState>();
NeededEnts = new HashSet<EntityUid>();
Relatives = new HashSet<IEntity>();
}
}
private readonly PlayerSeenEntityStatesResources _playerSeenEntityStatesResources
= new PlayerSeenEntityStatesResources(false);
/// <inheritdoc />
public List<EntityState>? UpdatePlayerSeenEntityStates(GameTick fromTick, IPlayerSession player, float range)
{
var playerEnt = player.AttachedEntity;
if (playerEnt == null)
{
// super-observer?
return GetEntityStates(fromTick);
}
var playerUid = playerEnt.Uid;
var transform = playerEnt.Transform;
var position = transform.WorldPosition;
var mapId = transform.MapID;
var viewbox = new Box2(position, position).Enlarged(MaxUpdateRange);
var seenMovers = GetSeenMovers(player);
var checkedEnts = _playerSeenEntityStatesResources.IncludedEnts;
var entityStates = _playerSeenEntityStatesResources.EntityStates;
var neededEnts = _playerSeenEntityStatesResources.NeededEnts;
var relatives = _playerSeenEntityStatesResources.Relatives;
checkedEnts.Clear();
entityStates.Clear();
neededEnts.Clear();
relatives.Clear();
foreach (var uid in seenMovers.ToList())
{
if (!TryGetEntity(uid, out var entity) || entity.Deleted)
{
seenMovers.Remove(uid);
continue;
}
if (entity.TryGetComponent(out PhysicsComponent body))
{
if (body.LinearVelocity.EqualsApprox(Vector2.Zero, MinimumMotionForMovers))
{
if (AnyParentInSet(uid, seenMovers))
{
// parent is moving
continue;
}
if (MathF.Abs(body.AngularVelocity) > 0)
{
if (entity.TryGetComponent(out TransformComponent txf) && txf.ChildCount > 0 )
{
// has children spinning
continue;
}
}
seenMovers.Remove(uid);
}
}
var state = GetEntityState(ComponentManager, uid, fromTick);
if (checkedEnts.Add(uid))
{
entityStates.Add(state);
// mover did not change
if (state.ComponentStates != null)
{
// mover can be seen
if (!viewbox.Intersects(GetWorldAabbFromEntity(entity)))
{
// mover changed and can't be seen
var idx = Array.FindIndex(state.ComponentStates, x => x is TransformComponent.TransformComponentState);
if (idx != -1)
{
// mover changed positional data and can't be seen
var oldState = (TransformComponent.TransformComponentState) state.ComponentStates[idx];
var newState = new TransformComponent.TransformComponentState(Vector2NaN, oldState.Rotation, oldState.ParentID);
state.ComponentStates[idx] = newState;
seenMovers.Remove(uid);
ClearLastSeenTick(player, uid);
checkedEnts.Add(uid);
var needed = oldState.ParentID;
if (needed == null || checkedEnts.Contains(needed.Value))
{
// either no parent attached or parent already included
continue;
}
var neededUid = needed.Value;
if (GetLastSeenTick(player, neededUid) == GameTick.Zero)
{
neededEnts.Add(needed.Value);
}
}
}
}
}
else
{
// mover already added?
if (!viewbox.Intersects(GetWorldAabbFromEntity(entity)))
{
// mover can't be seen
var oldState = (TransformComponent.TransformComponentState) entity.Transform.GetComponentState();
entityStates.Add(new EntityState(uid,
new ComponentChanged[]
{
new ComponentChanged(false, NetIDs.TRANSFORM, "Transform")
},
new ComponentState[]
{
new TransformComponent.TransformComponentState(Vector2NaN, oldState.Rotation, oldState.ParentID)
}));
seenMovers.Remove(uid);
ClearLastSeenTick(player, uid);
checkedEnts.Add(uid);
var needed = oldState.ParentID;
if (needed == null || checkedEnts.Contains(needed.Value))
{
// either no parent attached or parent already included
continue;
}
var neededUid = needed.Value;
if (GetLastSeenTick(player, neededUid) == GameTick.Zero)
{
neededEnts.Add(needed.Value);
}
}
}
}
var currentTick = CurrentTick;
// scan pvs box and include children and parents recursively
IncludeRelatives(GetEntitiesInRange(mapId, position, range, true), relatives);
// Exclude any entities that are currently invisible to the player.
ExcludeInvisible(relatives, player.VisibilityMask);
// Always send updates for all grid and map entities.
// If we don't, the client-side game state manager WILL blow up.
// TODO: Make map manager netcode aware of PVS to avoid the need for this workaround.
IncludeMapCriticalEntities(relatives);
foreach (var entity in relatives)
{
DebugTools.Assert(entity.Initialized && !entity.Deleted);
var lastChange = entity.LastModifiedTick;
var uid = entity.Uid;
var lastSeen = UpdateLastSeenTick(player, uid, currentTick);
DebugTools.Assert(lastSeen != currentTick);
if (uid != playerUid && entity.Prototype == playerEnt.Prototype && lastSeen < fromTick)
{
Logger.DebugS("pvs", $"Player {playerUid} is seeing player {uid}.");
}
if (checkedEnts.Contains(uid))
{
// already have it
continue;
}
if (lastChange <= lastSeen)
{
// hasn't changed since last seen
continue;
}
// should this be lastSeen or fromTick?
var entityState = GetEntityState(ComponentManager, uid, lastSeen);
checkedEnts.Add(uid);
if (entityState.ComponentStates == null)
{
// no changes
continue;
}
entityStates.Add(entityState);
if (uid == playerUid)
{
continue;
}
if (!entity.TryGetComponent(out PhysicsComponent body))
{
// can't be a mover w/o physics
continue;
}
if (!body.LinearVelocity.EqualsApprox(Vector2.Zero, MinimumMotionForMovers))
{
// has motion
seenMovers.Add(uid);
}
else
{
// not moving
seenMovers.Remove(uid);
}
}
var priorTick = new GameTick(fromTick.Value - 1);
foreach (var uid in GetLastSeenOn(player, priorTick))
{
if (checkedEnts.Contains(uid))
{
continue;
}
if (uid == playerUid)
{
continue;
}
if (!TryGetEntity(uid, out var entity) || entity.Deleted)
{
// TODO: remove from states list being sent?
continue;
}
if (viewbox.Intersects(GetWorldAabbFromEntity(entity)))
{
// can be seen
continue;
}
var state = GetEntityState(ComponentManager, uid, fromTick);
if (state.ComponentStates == null)
{
// nothing changed
continue;
}
checkedEnts.Add(uid);
entityStates.Add(state);
seenMovers.Remove(uid);
ClearLastSeenTick(player,uid);
var idx = Array.FindIndex(state.ComponentStates, x => x is TransformComponent.TransformComponentState);
if (idx == -1)
{
// no transform changes
continue;
}
var oldState = (TransformComponent.TransformComponentState) state.ComponentStates[idx];
var newState = new TransformComponent.TransformComponentState(Vector2NaN, oldState.Rotation, oldState.ParentID);
state.ComponentStates[idx] = newState;
var needed = oldState.ParentID;
if (needed == null || checkedEnts.Contains(needed.Value))
{
// don't need to include parent or already included
continue;
}
var neededUid = needed.Value;
if (GetLastSeenTick(player, neededUid) == GameTick.First)
{
neededEnts.Add(needed.Value);
}
}
do
{
var moreNeededEnts = new HashSet<EntityUid>();
foreach (var uid in moreNeededEnts)
{
if (checkedEnts.Contains(uid))
{
continue;
}
var entity = GetEntity(uid);
var state = GetEntityState(ComponentManager, uid, fromTick);
if (state.ComponentStates == null || viewbox.Intersects(GetWorldAabbFromEntity(entity)))
{
// no states or should already be seen
continue;
}
checkedEnts.Add(uid);
entityStates.Add(state);
var idx = Array.FindIndex(state.ComponentStates, x => x is TransformComponent.TransformComponentState);
if (idx == -1)
{
// no transform state
continue;
}
var oldState = (TransformComponent.TransformComponentState) state.ComponentStates[idx];
var newState = new TransformComponent.TransformComponentState(Vector2NaN, oldState.Rotation, oldState.ParentID);
state.ComponentStates[idx] = newState;
seenMovers.Remove(uid);
ClearLastSeenTick(player, uid);
var needed = oldState.ParentID;
if (needed == null || checkedEnts.Contains(needed.Value))
{
// done here
continue;
}
// check if further needed
var neededUid = needed.Value;
if (!checkedEnts.Contains(uid) && GetLastSeenTick(player, neededUid) == GameTick.Zero)
{
moreNeededEnts.Add(needed.Value);
}
}
neededEnts = moreNeededEnts;
} while (neededEnts.Count > 0);
// help the client out
entityStates.Sort((a,b) => a.Uid.CompareTo(b.Uid));
#if DEBUG_NULL_ENTITY_STATES
foreach ( var state in entityStates ) {
if (state.ComponentStates == null)
{
throw new NotImplementedException("Shouldn't send null states.");
}
}
#endif
// no point sending an empty collection
return entityStates.Count == 0 ? default : entityStates;
}
public override void DeleteEntity(IEntity e)
{
base.DeleteEntity(e);
_deletionHistory.Add((CurrentTick, e.Uid));
}
public List<EntityUid>? GetDeletedEntities(GameTick fromTick)
{
var list = new List<EntityUid>();
foreach (var (tick, id) in _deletionHistory)
{
if (tick >= fromTick)
{
list.Add(id);
}
}
// no point sending an empty collection
return list.Count == 0 ? default : list;
}
public void CullDeletionHistory(GameTick toTick)
{
_deletionHistory.RemoveAll(hist => hist.tick <= toTick);
}
public override bool UpdateEntityTree(IEntity entity)
{
var currentTick = CurrentTick;
var updated = base.UpdateEntityTree(entity);
if (entity.Deleted
|| !entity.Initialized
|| !Entities.ContainsKey(entity.Uid)
|| !entity.TryGetComponent(out ITransformComponent txf)
|| !txf.Initialized)
{
return updated;
}
// note: updated can be false even if something moved a bit
foreach (var (player, lastSeen) in _playerLastSeen)
{
var playerEnt = player.AttachedEntity;
if (playerEnt == null)
{
// player has no entity, gaf?
continue;
}
var playerUid = playerEnt.Uid;
var entityUid = entity.Uid;
if (entityUid == playerUid)
{
continue;
}
if (!lastSeen.TryGetValue(playerUid, out var playerTick))
{
// player can't "see" itself, gaf?
continue;
}
if (!playerEnt.TryGetComponent(out ITransformComponent playerTxf))
{
// not in world
continue;
}
var playerPos = playerTxf.WorldPosition;
var viewbox = new Box2(playerPos, playerPos).Enlarged(MaxUpdateRange);
if (!lastSeen.TryGetValue(entityUid, out var tick))
{
// never saw it other than first tick or was cleared
if (!AnyParentMoving(player, entityUid))
{
continue;
}
}
if (tick >= currentTick)
{
// currently seeing it
continue;
}
// saw it previously
// player can't see it now
if (!viewbox.Intersects(GetWorldAabbFromEntity(entity)))
{
var changes = GetEntityState(ComponentManager, entityUid, currentTick);
if (changes.ComponentStates != null && changes.ComponentStates
.Any(x => x is TransformComponent.TransformComponentState || x is PhysicsComponentState))
{
GetSeenMovers(player).Add(entityUid);
}
}
}
return updated;
}
private bool AnyParentMoving(IPlayerSession player, EntityUid entityUid)
=> AnyParentInSet(entityUid, GetSeenMovers(player));
private bool AnyParentInSet(EntityUid entityUid, ISet<EntityUid> set)
{
for (;;)
{
if (!TryGetEntity(entityUid, out var ent))
{
return false;
}
if (!ent.TryGetComponent(out TransformComponent txf))
{
return false;
}
entityUid = txf.ParentUid;
if (entityUid == EntityUid.Invalid)
{
return false;
}
if (set.Contains(entityUid))
{
return true;
}
}
}
#endregion IEntityManager Members
IEntity IServerEntityManagerInternal.AllocEntity(string? prototypeName, EntityUid? uid)
{
return AllocEntity(prototypeName, uid);
}
protected override EntityUid GenerateEntityUid()
{
return new EntityUid(_nextServerEntityUid++);
}
void IServerEntityManagerInternal.FinishEntityLoad(IEntity entity, IEntityLoadContext? context)
{
LoadEntity((Entity) entity, context);
}
void IServerEntityManagerInternal.FinishEntityInitialization(IEntity entity)
{
InitializeEntity((Entity) entity);
}
void IServerEntityManagerInternal.FinishEntityStartup(IEntity entity)
{
StartEntity((Entity) entity);
}
/// <inheritdoc />
public override void Startup()
{
base.Startup();
EntitySystemManager.Initialize();
Started = true;
}
/// <summary>
/// Generates a network entity state for the given entity.
/// </summary>
/// <param name="compMan">ComponentManager that contains the components for the entity.</param>
/// <param name="entityUid">Uid of the entity to generate the state from.</param>
/// <param name="fromTick">Only provide delta changes from this tick.</param>
/// <returns>New entity State for the given entity.</returns>
private static EntityState GetEntityState(IComponentManager compMan, EntityUid entityUid, GameTick fromTick)
{
var compStates = new List<ComponentState>();
var changed = new List<ComponentChanged>();
foreach (var comp in compMan.GetNetComponents(entityUid))
{
DebugTools.Assert(comp.Initialized);
// NOTE: When LastModifiedTick or CreationTick are 0 it means that the relevant data is
// "not different from entity creation".
// i.e. when the client spawns the entity and loads the entity prototype,
// the data it deserializes from the prototype SHOULD be equal
// to what the component state / ComponentChanged would send.
// As such, we can avoid sending this data in this case since the client "already has it".
if (comp.NetSyncEnabled && comp.LastModifiedTick != GameTick.Zero && comp.LastModifiedTick >= fromTick)
compStates.Add(comp.GetComponentState());
if (comp.CreationTick != GameTick.Zero && comp.CreationTick >= fromTick && !comp.Deleted)
{
// Can't be null since it's returned by GetNetComponents
// ReSharper disable once PossibleInvalidOperationException
changed.Add(ComponentChanged.Added(comp.NetID!.Value, comp.Name));
}
else if (comp.Deleted && comp.LastModifiedTick >= fromTick)
{
// Can't be null since it's returned by GetNetComponents
// ReSharper disable once PossibleInvalidOperationException
changed.Add(ComponentChanged.Removed(comp.NetID!.Value));
}
}
return new EntityState(entityUid, changed.ToArray(), compStates.ToArray());
}
private void IncludeMapCriticalEntities(HashSet<IEntity> set)
{
foreach (var mapId in _mapManager.GetAllMapIds())
{
if (_mapManager.HasMapEntity(mapId))
{
set.Add(_mapManager.GetMapEntity(mapId));
}
}
foreach (var grid in _mapManager.GetAllGrids())
{
if (grid.GridEntityId != EntityUid.Invalid)
{
set.Add(GetEntity(grid.GridEntityId));
}
}
}
private void ExcludeInvisible(HashSet<IEntity> set, int visibilityMask)
{
foreach (var entity in set.ToArray())
{
if (!entity.TryGetComponent(out VisibilityComponent visibility))
continue;
if ((visibilityMask & visibility.Layer) == 0)
set.Remove(entity);
}
}
public override void Update(float frameTime)
{
base.Update(frameTime);
EntitiesCount.Set(AllEntities.Count);
}
}
}