mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
800 lines
29 KiB
C#
800 lines
29 KiB
C#
using Prometheus;
|
|
using Robust.Shared.Log;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Profiling;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Serialization.Manager;
|
|
using Robust.Shared.Timing;
|
|
using Robust.Shared.Utility;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using Robust.Shared.Serialization.Markdown.Mapping;
|
|
|
|
namespace Robust.Shared.GameObjects
|
|
{
|
|
public delegate void EntityUidQueryCallback(EntityUid uid);
|
|
|
|
public delegate void ComponentQueryCallback<T>(EntityUid uid, T component) where T : Component;
|
|
|
|
/// <inheritdoc />
|
|
[Virtual]
|
|
public partial class EntityManager : IEntityManager
|
|
{
|
|
#region Dependencies
|
|
|
|
[IoC.Dependency] protected readonly IPrototypeManager PrototypeManager = default!;
|
|
[IoC.Dependency] protected readonly ILogManager LogManager = default!;
|
|
[IoC.Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
|
[IoC.Dependency] private readonly IMapManager _mapManager = default!;
|
|
[IoC.Dependency] private readonly IGameTiming _gameTiming = default!;
|
|
[IoC.Dependency] private readonly ISerializationManager _serManager = default!;
|
|
[IoC.Dependency] private readonly ProfManager _prof = default!;
|
|
|
|
// I feel like PJB might shed me for putting a system dependency here, but its required for setting entity
|
|
// positions on spawn....
|
|
private SharedTransformSystem _xforms = default!;
|
|
|
|
protected EntityQuery<MetaDataComponent> MetaQuery;
|
|
private EntityQuery<TransformComponent> _xformQuery;
|
|
|
|
#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 virtual IEntityNetworkManager? EntityNetManager => null;
|
|
|
|
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();
|
|
|
|
private EntityEventBus _eventBus = null!;
|
|
|
|
protected int NextEntityUid = (int) EntityUid.FirstUid;
|
|
|
|
protected int NextNetworkId = (int) NetEntity.First;
|
|
|
|
/// <inheritdoc />
|
|
public IEventBus EventBus => _eventBus;
|
|
|
|
public event Action<EntityUid>? EntityAdded;
|
|
public event Action<EntityUid>? EntityInitialized;
|
|
public event Action<EntityUid, MetaDataComponent>? EntityDeleted;
|
|
|
|
/// <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<EntityUid>? EntityDirtied; // only raised after initialization
|
|
|
|
private string _xformName = string.Empty;
|
|
|
|
private SharedMapSystem _mapSystem = default!;
|
|
|
|
private ISawmill _sawmill = default!;
|
|
private ISawmill _resolveSawmill = default!;
|
|
|
|
public bool Started { get; protected set; }
|
|
|
|
public bool ShuttingDown { get; protected set; }
|
|
|
|
public bool Initialized { get; protected set; }
|
|
|
|
/// <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");
|
|
|
|
_eventBus = new EntityEventBus(this);
|
|
|
|
InitializeComponents();
|
|
_xformName = _componentFactory.GetComponentName(typeof(TransformComponent));
|
|
_sawmill = LogManager.GetSawmill("entity");
|
|
_resolveSawmill = LogManager.GetSawmill("resolve");
|
|
|
|
Initialized = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the entity's data (apart from transform) is default.
|
|
/// </summary>
|
|
public bool IsDefault(EntityUid uid)
|
|
{
|
|
if (!MetaQuery.TryGetComponent(uid, out var metadata) || metadata.EntityPrototype == null)
|
|
return false;
|
|
|
|
var prototype = metadata.EntityPrototype;
|
|
|
|
// Prototype may or may not have metadata / transforms
|
|
var protoComps = prototype.Components.Keys.ToList();
|
|
|
|
protoComps.Remove(_xformName);
|
|
|
|
// Fast check if the component counts match.
|
|
if (protoComps.Count != ComponentCount(uid) - 2)
|
|
return false;
|
|
|
|
// Check if entity name / description match
|
|
if (metadata.EntityName != prototype.Name ||
|
|
metadata.EntityDescription != prototype.Description)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Get default prototype data
|
|
Dictionary<string, MappingDataNode> protoData = new();
|
|
try
|
|
{
|
|
_context.WritingReadingPrototypes = true;
|
|
|
|
foreach (var compType in protoComps)
|
|
{
|
|
if (compType == _xformName)
|
|
continue;
|
|
|
|
var comp = prototype.Components[compType];
|
|
protoData.Add(compType, _serManager.WriteValueAs<MappingDataNode>(comp.Component.GetType(), comp.Component, alwaysWrite: true, context: _context));
|
|
}
|
|
|
|
_context.WritingReadingPrototypes = false;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_sawmill.Error($"Failed to convert prototype {prototype.ID} into yaml. Exception: {e.Message}");
|
|
return false;
|
|
}
|
|
|
|
var comps = new HashSet<IComponent>(GetComponents(uid));
|
|
var compNames = new HashSet<string>(protoComps.Count);
|
|
foreach (var component in comps)
|
|
{
|
|
var compType = component.GetType();
|
|
var compName = _componentFactory.GetComponentName(compType);
|
|
|
|
if (compType == typeof(MetaDataComponent) || compType == typeof(TransformComponent))
|
|
continue;
|
|
|
|
compNames.Add(compName);
|
|
|
|
// If the component isn't on the prototype then it's custom.
|
|
if (!protoComps.Contains(compName))
|
|
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}");
|
|
return false;
|
|
}
|
|
|
|
if (protoData.TryGetValue(compName, out var protoMapping))
|
|
{
|
|
var diff = compMapping.Except(protoMapping);
|
|
|
|
if (diff != null && diff.Children.Count != 0)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// An entity may also remove components on init -> check no components are missing.
|
|
foreach (var compType in protoComps)
|
|
{
|
|
if (!compNames.Contains(compType))
|
|
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;
|
|
_eventBus.CalcOrdering();
|
|
_mapSystem = System<SharedMapSystem>();
|
|
_xforms = System<SharedTransformSystem>();
|
|
MetaQuery = GetEntityQuery<MetaDataComponent>();
|
|
_xformQuery = GetEntityQuery<TransformComponent>();
|
|
}
|
|
|
|
public virtual void Shutdown()
|
|
{
|
|
ShuttingDown = true;
|
|
FlushEntities();
|
|
_eventBus.ClearEventTables();
|
|
_entitySystemManager.Shutdown();
|
|
ClearComponents();
|
|
ShuttingDown = false;
|
|
Started = false;
|
|
}
|
|
|
|
public virtual void Cleanup()
|
|
{
|
|
_componentFactory.ComponentAdded -= OnComponentAdded;
|
|
_componentFactory.ComponentReferenceAdded -= OnComponentReferenceAdded;
|
|
ShuttingDown = true;
|
|
FlushEntities();
|
|
_entitySystemManager.Clear();
|
|
_eventBus.Dispose();
|
|
_eventBus = 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"))
|
|
{
|
|
_eventBus.ProcessEventQueue();
|
|
}
|
|
|
|
using (histogram?.WithLabels("QueuedDeletion").NewTimer())
|
|
using (_prof.Group("QueueDel"))
|
|
{
|
|
while (QueuedDeletions.TryDequeue(out var uid))
|
|
{
|
|
DeleteEntity(uid);
|
|
}
|
|
|
|
QueuedDeletionsSet.Clear();
|
|
}
|
|
|
|
using (histogram?.WithLabels("ComponentCull").NewTimer())
|
|
using (_prof.Group("ComponentCull"))
|
|
{
|
|
CullRemovedComponents();
|
|
}
|
|
}
|
|
|
|
public virtual void FrameUpdate(float frameTime)
|
|
{
|
|
_entitySystemManager.FrameUpdate(frameTime);
|
|
}
|
|
|
|
#region Entity Management
|
|
|
|
public EntityUid CreateEntityUninitialized(string? prototypeName, EntityUid euid, ComponentRegistry? overrides = null)
|
|
{
|
|
return CreateEntity(prototypeName, overrides);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, ComponentRegistry? overrides = null)
|
|
{
|
|
return CreateEntity(prototypeName, overrides);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, EntityCoordinates coordinates, ComponentRegistry? overrides = null)
|
|
{
|
|
var newEntity = CreateEntity(prototypeName, overrides);
|
|
_xforms.SetCoordinates(newEntity, _xformQuery.GetComponent(newEntity), coordinates, unanchor: false);
|
|
return newEntity;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual EntityUid CreateEntityUninitialized(string? prototypeName, MapCoordinates coordinates, ComponentRegistry? overrides = null)
|
|
{
|
|
var newEntity = CreateEntity(prototypeName, overrides);
|
|
var transform = _xformQuery.GetComponent(newEntity);
|
|
|
|
if (coordinates.MapId == MapId.Nullspace)
|
|
{
|
|
DebugTools.Assert(_mapManager.GetMapEntityId(coordinates.MapId) == EntityUid.Invalid);
|
|
transform._parent = EntityUid.Invalid;
|
|
transform.Anchored = false;
|
|
return newEntity;
|
|
}
|
|
|
|
var mapEnt = _mapManager.GetMapEntityId(coordinates.MapId);
|
|
if (!TryGetComponent(mapEnt, out TransformComponent? mapXform))
|
|
throw new ArgumentException($"Attempted to spawn entity on an invalid map. Coordinates: {coordinates}");
|
|
|
|
EntityCoordinates coords;
|
|
if (transform.Anchored && _mapManager.TryFindGridAt(coordinates, out var gridUid, out var grid))
|
|
{
|
|
coords = new EntityCoordinates(gridUid, _mapSystem.WorldToLocal(gridUid, grid, coordinates.Position));
|
|
_xforms.SetCoordinates(newEntity, transform, coords, unanchor: false);
|
|
}
|
|
else
|
|
{
|
|
coords = new EntityCoordinates(mapEnt, coordinates.Position);
|
|
_xforms.SetCoordinates(newEntity, transform, coords, null, 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 (metadata == null)
|
|
{
|
|
if (!_entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()].TryGetValue(uid, out var component))
|
|
throw new KeyNotFoundException($"Entity {uid} does not exist, cannot dirty it.");
|
|
metadata = (MetaDataComponent)component;
|
|
}
|
|
else
|
|
{
|
|
#pragma warning disable CS0618
|
|
DebugTools.Assert(metadata.Owner == uid);
|
|
#pragma warning restore CS0618
|
|
}
|
|
|
|
if (metadata.EntityLastModifiedTick == _gameTiming.CurTick)
|
|
return;
|
|
|
|
metadata.EntityLastModifiedTick = _gameTiming.CurTick;
|
|
|
|
if (metadata.EntityLifeStage > EntityLifeStage.Initializing)
|
|
{
|
|
EntityDirtied?.Invoke(uid);
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
[Obsolete("use override with an EntityUid")]
|
|
public void Dirty(Component component, MetaDataComponent? meta = null)
|
|
{
|
|
Dirty(component.Owner, component, meta);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual void Dirty(EntityUid uid, Component component, MetaDataComponent? meta = null)
|
|
{
|
|
if (component.LifeStage >= ComponentLifeStage.Removing || !component.NetSyncEnabled)
|
|
return;
|
|
|
|
DirtyEntity(uid, meta);
|
|
component.LastModifiedTick = CurrentTick;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Shuts-down and removes given Entity. This is also broadcast to all clients.
|
|
/// </summary>
|
|
/// <param name="e">Entity to remove</param>
|
|
public virtual void DeleteEntity(EntityUid e)
|
|
{
|
|
// 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(e, out var meta) || meta.EntityDeleted)
|
|
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);
|
|
|
|
// Then actually delete them
|
|
RecursiveDeleteEntity(e, meta);
|
|
}
|
|
|
|
private void RecursiveFlagEntityTermination(
|
|
EntityUid uid,
|
|
MetaDataComponent metadata)
|
|
{
|
|
var transform = _xformQuery.GetComponent(uid);
|
|
metadata.EntityLifeStage = EntityLifeStage.Terminating;
|
|
|
|
try
|
|
{
|
|
var ev = new EntityTerminatingEvent(uid);
|
|
EventBus.RaiseLocalEvent(uid, ref ev, true);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_sawmill.Error($"Caught exception while raising event {nameof(EntityTerminatingEvent)} on entity {ToPrettyString(uid, metadata)}\n{e}");
|
|
}
|
|
|
|
foreach (var child in transform._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)}.");
|
|
transform._children.Remove(child);
|
|
continue;
|
|
}
|
|
|
|
RecursiveFlagEntityTermination(child, childMeta);
|
|
}
|
|
}
|
|
|
|
private void RecursiveDeleteEntity(
|
|
EntityUid uid,
|
|
MetaDataComponent metadata)
|
|
{
|
|
// Note about this method: #if EXCEPTION_TOLERANCE is not used here because we're gonna it in the future...
|
|
var netEntity = GetNetEntity(uid, metadata);
|
|
var transform = _xformQuery.GetComponent(uid);
|
|
|
|
// 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).
|
|
if (transform.ParentUid != EntityUid.Invalid)
|
|
{
|
|
try
|
|
{
|
|
_xforms.DetachParentToNull(uid, transform);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_sawmill.Error($"Caught exception while trying to detach parent of entity '{ToPrettyString(uid, metadata)}' to null.\n{e}");
|
|
}
|
|
}
|
|
|
|
foreach (var child in transform._children)
|
|
{
|
|
try
|
|
{
|
|
RecursiveDeleteEntity(child, MetaQuery.GetComponent(child));
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
_sawmill.Error($"Caught exception while trying to recursively delete child entity '{ToPrettyString(child)}' of '{ToPrettyString(uid, metadata)}'\n{e}");
|
|
}
|
|
}
|
|
|
|
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
|
|
{
|
|
component.LifeShutdown(this);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_sawmill.Error($"Caught exception while trying to call shutdown on component of entity '{ToPrettyString(uid, metadata)}'\n{e}");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Dispose all my components, in a safe order so transform is available
|
|
DisposeComponents(uid);
|
|
metadata.EntityLifeStage = 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}");
|
|
}
|
|
|
|
_eventBus.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(netEntity);
|
|
}
|
|
|
|
public virtual void QueueDeleteEntity(EntityUid uid)
|
|
{
|
|
if (!QueuedDeletionsSet.Add(uid))
|
|
return;
|
|
|
|
QueuedDeletions.Enqueue(uid);
|
|
EntityQueueDeleted?.Invoke(uid);
|
|
}
|
|
|
|
public bool IsQueuedForDeletion(EntityUid uid) => QueuedDeletionsSet.Contains(uid);
|
|
|
|
public bool EntityExists(EntityUid uid)
|
|
{
|
|
return _entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()].ContainsKey(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 !_entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()].TryGetValue(uid, out var comp) || ((MetaDataComponent) comp).EntityDeleted;
|
|
}
|
|
|
|
public bool Deleted([NotNullWhen(false)] EntityUid? uid)
|
|
{
|
|
return !uid.HasValue || !_entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()].TryGetValue(uid.Value, out var comp) || ((MetaDataComponent) comp).EntityDeleted;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disposes all entities and clears all lists.
|
|
/// </summary>
|
|
public virtual void FlushEntities()
|
|
{
|
|
QueuedDeletions.Clear();
|
|
QueuedDeletionsSet.Clear();
|
|
foreach (var e in GetEntities().ToArray())
|
|
{
|
|
DeleteEntity(e);
|
|
}
|
|
|
|
if (Entities.Count != 0)
|
|
_sawmill.Error("Entities were spawned while flushing entities.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allocates an entity and stores it but does not load components or do initialization.
|
|
/// </summary>
|
|
private protected EntityUid AllocEntity(
|
|
EntityPrototype? prototype,
|
|
out MetaDataComponent metadata)
|
|
{
|
|
var entity = AllocEntity(out metadata);
|
|
metadata._entityPrototype = prototype;
|
|
Dirty(entity, metadata, metadata);
|
|
return entity;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allocates an entity and stores it but does not load components or do initialization.
|
|
/// </summary>
|
|
private EntityUid AllocEntity(out MetaDataComponent metadata)
|
|
{
|
|
var uid = GenerateEntityUid();
|
|
|
|
#if DEBUG
|
|
if (EntityExists(uid))
|
|
{
|
|
throw new InvalidOperationException($"UID already taken: {uid}");
|
|
}
|
|
#endif
|
|
|
|
// we want this called before adding components
|
|
EntityAdded?.Invoke(uid);
|
|
_eventBus.OnEntityAdded(uid);
|
|
var netEntity = GenerateNetEntity();
|
|
|
|
metadata = new MetaDataComponent
|
|
{
|
|
#pragma warning disable CS0618
|
|
Owner = uid,
|
|
#pragma warning restore CS0618
|
|
};
|
|
|
|
SetNetEntity(uid, netEntity, metadata);
|
|
|
|
Entities.Add(uid);
|
|
// add the required MetaDataComponent directly.
|
|
AddComponentInternal(uid, metadata, false, false);
|
|
|
|
// allocate the required TransformComponent
|
|
AddComponent<TransformComponent>(uid);
|
|
|
|
return uid;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allocates an entity and loads components but does not do initialization.
|
|
/// </summary>
|
|
private protected virtual EntityUid CreateEntity(string? prototypeName, IEntityLoadContext? context = null)
|
|
{
|
|
if (prototypeName == null)
|
|
return AllocEntity(out _);
|
|
|
|
if (!PrototypeManager.TryIndex<EntityPrototype>(prototypeName, out var prototype))
|
|
throw new EntityCreationException($"Attempted to spawn an entity with an invalid prototype: {prototypeName}");
|
|
|
|
return CreateEntity(prototype, context);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Allocates an entity and loads components but does not do initialization.
|
|
/// </summary>
|
|
private protected EntityUid CreateEntity(EntityPrototype prototype, IEntityLoadContext? context = null)
|
|
{
|
|
var entity = AllocEntity(prototype, out var metadata);
|
|
try
|
|
{
|
|
EntityPrototype.LoadEntity(metadata.EntityPrototype, entity, 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);
|
|
}
|
|
}
|
|
|
|
private protected void LoadEntity(EntityUid entity, IEntityLoadContext? context)
|
|
{
|
|
EntityPrototype.LoadEntity(MetaQuery.GetComponent(entity).EntityPrototype, entity, ComponentFactory, this, _serManager, context);
|
|
}
|
|
|
|
private protected void LoadEntity(EntityUid entity, IEntityLoadContext? context, EntityPrototype? prototype)
|
|
{
|
|
EntityPrototype.LoadEntity(prototype, entity, ComponentFactory, this, _serManager, context);
|
|
}
|
|
|
|
public void InitializeAndStartEntity(EntityUid entity, MapId? mapId = null)
|
|
{
|
|
try
|
|
{
|
|
var meta = MetaQuery.GetComponent(entity);
|
|
InitializeEntity(entity, meta);
|
|
StartEntity(entity);
|
|
|
|
// If the map we're initializing the entity on is initialized, run map init on it.
|
|
if (_mapManager.IsMapInitialized(mapId ?? _xformQuery.GetComponent(entity).MapID))
|
|
RunMapInit(entity, meta);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
DeleteEntity(entity);
|
|
throw new EntityCreationException("Exception inside InitializeAndStartEntity", e);
|
|
}
|
|
}
|
|
|
|
public void InitializeEntity(EntityUid entity, MetaDataComponent? meta = null)
|
|
{
|
|
InitializeComponents(entity, meta);
|
|
EntityInitialized?.Invoke(entity);
|
|
}
|
|
|
|
public void StartEntity(EntityUid entity)
|
|
{
|
|
StartComponents(entity);
|
|
}
|
|
|
|
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}");
|
|
meta.EntityLifeStage = EntityLifeStage.MapInitialized;
|
|
|
|
EventBus.RaiseLocalEvent(entity, MapInitEventInstance, false);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual EntityStringRepresentation ToPrettyString(EntityUid uid)
|
|
{
|
|
// We want to retrieve the MetaData component even if it is deleted.
|
|
if (!_entTraitArray[CompIdx.ArrayIndex<MetaDataComponent>()].TryGetValue(uid, out var component))
|
|
return new EntityStringRepresentation(uid, true);
|
|
|
|
var metadata = (MetaDataComponent) component;
|
|
|
|
return ToPrettyString(uid, metadata);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public EntityStringRepresentation ToPrettyString(NetEntity netEntity)
|
|
{
|
|
return ToPrettyString(GetEntity(netEntity));
|
|
}
|
|
|
|
private EntityStringRepresentation ToPrettyString(EntityUid uid, MetaDataComponent metadata)
|
|
{
|
|
return new EntityStringRepresentation(uid, metadata.EntityDeleted, metadata.EntityName, metadata.EntityPrototype?.ID);
|
|
}
|
|
|
|
#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>
|
|
/// 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++);
|
|
|
|
private sealed class EntityDiffContext : ISerializationContext
|
|
{
|
|
public SerializationManager.SerializerProvider SerializerProvider { get; }
|
|
public bool WritingReadingPrototypes { get; set; }
|
|
|
|
public EntityDiffContext()
|
|
{
|
|
SerializerProvider = new();
|
|
SerializerProvider.RegisterSerializer(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
public enum EntityMessageType : byte
|
|
{
|
|
Error = 0,
|
|
SystemMessage
|
|
}
|
|
}
|