Entity creation performance improvements (#2911)

This commit is contained in:
Pieter-Jan Briers
2022-06-06 14:49:36 +02:00
committed by GitHub
parent 3941930c2f
commit 832672149b
25 changed files with 398 additions and 131 deletions
+9
View File
@@ -94,3 +94,12 @@ cmd-dump_net_comps-help = Usage: dump_net-comps
cmd-dump_net_comps-error-writeable = Registration still writeable, network ids have not been generated.
cmd-dump_net_comps-header = Networked Component Registrations:
## 'dump_event_tables' command
cmd-dump_event_tables-desc = Prints directed event tables for an entity.
cmd-dump_event_tables-help = Usage: dump_event_tables <entityUid>
cmd-dump_event_tables-missing-arg-entity = Missing entity argument
cmd-dump_event_tables-error-entity = Invalid entity
cmd-dump_event_tables-arg-entity = <entityUid>
+1 -1
View File
@@ -123,7 +123,7 @@ namespace Robust.Client.Console
if (AvailableCommands.ContainsKey(commandName))
{
#if !DEBUG
#if FULL_RELEASE
var playerManager = IoCManager.Resolve<IPlayerManager>();
if (!_conGroup.CanCommand(commandName) && playerManager.LocalPlayer?.Session.Status > SessionStatus.Connecting)
{
@@ -47,7 +47,7 @@ namespace Robust.Client.GameObjects
return null;
}
return TextureForConfig((IconComponent)compData, resourceCache);
return TextureForConfig((IconComponent)compData.Component, resourceCache);
}
}
}
@@ -75,7 +75,7 @@ public sealed partial class SpriteSystem
{
// IconComponent takes precedence. If it has a valid icon, return that. Otherwise, continue as normal.
if (prototype.Components.TryGetValue("Icon", out var compData)
&& compData is IconComponent {Icon: {} icon})
&& compData.Component is IconComponent {Icon: {} icon})
{
return icon.Default;
}
+1 -1
View File
@@ -937,7 +937,7 @@ namespace Robust.Server.Maps
prototypeCompCache[prototype.ID] = cache = new Dictionary<string, MappingDataNode>();
foreach (var (compType, comp) in prototype.Components)
{
cache.Add(compType, serializationManager.WriteValueAs<MappingDataNode>(comp.GetType(), comp));
cache.Add(compType, serializationManager.WriteValueAs<MappingDataNode>(comp.Component.GetType(), comp.Component));
}
}
}
@@ -0,0 +1,56 @@
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
namespace Robust.Shared.Console.Commands;
internal sealed class DumpEventTablesCommand : IConsoleCommand
{
public string Command => "dump_event_tables";
public string Description => Loc.GetString("cmd-dump_event_tables-desc");
public string Help => Loc.GetString("cmd-dump_event_tables-help");
public void Execute(IConsoleShell shell, string argStr, string[] args)
{
var entMgr = IoCManager.Resolve<EntityManager>();
var compFactory = IoCManager.Resolve<IComponentFactory>();
if (args.Length < 1)
{
shell.WriteError(Loc.GetString("cmd-dump_event_tables-missing-arg-entity"));
return;
}
if (!EntityUid.TryParse(args[0], out var entity) || !entMgr.EntityExists(entity))
{
shell.WriteError(Loc.GetString("cmd-dump_event_tables-error-entity"));
return;
}
var eventBus = (EntityEventBus)entMgr.EventBus;
var table = eventBus._entEventTables[entity];
foreach (var (evType, comps) in table.EventIndices)
{
shell.WriteLine($"{evType}:");
var idx = comps;
while (idx != -1)
{
ref var entry = ref table.ComponentLists[idx];
idx = entry.Next;
var reg = compFactory.IdxToType(entry.Component);
shell.WriteLine($" {reg.Name}");
}
}
}
public CompletionResult GetCompletion(IConsoleShell shell, string[] args)
{
if (args.Length == 1)
return CompletionResult.FromHint(Loc.GetString("cmd-dump_event_tables-arg-entity"));
return CompletionResult.Empty;
}
}
@@ -1,8 +0,0 @@
using System;
namespace Robust.Shared.GameObjects;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public sealed class ByRefEventAttribute : Attribute
{
}
+9 -4
View File
@@ -38,13 +38,13 @@ namespace Robust.Shared.GameObjects
/// Increases the life stage from <see cref="ComponentLifeStage.PreAdd" /> to <see cref="ComponentLifeStage.Added" />,
/// after raising a <see cref="ComponentAdd"/> event.
/// </summary>
internal void LifeAddToEntity(IEntityManager entManager)
internal void LifeAddToEntity(IEntityManager entManager, CompIdx type)
{
DebugTools.Assert(LifeStage == ComponentLifeStage.PreAdd);
LifeStage = ComponentLifeStage.Adding;
CreationTick = entManager.CurrentTick;
entManager.EventBus.RaiseComponentEvent(this, CompAddInstance);
entManager.EventBus.RaiseComponentEvent(this, type, CompAddInstance);
LifeStage = ComponentLifeStage.Added;
}
@@ -52,12 +52,12 @@ namespace Robust.Shared.GameObjects
/// Increases the life stage from <see cref="ComponentLifeStage.Added" /> to <see cref="ComponentLifeStage.Initialized" />,
/// calling <see cref="Initialize" />.
/// </summary>
internal void LifeInitialize(IEntityManager entManager)
internal void LifeInitialize(IEntityManager entManager, CompIdx type)
{
DebugTools.Assert(LifeStage == ComponentLifeStage.Added);
LifeStage = ComponentLifeStage.Initializing;
entManager.EventBus.RaiseComponentEvent(this, CompInitInstance);
entManager.EventBus.RaiseComponentEvent(this, type, CompInitInstance);
Initialize();
#if DEBUG
@@ -299,29 +299,34 @@ namespace Robust.Shared.GameObjects
/// The component has been added to the entity. This is the first function
/// to be called after the component has been allocated and (optionally) deserialized.
/// </summary>
[ComponentEvent]
public sealed class ComponentAdd : EntityEventArgs { }
/// <summary>
/// Raised when all of the entity's other components have been added and are available,
/// But are not necessarily initialized yet. DO NOT depend on the values of other components to be correct.
/// </summary>
[ComponentEvent]
public sealed class ComponentInit : EntityEventArgs { }
/// <summary>
/// Starts up a component. This is called automatically after all components are Initialized and the entity is Initialized.
/// This can be called multiple times during the component's life, and at any time.
/// </summary>
[ComponentEvent]
public sealed class ComponentStartup : EntityEventArgs { }
/// <summary>
/// Shuts down the component. The is called Automatically by OnRemove. This can be called multiple times during
/// the component's life, and at any time.
/// </summary>
[ComponentEvent]
public sealed class ComponentShutdown : EntityEventArgs { }
/// <summary>
/// The component has been removed from the entity. This is the last function
/// that is called before the component is freed.
/// </summary>
[ComponentEvent]
public sealed class ComponentRemove : EntityEventArgs { }
}
@@ -31,10 +31,12 @@ namespace Robust.Shared.GameObjects
public readonly struct AddedComponentEventArgs
{
public readonly ComponentEventArgs BaseArgs;
public readonly CompIdx ComponentType;
public AddedComponentEventArgs(ComponentEventArgs baseArgs)
public AddedComponentEventArgs(ComponentEventArgs baseArgs, CompIdx componentType)
{
BaseArgs = baseArgs;
ComponentType = componentType;
}
}
@@ -279,6 +279,11 @@ namespace Robust.Shared.GameObjects
return _typeFactory.CreateInstanceUnchecked<T>(types[typeof(T)].Type);
}
public IComponent GetComponent(ComponentRegistration reg)
{
return (IComponent) _typeFactory.CreateInstanceUnchecked(reg.Type);
}
public IComponent GetComponent(string componentName, bool ignoreCase = false)
{
if (ignoreCase && _lowerCaseNames.TryGetValue(componentName, out var lowerCaseName))
@@ -35,7 +35,9 @@ public sealed class ComponentRegistration
public ValueList<CompIdx> References;
public ComponentRegistration(string name, Type type, CompIdx idx)
// Internal for sandboxing.
// Avoid content passing an instance of this to ComponentFactory to get any type they want instantiated.
internal ComponentRegistration(string name, Type type, CompIdx idx)
{
Name = name;
Type = type;
@@ -24,10 +24,11 @@ internal sealed partial class EntityEventBus : IEventBus
private readonly Queue<(EventSource source, object args)> _eventQueue = new();
// eUid -> EventType -> { CompType1, ... CompTypeN }
private Dictionary<EntityUid, Dictionary<Type, HashSet<CompIdx>>> _entEventTables = new();
// See EventTable declaration for layout details
internal Dictionary<EntityUid, EventTable> _entEventTables = new();
// CompType -> EventType -> Handler
private Dictionary<Type, DirectedRegistration>?[] _entSubscriptions =
internal Dictionary<Type, DirectedRegistration>?[] _entSubscriptions =
Array.Empty<Dictionary<Type, DirectedRegistration>?>();
// EventType -> { CompType1, ... CompType N }
@@ -72,6 +73,10 @@ internal sealed partial class EntityEventBus : IEventBus
/// </summary>
private sealed class EventData
{
/// <summary>
/// <see cref="ComponentEventAttribute"/> set?
/// </summary>
public bool ComponentEvent;
public bool IsOrdered;
public bool OrderingUpToDate;
public ValueList<BroadcastRegistration> BroadcastRegistrations;
@@ -81,7 +86,7 @@ internal sealed partial class EntityEventBus : IEventBus
// It should always be cast to/from with Unsafe.As<,>
private readonly struct Unit
internal readonly struct Unit
{
}
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Robust.Shared.Collections;
using Robust.Shared.Utility;
@@ -64,6 +65,20 @@ namespace Robust.Shared.GameObjects
internal void RaiseComponentEvent<TEvent>(IComponent component, TEvent args)
where TEvent : notnull;
/// <summary>
/// Dispatches an event directly to a specific component.
/// </summary>
/// <remarks>
/// This has a very specific purpose, and has massive potential to be abused.
/// DO NOT EXPOSE THIS TO CONTENT.
/// </remarks>
/// <typeparam name="TEvent">Event to dispatch.</typeparam>
/// <param name="component">Component receiving the event.</param>
/// <param name="idx">Type of the component, for faster lookups.</param>
/// <param name="args">Event arguments for the event.</param>
internal void RaiseComponentEvent<TEvent>(IComponent component, CompIdx idx, TEvent args)
where TEvent : notnull;
/// <summary>
/// Dispatches an event directly to a specific component, by-ref.
/// </summary>
@@ -82,7 +97,7 @@ namespace Robust.Shared.GameObjects
internal partial class EntityEventBus : IDisposable
{
private delegate void DirectedEventHandler(EntityUid uid, IComponent comp, ref Unit args);
internal delegate void DirectedEventHandler(EntityUid uid, IComponent comp, ref Unit args);
private delegate void DirectedEventHandler<TEvent>(EntityUid uid, IComponent comp, ref TEvent args)
where TEvent : notnull;
@@ -116,7 +131,24 @@ namespace Robust.Shared.GameObjects
{
ref var unitRef = ref Unsafe.As<TEvent, Unit>(ref args);
DispatchComponent<TEvent>(component.Owner, component, ref unitRef, false);
DispatchComponent<TEvent>(
component.Owner,
component,
CompIdx.Index(component.GetType()),
ref unitRef,
false);
}
void IDirectedEventBus.RaiseComponentEvent<TEvent>(IComponent component, CompIdx type, TEvent args)
{
ref var unitRef = ref Unsafe.As<TEvent, Unit>(ref args);
DispatchComponent<TEvent>(
component.Owner,
component,
type,
ref unitRef,
false);
}
/// <inheritdoc />
@@ -124,7 +156,12 @@ namespace Robust.Shared.GameObjects
{
ref var unitRef = ref Unsafe.As<TEvent, Unit>(ref args);
DispatchComponent<TEvent>(component.Owner, component, ref unitRef, true);
DispatchComponent<TEvent>(
component.Owner,
component,
CompIdx.Index(component.GetType()),
ref unitRef,
true);
}
public void OnlyCallOnRobustUnitTestISwearToGodPleaseSomebodyKillThisNightmare()
@@ -290,14 +327,14 @@ namespace Robust.Shared.GameObjects
EntRemoveEntity(e);
}
public void OnComponentAdded(AddedComponentEventArgs e)
public void OnComponentAdded(in AddedComponentEventArgs e)
{
_subscriptionLock = true;
EntAddComponent(e.BaseArgs.Owner, CompIdx.Index(e.BaseArgs.Component.GetType()));
EntAddComponent(e.BaseArgs.Owner, e.ComponentType);
}
public void OnComponentRemoved(RemovedComponentEventArgs e)
public void OnComponentRemoved(in RemovedComponentEventArgs e)
{
EntRemoveComponent(e.BaseArgs.Owner, CompIdx.Index(e.BaseArgs.Component.GetType()));
}
@@ -334,7 +371,8 @@ namespace Robust.Shared.GameObjects
var invSubs = _entSubscriptionsInv.GetOrNew(eventType);
invSubs.Add(compType);
RegisterCommon(eventType, registration.Ordering, out _);
RegisterCommon(eventType, registration.Ordering, out var data);
data.ComponentEvent = eventType.HasCustomAttribute<ComponentEventAttribute>();
}
private void EntSubscribe<TEvent>(
@@ -375,7 +413,7 @@ namespace Robust.Shared.GameObjects
{
// odds are at least 1 component will subscribe to an event on the entity, so just
// preallocate the table now. Dispatch does not need to check this later.
_entEventTables.Add(euid, new Dictionary<Type, HashSet<CompIdx>>());
_entEventTables.Add(euid, new EventTable());
}
private void EntRemoveEntity(EntityUid euid)
@@ -392,19 +430,63 @@ namespace Robust.Shared.GameObjects
{
var compSubs = _entSubscriptions[type.Value]!;
foreach (var kvSub in compSubs)
foreach (var (evType, _) in compSubs)
{
if (!eventTable.TryGetValue(kvSub.Key, out var subscribedComps))
{
subscribedComps = new HashSet<CompIdx>();
eventTable.Add(kvSub.Key, subscribedComps);
}
// Skip adding this to significantly reduce memory use and GC noise on entity create.
if (_eventData[evType].ComponentEvent)
continue;
subscribedComps.Add(type);
if (eventTable.Free < 0)
GrowEventTable(eventTable);
DebugTools.Assert(eventTable.Free >= 0);
ref var eventStartIdx = ref CollectionsMarshal.GetValueRefOrAddDefault(
eventTable.EventIndices,
evType,
out var exists);
// Allocate linked list entry by popping free list.
var entryIdx = eventTable.Free;
ref var entry = ref eventTable.ComponentLists[entryIdx];
eventTable.Free = entry.Next;
// Set it up
entry.Component = type;
entry.Next = exists ? eventStartIdx : -1;
// Assign new list entry to EventIndices dictionary.
eventStartIdx = entryIdx;
}
}
}
private static void GrowEventTable(EventTable table)
{
var newSize = table.ComponentLists.Length * 2;
var oldArray = table.ComponentLists;
var newArray = GC.AllocateUninitializedArray<EventTableListEntry>(newSize);
Array.Copy(oldArray, newArray, oldArray.Length);
InitEventTableFreeList(newArray, newArray.Length, oldArray.Length);
table.Free = oldArray.Length;
table.ComponentLists = newArray;
}
private static void InitEventTableFreeList(EventTableListEntry[] entries, int end, int start)
{
var lastFree = -1;
for (var i = end - 1; i >= start; i--)
{
ref var entry = ref entries[i];
entry.Component = default;
entry.Next = lastFree;
lastFree = i;
}
}
private void EntRemoveComponent(EntityUid euid, CompIdx compType)
{
var eventTable = _entEventTables[euid];
@@ -414,12 +496,44 @@ namespace Robust.Shared.GameObjects
{
var compSubs = _entSubscriptions[type.Value]!;
foreach (var kvSub in compSubs)
foreach (var (evType, _) in compSubs)
{
if (!eventTable.TryGetValue(kvSub.Key, out var subscribedComps))
return;
ref var dictIdx = ref CollectionsMarshal.GetValueRefOrNullRef(eventTable.EventIndices, evType);
if (Unsafe.IsNullRef(ref dictIdx))
continue;
subscribedComps.Remove(type);
ref var updateNext = ref dictIdx;
// Go over linked list to find index of component.
var entryIdx = dictIdx;
ref var entry = ref Unsafe.NullRef<EventTableListEntry>();
while (true)
{
entry = ref eventTable.ComponentLists[entryIdx];
if (entry.Component == type)
{
// Found
break;
}
entryIdx = entry.Next;
updateNext = ref entry.Next;
}
if (entry.Next == -1 && Unsafe.AreSame(ref dictIdx, ref updateNext))
{
// Last entry for this event type, remove from dict.
eventTable.EventIndices.Remove(evType);
}
else
{
// Rewrite previous index to point to next in chain.
updateNext = entry.Next;
}
// Push entry back onto free list.
entry.Next = eventTable.Free;
eventTable.Free = entryIdx;
}
}
}
@@ -429,9 +543,8 @@ namespace Robust.Shared.GameObjects
if (!EntTryGetSubscriptions(eventType, euid, out var enumerator))
return;
while (enumerator.MoveNext(out var tuple))
while (enumerator.MoveNext(out var component, out var reg))
{
var (component, reg) = tuple.Value;
if (reg.ReferenceEvent != dispatchByReference)
ThrowByRefMisMatch();
@@ -445,32 +558,27 @@ namespace Robust.Shared.GameObjects
ref ValueList<OrderedEventDispatch> found,
bool byRef)
{
var eventTable = _entEventTables[euid];
if (!eventTable.TryGetValue(eventType, out var subscribedComps))
if (!EntTryGetSubscriptions(eventType, euid, out var enumerator))
return;
foreach (var compType in subscribedComps)
while (enumerator.MoveNext(out var component, out var reg))
{
var compSubs = _entSubscriptions[compType.Value]!;
if (!compSubs.TryGetValue(eventType, out var reg))
return;
if (reg.ReferenceEvent != byRef)
ThrowByRefMisMatch();
var component = _entMan.GetComponent(euid, compType);
found.Add(new OrderedEventDispatch((ref Unit ev) => reg.Handler(euid, component, ref ev), reg.Order));
}
}
private void DispatchComponent<TEvent>(EntityUid euid, IComponent component, ref Unit args,
private void DispatchComponent<TEvent>(
EntityUid euid,
IComponent component,
CompIdx baseType,
ref Unit args,
bool dispatchByReference)
where TEvent : notnull
{
var enumerator = EntGetReferences(CompIdx.Index(component.GetType()));
var enumerator = EntGetReferences(baseType);
while (enumerator.MoveNext(out var type))
{
var compSubs = _entSubscriptions[type.Value]!;
@@ -505,13 +613,13 @@ namespace Robust.Shared.GameObjects
}
// No subscriptions to this event type, return null.
if (!eventTable.TryGetValue(eventType, out var subscribedComps))
if (!eventTable.EventIndices.TryGetValue(eventType, out var startEntry))
{
enumerator = default;
return false;
}
enumerator = new(eventType, subscribedComps.GetEnumerator(), _entSubscriptions, euid, _entMan);
enumerator = new(eventType, startEntry, eventTable.ComponentLists, _entSubscriptions, euid, _entMan);
return true;
}
@@ -581,58 +689,60 @@ namespace Robust.Shared.GameObjects
}
}
private struct SubscriptionsEnumerator : IDisposable
private struct SubscriptionsEnumerator
{
private readonly Type _eventType;
private HashSet<CompIdx>.Enumerator _enumerator;
private readonly Dictionary<Type, DirectedRegistration>?[] _subscriptions;
private readonly EntityUid _uid;
private readonly Dictionary<Type, DirectedRegistration>?[] _subscriptions;
private readonly IEntityManager _entityManager;
private readonly EventTableListEntry[] _list;
private int _idx;
public SubscriptionsEnumerator(
Type eventType,
HashSet<CompIdx>.Enumerator enumerator,
int startEntry,
EventTableListEntry[] list,
Dictionary<Type, DirectedRegistration>?[] subscriptions,
EntityUid uid,
IEntityManager entityManager)
{
_eventType = eventType;
_enumerator = enumerator;
_list = list;
_subscriptions = subscriptions;
_idx = startEntry;
_entityManager = entityManager;
_uid = uid;
}
public bool MoveNext(
[NotNullWhen(true)] out (IComponent Component, DirectedRegistration Registration)? tuple)
[NotNullWhen(true)] out IComponent? component,
[NotNullWhen(true)] out DirectedRegistration? registration)
{
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
if (!_enumerator.MoveNext())
if (_idx == -1)
{
tuple = null;
component = null;
registration = null;
return false;
}
var compType = _enumerator.Current;
ref var entry = ref _list[_idx];
_idx = entry.Next;
var compType = entry.Component;
var compSubs = _subscriptions[compType.Value]!;
if (!compSubs.TryGetValue(_eventType, out var registration))
if (!compSubs.TryGetValue(_eventType, out registration))
{
tuple = null;
component = default;
return false;
}
tuple = (_entityManager.GetComponent(_uid, compType), registration);
component = _entityManager.GetComponent(_uid, compType);
return true;
}
public void Dispose()
{
_enumerator.Dispose();
}
}
private sealed class DirectedRegistration : OrderedRegistration
internal sealed class DirectedRegistration : OrderedRegistration
{
public readonly Delegate Original;
public readonly DirectedEventHandler Handler;
@@ -654,6 +764,32 @@ namespace Robust.Shared.GameObjects
Order = order;
}
}
internal sealed class EventTable
{
private const int InitialListSize = 8;
// Event -> { Comp, Comp, ... } is stored in a simple linked list.
// EventIndices contains indices into ComponentLists where linked list nodes start.
// Free contains the first free linked list node, or -1 if there is none.
// Free nodes form their own linked list.
// ComponentList is the actual region of memory containing linked list nodes.
public readonly Dictionary<Type, int> EventIndices = new();
public int Free;
public EventTableListEntry[] ComponentLists = new EventTableListEntry[InitialListSize];
public EventTable()
{
InitEventTableFreeList(ComponentLists, ComponentLists.Length, 0);
Free = 0;
}
}
internal struct EventTableListEntry
{
public int Next;
public CompIdx Component;
}
}
public delegate void ComponentEventHandler<in TComp, in TEvent>(EntityUid uid, TComp component, TEvent args)
@@ -127,7 +127,7 @@ namespace Robust.Shared.GameObjects
// Still, it's a single .Sort() now for those instead of the whole topological short shebang.
}
private sealed record OrderingData(Type OrderType, Type[] Before, Type[] After)
internal sealed record OrderingData(Type OrderType, Type[] Before, Type[] After)
{
public bool Equals(OrderingData? other)
{
@@ -174,7 +174,7 @@ namespace Robust.Shared.GameObjects
/// <summary>
/// Base type for directed and broadcast subscriptions. Contains ordering data.
/// </summary>
private abstract class OrderedRegistration
internal abstract class OrderedRegistration
{
public int Order;
public readonly OrderingData? Ordering;
@@ -114,20 +114,20 @@ namespace Robust.Shared.GameObjects
// Init transform first, we always have it.
var transform = GetComponent<TransformComponent>(uid);
if (transform.LifeStage < ComponentLifeStage.Initialized)
transform.LifeInitialize(this);
transform.LifeInitialize(this, CompIdx.Index<TransformComponent>());
// Init physics second if it exists.
if (TryGetComponent<PhysicsComponent>(uid, out var phys)
&& phys.LifeStage < ComponentLifeStage.Initialized)
{
phys.LifeInitialize(this);
phys.LifeInitialize(this, CompIdx.Index<PhysicsComponent>());
}
// Do rest of components.
foreach (var comp in comps)
{
if (comp is { LifeStage: < ComponentLifeStage.Initialized })
comp.LifeInitialize(this);
comp.LifeInitialize(this, CompIdx.Index(comp.GetType()));
}
#if DEBUG
@@ -190,12 +190,14 @@ namespace Robust.Shared.GameObjects
where T : Component
{
private readonly IEntityManager _entMan;
public readonly CompIdx CompType;
public readonly T Comp;
public CompInitializeHandle(IEntityManager entityManager, T comp)
public CompInitializeHandle(IEntityManager entityManager, T comp, CompIdx compType)
{
_entMan = entityManager;
Comp = comp;
CompType = compType;
}
public void Dispose()
@@ -206,7 +208,7 @@ namespace Robust.Shared.GameObjects
return;
if (!Comp.Initialized)
Comp.LifeInitialize(_entMan);
Comp.LifeInitialize(_entMan, CompType);
if (metadata.EntityInitialized && !Comp.Running)
Comp.LifeStartup(_entMan);
@@ -221,7 +223,8 @@ namespace Robust.Shared.GameObjects
/// <inheritdoc />
public CompInitializeHandle<T> AddComponentUninitialized<T>(EntityUid uid) where T : Component, new()
{
var newComponent = _componentFactory.GetComponent<T>();
var reg = _componentFactory.GetRegistration<T>();
var newComponent = (T)_componentFactory.GetComponent(reg);
newComponent.Owner = uid;
if (!uid.IsValid() || !EntityExists(uid))
@@ -233,7 +236,7 @@ namespace Robust.Shared.GameObjects
AddComponentInternal(uid, newComponent, false, true);
return new CompInitializeHandle<T>(this, newComponent);
return new CompInitializeHandle<T>(this, newComponent, reg.Idx);
}
/// <inheritdoc />
@@ -293,11 +296,11 @@ namespace Robust.Shared.GameObjects
Dirty(component);
}
var eventArgs = new AddedComponentEventArgs(new ComponentEventArgs(component, uid));
var eventArgs = new AddedComponentEventArgs(new ComponentEventArgs(component, uid), reg.Idx);
ComponentAdded?.Invoke(eventArgs);
_eventBus.OnComponentAdded(eventArgs);
component.LifeAddToEntity(this);
component.LifeAddToEntity(this, reg.Idx);
if (skipInit)
return;
@@ -307,7 +310,7 @@ namespace Robust.Shared.GameObjects
if (!metadata.EntityInitialized && !metadata.EntityInitializing)
return;
component.LifeInitialize(this);
component.LifeInitialize(this, reg.Idx);
if (metadata.EntityInitialized)
component.LifeStartup(this);
@@ -707,6 +710,22 @@ namespace Robust.Shared.GameObjects
return false;
}
public bool TryGetComponent(EntityUid uid, CompIdx type, [NotNullWhen(true)] out IComponent? component)
{
var dict = _entTraitArray[type.Value];
if (dict.TryGetValue(uid, out var comp))
{
if (!comp.Deleted)
{
component = comp;
return true;
}
}
component = null;
return false;
}
/// <inheritdoc />
public bool TryGetComponent([NotNullWhen(true)] EntityUid? uid, Type type,
[NotNullWhen(true)] out IComponent? component)
@@ -0,0 +1,17 @@
using System;
namespace Robust.Shared.GameObjects;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public sealed class ByRefEventAttribute : Attribute
{
}
/// <summary>
/// Indicates that an eventbus event should only ever be raised through <see cref="IDirectedEventBus.RaiseComponentEvent{TEvent}(IComponent, TEvent)"/>.
/// This allows extra optimizations.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
internal sealed class ComponentEventAttribute : Attribute
{
}
@@ -117,6 +117,12 @@ namespace Robust.Shared.GameObjects
/// </exception>
T GetComponent<T>() where T : IComponent, new();
/// <summary>
/// Gets a new component instantiated from the specified component registration.
/// </summary>
/// <returns>A Component</returns>
IComponent GetComponent(ComponentRegistration reg);
/// <summary>
/// Gets a new component instantiated of the specified <see cref="IComponent.Name"/>.
/// </summary>
@@ -241,6 +241,15 @@ namespace Robust.Shared.GameObjects
/// <returns>If the component existed in the entity.</returns>
bool TryGetComponent(EntityUid uid, Type type, [NotNullWhen(true)] out IComponent? component);
/// <summary>
/// Returns the component of a specific type.
/// </summary>
/// <param name="uid">Entity UID to check.</param>
/// <param name="type">A trait or component type to check for.</param>
/// <param name="component">Component of the specified type (if exists).</param>
/// <returns>If the component existed in the entity.</returns>
bool TryGetComponent([NotNullWhen(true)] EntityUid uid, CompIdx type, [NotNullWhen(true)] out IComponent? component);
/// <summary>
/// Returns the component of a specific type.
/// </summary>
@@ -3,7 +3,7 @@ using Robust.Shared.Players;
namespace Robust.Shared.GameStates
{
[ByRefEvent]
[ByRefEvent, ComponentEvent]
public readonly struct ComponentHandleState
{
public ComponentState? Current { get; }
@@ -19,7 +19,7 @@ namespace Robust.Shared.GameStates
/// <summary>
/// Component event for getting the component state for a specific player.
/// </summary>
[ByRefEvent]
[ByRefEvent, ComponentEvent]
public struct ComponentGetState
{
/// <summary>
@@ -28,7 +28,7 @@ namespace Robust.Shared.GameStates
public ComponentState? State { get; set; }
}
[ByRefEvent]
[ByRefEvent, ComponentEvent]
public struct ComponentGetStateAttemptEvent
{
/// <summary>
+25 -24
View File
@@ -154,9 +154,9 @@ namespace Robust.Shared.Prototypes
public EntityPrototype()
{
// Everybody gets a transform component!
Components.Add("Transform", new TransformComponent());
Components.Add("Transform", new ComponentRegistryEntry(new TransformComponent(), new MappingDataNode()));
// And a metadata component too!
Components.Add("MetaData", new MetaDataComponent());
Components.Add("MetaData", new ComponentRegistryEntry(new MetaDataComponent(), new MappingDataNode()));
}
void ISerializationHooks.AfterDeserialization()
@@ -185,7 +185,7 @@ namespace Robust.Shared.Prototypes
// There are no duplicate component names
// TODO Sanity check with names being in an attribute of the type instead
component = (T) componentUnCast;
component = (T) componentUnCast.Component;
return true;
}
@@ -262,25 +262,12 @@ namespace Robust.Shared.Prototypes
{
prototypeManager.TryGetMapping(typeof(EntityPrototype), prototype.ID, out var prototypeData);
foreach (var (name, _) in prototype.Components)
foreach (var (name, entry) in prototype.Components)
{
MappingDataNode? fullData = null;
if (prototypeData != null && prototypeData.TryGet<SequenceDataNode>("components", out var compList))
{
foreach (var data in compList)
{
if(data is not MappingDataNode mappingDataNode || !mappingDataNode.TryGet<ValueDataNode>("type", out var typeNode) || typeNode.Value != name ) continue;
fullData = mappingDataNode;
break;
}
}
fullData ??= new MappingDataNode();
var fullData = entry.Mapping;
if (context != null)
{
fullData = context.GetComponentData(name, fullData);
}
EnsureCompExistsAndDeserialize(entity, factory, entityManager, serManager, name, fullData, context as ISerializationContext);
}
@@ -310,11 +297,12 @@ namespace Robust.Shared.Prototypes
IEntityManager entityManager,
ISerializationManager serManager,
string compName,
MappingDataNode data, ISerializationContext? context)
MappingDataNode data,
ISerializationContext? context)
{
var compType = factory.GetRegistration(compName).Type;
var compReg = factory.GetRegistration(compName);
if (!entityManager.TryGetComponent(entity, compType, out var component))
if (!entityManager.TryGetComponent(entity, compReg.Idx, out var component))
{
var newComponent = (Component) factory.GetComponent(compName);
newComponent.Owner = entity;
@@ -323,7 +311,7 @@ namespace Robust.Shared.Prototypes
}
// TODO use this value to support struct components
serManager.Read(compType, data, context, value: component);
serManager.Read(compReg.Type, data, context, value: component);
}
public override string ToString()
@@ -331,17 +319,30 @@ namespace Robust.Shared.Prototypes
return $"EntityPrototype({ID})";
}
public sealed class ComponentRegistry : Dictionary<string, IComponent>
public sealed class ComponentRegistry : Dictionary<string, ComponentRegistryEntry>
{
public ComponentRegistry()
{
}
public ComponentRegistry(Dictionary<string, IComponent> components) : base(components)
public ComponentRegistry(Dictionary<string, ComponentRegistryEntry> components) : base(components)
{
}
}
public sealed class ComponentRegistryEntry
{
public readonly IComponent Component;
// Mapping is just a quick reference to speed up entity creation.
public readonly MappingDataNode Mapping;
public ComponentRegistryEntry(IComponent component, MappingDataNode mapping)
{
Component = component;
Mapping = mapping;
}
}
[DataDefinition]
public sealed class EntityPlacementProperties
{
+1 -3
View File
@@ -716,9 +716,7 @@ namespace Robust.Shared.Prototypes
public bool TryGetMapping(Type type, string id, [NotNullWhen(true)] out MappingDataNode? mappings)
{
var ret = _prototypeResults[type].TryGetValue(id, out var originalMappings);
mappings = originalMappings?.Copy();
return ret;
return _prototypeResults[type].TryGetValue(id, out mappings);
}
/// <inheritdoc />
@@ -60,7 +60,7 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations
var type = factory.GetRegistration(compType).Type;
var read = (IComponent)serializationManager.Read(type, copy, skipHook: skipHook)!;
components[compType] = read;
components[compType] = new ComponentRegistryEntry(read, copy);
}
var referenceTypes = new List<CompIdx>();
@@ -153,7 +153,12 @@ namespace Robust.Shared.Serialization.TypeSerializers.Implementations
var compSequence = new SequenceDataNode();
foreach (var (type, component) in value)
{
var node = serializationManager.WriteValue(component.GetType(), component, alwaysWrite, context);
var node = serializationManager.WriteValue(
component.Component.GetType(),
component.Component,
alwaysWrite,
context);
if (node is not MappingDataNode mapping) throw new InvalidNodeTypeException();
mapping.Add("type", new ValueDataNode(type));
@@ -43,7 +43,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
// add a component to the system
bus.OnEntityAdded(entUid);
bus.OnComponentAdded(new AddedComponentEventArgs(new ComponentEventArgs(compInstance, entUid)));
bus.OnComponentAdded(new AddedComponentEventArgs(new ComponentEventArgs(compInstance, entUid), CompIdx.Index<MetaDataComponent>()));
// Raise
var evntArgs = new TestEvent(5);
@@ -98,7 +98,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
// add a component to the system
bus.OnEntityAdded(entUid);
bus.OnComponentAdded(new AddedComponentEventArgs(new ComponentEventArgs(compInstance, entUid)));
bus.OnComponentAdded(new AddedComponentEventArgs(new ComponentEventArgs(compInstance, entUid), CompIdx.Index<MetaDataComponent>()));
// Raise
var evntArgs = new TestEvent(5);
@@ -151,7 +151,7 @@ namespace Robust.UnitTesting.Shared.GameObjects
// add a component to the system
entManMock.Raise(m => m.EntityAdded += null, entUid);
entManMock.Raise(m => m.ComponentAdded += null, new AddedComponentEventArgs(new ComponentEventArgs(compInstance, entUid)));
entManMock.Raise(m => m.ComponentAdded += null, new AddedComponentEventArgs(new ComponentEventArgs(compInstance, entUid), CompIdx.Index<MetaDataComponent>()));
// Raise
((IEventBus)bus).RaiseComponentEvent(compInstance, new ComponentInit());
@@ -227,9 +227,9 @@ namespace Robust.UnitTesting.Shared.GameObjects
// add a component to the system
bus.OnEntityAdded(entUid);
bus.OnComponentAdded(new AddedComponentEventArgs(new ComponentEventArgs(instA, entUid)));
bus.OnComponentAdded(new AddedComponentEventArgs(new ComponentEventArgs(instB, entUid)));
bus.OnComponentAdded(new AddedComponentEventArgs(new ComponentEventArgs(instC, entUid)));
bus.OnComponentAdded(new AddedComponentEventArgs(new ComponentEventArgs(instA, entUid), CompIdx.Index<OrderAComponent>()));
bus.OnComponentAdded(new AddedComponentEventArgs(new ComponentEventArgs(instB, entUid), CompIdx.Index<OrderBComponent>()));
bus.OnComponentAdded(new AddedComponentEventArgs(new ComponentEventArgs(instC, entUid), CompIdx.Index<OrderCComponent>()));
// Raise
var evntArgs = new TestEvent(5);
@@ -37,7 +37,7 @@ namespace Robust.UnitTesting.Shared.Prototypes
var prototype = manager.Index<EntityPrototype>("wrench");
Assert.That(prototype.Name, Is.EqualTo("Not a wrench. Tricked!"));
var mapping = prototype.Components["TestBasicPrototype"] as TestBasicPrototypeComponent;
var mapping = prototype.Components["TestBasicPrototype"].Component as TestBasicPrototypeComponent;
Assert.That(mapping!.Foo, Is.EqualTo("bar!"));
}
@@ -55,7 +55,7 @@ namespace Robust.UnitTesting.Shared.Prototypes
Assert.That(prototype.Components, Contains.Key("PointLight"));
});
var componentData = prototype.Components["PointLight"] as PointLightComponent;
var componentData = prototype.Components["PointLight"].Component as PointLightComponent;
Assert.That(componentData!.NetSyncEnabled, Is.EqualTo(false));
}
@@ -66,7 +66,7 @@ namespace Robust.UnitTesting.Shared.Prototypes
var prototype = manager.Index<EntityPrototype>("yamltester");
Assert.That(prototype.Components, Contains.Key("TestBasicPrototype"));
var componentData = prototype.Components["TestBasicPrototype"] as TestBasicPrototypeComponent;
var componentData = prototype.Components["TestBasicPrototype"].Component as TestBasicPrototypeComponent;
Assert.NotNull(componentData);
Assert.That(componentData!.Str, Is.EqualTo("hi!"));
@@ -29,7 +29,7 @@ namespace Robust.UnitTesting.Shared.Serialization.TypeSerializers
public void SerializationTest()
{
var component = new TestComponent();
var registry = new ComponentRegistry {{"Test", component}};
var registry = new ComponentRegistry {{"Test", new ComponentRegistryEntry(component, new MappingDataNode())}};
var node = Serialization.WriteValueAs<SequenceDataNode>(registry);
Assert.That(node.Sequence.Count, Is.EqualTo(1));
@@ -52,7 +52,7 @@ namespace Robust.UnitTesting.Shared.Serialization.TypeSerializers
Assert.That(deserializedRegistry.Count, Is.EqualTo(1));
Assert.That(deserializedRegistry.ContainsKey("Test"));
Assert.IsInstanceOf<TestComponent>(deserializedRegistry["Test"]);
Assert.IsInstanceOf<TestComponent>(deserializedRegistry["Test"].Component);
}
}