mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-02 01:57:07 +02:00
* Move MapManager queries to SharedMapSystem Moves all variants of FindGridsIntersecting/TryFindGridAt to SharedMapSystem Hollows out the MapManager methods and converts them into relays to SharedMapSystem * Move CreateGrid to SharedMapSystem Moves the functionality for CreateGrid and its variants to SharedMapSystem Hollows out the MapManager methods and converts them into relays for the SharedMapSystem methods Obsoletes them too Also moves over the GetAllMapGrids and GetAllGrids methods * Move RaiseOnTileChanged to SharedMapSystem Also moves the SuppressOnTileChanged flag member to SharedMapSystem Hollows out and obsoletes the MapManager versions * Move default value constants to SharedMapSystem * Converts map pausing events into LocalizedEntityCommands * Move MapManager related delegates/structs to SharedMapSystem Moves the GridCreationOptions struct to the same namespace as SharedMapSystem and converts it into a record struct Move the GridCallback delegates to the same namespace as SharedMapSystem * Move CullDeletionHistory to SharedMapSystem Well, that was less painful than I thought it would be * Actually obsolete the NetworkedMapManager method * Rename file * Doc comments for new SharedMapSystem methods * Doc comment * Doc comments * Fix access * Purge all references to IMapManagerInternal * Cull easy IMapManager references * The rest * Purge IMapManager * Private internal FindGridsIntersecting method * please die * 41 * notes --------- Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
1140 lines
44 KiB
C#
1140 lines
44 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using Prometheus;
|
|
using Robust.Shared.Console;
|
|
using Robust.Shared.Containers;
|
|
using Robust.Shared.GameStates;
|
|
using Robust.Shared.Log;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Map.Components;
|
|
using Robust.Shared.Maths;
|
|
using Robust.Shared.Network;
|
|
using Robust.Shared.Physics.Components;
|
|
using Robust.Shared.Player;
|
|
using Robust.Shared.Profiling;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Reflection;
|
|
using Robust.Shared.Serialization.Manager;
|
|
using Robust.Shared.Serialization.Markdown.Mapping;
|
|
using Robust.Shared.Timing;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Robust.Shared.GameObjects
|
|
{
|
|
public delegate void EntityUidQueryCallback(EntityUid uid);
|
|
|
|
public delegate void ComponentQueryCallback<T>(EntityUid uid, T component) where T : IComponent;
|
|
|
|
/// <inheritdoc />
|
|
public abstract partial class EntityManager : IEntityManager
|
|
{
|
|
#region Dependencies
|
|
|
|
[IoC.Dependency] protected IPrototypeManager PrototypeManager = default!;
|
|
[IoC.Dependency] protected ILogManager LogManager = default!;
|
|
[IoC.Dependency] private IEntitySystemManager _entitySystemManager = default!;
|
|
[IoC.Dependency] private IGameTiming _gameTiming = default!;
|
|
[IoC.Dependency] private ISerializationManager _serManager = default!;
|
|
[IoC.Dependency] private ProfManager _prof = default!;
|
|
[IoC.Dependency] private INetManager _netMan = default!;
|
|
[IoC.Dependency] private IReflectionManager _reflection = default!;
|
|
[IoC.Dependency] private EntityConsoleHost _entityConsoleHost = default!;
|
|
|
|
// I feel like PJB might shed me for putting a system dependency here, but its required for setting entity
|
|
// positions on spawn....
|
|
protected SharedTransformSystem _xforms = default!;
|
|
private SharedContainerSystem _containers = default!;
|
|
|
|
public EntityQuery<MetaDataComponent> MetaQuery;
|
|
public EntityQuery<TransformComponent> TransformQuery;
|
|
private EntityQuery<PhysicsComponent> _physicsQuery;
|
|
private EntityQuery<ActorComponent> _actorQuery;
|
|
|
|
#endregion Dependencies
|
|
|
|
/// <inheritdoc />
|
|
public GameTick CurrentTick => _gameTiming.CurTick;
|
|
|
|
public static readonly MapInitEvent MapInitEventInstance = new();
|
|
|
|
IComponentFactory IEntityManager.ComponentFactory => ComponentFactory;
|
|
|
|
/// <inheritdoc />
|
|
public IEntitySystemManager EntitySysManager => _entitySystemManager;
|
|
|
|
/// <inheritdoc />
|
|
public abstract IEntityNetworkManager EntityNetManager { get; }
|
|
|
|
protected readonly Queue<EntityUid> QueuedDeletions = new();
|
|
protected readonly HashSet<EntityUid> QueuedDeletionsSet = new();
|
|
|
|
private EntityDiffContext _context = new();
|
|
|
|
/// <summary>
|
|
/// All entities currently stored in the manager.
|
|
/// </summary>
|
|
protected readonly HashSet<EntityUid> Entities = new();
|
|
|
|
internal EntityEventBus EventBusInternal = null!;
|
|
|
|
protected int NextEntityUid = (int) EntityUid.FirstUid;
|
|
|
|
protected int NextNetworkId = (int) NetEntity.First;
|
|
|
|
/// <inheritdoc />
|
|
public IEventBus EventBus => EventBusInternal;
|
|
|
|
public event Action<Entity<MetaDataComponent>>? EntityAdded;
|
|
public event Action<Entity<MetaDataComponent>>? EntityInitialized;
|
|
public event Action<Entity<MetaDataComponent>>? EntityDeleted;
|
|
|
|
/// <summary>
|
|
/// Internal termination event handlers. This is mainly for exception tolerance, we want to ensure that PVS,
|
|
/// and other important engine systems can get updated before some content code throws an exception.
|
|
/// </summary>
|
|
internal event TerminatingEventHandler? BeforeEntityTerminating;
|
|
public delegate void TerminatingEventHandler(ref EntityTerminatingEvent ev);
|
|
|
|
public event Action? BeforeEntityFlush;
|
|
public event Action? AfterEntityFlush;
|
|
|
|
/// <summary>
|
|
/// Raised when an entity is queued for deletion. Not raised if an entity is deleted.
|
|
/// </summary>
|
|
public event Action<EntityUid>? EntityQueueDeleted;
|
|
public event Action<Entity<MetaDataComponent>>? EntityDirtied;
|
|
|
|
private string _xformName = string.Empty;
|
|
|
|
private ComponentRegistration _metaReg = default!;
|
|
private ComponentRegistration _xformReg = default!;
|
|
|
|
private SharedMapSystem _mapSystem = default!;
|
|
|
|
private ISawmill _sawmill = default!;
|
|
internal ISawmill ResolveSawmill = default!;
|
|
|
|
public bool Started { get; protected set; }
|
|
|
|
public bool ShuttingDown { get; protected set; }
|
|
|
|
public bool Initialized { get; protected set; }
|
|
|
|
#if DEBUG
|
|
private int _mainThreadId;
|
|
#endif
|
|
|
|
/// <summary>
|
|
/// Constructs a new instance of <see cref="EntityManager"/>.
|
|
/// </summary>
|
|
public EntityManager()
|
|
{
|
|
}
|
|
|
|
public virtual void Initialize()
|
|
{
|
|
if (Initialized)
|
|
throw new InvalidOperationException("Initialize() called multiple times");
|
|
|
|
EventBusInternal = new EntityEventBus(this, _reflection);
|
|
|
|
InitializeComponents();
|
|
_metaReg = _componentFactory.GetRegistration(typeof(MetaDataComponent));
|
|
_xformReg = _componentFactory.GetRegistration(typeof(TransformComponent));
|
|
_xformName = _xformReg.Name;
|
|
_sawmill = LogManager.GetSawmill("entity");
|
|
ResolveSawmill = LogManager.GetSawmill("resolve");
|
|
|
|
#if DEBUG
|
|
_mainThreadId = Environment.CurrentManagedThreadId;
|
|
#endif
|
|
|
|
Initialized = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the entity's data (apart from transform) is default.
|
|
/// </summary>
|
|
public bool IsDefault(EntityUid uid, ICollection<string>? ignoredComps = null)
|
|
{
|
|
if (!MetaQuery.TryGetComponent(uid, out var metadata) || metadata.EntityPrototype == null)
|
|
return false;
|
|
|
|
var prototype = metadata.EntityPrototype;
|
|
|
|
// Check if entity name / description match
|
|
if (metadata.EntityName != prototype.Name ||
|
|
metadata.EntityDescription != prototype.Description)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var protoData = PrototypeManager.GetPrototypeData(prototype);
|
|
var comps = _entCompIndex[uid];
|
|
|
|
// Fast check if the component counts match.
|
|
// Note that transform and metadata are not included in the prototype data.
|
|
if (protoData.Count + 2 != comps.Count)
|
|
return false;
|
|
|
|
foreach (var component in comps)
|
|
{
|
|
if (component.Deleted)
|
|
return false;
|
|
|
|
var compType = component.GetType();
|
|
|
|
if (compType == typeof(TransformComponent) || compType == typeof(MetaDataComponent))
|
|
continue;
|
|
|
|
var compName = _componentFactory.GetComponentName(compType);
|
|
|
|
if (ignoredComps?.Contains(compName) == true)
|
|
continue;
|
|
|
|
// If the component isn't on the prototype then it's custom.
|
|
if (!protoData.TryGetValue(compName, out var protoMapping))
|
|
return false;
|
|
|
|
MappingDataNode compMapping;
|
|
try
|
|
{
|
|
compMapping = _serManager.WriteValueAs<MappingDataNode>(compType, component, alwaysWrite: true, context: _context);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_sawmill.Error($"Failed to serialize {compName} component of entity prototype {prototype.ID}. Exception: {e.Message}");
|
|
#if !EXCEPTION_TOLERANCE
|
|
throw;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
if (compMapping.AnyExcept(protoMapping))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public virtual void Startup()
|
|
{
|
|
if(!Initialized)
|
|
throw new InvalidOperationException("Startup() called without Initialized");
|
|
if (Started)
|
|
throw new InvalidOperationException("Startup() called multiple times");
|
|
|
|
// TODO: Probably better to call this on its own given it's so infrequent.
|
|
_entitySystemManager.Initialize();
|
|
Started = true;
|
|
EventBusInternal.LockSubscriptions();
|
|
_mapSystem = System<SharedMapSystem>();
|
|
_xforms = System<SharedTransformSystem>();
|
|
_containers = System<SharedContainerSystem>();
|
|
MetaQuery = GetEntityQuery<MetaDataComponent>();
|
|
TransformQuery = GetEntityQuery<TransformComponent>();
|
|
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
|
_actorQuery = GetEntityQuery<ActorComponent>();
|
|
_entityConsoleHost.Startup();
|
|
}
|
|
|
|
public virtual void Shutdown()
|
|
{
|
|
ShuttingDown = true;
|
|
FlushEntities();
|
|
EventBusInternal.ClearSubscriptions();
|
|
_entitySystemManager.Shutdown();
|
|
ClearComponents();
|
|
ShuttingDown = false;
|
|
Started = false;
|
|
_entityConsoleHost.Shutdown();
|
|
}
|
|
|
|
public virtual void Cleanup()
|
|
{
|
|
_componentFactory.ComponentsAdded -= OnComponentsAdded;
|
|
ShuttingDown = true;
|
|
FlushEntities();
|
|
_entitySystemManager.Clear();
|
|
EventBusInternal.Dispose();
|
|
EventBusInternal = null!;
|
|
ClearComponents();
|
|
|
|
ShuttingDown = false;
|
|
Initialized = false;
|
|
Started = false;
|
|
}
|
|
|
|
public virtual void TickUpdate(float frameTime, bool noPredictions, Histogram? histogram)
|
|
{
|
|
using (histogram?.WithLabels("EntitySystems").NewTimer())
|
|
using (_prof.Group("Systems"))
|
|
{
|
|
_entitySystemManager.TickUpdate(frameTime, noPredictions);
|
|
}
|
|
|
|
using (histogram?.WithLabels("EntityEventBus").NewTimer())
|
|
using (_prof.Group("Events"))
|
|
{
|
|
EventBusInternal.ProcessEventQueue();
|
|
}
|
|
|
|
using (histogram?.WithLabels("QueuedDeletion").NewTimer())
|
|
using (_prof.Group("QueueDel"))
|
|
{
|
|
ProcessQueueudDeletions();
|
|
}
|
|
|
|
using (histogram?.WithLabels("ComponentCull").NewTimer())
|
|
using (_prof.Group("ComponentCull"))
|
|
{
|
|
CullRemovedComponents();
|
|
}
|
|
}
|
|
|
|
internal virtual void ProcessQueueudDeletions()
|
|
{
|
|
while (QueuedDeletions.TryDequeue(out var uid))
|
|
{
|
|
DeleteEntity(uid);
|
|
}
|
|
|
|
QueuedDeletionsSet.Clear();
|
|
}
|
|
|
|
public virtual void FrameUpdate(float frameTime)
|
|
{
|
|
_entitySystemManager.FrameUpdate(frameTime);
|
|
}
|
|
|
|
#region Entity Management
|
|
|
|
/// <inheritdoc />
|
|
public EntityUid CreateEntityUninitialized(string? prototypeName, EntityUid euid, ComponentRegistry? overrides = null)
|
|
{
|
|
return CreateEntity(prototypeName, out _, overrides);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public EntityUid CreateEntityUninitialized(string? prototypeName, ComponentRegistry? overrides = null)
|
|
{
|
|
return CreateEntity(prototypeName, out _, overrides);
|
|
}
|
|
|
|
public EntityUid CreateEntityUninitialized(string? prototypeName, out MetaDataComponent meta, ComponentRegistry? overrides = null)
|
|
{
|
|
return CreateEntity(prototypeName, out meta, overrides);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default)
|
|
{
|
|
var newEntity = CreateEntity(prototypeName, out _, overrides);
|
|
|
|
var xformComp = TransformQuery.GetComponent(newEntity);
|
|
_xforms.SetCoordinates(newEntity, xformComp, coordinates, rotation: rotation, unanchor: false);
|
|
return newEntity;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null, Angle rotation = default!)
|
|
{
|
|
var newEntity = CreateEntity(prototypeName, out _, overrides);
|
|
var transform = TransformQuery.GetComponent(newEntity);
|
|
|
|
if (coordinates.MapId == MapId.Nullspace)
|
|
{
|
|
transform._parent = EntityUid.Invalid;
|
|
transform.Anchored = false;
|
|
return newEntity;
|
|
}
|
|
|
|
var mapEnt = _mapSystem.GetMap(coordinates.MapId);
|
|
if (!TryGetComponent(mapEnt, out TransformComponent? mapXform))
|
|
throw new ArgumentException($"Attempted to spawn entity on an invalid map. Coordinates: {coordinates}");
|
|
|
|
EntityCoordinates coords;
|
|
if (_mapSystem.TryFindGridAt(coordinates, out var gridUid, out var grid)
|
|
&& MetaQuery.TryGetComponentInternal(gridUid, out var meta)
|
|
&& meta.EntityLifeStage < EntityLifeStage.Terminating)
|
|
{
|
|
coords = new EntityCoordinates(gridUid, _mapSystem.WorldToLocal(gridUid, grid, coordinates.Position));
|
|
var relativeRotation = rotation - _xforms.GetWorldRotation(gridUid);
|
|
_xforms.SetCoordinates(newEntity, transform, coords, relativeRotation, unanchor: false);
|
|
}
|
|
else
|
|
{
|
|
coords = new EntityCoordinates(mapEnt, coordinates.Position);
|
|
_xforms.SetCoordinates(newEntity, transform, coords, rotation, newParent: mapXform);
|
|
}
|
|
|
|
return newEntity;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public int EntityCount => Entities.Count;
|
|
|
|
/// <inheritdoc />
|
|
public IEnumerable<EntityUid> GetEntities() => Entities;
|
|
|
|
/// <inheritdoc />
|
|
public virtual void DirtyEntity(EntityUid uid, MetaDataComponent? metadata = null)
|
|
{
|
|
// We want to retrieve MetaDataComponent even if its Deleted flag is set.
|
|
if (!MetaQuery.ResolveInternal(uid, ref metadata))
|
|
return;
|
|
|
|
if (metadata.EntityLastModifiedTick == _gameTiming.CurTick)
|
|
return;
|
|
|
|
metadata.EntityLastModifiedTick = _gameTiming.CurTick;
|
|
|
|
if (metadata.EntityLifeStage > EntityLifeStage.Initializing)
|
|
{
|
|
EntityDirtied?.Invoke((uid, metadata));
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
[Obsolete("use override with an EntityUid or Entity<T>")]
|
|
public void Dirty(IComponent component, MetaDataComponent? meta = null)
|
|
{
|
|
Dirty(component.Owner, component, meta);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual void Dirty(EntityUid uid, IComponent component, MetaDataComponent? meta = null)
|
|
{
|
|
DebugTools.Assert(component.GetType().HasCustomAttribute<NetworkedComponentAttribute>(),
|
|
$"Attempted to dirty a non-networked component: {component.GetType()}");
|
|
DebugTools.AssertOwner(uid, component);
|
|
|
|
if (component.LifeStage >= ComponentLifeStage.Removing || !component.NetSyncEnabled)
|
|
return;
|
|
|
|
if (component.LastModifiedTick == CurrentTick)
|
|
return;
|
|
|
|
DirtyEntity(uid, meta);
|
|
component.LastModifiedTick = CurrentTick;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual void Dirty<T>(Entity<T> ent, MetaDataComponent? meta = null) where T : IComponent
|
|
{
|
|
DebugTools.Assert(ent.Comp.GetType().HasCustomAttribute<NetworkedComponentAttribute>(),
|
|
$"Attempted to dirty a non-networked component: {ent.Comp.GetType()}");
|
|
|
|
if (ent.Comp.LifeStage >= ComponentLifeStage.Removing || !ent.Comp.NetSyncEnabled)
|
|
return;
|
|
|
|
if (ent.Comp.LastModifiedTick == CurrentTick)
|
|
return;
|
|
|
|
DirtyEntity(ent, meta);
|
|
ent.Comp.LastModifiedTick = CurrentTick;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual void Dirty<T1, T2>(Entity<T1, T2> ent, MetaDataComponent? meta = null)
|
|
where T1 : IComponent
|
|
where T2 : IComponent
|
|
{
|
|
DebugTools.Assert(ent.Comp1.GetType().HasCustomAttribute<NetworkedComponentAttribute>(),
|
|
$"Attempted to dirty a non-networked component: {ent.Comp1.GetType()}");
|
|
DebugTools.Assert(ent.Comp2.GetType().HasCustomAttribute<NetworkedComponentAttribute>(),
|
|
$"Attempted to dirty a non-networked component: {ent.Comp2.GetType()}");
|
|
|
|
// We're not gonna bother checking ent.Comp.NetSyncEnabled
|
|
// chances are at least one of these components didn't get net-sync disabled.
|
|
DirtyEntity(ent, meta);
|
|
ent.Comp1.LastModifiedTick = CurrentTick;
|
|
ent.Comp2.LastModifiedTick = CurrentTick;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual void Dirty<T1, T2, T3>(Entity<T1, T2, T3> ent, MetaDataComponent? meta = null)
|
|
where T1 : IComponent
|
|
where T2 : IComponent
|
|
where T3 : IComponent
|
|
{
|
|
DebugTools.Assert(ent.Comp1.GetType().HasCustomAttribute<NetworkedComponentAttribute>(),
|
|
$"Attempted to dirty a non-networked component: {ent.Comp1.GetType()}");
|
|
DebugTools.Assert(ent.Comp2.GetType().HasCustomAttribute<NetworkedComponentAttribute>(),
|
|
$"Attempted to dirty a non-networked component: {ent.Comp2.GetType()}");
|
|
DebugTools.Assert(ent.Comp3.GetType().HasCustomAttribute<NetworkedComponentAttribute>(),
|
|
$"Attempted to dirty a non-networked component: {ent.Comp3.GetType()}");
|
|
|
|
// We're not gonna bother checking ent.Comp.NetSyncEnabled
|
|
// chances are at least one of these components didn't get net-sync disabled.
|
|
DirtyEntity(ent, meta);
|
|
ent.Comp1.LastModifiedTick = CurrentTick;
|
|
ent.Comp2.LastModifiedTick = CurrentTick;
|
|
ent.Comp3.LastModifiedTick = CurrentTick;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual void Dirty<T1, T2, T3, T4>(Entity<T1, T2, T3, T4> ent, MetaDataComponent? meta = null)
|
|
where T1 : IComponent
|
|
where T2 : IComponent
|
|
where T3 : IComponent
|
|
where T4 : IComponent
|
|
{
|
|
DebugTools.Assert(ent.Comp1.GetType().HasCustomAttribute<NetworkedComponentAttribute>(),
|
|
$"Attempted to dirty a non-networked component: {ent.Comp1.GetType()}");
|
|
DebugTools.Assert(ent.Comp2.GetType().HasCustomAttribute<NetworkedComponentAttribute>(),
|
|
$"Attempted to dirty a non-networked component: {ent.Comp2.GetType()}");
|
|
DebugTools.Assert(ent.Comp3.GetType().HasCustomAttribute<NetworkedComponentAttribute>(),
|
|
$"Attempted to dirty a non-networked component: {ent.Comp3.GetType()}");
|
|
DebugTools.Assert(ent.Comp4.GetType().HasCustomAttribute<NetworkedComponentAttribute>(),
|
|
$"Attempted to dirty a non-networked component: {ent.Comp4.GetType()}");
|
|
|
|
// We're not gonna bother checking ent.Comp.NetSyncEnabled
|
|
// chances are at least one of these components didn't get net-sync disabled.
|
|
DirtyEntity(ent, meta);
|
|
ent.Comp1.LastModifiedTick = CurrentTick;
|
|
ent.Comp2.LastModifiedTick = CurrentTick;
|
|
ent.Comp3.LastModifiedTick = CurrentTick;
|
|
ent.Comp4.LastModifiedTick = CurrentTick;
|
|
}
|
|
|
|
public bool TryQueueDeleteEntity(EntityUid? uid)
|
|
{
|
|
if (uid == null)
|
|
return false;
|
|
|
|
if (Deleted(uid.Value))
|
|
return false;
|
|
|
|
if (QueuedDeletionsSet.Contains(uid.Value))
|
|
return false;
|
|
|
|
QueueDeleteEntity(uid);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Shuts-down and removes given Entity. This is also broadcast to all clients.
|
|
/// </summary>
|
|
public virtual void DeleteEntity(EntityUid? uid)
|
|
{
|
|
if (uid == null)
|
|
return;
|
|
|
|
// Some UIs get disposed after entity-manager has shut down and already deleted all entities.
|
|
if (!Started)
|
|
return;
|
|
|
|
// Networking blindly spams entities at this function, they can already be
|
|
// deleted from being a child of a previously deleted entity
|
|
// TODO: Why does networking need to send deletes for child entities?
|
|
if (MetaQuery.TryGetComponent(uid.Value, out var meta))
|
|
DeleteEntity(uid.Value, meta, TransformQuery.GetComponent(uid.Value));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Shuts-down and removes given Entity. This is also broadcast to all clients.
|
|
/// </summary>
|
|
public void DeleteEntity(EntityUid e, MetaDataComponent meta, TransformComponent xform)
|
|
{
|
|
// Some UIs get disposed after entity-manager has shut down and already deleted all entities.
|
|
if (!Started)
|
|
return;
|
|
|
|
ThreadCheck();
|
|
|
|
if (meta.EntityLifeStage >= EntityLifeStage.Deleted)
|
|
return;
|
|
|
|
if (meta.EntityLifeStage == EntityLifeStage.Terminating)
|
|
{
|
|
var msg = $"Called Delete on an entity already being deleted. Entity: {ToPrettyString(e)}";
|
|
#if !EXCEPTION_TOLERANCE
|
|
throw new InvalidOperationException(msg);
|
|
#else
|
|
_sawmill.Error($"{msg}. Trace: {Environment.StackTrace}");
|
|
#endif
|
|
}
|
|
|
|
// Notify all entities they are being terminated prior to detaching & deleting
|
|
RecursiveFlagEntityTermination(e, meta, xform);
|
|
|
|
TransformComponent? parentXform = null;
|
|
if (xform.ParentUid.IsValid())
|
|
{
|
|
if (xform.LifeStage < ComponentLifeStage.Initialized)
|
|
{
|
|
// Entity is being deleted before initialization ever finished.
|
|
// The entity will not yet have been added to the parent's transform component.
|
|
// This is seemingly pretty error prone ATM, and I'm not even sure if it should be supported?
|
|
|
|
// Just in case it HAS somehow been added, make sure we remove it.
|
|
if (TransformQuery.TryComp(xform.ParentUid, out parentXform) && parentXform._children.Remove(e))
|
|
DebugTools.Assert($"Child entity {ToPrettyString(e)} was added to the parent's child set prior to being initialized?");
|
|
|
|
parentXform = null;
|
|
xform._parent = EntityUid.Invalid;
|
|
xform._anchored = false;
|
|
}
|
|
else
|
|
{
|
|
// Use resolve for automatic error logging.
|
|
// ReSharper disable once ReturnValueOfPureMethodIsNotUsed
|
|
TransformQuery.Resolve(xform.ParentUid, ref parentXform);
|
|
}
|
|
}
|
|
|
|
// Then actually delete them
|
|
RecursiveDeleteEntity(e, meta, xform, parentXform);
|
|
}
|
|
|
|
private void RecursiveFlagEntityTermination(EntityUid uid,
|
|
MetaDataComponent metadata,
|
|
TransformComponent xform)
|
|
{
|
|
DebugTools.Assert(metadata.EntityLifeStage < EntityLifeStage.Terminating);
|
|
SetLifeStage(metadata, EntityLifeStage.Terminating);
|
|
|
|
try
|
|
{
|
|
var ev = new EntityTerminatingEvent((uid, metadata));
|
|
BeforeEntityTerminating?.Invoke(ref ev);
|
|
EventBusInternal.RaiseLocalEvent(uid, ref ev, true);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_sawmill.Error($"Caught exception while raising event {nameof(EntityTerminatingEvent)} on entity {ToPrettyString(uid, metadata)}\n{e}");
|
|
#if !EXCEPTION_TOLERANCE
|
|
throw;
|
|
#endif
|
|
}
|
|
|
|
foreach (var child in xform._children)
|
|
{
|
|
if (!MetaQuery.TryGetComponent(child, out var childMeta) || childMeta.EntityDeleted)
|
|
{
|
|
_sawmill.Error($"A deleted entity was still the transform child of another entity. Parent: {ToPrettyString(uid, metadata)}.");
|
|
xform._children.Remove(child);
|
|
continue;
|
|
}
|
|
|
|
RecursiveFlagEntityTermination(child, childMeta, TransformQuery.GetComponent(child));
|
|
}
|
|
}
|
|
|
|
private void RecursiveDeleteEntity(
|
|
EntityUid uid,
|
|
MetaDataComponent metadata,
|
|
TransformComponent transform,
|
|
TransformComponent? parentXform)
|
|
{
|
|
DebugTools.Assert(transform.ParentUid.IsValid() == (parentXform != null));
|
|
DebugTools.Assert(parentXform == null || parentXform._children.Contains(uid));
|
|
|
|
// Note about this method: #if EXCEPTION_TOLERANCE is not used here because we're gonna it in the future...
|
|
|
|
// Detach the base entity to null before iterating over children
|
|
// This also ensures that the entity-lookup updates don't have to be re-run for every child (which recurses up the transform hierarchy).
|
|
_xforms.DetachEntity(uid, transform, metadata, parentXform, true);
|
|
|
|
foreach (var child in transform._children)
|
|
{
|
|
try
|
|
{
|
|
var childMeta = MetaQuery.GetComponent(child);
|
|
var childXform = TransformQuery.GetComponent(child);
|
|
DebugTools.AssertEqual(childXform.ParentUid, uid);
|
|
RecursiveDeleteEntity(child, childMeta, childXform, transform);
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
_sawmill.Error($"Caught exception while trying to recursively delete child entity '{ToPrettyString(child)}' of '{ToPrettyString(uid, metadata)}'\n{e}");
|
|
#if !EXCEPTION_TOLERANCE
|
|
throw;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if (transform._children.Count != 0)
|
|
_sawmill.Error($"Failed to delete all children of entity: {ToPrettyString(uid)}");
|
|
|
|
// Shut down all components.
|
|
foreach (var component in InSafeOrder(_entCompIndex[uid]))
|
|
{
|
|
if (component.Running)
|
|
{
|
|
try
|
|
{
|
|
LifeShutdown(uid, component, _componentFactory.GetIndex(component.GetType()));
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_sawmill.Error($"Caught exception while trying to call shutdown on component of entity '{ToPrettyString(uid, metadata)}'\n{e}");
|
|
#if !EXCEPTION_TOLERANCE
|
|
throw;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
// Dispose all my components, in a safe order so transform is available
|
|
DisposeComponents(uid, metadata);
|
|
SetLifeStage(metadata, EntityLifeStage.Deleted);
|
|
|
|
try
|
|
{
|
|
EntityDeleted?.Invoke((uid, metadata));
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_sawmill.Error($"Caught exception while invoking event {nameof(EntityDeleted)} on '{ToPrettyString(uid, metadata)}'\n{e}");
|
|
#if !EXCEPTION_TOLERANCE
|
|
throw;
|
|
#endif
|
|
}
|
|
|
|
EventBusInternal.OnEntityDeleted(uid);
|
|
Entities.Remove(uid);
|
|
// Need to get the ID above before MetadataComponent shutdown but only remove it after everything else is done.
|
|
NetEntityLookup.Remove(metadata.NetEntity);
|
|
}
|
|
|
|
public virtual void QueueDeleteEntity(EntityUid? uid)
|
|
{
|
|
if (uid == null)
|
|
return;
|
|
|
|
if (!QueuedDeletionsSet.Add(uid.Value))
|
|
return;
|
|
|
|
QueuedDeletions.Enqueue(uid.Value);
|
|
EntityQueueDeleted?.Invoke(uid.Value);
|
|
}
|
|
|
|
public virtual bool IsQueuedForDeletion(EntityUid uid) => QueuedDeletionsSet.Contains(uid);
|
|
|
|
/// <inheritdoc />
|
|
public virtual void PredictedDeleteEntity(Entity<MetaDataComponent?, TransformComponent?> ent)
|
|
{
|
|
DeleteEntity(ent.Owner);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void PredictedDeleteEntity(Entity<MetaDataComponent?, TransformComponent?>? ent)
|
|
{
|
|
if (ent == null)
|
|
return;
|
|
|
|
PredictedDeleteEntity(ent.Value);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual void PredictedQueueDeleteEntity(Entity<MetaDataComponent?> ent)
|
|
{
|
|
QueueDeleteEntity(ent);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void PredictedQueueDeleteEntity(Entity<MetaDataComponent?>? ent)
|
|
{
|
|
if (ent != null)
|
|
PredictedQueueDeleteEntity(ent.Value);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
[Obsolete("use variant without TransformComponent")]
|
|
public void PredictedQueueDeleteEntity(Entity<MetaDataComponent?, TransformComponent?> ent)
|
|
{
|
|
PredictedQueueDeleteEntity(new Entity<MetaDataComponent?>(ent.Owner, ent.Comp1));
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
[Obsolete("use variant without TransformComponent")]
|
|
public void PredictedQueueDeleteEntity(Entity<MetaDataComponent?, TransformComponent?>? ent)
|
|
{
|
|
if (ent != null)
|
|
PredictedQueueDeleteEntity(new Entity<MetaDataComponent?>(ent.Value.Owner, ent.Value.Comp1));
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void PredictedQueueDeleteEntity(EntityUid uid)
|
|
{
|
|
PredictedQueueDeleteEntity(new Entity<MetaDataComponent?>(uid, null));
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void PredictedQueueDeleteEntity(EntityUid? uid)
|
|
{
|
|
if (uid != null)
|
|
PredictedQueueDeleteEntity(new Entity<MetaDataComponent?>(uid.Value, null));
|
|
}
|
|
|
|
public bool EntityExists(EntityUid uid)
|
|
{
|
|
return MetaQuery.HasComponentInternal(uid);
|
|
}
|
|
|
|
public bool EntityExists(EntityUid? uid)
|
|
{
|
|
return uid.HasValue && EntityExists(uid.Value);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public bool IsPaused(EntityUid? uid, MetaDataComponent? metadata = null)
|
|
{
|
|
if (uid == null)
|
|
return false;
|
|
|
|
return MetaQuery.Resolve(uid.Value, ref metadata) && metadata.EntityPaused;
|
|
}
|
|
|
|
public bool Deleted(EntityUid uid)
|
|
{
|
|
return !MetaQuery.TryGetComponentInternal(uid, out var comp) || comp.EntityDeleted;
|
|
}
|
|
|
|
public bool Deleted([NotNullWhen(false)] EntityUid? uid)
|
|
{
|
|
return !uid.HasValue || !MetaQuery.TryGetComponentInternal(uid.Value, out var comp) || comp.EntityDeleted;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disposes all entities and clears all lists.
|
|
/// </summary>
|
|
public virtual void FlushEntities()
|
|
{
|
|
_sawmill.Info($"Flushing entities. Entity count: {Entities.Count}");
|
|
BeforeEntityFlush?.Invoke();
|
|
FlushEntitiesInternal();
|
|
|
|
if (Entities.Count != 0)
|
|
{
|
|
_sawmill.Error($"Failed to flush all entities. Entity count: {Entities.Count}");
|
|
// Dump entity info, but avoid dumping ~50k errors if for whatever reason we failed to delete almost all entities.
|
|
// Using 512 as the limit, in case the problem entities are related to player counts on high-pop servers.
|
|
if (Entities.Count < 512)
|
|
{
|
|
foreach (var uid in Entities)
|
|
{
|
|
_sawmill.Error($"Entity exists after flush: {ToPrettyString(uid)}");
|
|
}
|
|
}
|
|
}
|
|
|
|
#if EXCEPTION_TOLERANCE
|
|
// Attempt to flush entities a second time, just in case something somehow caused an entity to be spawned
|
|
// while flushing entities
|
|
FlushEntitiesInternal();
|
|
#endif
|
|
|
|
if (Entities.Count != 0)
|
|
throw new Exception($"Failed to flush all entities. Entity count: {Entities.Count}");
|
|
|
|
AfterEntityFlush?.Invoke();
|
|
}
|
|
|
|
private void FlushEntitiesInternal()
|
|
{
|
|
QueuedDeletions.Clear();
|
|
QueuedDeletionsSet.Clear();
|
|
|
|
// First, we directly delete all maps. This will delete most entities while reducing the number of component
|
|
// lookups
|
|
|
|
var maps = _entTraitDict[typeof(MapComponent)].Keys.ToArray();
|
|
foreach (var map in maps)
|
|
{
|
|
try
|
|
{
|
|
DeleteEntity(map);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_sawmill.Log(LogLevel.Error, e,
|
|
$"Caught exception while trying to delete map entity {ToPrettyString(map)}, this might corrupt the game state...");
|
|
#if !EXCEPTION_TOLERANCE
|
|
throw;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// Then delete all other entities.
|
|
var ents = _entTraitDict[typeof(MetaDataComponent)].ToArray();
|
|
DebugTools.Assert(ents.Length == Entities.Count);
|
|
foreach (var (uid, comp) in ents)
|
|
{
|
|
var meta = (MetaDataComponent) comp;
|
|
if (meta.EntityLifeStage >= EntityLifeStage.Terminating)
|
|
continue;
|
|
|
|
try
|
|
{
|
|
DeleteEntity(uid, meta, TransformQuery.GetComponent(uid));
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_sawmill.Log(LogLevel.Error, e,
|
|
$"Caught exception while trying to delete entity {ToPrettyString(uid, meta)}, this might corrupt the game state...");
|
|
#if !EXCEPTION_TOLERANCE
|
|
throw;
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allocates an entity and stores it but does not load components or do initialization.
|
|
/// </summary>
|
|
protected internal EntityUid AllocEntity(
|
|
EntityPrototype? prototype,
|
|
out MetaDataComponent metadata)
|
|
{
|
|
var entity = AllocEntity(out metadata);
|
|
metadata._entityPrototype = prototype;
|
|
Dirty(entity, metadata, metadata);
|
|
return entity;
|
|
}
|
|
|
|
/// <inheritdoc cref="AllocEntity(Robust.Shared.Prototypes.EntityPrototype?,out Robust.Shared.GameObjects.MetaDataComponent)"/>
|
|
internal EntityUid AllocEntity(EntityPrototype? prototype) => AllocEntity(prototype, out _);
|
|
|
|
/// <summary>
|
|
/// Allocates an entity and stores it but does not load components or do initialization.
|
|
/// </summary>
|
|
private EntityUid AllocEntity(out MetaDataComponent metadata)
|
|
{
|
|
ThreadCheck();
|
|
|
|
var uid = GenerateEntityUid();
|
|
|
|
#if DEBUG
|
|
if (EntityExists(uid))
|
|
{
|
|
throw new InvalidOperationException($"UID already taken: {uid}");
|
|
}
|
|
#endif
|
|
|
|
metadata = new MetaDataComponent
|
|
{
|
|
#pragma warning disable CS0618
|
|
Owner = uid,
|
|
#pragma warning restore CS0618
|
|
EntityLastModifiedTick = _gameTiming.CurTick
|
|
};
|
|
|
|
var netEntity = GenerateNetEntity();
|
|
SetNetEntity(uid, netEntity, metadata);
|
|
|
|
// we want this called before adding components
|
|
EntityAdded?.Invoke((uid, metadata));
|
|
EventBusInternal.OnEntityAdded(uid);
|
|
|
|
Entities.Add(uid);
|
|
// add the required MetaDataComponent directly.
|
|
AddComponentInternal(uid, metadata, _metaReg, false, true, metadata);
|
|
|
|
// allocate the required TransformComponent
|
|
var xformComp = Unsafe.As<TransformComponent>(_componentFactory.GetComponent(_xformReg));
|
|
#pragma warning disable CS0618 // Type or member is obsolete
|
|
xformComp.Owner = uid;
|
|
#pragma warning restore CS0618 // Type or member is obsolete
|
|
AddComponentInternal(uid, xformComp, false, true, metadata);
|
|
|
|
return uid;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allocates an entity and loads components but does not do initialization.
|
|
/// </summary>
|
|
internal virtual EntityUid CreateEntity(string? prototypeName, out MetaDataComponent metadata, IEntityLoadContext? context = null)
|
|
{
|
|
if (prototypeName == null)
|
|
return AllocEntity(out metadata);
|
|
|
|
if (!PrototypeManager.TryIndex<EntityPrototype>(prototypeName, out var prototype))
|
|
throw new EntityCreationException($"Attempted to spawn an entity with an invalid prototype: {prototypeName}");
|
|
|
|
return CreateEntity(prototype, out metadata, context);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allocates an entity and loads components but does not do initialization.
|
|
/// </summary>
|
|
private protected EntityUid CreateEntity(EntityPrototype prototype, out MetaDataComponent metadata, IEntityLoadContext? context = null)
|
|
{
|
|
var entity = AllocEntity(prototype, out metadata);
|
|
try
|
|
{
|
|
EntityPrototype.LoadEntity((entity, metadata), ComponentFactory, this, _serManager, context);
|
|
return entity;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// Exception during entity loading.
|
|
// Need to delete the entity to avoid corrupt state causing crashes later.
|
|
DeleteEntity(entity);
|
|
throw new EntityCreationException($"Exception inside CreateEntity with prototype {prototype.ID}", e);
|
|
}
|
|
}
|
|
|
|
public void InitializeAndStartEntity(EntityUid entity, MapId? mapId = null)
|
|
{
|
|
var doMapInit = _mapSystem.IsInitialized(mapId ?? TransformQuery.GetComponent(entity).MapID);
|
|
InitializeAndStartEntity(entity, doMapInit);
|
|
}
|
|
|
|
public void InitializeAndStartEntity(Entity<MetaDataComponent?> entity, bool doMapInit)
|
|
{
|
|
if (!MetaQuery.Resolve(entity.Owner, ref entity.Comp))
|
|
return;
|
|
|
|
try
|
|
{
|
|
InitializeEntity(entity.Owner, entity.Comp);
|
|
StartEntity(entity.Owner);
|
|
|
|
if (doMapInit)
|
|
RunMapInit(entity.Owner, entity.Comp);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
DeleteEntity(entity);
|
|
throw new EntityCreationException("Exception inside InitializeAndStartEntity", e);
|
|
}
|
|
}
|
|
|
|
public void InitializeEntity(EntityUid entity, MetaDataComponent? meta = null)
|
|
{
|
|
// Ideally, entities only ever get initialized once their parent has already been initialized.
|
|
// Note that this doesn't guarantee that an uninitialized entity will never have initialized children.
|
|
// In particular, for the client this might happen when applying a new game state that re-parents an
|
|
// existing entity to a newly created entity. The new entity only gets initialiuzed & started at the end,
|
|
// after the old/existing entity was already moved to the new parent.
|
|
DebugTools.Assert(TransformQuery.GetComponent(entity).ParentUid is not { Valid: true } parent
|
|
|| MetaQuery.GetComponent(parent).EntityLifeStage >= EntityLifeStage.Initialized);
|
|
|
|
DebugTools.AssertOwner(entity, meta);
|
|
meta ??= GetComponent<MetaDataComponent>(entity);
|
|
#pragma warning disable CS0618 // Type or member is obsolete
|
|
InitializeComponents(entity, meta);
|
|
#pragma warning restore CS0618 // Type or member is obsolete
|
|
EntityInitialized?.Invoke((entity, meta));
|
|
}
|
|
|
|
public void StartEntity(EntityUid entity)
|
|
{
|
|
// Ideally, entities only ever get initialized once their parent has already been initialized.
|
|
// Note that this doesn't guarantee that an uninitialized entity will never have initialized children.
|
|
// In particular, for the client this might happen when applying a new game state that re-parents an
|
|
// existing entity to a newly created entity. The new entity only gets initialiuzed & started at the end,
|
|
// after the old/existing entity was already moved to the new parent.
|
|
DebugTools.Assert(TransformQuery.GetComponent(entity).ParentUid is not { Valid: true } parent
|
|
|| MetaQuery.GetComponent(parent).EntityLifeStage >= EntityLifeStage.Initialized);
|
|
|
|
#pragma warning disable CS0618 // Type or member is obsolete
|
|
StartComponents(entity);
|
|
#pragma warning restore CS0618 // Type or member is obsolete
|
|
}
|
|
|
|
public void RunMapInit(EntityUid entity, MetaDataComponent meta)
|
|
{
|
|
if (meta.EntityLifeStage == EntityLifeStage.MapInitialized)
|
|
return; // Already map initialized, do nothing.
|
|
|
|
DebugTools.Assert(meta.EntityLifeStage == EntityLifeStage.Initialized, $"Expected entity {ToPrettyString(entity)} to be initialized, was {meta.EntityLifeStage}");
|
|
SetLifeStage(meta, EntityLifeStage.MapInitialized);
|
|
|
|
EventBusInternal.RaiseLocalEvent(entity, MapInitEventInstance);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
[return: NotNullIfNotNull("uid")]
|
|
public EntityStringRepresentation? ToPrettyString(EntityUid? uid, MetaDataComponent? metadata = null)
|
|
{
|
|
return uid == null ? null : ToPrettyString(uid.Value, metadata);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public EntityStringRepresentation ToPrettyString(EntityUid uid, MetaDataComponent? metadata)
|
|
=> ToPrettyString((uid, metadata));
|
|
|
|
/// <inheritdoc />
|
|
public EntityStringRepresentation ToPrettyString(Entity<MetaDataComponent?> entity)
|
|
{
|
|
if (entity.Comp == null && !MetaQuery.Resolve(entity.Owner, ref entity.Comp, false))
|
|
return new EntityStringRepresentation(entity.Owner, default, true);
|
|
|
|
return new EntityStringRepresentation(entity.Owner, entity.Comp, _actorQuery.CompOrNull(entity));
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
[return: NotNullIfNotNull("netEntity")]
|
|
public EntityStringRepresentation? ToPrettyString(NetEntity? netEntity)
|
|
{
|
|
return netEntity == null ? null : ToPrettyString(netEntity.Value);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public EntityStringRepresentation ToPrettyString(NetEntity netEntity)
|
|
{
|
|
if (!TryGetEntityData(netEntity, out var uid, out var meta))
|
|
return new EntityStringRepresentation(EntityUid.Invalid, netEntity, true);
|
|
|
|
return ToPrettyString(uid.Value, meta);
|
|
}
|
|
|
|
#endregion Entity Management
|
|
|
|
public virtual void RaisePredictiveEvent<T>(T msg) where T : EntityEventArgs
|
|
{
|
|
// Part of shared the EntityManager so that systems can have convenient proxy methods, but the
|
|
// server should never be calling this.
|
|
DebugTools.Assert("Why are you raising predictive events on the server?");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises an event locally on client or networked on server.
|
|
/// </summary>
|
|
public abstract void RaiseSharedEvent<T>(T message, EntityUid? user = null) where T : EntityEventArgs;
|
|
|
|
/// <summary>
|
|
/// Raises an event locally on client or networked on server.
|
|
/// </summary>
|
|
public abstract void RaiseSharedEvent<T>(T message, ICommonSession? user = null) where T : EntityEventArgs;
|
|
|
|
/// <summary>
|
|
/// Factory for generating a new EntityUid for an entity currently being created.
|
|
/// </summary>
|
|
internal EntityUid GenerateEntityUid()
|
|
{
|
|
return new EntityUid(NextEntityUid++);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a unique network id and increments <see cref="NextNetworkId"/>
|
|
/// </summary>
|
|
protected virtual NetEntity GenerateNetEntity() => new(NextNetworkId++);
|
|
|
|
[Conditional("DEBUG")]
|
|
protected void ThreadCheck()
|
|
{
|
|
#if DEBUG
|
|
DebugTools.Assert(
|
|
Environment.CurrentManagedThreadId == _mainThreadId,
|
|
"Environment.CurrentManagedThreadId == _mainThreadId");
|
|
#endif
|
|
}
|
|
}
|
|
|
|
public enum EntityMessageType : byte
|
|
{
|
|
Error = 0,
|
|
SystemMessage
|
|
}
|
|
}
|