diff --git a/Robust.Server/Prototypes/ServerPrototypeManager.cs b/Robust.Server/Prototypes/ServerPrototypeManager.cs index 02e44b91122..65fe5bbbd0a 100644 --- a/Robust.Server/Prototypes/ServerPrototypeManager.cs +++ b/Robust.Server/Prototypes/ServerPrototypeManager.cs @@ -4,58 +4,56 @@ using Robust.Server.Console; using Robust.Server.Player; using Robust.Shared.IoC; -using Robust.Shared.Log; using Robust.Shared.Network; using Robust.Shared.Network.Messages; using Robust.Shared.Prototypes; -namespace Robust.Server.Prototypes +namespace Robust.Server.Prototypes; + +public sealed partial class ServerPrototypeManager : PrototypeManager { - public sealed partial class ServerPrototypeManager : PrototypeManager - { #if TOOLS - [Dependency] private IPlayerManager _playerManager = default!; - [Dependency] private IConGroupController _conGroups = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IConGroupController _conGroups = default!; #endif - [Dependency] private INetManager _netManager = default!; - [Dependency] private IBaseServerInternal _server = default!; + [Dependency] private readonly INetManager _netManager = default!; + [Dependency] private readonly IBaseServerInternal _server = default!; - public ServerPrototypeManager() - { - RegisterIgnore("shader"); - RegisterIgnore("uiTheme"); - RegisterIgnore("font"); - } + public ServerPrototypeManager() + { + RegisterIgnore("shader"); + RegisterIgnore("uiTheme"); + RegisterIgnore("font"); + } - public override void Initialize() - { - base.Initialize(); + public override void Initialize() + { + base.Initialize(); - _netManager.RegisterNetMessage(HandleReloadPrototypes, NetMessageAccept.Server); - } + _netManager.RegisterNetMessage(HandleReloadPrototypes, NetMessageAccept.Server); + } - private void HandleReloadPrototypes(MsgReloadPrototypes msg) - { + private void HandleReloadPrototypes(MsgReloadPrototypes msg) + { #if TOOLS - if (!_playerManager.TryGetSessionByChannel(msg.MsgChannel, out var player) || - !_conGroups.CanAdminReloadPrototypes(player)) - { - return; - } + if (!_playerManager.TryGetSessionByChannel(msg.MsgChannel, out var player) || + !_conGroups.CanAdminReloadPrototypes(player)) + { + return; + } - var sw = Stopwatch.StartNew(); + var sw = Stopwatch.StartNew(); - ReloadPrototypes(msg.Paths); + ReloadPrototypes(msg.Paths); - Sawmill.Info($"Reloaded prototypes in {sw.ElapsedMilliseconds} ms"); + Sawmill.Info($"Reloaded prototypes in {sw.ElapsedMilliseconds} ms"); #endif - } + } - public override void LoadDefaultPrototypes(Dictionary>? changed = null) - { - LoadDirectory(new("/EnginePrototypes/"), changed: changed); - LoadDirectory(_server.Options.PrototypeDirectory, changed: changed); - ResolveResults(); - } + public override void LoadDefaultPrototypes(Dictionary>? changed = null) + { + LoadDirectory(new("/EnginePrototypes/"), changed: changed); + LoadDirectory(_server.Options.PrototypeDirectory, changed: changed); + ResolveResults(); } } diff --git a/Robust.Shared/Prototypes/EntityPrototype.cs b/Robust.Shared/Prototypes/EntityPrototype.cs index b5be7c3702b..60dce625752 100644 --- a/Robust.Shared/Prototypes/EntityPrototype.cs +++ b/Robust.Shared/Prototypes/EntityPrototype.cs @@ -5,7 +5,6 @@ using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Localization; -using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Serialization; using Robust.Shared.Serialization.Manager; @@ -15,440 +14,438 @@ using Robust.Shared.Utility; using Robust.Shared.ViewVariables; -namespace Robust.Shared.Prototypes +namespace Robust.Shared.Prototypes; + +/// +/// Prototype that represents game entities. +/// +[Prototype(-1)] +public sealed partial class EntityPrototype : IPrototype, IInheritingPrototype, ISerializationHooks { + private ILocalizationManager _loc = default!; + + private static readonly Dictionary LocPropertiesDefault = new(); + + // LOCALIZATION NOTE: + // Localization-related properties in here are manually localized in LocalizationManager. + // As such, they should NOT be inherited to avoid confusing the system. + + private const int DEFAULT_RANGE = 200; + + [DataField("loc")] + private Dictionary? _locPropertiesSet; + + /// + /// The "in code name" of the object. Must be unique. + /// + [ViewVariables] + [IdDataFieldAttribute] + public string ID { get; private set; } = default!; + + /// + /// The name set on this level of the prototype. This does NOT handle localization or inheritance. + /// You probably want instead. + /// + /// + [DataField("name")] + public string? SetName { get; private set; } + + [DataField("description")] + public string? SetDesc { get; private set; } + + [DataField("suffix")] + public string? SetSuffix { get; private set; } + + [DataField("categories"), Access(typeof(PrototypeManager))] + [NeverPushInheritance] + internal HashSet>? CategoriesInternal; + + /// + /// What categories this prototype belongs to. This includes categories inherited from parents and categories + /// that were automatically inferred from the prototype's components. + /// + [ViewVariables] + public IReadOnlySet Categories { get; internal set; } = new HashSet(); + + [ViewVariables] + public IReadOnlyDictionary LocProperties => _locPropertiesSet ?? LocPropertiesDefault; + + /// + /// The "in game name" of the object. What is displayed to most players. + /// + [ViewVariables] + public string Name => _loc.GetEntityData(ID).Name; + + /// + /// The description of the object that shows upon using examine + /// + [ViewVariables] + public string Description => _loc.GetEntityData(ID).Desc; + + /// + /// Optional suffix to display in development menus like the entity spawn panel, + /// to provide additional info without ruining the Name property itself. + /// + [ViewVariables] + public string? EditorSuffix => _loc.GetEntityData(ID).Suffix; + + /// + /// Fluent messageId used to lookup the entity's name and localization attributes. + /// + [DataField("localizationId")] + public string? CustomLocalizationID { get; private set; } + + /// + /// If true, this object should not show up in the entity spawn panel. + /// + [Access(typeof(PrototypeManager))] + public bool HideSpawnMenu { get; internal set; } + + [DataField("placement")] + private EntityPlacementProperties PlacementProperties = new(); + + /// + /// The different mounting points on walls. (If any). + /// + [ViewVariables] + public List? MountingPoints => PlacementProperties.MountingPoints; + + /// + /// The Placement mode used for client-initiated placement. This is used for admin and editor placement. The serverside version controls what type the server assigns in normal gameplay. + /// + [ViewVariables] + public string PlacementMode => PlacementProperties.PlacementMode; + + /// + /// The Range this entity can be placed from. This is only used serverside since the server handles normal gameplay. The client uses unlimited range since it handles things like admin spawning and editing. + /// + [ViewVariables] + public int PlacementRange => PlacementProperties.PlacementRange; + + /// + /// Offset that is added to the position when placing. (if any). Client only. + /// + [ViewVariables] + public Vector2i PlacementOffset => PlacementProperties.PlacementOffset; + + /// + /// True if this entity will be saved by the map loader. + /// + [DataField("save")] + public bool MapSavable { get; set; } = true; /// - /// Prototype that represents game entities. + /// The prototype we inherit from. /// - [Prototype(-1)] - public sealed partial class EntityPrototype : IPrototype, IInheritingPrototype, ISerializationHooks + [ViewVariables] + [ParentDataField(typeof(AbstractPrototypeIdArraySerializer))] + public string[]? Parents { get; private set; } + + [ViewVariables] + [NeverPushInheritance] + [AbstractDataField] + public bool Abstract { get; private set; } + + /// + /// A dictionary mapping the component type list to the YAML mapping containing their settings. + /// + [DataField("components")] + [AlwaysPushInheritance] + public ComponentRegistry Components = new(); + + public EntityPrototype() { - private ILocalizationManager _loc = default!; - - private static readonly Dictionary LocPropertiesDefault = new(); - - // LOCALIZATION NOTE: - // Localization-related properties in here are manually localized in LocalizationManager. - // As such, they should NOT be inherited to avoid confusing the system. - - private const int DEFAULT_RANGE = 200; - - [DataField("loc")] - private Dictionary? _locPropertiesSet; - - /// - /// The "in code name" of the object. Must be unique. - /// - [ViewVariables] - [IdDataFieldAttribute] - public string ID { get; private set; } = default!; - - /// - /// The name set on this level of the prototype. This does NOT handle localization or inheritance. - /// You probably want instead. - /// - /// - [DataField("name")] - public string? SetName { get; private set; } - - [DataField("description")] - public string? SetDesc { get; private set; } - - [DataField("suffix")] - public string? SetSuffix { get; private set; } - - [DataField("categories"), Access(typeof(PrototypeManager))] - [NeverPushInheritance] - internal HashSet>? CategoriesInternal; - - /// - /// What categories this prototype belongs to. This includes categories inherited from parents and categories - /// that were automatically inferred from the prototype's components. - /// - [ViewVariables] - public IReadOnlySet Categories { get; internal set; } = new HashSet(); - - [ViewVariables] - public IReadOnlyDictionary LocProperties => _locPropertiesSet ?? LocPropertiesDefault; - - /// - /// The "in game name" of the object. What is displayed to most players. - /// - [ViewVariables] - public string Name => _loc.GetEntityData(ID).Name; - - /// - /// The description of the object that shows upon using examine - /// - [ViewVariables] - public string Description => _loc.GetEntityData(ID).Desc; - - /// - /// Optional suffix to display in development menus like the entity spawn panel, - /// to provide additional info without ruining the Name property itself. - /// - [ViewVariables] - public string? EditorSuffix => _loc.GetEntityData(ID).Suffix; - - /// - /// Fluent messageId used to lookup the entity's name and localization attributes. - /// - [DataField("localizationId")] - public string? CustomLocalizationID { get; private set; } - - /// - /// If true, this object should not show up in the entity spawn panel. - /// - [Access(typeof(PrototypeManager))] - public bool HideSpawnMenu { get; internal set; } - - [DataField("placement")] - private EntityPlacementProperties PlacementProperties = new(); - - /// - /// The different mounting points on walls. (If any). - /// - [ViewVariables] - public List? MountingPoints => PlacementProperties.MountingPoints; - - /// - /// The Placement mode used for client-initiated placement. This is used for admin and editor placement. The serverside version controls what type the server assigns in normal gameplay. - /// - [ViewVariables] - public string PlacementMode => PlacementProperties.PlacementMode; - - /// - /// The Range this entity can be placed from. This is only used serverside since the server handles normal gameplay. The client uses unlimited range since it handles things like admin spawning and editing. - /// - [ViewVariables] - public int PlacementRange => PlacementProperties.PlacementRange; - - /// - /// Offset that is added to the position when placing. (if any). Client only. - /// - [ViewVariables] - public Vector2i PlacementOffset => PlacementProperties.PlacementOffset; - - /// - /// True if this entity will be saved by the map loader. - /// - [DataField("save")] - public bool MapSavable { get; set; } = true; - - /// - /// The prototype we inherit from. - /// - [ViewVariables] - [ParentDataFieldAttribute(typeof(AbstractPrototypeIdArraySerializer))] - public string[]? Parents { get; private set; } - - [ViewVariables] - [NeverPushInheritance] - [AbstractDataField] - public bool Abstract { get; private set; } - - /// - /// A dictionary mapping the component type list to the YAML mapping containing their settings. - /// - [DataField("components")] - [AlwaysPushInheritance] - public ComponentRegistry Components = new(); - - public EntityPrototype() - { - // Everybody gets a transform component! - Components.Add("Transform", new ComponentRegistryEntry(new TransformComponent(), new MappingDataNode())); - // And a metadata component too! - Components.Add("MetaData", new ComponentRegistryEntry(new MetaDataComponent(), new MappingDataNode())); - } + // Everybody gets a transform component! + Components.Add("Transform", new ComponentRegistryEntry(new TransformComponent(), new MappingDataNode())); + // And a metadata component too! + Components.Add("MetaData", new ComponentRegistryEntry(new MetaDataComponent(), new MappingDataNode())); + } - void ISerializationHooks.AfterDeserialization() - { - _loc = IoCManager.Resolve(); - } + void ISerializationHooks.AfterDeserialization() + { + _loc = IoCManager.Resolve(); + } + + [Obsolete("Pass in IComponentFactory")] + public bool TryGetComponent([NotNullWhen(true)] out T? component) + where T : IComponent, new() + { + var compName = IoCManager.Resolve().GetComponentName(); + return TryGetComponent(compName, out component); + } + + public bool TryGetComponent([NotNullWhen(true)] out T? component, IComponentFactory factory) where T : IComponent, new() + { + var compName = factory.GetComponentName(); + return TryGetComponent(compName, out component); + } + + public bool TryGetComponent(string name, [NotNullWhen(true)] out T? component) where T : IComponent, new() + { + DebugTools.AssertEqual(IoCManager.Resolve().GetComponentName(), name); - [Obsolete("Pass in IComponentFactory")] - public bool TryGetComponent([NotNullWhen(true)] out T? component) - where T : IComponent, new() + if (!Components.TryGetValue(name, out var componentUnCast)) { - var compName = IoCManager.Resolve().GetComponentName(); - return TryGetComponent(compName, out component); + component = default; + return false; } - public bool TryGetComponent([NotNullWhen(true)] out T? component, IComponentFactory factory) where T : IComponent, new() + if (componentUnCast.Component is not T cast) { - var compName = factory.GetComponentName(); - return TryGetComponent(compName, out component); + component = default; + return false; } - public bool TryGetComponent(string name, [NotNullWhen(true)] out T? component) where T : IComponent, new() - { - DebugTools.AssertEqual(IoCManager.Resolve().GetComponentName(), name); + component = cast; + return true; + } - if (!Components.TryGetValue(name, out var componentUnCast)) - { - component = default; - return false; - } + internal static void LoadEntity( + Entity ent, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serManager, + IEntityLoadContext? context) //yeah officer this method right here + { + var (entity, meta) = ent; + var prototype = meta.EntityPrototype; + var ctx = context as ISerializationContext; - if (componentUnCast.Component is not T cast) + if (prototype != null) + { + foreach (var (name, entry) in prototype.Components) { - component = default; - return false; - } + if (context != null && context.ShouldSkipComponent(name)) + continue; - component = cast; - return true; + var fullData = context != null && context.TryGetComponent(name, out var data) ? data : entry.Component; + var compReg = factory.GetRegistration(name); + EnsureCompExistsAndDeserialize(entity, compReg, factory, entityManager, serManager, name, fullData, ctx); + + if (!entry.Component.NetSyncEnabled && compReg.NetID is {} netId) + meta.NetComponents.Remove(netId); + } } - internal static void LoadEntity( - Entity ent, - IComponentFactory factory, - IEntityManager entityManager, - ISerializationManager serManager, - IEntityLoadContext? context) //yeah officer this method right here + if (context != null) { - var (entity, meta) = ent; - var prototype = meta.EntityPrototype; - var ctx = context as ISerializationContext; - - if (prototype != null) + foreach (var name in context.GetExtraComponentTypes()) { - foreach (var (name, entry) in prototype.Components) + if (prototype != null && prototype.Components.ContainsKey(name)) { - if (context != null && context.ShouldSkipComponent(name)) - continue; - - var fullData = context != null && context.TryGetComponent(name, out var data) ? data : entry.Component; - var compReg = factory.GetRegistration(name); - EnsureCompExistsAndDeserialize(entity, compReg, factory, entityManager, serManager, name, fullData, ctx); - - if (!entry.Component.NetSyncEnabled && compReg.NetID is {} netId) - meta.NetComponents.Remove(netId); + // This component also exists in the prototype. + // This means that the previous step already caught both the prototype data AND map data. + // Meaning that re-running EnsureCompExistsAndDeserialize would wipe prototype data. + continue; } - } - if (context != null) - { - foreach (var name in context.GetExtraComponentTypes()) + if (!context.TryGetComponent(name, out var data)) { - if (prototype != null && prototype.Components.ContainsKey(name)) - { - // This component also exists in the prototype. - // This means that the previous step already caught both the prototype data AND map data. - // Meaning that re-running EnsureCompExistsAndDeserialize would wipe prototype data. - continue; - } - - if (!context.TryGetComponent(name, out var data)) - { - throw new InvalidOperationException( - $"{nameof(IEntityLoadContext)} provided component name {name} but refused to provide data"); - } - - var compReg = factory.GetRegistration(name); - EnsureCompExistsAndDeserialize(entity, compReg, factory, entityManager, serManager, name, data, ctx); + throw new InvalidOperationException( + $"{nameof(IEntityLoadContext)} provided component name {name} but refused to provide data"); } + + var compReg = factory.GetRegistration(name); + EnsureCompExistsAndDeserialize(entity, compReg, factory, entityManager, serManager, name, data, ctx); } } + } - public static void EnsureCompExistsAndDeserialize(EntityUid entity, - ComponentRegistration compReg, - IComponentFactory factory, - IEntityManager entityManager, - ISerializationManager serManager, - string compName, - IComponent data, - ISerializationContext? context) + public static void EnsureCompExistsAndDeserialize(EntityUid entity, + ComponentRegistration compReg, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serManager, + string compName, + IComponent data, + ISerializationContext? context) + { + if (!entityManager.TryGetComponent(entity, compReg.Idx, out var component)) { - if (!entityManager.TryGetComponent(entity, compReg.Idx, out var component)) - { - var newComponent = factory.GetComponent(compName); - entityManager.AddComponent(entity, newComponent); - component = newComponent; - } - - if (context is not EntityDeserializer map) - { - serManager.CopyTo(data, ref component, context, notNullableOverride: true); - return; - } - - map.CurrentComponent = compName; - serManager.CopyTo(data, ref component, context, notNullableOverride: true); - map.CurrentComponent = null; + var newComponent = factory.GetComponent(compName); + entityManager.AddComponent(entity, newComponent); + component = newComponent; } - public override string ToString() + if (context is not EntityDeserializer map) { - return $"EntityPrototype({ID})"; + serManager.CopyTo(data, ref component, context, notNullableOverride: true); + return; } - [DataRecord] - public partial record ComponentRegistryEntry(IComponent Component, MappingDataNode Mapping); + map.CurrentComponent = compName; + serManager.CopyTo(data, ref component, context, notNullableOverride: true); + map.CurrentComponent = null; + } + + public override string ToString() + { + return $"EntityPrototype({ID})"; + } + + [DataRecord] + public partial record ComponentRegistryEntry(IComponent Component, MappingDataNode Mapping); - [DataDefinition] - public sealed partial class EntityPlacementProperties - { - public bool PlacementOverriden { get; private set; } - public bool SnapOverriden { get; private set; } - private string _placementMode = "PlaceFree"; - private Vector2i _placementOffset; + [DataDefinition] + public sealed partial class EntityPlacementProperties + { + public bool PlacementOverriden { get; private set; } + public bool SnapOverriden { get; private set; } + private string _placementMode = "PlaceFree"; + private Vector2i _placementOffset; - [DataField("mode")] - public string PlacementMode + [DataField("mode")] + public string PlacementMode + { + get => _placementMode; + set { - get => _placementMode; - set - { - PlacementOverriden = true; - _placementMode = value; - } + PlacementOverriden = true; + _placementMode = value; } + } - [DataField("offset")] - public Vector2i PlacementOffset + [DataField("offset")] + public Vector2i PlacementOffset + { + get => _placementOffset; + set { - get => _placementOffset; - set - { - PlacementOverriden = true; - _placementOffset = value; - } + PlacementOverriden = true; + _placementOffset = value; } + } - [DataField("nodes")] public List? MountingPoints; + [DataField("nodes")] public List? MountingPoints; - [DataField("range")] public int PlacementRange = DEFAULT_RANGE; - private HashSet _snapFlags = new(); + [DataField("range")] public int PlacementRange = DEFAULT_RANGE; + private HashSet _snapFlags = new(); - [DataField("snap")] - public HashSet SnapFlags + [DataField("snap")] + public HashSet SnapFlags + { + get => _snapFlags; + set { - get => _snapFlags; - set - { - SnapOverriden = true; - _snapFlags = value; - } + SnapOverriden = true; + _snapFlags = value; } } - /*private class PrototypeSerializationContext : YamlObjectSerializer.Context + } + /*private class PrototypeSerializationContext : YamlObjectSerializer.Context + { + readonly EntityPrototype? prototype; + + public PrototypeSerializationContext(EntityPrototype? owner) { - readonly EntityPrototype? prototype; + prototype = owner; + } - public PrototypeSerializationContext(EntityPrototype? owner) + public override void SetCachedField(string field, T value) + { + if (StackDepth != 0 || prototype?.CurrentDeserializingComponent == null) { - prototype = owner; + base.SetCachedField(field, value); + return; } - public override void SetCachedField(string field, T value) + if (!prototype.FieldCache.TryGetValue(prototype.CurrentDeserializingComponent, out var fieldList)) { - if (StackDepth != 0 || prototype?.CurrentDeserializingComponent == null) - { - base.SetCachedField(field, value); - return; - } - - if (!prototype.FieldCache.TryGetValue(prototype.CurrentDeserializingComponent, out var fieldList)) - { - fieldList = new Dictionary<(string, Type), object?>(); - prototype.FieldCache[prototype.CurrentDeserializingComponent] = fieldList; - } - - fieldList[(field, typeof(T))] = value; + fieldList = new Dictionary<(string, Type), object?>(); + prototype.FieldCache[prototype.CurrentDeserializingComponent] = fieldList; } - public override bool TryGetCachedField(string field, [MaybeNullWhen(false)] out T value) - { - if (StackDepth != 0 || prototype?.CurrentDeserializingComponent == null) - { - return base.TryGetCachedField(field, out value); - } - - if (prototype.FieldCache.TryGetValue(prototype.CurrentDeserializingComponent, out var dict)) - { - if (dict.TryGetValue((field, typeof(T)), out var theValue)) - { - value = (T) theValue!; - return true; - } - } - - value = default!; - return false; - } + fieldList[(field, typeof(T))] = value; + } - public override void SetDataCache(string field, object value) + public override bool TryGetCachedField(string field, [MaybeNullWhen(false)] out T value) + { + if (StackDepth != 0 || prototype?.CurrentDeserializingComponent == null) { - if (StackDepth != 0 || prototype == null) - { - base.SetDataCache(field, value); - return; - } - - prototype.DataCache[field] = value; + return base.TryGetCachedField(field, out value); } - public override bool TryGetDataCache(string field, out object? value) + if (prototype.FieldCache.TryGetValue(prototype.CurrentDeserializingComponent, out var dict)) { - if (StackDepth != 0 || prototype == null) + if (dict.TryGetValue((field, typeof(T)), out var theValue)) { - return base.TryGetDataCache(field, out value); + value = (T) theValue!; + return true; } - - return prototype.DataCache.TryGetValue(field, out value); } - }*/ - } - public sealed class ComponentRegistry : Dictionary, IEntityLoadContext - { - public ComponentRegistry() - { - } - - public ComponentRegistry(Dictionary components) : base(components) - { + value = default!; + return false; } - /// - public bool TryGetComponent(string componentName, [NotNullWhen(true)] out IComponent? component) + public override void SetDataCache(string field, object value) { - var success = TryGetValue(componentName, out var comp); - component = comp?.Component; + if (StackDepth != 0 || prototype == null) + { + base.SetDataCache(field, value); + return; + } - return success; + prototype.DataCache[field] = value; } - /// - public bool TryGetComponent( - IComponentFactory componentFactory, - [NotNullWhen(true)] out TComponent? component - ) where TComponent : class, IComponent, new() + public override bool TryGetDataCache(string field, out object? value) { - component = null; - var componentName = componentFactory.GetComponentName(); - if (TryGetComponent(componentName, out var foundComponent)) + if (StackDepth != 0 || prototype == null) { - component = (TComponent)foundComponent; - return true; + return base.TryGetDataCache(field, out value); } - return false; + return prototype.DataCache.TryGetValue(field, out value); } + }*/ +} - /// - public IEnumerable GetExtraComponentTypes() - { - return Keys; - } +public sealed class ComponentRegistry : Dictionary, IEntityLoadContext +{ + public ComponentRegistry() + { + } - /// - public bool ShouldSkipComponent(string compName) + public ComponentRegistry(Dictionary components) : base(components) + { + } + + /// + public bool TryGetComponent(string componentName, [NotNullWhen(true)] out IComponent? component) + { + var success = TryGetValue(componentName, out var comp); + component = comp?.Component; + + return success; + } + + /// + public bool TryGetComponent( + IComponentFactory componentFactory, + [NotNullWhen(true)] out TComponent? component + ) where TComponent : class, IComponent, new() + { + component = null; + var componentName = componentFactory.GetComponentName(); + if (TryGetComponent(componentName, out var foundComponent)) { - return false; //Registries cannot represent the "remove this component" state. + component = (TComponent)foundComponent; + return true; } + + return false; + } + + /// + public IEnumerable GetExtraComponentTypes() + { + return Keys; + } + + /// + public bool ShouldSkipComponent(string compName) + { + return false; //Registries cannot represent the "remove this component" state. } } diff --git a/Robust.Shared/Prototypes/Exceptions.cs b/Robust.Shared/Prototypes/Exceptions.cs index 110941c1046..d2b1b3cfa8f 100644 --- a/Robust.Shared/Prototypes/Exceptions.cs +++ b/Robust.Shared/Prototypes/Exceptions.cs @@ -22,15 +22,9 @@ public PrototypeLoadException(string message, Exception inner) : base(message, i [Serializable] [Virtual] -public class UnknownPrototypeException : Exception +public class UnknownPrototypeException(string prototype, Type kind) : Exception { public override string Message => $"Unknown {Kind.Name} prototype: {Prototype}" ; - public readonly string Prototype; - public readonly Type Kind; - - public UnknownPrototypeException(string prototype, Type kind) - { - Prototype = prototype; - Kind = kind; - } + public readonly string Prototype = prototype; + public readonly Type Kind = kind; } diff --git a/Robust.Shared/Prototypes/IPrototype.cs b/Robust.Shared/Prototypes/IPrototype.cs index 7dff43f277f..6606ae26539 100644 --- a/Robust.Shared/Prototypes/IPrototype.cs +++ b/Robust.Shared/Prototypes/IPrototype.cs @@ -4,102 +4,92 @@ using Robust.Shared.ViewVariables; #endif -namespace Robust.Shared.Prototypes +namespace Robust.Shared.Prototypes; + +/// +/// IPrototype, when combined with , defines a type that the game can load from +/// the global YAML prototypes folder during init or runtime. It's a way of defining data for the game to read +/// and act on. +/// +/// +/// +/// +/// +public interface IPrototype { /// - /// IPrototype, when combined with , defines a type that the game can load from - /// the global YAML prototypes folder during init or runtime. It's a way of defining data for the game to read - /// and act on. + /// A unique ID for this prototype instance. + /// This will never be a duplicate, and the game will error during loading if there are multiple prototypes + /// with the same unique ID. /// - /// - /// - /// - /// - public interface IPrototype - { - /// - /// A unique ID for this prototype instance. - /// This will never be a duplicate, and the game will error during loading if there are multiple prototypes - /// with the same unique ID. - /// #if !ROBUST_ANALYZERS_TEST - [ViewVariables(VVAccess.ReadOnly)] + [ViewVariables(VVAccess.ReadOnly)] #endif - string ID { get; } - } + string ID { get; } +} +/// +/// An extension of that allows for a prototype to have parents that it inherits data +/// from. This, alongside and +/// , allow data-based multiple inheritance. +/// +/// +/// An example of this in practice is . +/// +/// +/// +/// +/// +public interface IInheritingPrototype +{ /// - /// An extension of that allows for a prototype to have parents that it inherits data - /// from. This, alongside and - /// , allow data-based multiple inheritance. + /// The collection of parents for this prototype. Parents' data is applied to the child in order of + /// specification in the array. /// - /// - /// An example of this in practice is . - /// - /// - /// - /// - /// - public interface IInheritingPrototype - { - /// - /// The collection of parents for this prototype. Parents' data is applied to the child in order of - /// specification in the array. - /// - string[]? Parents { get; } - - /// - /// Whether this prototype is "abstract". This behaves ike an abstract class, abstract prototypes are never - /// indexable and do not show up when enumerating prototypes, as they're just a source of data to inherit - /// from. - /// - bool Abstract { get; } - } + string[]? Parents { get; } /// - /// Marks a field as a prototype's unique identifier. This field must always be a string?. - ///
- /// This field is always required. + /// Whether this prototype is "abstract". This behaves ike an abstract class, abstract prototypes are never + /// indexable and do not show up when enumerating prototypes, as they're just a source of data to inherit + /// from. ///
- /// - public sealed class IdDataFieldAttribute : DataFieldAttribute - { - public const string Name = "id"; - public IdDataFieldAttribute(int priority = 1, Type? customTypeSerializer = null) : - base(Name, false, priority, true, false, customTypeSerializer) - { - } - } + bool Abstract { get; } +} - /// - /// Marks a field as the parent/parents field for this prototype, as required by - /// . This must either be a string?, or string[]?. - ///
- /// This field is never required. - ///
- /// - public sealed class ParentDataFieldAttribute : DataFieldAttribute - { - public const string Name = "parent"; - public ParentDataFieldAttribute(Type prototypeIdSerializer, int priority = 1) : - base(Name, false, priority, false, false, prototypeIdSerializer) - { - } - } +/// +/// Marks a field as a prototype's unique identifier. This field must always be a string?. +///
+/// This field is always required. +///
+/// +public sealed class IdDataFieldAttribute(int priority = 1, Type? customTypeSerializer = null) + : DataFieldAttribute(Name, false, priority, true, false, customTypeSerializer) +{ + public const string Name = "id"; +} - /// - /// Marks a field as the abstract field for this prototype, as required by - /// . This must be a bool. - ///
- /// This field is never required. - ///
- /// - public sealed class AbstractDataFieldAttribute : DataFieldAttribute - { - public const string Name = "abstract"; - public AbstractDataFieldAttribute(int priority = 1) : - base(Name, false, priority, false, false, null) - { - } - } +/// +/// Marks a field as the parent/parents field for this prototype, as required by +/// . This must either be a string?, or string[]?. +///
+/// This field is never required. +///
+/// +public sealed class ParentDataFieldAttribute(Type prototypeIdSerializer, int priority = 1) + : DataFieldAttribute(Name, false, priority, false, false, prototypeIdSerializer) +{ + public const string Name = "parent"; +} + +/// +/// Marks a field as the abstract field for this prototype, as required by +/// . This must be a bool. +///
+/// This field is never required. +///
+/// +public sealed class AbstractDataFieldAttribute(int priority = 1) + : DataFieldAttribute(Name, false, priority, false, false, null) +{ + public const string Name = "abstract"; } diff --git a/Robust.Shared/Prototypes/IPrototypeManager.cs b/Robust.Shared/Prototypes/IPrototypeManager.cs index fcb5cedb18e..b84792888d7 100644 --- a/Robust.Shared/Prototypes/IPrototypeManager.cs +++ b/Robust.Shared/Prototypes/IPrototypeManager.cs @@ -568,7 +568,7 @@ void ReloadPrototypes( /// /// Loads several prototype kinds into the manager. Note that this will re-build a frozen dictionary and should be avoided if possible. /// - /// + /// /// The type of the prototype kind that implements . This type also /// requires a with a non-empty class string. /// diff --git a/Robust.Shared/Prototypes/MultiRootInheritanceGraph.cs b/Robust.Shared/Prototypes/MultiRootInheritanceGraph.cs index 66cdc5d6d2f..5b9c3b7ff72 100644 --- a/Robust.Shared/Prototypes/MultiRootInheritanceGraph.cs +++ b/Robust.Shared/Prototypes/MultiRootInheritanceGraph.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using Robust.Shared.Utility; namespace Robust.Shared.Prototypes; @@ -48,8 +47,11 @@ public void Add(T id, params T[] parents) while (queue.TryDequeue(out var parentL1)) { if (EqualityComparer.Default.Equals(parentL1,id)) + { throw new InvalidOperationException( $"Circular Inheritance detected for id \"{id}\" and parent \"{parent}\""); + } + var parentsL2 = GetParents(parentL1); if (parentsL2 != null) { diff --git a/Robust.Shared/Prototypes/PrototypeInheritanceTree.cs b/Robust.Shared/Prototypes/PrototypeInheritanceTree.cs index 5ec2ef16228..ae434adc454 100644 --- a/Robust.Shared/Prototypes/PrototypeInheritanceTree.cs +++ b/Robust.Shared/Prototypes/PrototypeInheritanceTree.cs @@ -1,113 +1,112 @@ using System; using System.Collections.Generic; -namespace Robust.Shared.Prototypes +namespace Robust.Shared.Prototypes; + +public sealed class PrototypeInheritanceTree { - public sealed class PrototypeInheritanceTree - { - private Dictionary> _nodes = new(); + private Dictionary> _nodes = new(); - private Dictionary> _pendingParent = new(); + private Dictionary> _pendingParent = new(); - private HashSet _baseNodes = new(); + private HashSet _baseNodes = new(); - private Dictionary _parents = new(); + private Dictionary _parents = new(); - public IReadOnlySet BaseNodes => _baseNodes; + public IReadOnlySet BaseNodes => _baseNodes; - public IReadOnlySet Children(string id) - { - if (!_nodes.ContainsKey(id)) - throw new ArgumentException($"ID {id} not present in InheritanceTree", nameof(id)); - return _nodes[id]; - } + public IReadOnlySet Children(string id) + { + if (!_nodes.ContainsKey(id)) + throw new ArgumentException($"ID {id} not present in InheritanceTree", nameof(id)); + return _nodes[id]; + } - public string GetBaseNode(string id) + public string GetBaseNode(string id) + { + if (!_nodes.ContainsKey(id)) + throw new ArgumentException($"ID {id} not present in InheritanceTree", nameof(id)); + + var parent = id; + while (_parents.TryGetValue(parent, out var nextParent)) { - if (!_nodes.ContainsKey(id)) - throw new ArgumentException($"ID {id} not present in InheritanceTree", nameof(id)); + parent = nextParent; + } - var parent = id; - while (_parents.TryGetValue(parent, out var nextParent)) - { - parent = nextParent; - } + return parent; + } - return parent; - } + public string? GetParent(string id) + { + return _parents.GetValueOrDefault(id); + } - public string? GetParent(string id) + public void AddId(string id, string? parent, bool overwrite = false) + { + if (overwrite && HasId(id)) { - return _parents.GetValueOrDefault(id); + RemoveId(id); } - public void AddId(string id, string? parent, bool overwrite = false) + if (_nodes.ContainsKey(id)) + throw new ArgumentException($"ID {id} already present in InheritanceTree", nameof(id)); + + if (parent != null) { - if (overwrite && HasId(id)) + _parents.Add(id, parent); + + if (_nodes.TryGetValue(parent, out var parentsChildren)) { - RemoveId(id); + parentsChildren.Add(id); } - - if (_nodes.ContainsKey(id)) - throw new ArgumentException($"ID {id} already present in InheritanceTree", nameof(id)); - - if (parent != null) + else { - _parents.Add(id, parent); - - if (_nodes.TryGetValue(parent, out var parentsChildren)) + if (!_pendingParent.TryGetValue(parent, out _)) { - parentsChildren.Add(id); - } - else - { - if (!_pendingParent.TryGetValue(parent, out _)) - { - _pendingParent[parent] = new HashSet(); - } - - _pendingParent[parent].Add(id); + _pendingParent[parent] = new HashSet(); } - //cycle detection - var currentParent = parent; - while (currentParent != null) - { - if (currentParent == id) - throw new InvalidOperationException( - $"Cycle detected when trying to add id {id} with parent {parent}"); - _parents.TryGetValue(currentParent, out currentParent); - } + _pendingParent[parent].Add(id); } - else + + //cycle detection + var currentParent = parent; + while (currentParent != null) { - _baseNodes.Add(id); + if (currentParent == id) + throw new InvalidOperationException( + $"Cycle detected when trying to add id {id} with parent {parent}"); + _parents.TryGetValue(currentParent, out currentParent); } - - if (!_pendingParent.TryGetValue(id, out var ourChildren)) - ourChildren = new HashSet(); - - _nodes.Add(id, ourChildren); } - - public bool HasId(string id) + else { - return _nodes.ContainsKey(id); + _baseNodes.Add(id); } - public void RemoveId(string id) - { - if (!_nodes.ContainsKey(id)) - throw new ArgumentException($"ID {id} not present in InheritanceTree", nameof(id)); + if (!_pendingParent.TryGetValue(id, out var ourChildren)) + ourChildren = new HashSet(); - _nodes.Remove(id); - foreach (var (_, children) in _pendingParent) - { - children.Remove(id); - } + _nodes.Add(id, ourChildren); + } + + public bool HasId(string id) + { + return _nodes.ContainsKey(id); + } + + public void RemoveId(string id) + { + if (!_nodes.ContainsKey(id)) + throw new ArgumentException($"ID {id} not present in InheritanceTree", nameof(id)); - _baseNodes.Remove(id); - _parents.Remove(id); + _nodes.Remove(id); + foreach (var (_, children) in _pendingParent) + { + children.Remove(id); } + + _baseNodes.Remove(id); + _parents.Remove(id); } } diff --git a/Robust.Shared/Prototypes/PrototypeManager.Categories.cs b/Robust.Shared/Prototypes/PrototypeManager.Categories.cs index 739a8eee3ba..2bc78b1ca88 100644 --- a/Robust.Shared/Prototypes/PrototypeManager.Categories.cs +++ b/Robust.Shared/Prototypes/PrototypeManager.Categories.cs @@ -1,9 +1,7 @@ -using System; -using System.Collections.Frozen; +using System.Collections.Frozen; using System.Collections.Generic; using System.Linq; using System.Reflection; -using Robust.Shared.Serialization.Markdown.Mapping; using Robust.Shared.Serialization.Markdown.Sequence; using Robust.Shared.Serialization.Markdown.Value; using Robust.Shared.Utility; @@ -11,7 +9,7 @@ namespace Robust.Shared.Prototypes; // This partial class handles entity prototype categories -public abstract partial class PrototypeManager : IPrototypeManagerInternal +public abstract partial class PrototypeManager { /// /// Cached array of components with the diff --git a/Robust.Shared/Prototypes/PrototypeManager.ValidateFields.cs b/Robust.Shared/Prototypes/PrototypeManager.ValidateFields.cs index 62801514590..a0c021b9de7 100644 --- a/Robust.Shared/Prototypes/PrototypeManager.ValidateFields.cs +++ b/Robust.Shared/Prototypes/PrototypeManager.ValidateFields.cs @@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; -using Robust.Shared.GameObjects; using Robust.Shared.Serialization.Manager.Attributes; using Robust.Shared.Utility; using BindingFlags = System.Reflection.BindingFlags; diff --git a/Robust.Shared/Prototypes/PrototypeManager.YamlLoad.cs b/Robust.Shared/Prototypes/PrototypeManager.YamlLoad.cs index e92ab3a92f2..c78590135e7 100644 --- a/Robust.Shared/Prototypes/PrototypeManager.YamlLoad.cs +++ b/Robust.Shared/Prototypes/PrototypeManager.YamlLoad.cs @@ -3,7 +3,6 @@ using System.IO; using System.Linq; using System.Threading; -using Robust.Shared.Random; using Robust.Shared.Serialization.Markdown; using Robust.Shared.Serialization.Markdown.Mapping; using Robust.Shared.Serialization.Markdown.Sequence; diff --git a/Robust.Shared/Prototypes/PrototypeManager.YamlValidate.cs b/Robust.Shared/Prototypes/PrototypeManager.YamlValidate.cs index 65035b42836..6758269ac49 100644 --- a/Robust.Shared/Prototypes/PrototypeManager.YamlValidate.cs +++ b/Robust.Shared/Prototypes/PrototypeManager.YamlValidate.cs @@ -188,22 +188,15 @@ private HashSet ValidateProto(Type type, IPrototype instance, ISerial } } - private sealed class PrototypeValidationData + private sealed class PrototypeValidationData(string id, MappingDataNode mapping, string file) { - public readonly string Id; - public MappingDataNode Mapping; - public readonly string File; + public readonly string Id = id; + public MappingDataNode Mapping = mapping; + public readonly string File = file; public bool Pushed; public string[]? Parents; public MappingDataNode[]? ParentMappings; - - public PrototypeValidationData(string id, MappingDataNode mapping, string file) - { - Id = id; - File = file; - Mapping = mapping; - } } private void EnsurePushed( diff --git a/Robust.Shared/Prototypes/PrototypeManager.cs b/Robust.Shared/Prototypes/PrototypeManager.cs index 527ae7ea2cf..20d73a12efd 100644 --- a/Robust.Shared/Prototypes/PrototypeManager.cs +++ b/Robust.Shared/Prototypes/PrototypeManager.cs @@ -7,8 +7,6 @@ using System.Threading; using System.Threading.Channels; using System.Threading.Tasks; -using Robust.Shared.Asynchronous; -using Robust.Shared.Collections; using Robust.Shared.ContentPack; using Robust.Shared.GameObjects; using Robust.Shared.IoC; @@ -24,1249 +22,1247 @@ using Robust.Shared.Timing; using Robust.Shared.Utility; -namespace Robust.Shared.Prototypes +namespace Robust.Shared.Prototypes; + +public abstract partial class PrototypeManager : IPrototypeManagerInternal { - public abstract partial class PrototypeManager : IPrototypeManagerInternal - { - [Dependency] private IReflectionManager _reflectionManager = default!; - [Dependency] protected IResourceManager Resources = default!; - [Dependency] protected ITaskManager TaskManager = default!; - [Dependency] private ISerializationManager _serializationManager = default!; - [Dependency] private ILogManager _logManager = default!; - [Dependency] private ILocalizationManager _locMan = default!; - [Dependency] private IComponentFactory _factory = default!; - [Dependency] private IEntityManager _entMan = default!; - [Dependency] private IRobustRandom _random = default!; + [Dependency] private IReflectionManager _reflectionManager = default!; + [Dependency] protected IResourceManager Resources = default!; + [Dependency] private ISerializationManager _serializationManager = default!; + [Dependency] private ILogManager _logManager = default!; + [Dependency] private ILocalizationManager _locMan = default!; + [Dependency] private IComponentFactory _factory = default!; + [Dependency] private IEntityManager _entMan = default!; + [Dependency] private IRobustRandom _random = default!; - private readonly Dictionary> _prototypeDataCache = new(); - private EntityDiffContext _context = new(); + private readonly Dictionary> _prototypeDataCache = new(); + private EntityDiffContext _context = new(); - private readonly Dictionary _kindNames = new(); - private readonly Dictionary _kindPriorities = new(); + private readonly Dictionary _kindNames = new(); + private readonly Dictionary _kindPriorities = new(); - protected ISawmill Sawmill = default!; + protected ISawmill Sawmill = default!; - private bool _initialized; - private bool _hasEverBeenReloaded; + private bool _initialized; + private bool _hasEverBeenReloaded; - #region IPrototypeManager members + #region IPrototypeManager members - private FrozenDictionary _kinds = FrozenDictionary.Empty; + private FrozenDictionary _kinds = FrozenDictionary.Empty; - private readonly HashSet _ignoredPrototypeTypes = new(); + private readonly HashSet _ignoredPrototypeTypes = new(); - public virtual void Initialize() - { - if (_initialized) - return; + public virtual void Initialize() + { + if (_initialized) + return; - Sawmill = _logManager.GetSawmill("proto"); + Sawmill = _logManager.GetSawmill("proto"); - _initialized = true; - ReloadPrototypeKinds(); - PrototypesReloaded += OnReload; - } + _initialized = true; + ReloadPrototypeKinds(); + PrototypesReloaded += OnReload; + } - /// - public IEnumerable GetPrototypeKinds() - { - return _kindNames.Keys; - } + /// + public IEnumerable GetPrototypeKinds() + { + return _kindNames.Keys; + } - /// - public int Count() where T : class, IPrototype - { - return _kinds[typeof(T)].Instances.Count; - } + /// + public int Count() where T : class, IPrototype + { + return _kinds[typeof(T)].Instances.Count; + } + + /// + public IEnumerable EnumeratePrototypes() where T : class, IPrototype + { + return GetInstances().Values; + } - /// - public IEnumerable EnumeratePrototypes() where T : class, IPrototype + /// + public IEnumerable EnumeratePrototypes(Type kind) + { + if (!_hasEverBeenReloaded) { - return GetInstances().Values; + throw new InvalidOperationException("No prototypes have been loaded yet."); } - /// - public IEnumerable EnumeratePrototypes(Type kind) - { - if (!_hasEverBeenReloaded) - { - throw new InvalidOperationException("No prototypes have been loaded yet."); - } + return _kinds[kind].Instances.Values; + } - return _kinds[kind].Instances.Values; - } + /// + public IEnumerable EnumeratePrototypes(string variant) + { + return EnumeratePrototypes(GetKindType(variant)); + } - /// - public IEnumerable EnumeratePrototypes(string variant) - { - return EnumeratePrototypes(GetKindType(variant)); - } + /// + public IEnumerable EnumerateParents(T proto, bool includeSelf = false) + where T : class, IPrototype, IInheritingPrototype + { + return EnumerateParents(proto.ID, includeSelf); + } - /// - public IEnumerable EnumerateParents(T proto, bool includeSelf = false) - where T : class, IPrototype, IInheritingPrototype + /// + public IEnumerable EnumerateParents(string id, bool includeSelf = false) + where T : class, IPrototype, IInheritingPrototype + { + if (!_hasEverBeenReloaded) { - return EnumerateParents(proto.ID, includeSelf); + throw new InvalidOperationException("No prototypes have been loaded yet."); } - /// - public IEnumerable EnumerateParents(string id, bool includeSelf = false) - where T : class, IPrototype, IInheritingPrototype - { - if (!_hasEverBeenReloaded) - { - throw new InvalidOperationException("No prototypes have been loaded yet."); - } - - if (!TryIndex(id, out var prototype)) - yield break; + if (!TryIndex(id, out var prototype)) + yield break; - if (includeSelf) - yield return prototype; + if (includeSelf) + yield return prototype; - if (prototype.Parents == null) - yield break; + if (prototype.Parents == null) + yield break; - var queue = new Queue(prototype.Parents); - while (queue.TryDequeue(out var prototypeId)) - { - if (!TryIndex(prototypeId, out var parent)) - continue; // Abstract parent? + var queue = new Queue(prototype.Parents); + while (queue.TryDequeue(out var prototypeId)) + { + if (!TryIndex(prototypeId, out var parent)) + continue; // Abstract parent? - yield return parent; - if (parent.Parents == null) - continue; + yield return parent; + if (parent.Parents == null) + continue; - foreach (var parentId in parent.Parents) - { - queue.Enqueue(parentId); - } + foreach (var parentId in parent.Parents) + { + queue.Enqueue(parentId); } } + } - /// - public IEnumerable EnumerateParents(Type kind, string id, bool includeSelf = false) + /// + public IEnumerable EnumerateParents(Type kind, string id, bool includeSelf = false) + { + if (!_hasEverBeenReloaded) { - if (!_hasEverBeenReloaded) - { - throw new InvalidOperationException("No prototypes have been loaded yet."); - } + throw new InvalidOperationException("No prototypes have been loaded yet."); + } - if (!kind.IsAssignableTo(typeof(IInheritingPrototype))) - { - throw new InvalidOperationException("The provided prototype type is not an inheriting prototype"); - } + if (!kind.IsAssignableTo(typeof(IInheritingPrototype))) + { + throw new InvalidOperationException("The provided prototype type is not an inheriting prototype"); + } - if (!TryIndex(kind, id, out var prototype)) - yield break; + if (!TryIndex(kind, id, out var prototype)) + yield break; - if (includeSelf) - yield return prototype; + if (includeSelf) + yield return prototype; - var iPrototype = (IInheritingPrototype)prototype; - if (iPrototype.Parents == null) - yield break; + var iPrototype = (IInheritingPrototype)prototype; + if (iPrototype.Parents == null) + yield break; - var queue = new Queue(iPrototype.Parents); - while (queue.TryDequeue(out var prototypeId)) - { - if (!TryIndex(kind, prototypeId, out var parent)) - continue; // Abstract parent? + var queue = new Queue(iPrototype.Parents); + while (queue.TryDequeue(out var prototypeId)) + { + if (!TryIndex(kind, prototypeId, out var parent)) + continue; // Abstract parent? - yield return parent; - iPrototype = (IInheritingPrototype)parent; - if (iPrototype.Parents == null) - continue; + yield return parent; + iPrototype = (IInheritingPrototype)parent; + if (iPrototype.Parents == null) + continue; - foreach (var parentId in iPrototype.Parents) - { - queue.Enqueue(parentId); - } + foreach (var parentId in iPrototype.Parents) + { + queue.Enqueue(parentId); } } + } - /// - public IEnumerable<(string id, T?)> EnumerateAllParents(string id, bool includeSelf = false) - where T : class, IPrototype, IInheritingPrototype - { - if (!_hasEverBeenReloaded) - throw new InvalidOperationException("No prototypes have been loaded yet."); + /// + public IEnumerable<(string id, T?)> EnumerateAllParents(string id, bool includeSelf = false) + where T : class, IPrototype, IInheritingPrototype + { + if (!_hasEverBeenReloaded) + throw new InvalidOperationException("No prototypes have been loaded yet."); - if (!_kinds.TryGetValue(typeof(T), out var kindData)) - throw new UnknownPrototypeException(id, typeof(T)); + if (!_kinds.TryGetValue(typeof(T), out var kindData)) + throw new UnknownPrototypeException(id, typeof(T)); - if (!kindData.Results.ContainsKey(id)) - yield break; + if (!kindData.Results.ContainsKey(id)) + yield break; - IPrototype? uncast; - T? instance; + IPrototype? uncast; + T? instance; - if (includeSelf) - { - kindData.Instances.TryGetValue(id, out uncast); - instance = uncast as T; - yield return (id, instance); - } + if (includeSelf) + { + kindData.Instances.TryGetValue(id, out uncast); + instance = uncast as T; + yield return (id, instance); + } - if (!kindData.Inheritance!.TryGetParents(id, out var parents)) - yield break; + if (!kindData.Inheritance!.TryGetParents(id, out var parents)) + yield break; - var queue = new Queue(parents); - while (queue.TryDequeue(out var prototypeId)) + var queue = new Queue(parents); + while (queue.TryDequeue(out var prototypeId)) + { + if (!kindData.Results.ContainsKey(prototypeId)) { - if (!kindData.Results.ContainsKey(prototypeId)) - { - Sawmill.Error($"Encountered invalid prototype while enumerating parents. Kind: {typeof(T).Name}. Child: {id}. Invalid: {prototypeId}"); - continue; - } + Sawmill.Error($"Encountered invalid prototype while enumerating parents. Kind: {typeof(T).Name}. Child: {id}. Invalid: {prototypeId}"); + continue; + } - kindData.Instances.TryGetValue(prototypeId, out uncast); - instance = uncast as T; - yield return (prototypeId, instance); + kindData.Instances.TryGetValue(prototypeId, out uncast); + instance = uncast as T; + yield return (prototypeId, instance); - if (!kindData.Inheritance.TryGetParents(prototypeId, out parents)) - continue; + if (!kindData.Inheritance.TryGetParents(prototypeId, out parents)) + continue; - foreach (var parentId in parents) - { - queue.Enqueue(parentId); - } + foreach (var parentId in parents) + { + queue.Enqueue(parentId); } } + } + + public IEnumerable EnumeratePrototypeKinds() + { + if (!_hasEverBeenReloaded) + throw new InvalidOperationException("No prototypes have been loaded yet."); + return _kinds.Keys; + } - public IEnumerable EnumeratePrototypeKinds() + /// + public T Index(string id) where T : class, IPrototype + { + if (!_hasEverBeenReloaded) { - if (!_hasEverBeenReloaded) - throw new InvalidOperationException("No prototypes have been loaded yet."); - return _kinds.Keys; + throw new InvalidOperationException("No prototypes have been loaded yet."); } - /// - public T Index(string id) where T : class, IPrototype + try { - if (!_hasEverBeenReloaded) - { - throw new InvalidOperationException("No prototypes have been loaded yet."); - } - - try - { - return (T)_kinds[typeof(T)].Instances[id]; - } - catch (KeyNotFoundException) - { - throw new UnknownPrototypeException(id, typeof(T)); - } + return (T)_kinds[typeof(T)].Instances[id]; } - - /// - public EntityPrototype Index(EntProtoId id) + catch (KeyNotFoundException) { - return Index(id.Id); + throw new UnknownPrototypeException(id, typeof(T)); } + } - /// - public T Index(ProtoId id) where T : class, IPrototype + /// + public EntityPrototype Index(EntProtoId id) + { + return Index(id.Id); + } + + /// + public T Index(ProtoId id) where T : class, IPrototype + { + return Index(id.Id); + } + + /// + public IPrototype Index(Type kind, string id) + { + if (!_hasEverBeenReloaded) { - return Index(id.Id); + throw new InvalidOperationException("No prototypes have been loaded yet."); } - /// - public IPrototype Index(Type kind, string id) + try { - if (!_hasEverBeenReloaded) - { - throw new InvalidOperationException("No prototypes have been loaded yet."); - } - - try - { - return _kinds[kind].Instances[id]; - } - catch (KeyNotFoundException) - { - throw new UnknownPrototypeException(id, kind); - } + return _kinds[kind].Instances[id]; } - - /// - public void Clear() + catch (KeyNotFoundException) { - _kindNames.Clear(); - _kinds = FrozenDictionary.Empty; + throw new UnknownPrototypeException(id, kind); } + } - /// - public void Reset() - { - var removed = _kinds.ToDictionary( - x => x.Key, - x => x.Value.Instances.Keys.ToHashSet()); + /// + public void Clear() + { + _kindNames.Clear(); + _kinds = FrozenDictionary.Empty; + } - ReloadPrototypeKinds(); - Dictionary> prototypes = new(); - LoadDefaultPrototypes(prototypes); + /// + public void Reset() + { + var removed = _kinds.ToDictionary( + x => x.Key, + x => x.Value.Instances.Keys.ToHashSet()); - foreach (var (kind, ids) in prototypes) - { - if (!removed.TryGetValue(kind, out var removedIds)) - continue; + ReloadPrototypeKinds(); + Dictionary> prototypes = new(); + LoadDefaultPrototypes(prototypes); - removedIds.ExceptWith(ids); - if (removedIds.Count == 0) - removed.Remove(kind); - } + foreach (var (kind, ids) in prototypes) + { + if (!removed.TryGetValue(kind, out var removedIds)) + continue; - ReloadPrototypes(prototypes, removed); - _locMan.ReloadLocalizations(); + removedIds.ExceptWith(ids); + if (removedIds.Count == 0) + removed.Remove(kind); } - /// - public abstract void LoadDefaultPrototypes(Dictionary>? changed = null); + ReloadPrototypes(prototypes, removed); + _locMan.ReloadLocalizations(); + } - private int SortPrototypesByPriority(Type a, Type b) - { - return _kindPriorities[b].CompareTo(_kindPriorities[a]); - } + /// + public abstract void LoadDefaultPrototypes(Dictionary>? changed = null); - protected void ReloadPrototypes(IEnumerable filePaths) - { + private int SortPrototypesByPriority(Type a, Type b) + { + return _kindPriorities[b].CompareTo(_kindPriorities[a]); + } + + protected void ReloadPrototypes(IEnumerable filePaths) + { #if TOOLS - var changed = new Dictionary>(); - foreach (var filePath in filePaths) - { - LoadFile(filePath.ToRootedPath(), true, changed); - } + var changed = new Dictionary>(); + foreach (var filePath in filePaths) + { + LoadFile(filePath.ToRootedPath(), true, changed); + } - ReloadPrototypes(changed); + ReloadPrototypes(changed); #endif - } + } - /// - public void ReloadPrototypes( - Dictionary> modified, - Dictionary>? removed = null) - { - var prototypeTypeOrder = modified.Keys.ToList(); - prototypeTypeOrder.Sort(SortPrototypesByPriority); + /// + public void ReloadPrototypes( + Dictionary> modified, + Dictionary>? removed = null) + { + var prototypeTypeOrder = modified.Keys.ToList(); + prototypeTypeOrder.Sort(SortPrototypesByPriority); - var byType = new Dictionary(); - var modifiedKinds = new HashSet(); - var toProcess = new HashSet(); - var processQueue = new Queue(); + var byType = new Dictionary(); + var modifiedKinds = new HashSet(); + var toProcess = new HashSet(); + var processQueue = new Queue(); - foreach (var kind in prototypeTypeOrder) - { - var modifiedInstances = new Dictionary(); - var kindData = _kinds[kind]; + foreach (var kind in prototypeTypeOrder) + { + var modifiedInstances = new Dictionary(); + var kindData = _kinds[kind]; - var tree = kindData.Inheritance; - toProcess.Clear(); - processQueue.Clear(); + var tree = kindData.Inheritance; + toProcess.Clear(); + processQueue.Clear(); - DebugTools.AssertEqual(kind.IsAssignableTo(typeof(IInheritingPrototype)), tree != null); - DebugTools.Assert(tree != null || kindData.RawResults == kindData.Results); + DebugTools.AssertEqual(kind.IsAssignableTo(typeof(IInheritingPrototype)), tree != null); + DebugTools.Assert(tree != null || kindData.RawResults == kindData.Results); - foreach (var id in modified[kind]) - { - AddToQueue(id); - } + foreach (var id in modified[kind]) + { + AddToQueue(id); + } - void AddToQueue(string id) - { - if (!toProcess.Add(id)) - return; - processQueue.Enqueue(id); + void AddToQueue(string id) + { + if (!toProcess.Add(id)) + return; + processQueue.Enqueue(id); - if (tree == null) - return; + if (tree == null) + return; - if (!tree.TryGetChildren(id, out var children)) - return; + if (!tree.TryGetChildren(id, out var children)) + return; - foreach (var child in children!) - { - AddToQueue(child); - } + foreach (var child in children!) + { + AddToQueue(child); } + } - while (processQueue.TryDequeue(out var id)) + while (processQueue.TryDequeue(out var id)) + { + DebugTools.Assert(toProcess.Contains(id)); + if (tree != null) { - DebugTools.Assert(toProcess.Contains(id)); - if (tree != null) + if (tree.TryGetParents(id, out var parents)) { - if (tree.TryGetParents(id, out var parents)) + DebugTools.Assert(parents.Length > 0); + var nonPushedParent = false; + foreach (var parent in parents) { - DebugTools.Assert(parents.Length > 0); - var nonPushedParent = false; - foreach (var parent in parents) - { - if (!toProcess.Contains(parent)) - continue; - - // our parent has been modified, but has not yet been processed. - // we re-queue ourselves at the end of the queue. - DebugTools.Assert(processQueue.Contains(parent)); - processQueue.Enqueue(id); - nonPushedParent = true; - break; - } - - if (nonPushedParent) + if (!toProcess.Contains(parent)) continue; - if (parents.Length == 1) - { - kindData.Results[id] = _serializationManager.PushCompositionWithGenericNode( - kind, - kindData.Results[parents[0]], - kindData.RawResults[id]); - } - else - { - var parentMaps = new MappingDataNode[parents.Length]; - for (var i = 0; i < parentMaps.Length; i++) - { - parentMaps[i] = kindData.Results[parents[i]]; - } - - kindData.Results[id] = _serializationManager.PushCompositionWithGenericNode( - kind, - parentMaps, - kindData.RawResults[id]); - } + // our parent has been modified, but has not yet been processed. + // we re-queue ourselves at the end of the queue. + DebugTools.Assert(processQueue.Contains(parent)); + processQueue.Enqueue(id); + nonPushedParent = true; + break; + } + + if (nonPushedParent) + continue; + + if (parents.Length == 1) + { + kindData.Results[id] = _serializationManager.PushCompositionWithGenericNode( + kind, + kindData.Results[parents[0]], + kindData.RawResults[id]); } else { - kindData.Results[id] = kindData.RawResults[id]; + var parentMaps = new MappingDataNode[parents.Length]; + for (var i = 0; i < parentMaps.Length; i++) + { + parentMaps[i] = kindData.Results[parents[i]]; + } + + kindData.Results[id] = _serializationManager.PushCompositionWithGenericNode( + kind, + parentMaps, + kindData.RawResults[id]); } } - - toProcess.Remove(id); - - var prototype = TryReadPrototype(kind, id, kindData.Results[id], SerializationHookContext.DontSkipHooks); - if (prototype == null) - continue; - - kindData.UnfrozenInstances ??= kindData.Instances.ToDictionary(); - kindData.UnfrozenInstances[id] = prototype; - modifiedInstances.Add(id, prototype); + else + { + kindData.Results[id] = kindData.RawResults[id]; + } } - if (modifiedInstances.Count == 0) + toProcess.Remove(id); + + var prototype = TryReadPrototype(kind, id, kindData.Results[id], SerializationHookContext.DontSkipHooks); + if (prototype == null) continue; - byType.Add(kindData.Type, new(modifiedInstances)); - modifiedKinds.Add(kindData); + kindData.UnfrozenInstances ??= kindData.Instances.ToDictionary(); + kindData.UnfrozenInstances[id] = prototype; + modifiedInstances.Add(id, prototype); } - Freeze(modifiedKinds); + if (modifiedInstances.Count == 0) + continue; - if (modifiedKinds.Any(x => x.Type == typeof(EntityPrototype) || x.Type == typeof(EntityCategoryPrototype))) - UpdateCategories(); + byType.Add(kindData.Type, new(modifiedInstances)); + modifiedKinds.Add(kindData); + } - var modifiedTypes = new HashSet(byType.Keys); - if (removed != null) - modifiedTypes.UnionWith(removed.Keys); + Freeze(modifiedKinds); - var ev = new PrototypesReloadedEventArgs(modifiedTypes, byType, removed); - PrototypesReloaded?.Invoke(ev); - _entMan.EventBus.RaiseEvent(EventSource.Local, ev); - } + if (modifiedKinds.Any(x => x.Type == typeof(EntityPrototype) || x.Type == typeof(EntityCategoryPrototype))) + UpdateCategories(); - private void Freeze(IEnumerable kinds) - { - var st = RStopwatch.StartNew(); - foreach (var kind in kinds) - { - kind.Freeze(); - } + var modifiedTypes = new HashSet(byType.Keys); + if (removed != null) + modifiedTypes.UnionWith(removed.Keys); - // fun fact: Sawmill can be null in tests???? - Sawmill?.Verbose($"Freezing prototype instances took {st.Elapsed.TotalMilliseconds:f2}ms"); - } + var ev = new PrototypesReloadedEventArgs(modifiedTypes, byType, removed); + PrototypesReloaded?.Invoke(ev); + _entMan.EventBus.RaiseEvent(EventSource.Local, ev); + } - /// - /// Resolves the mappings stored in memory to actual prototypeinstances. - /// - public void ResolveResults() + private void Freeze(IEnumerable kinds) + { + var st = RStopwatch.StartNew(); + foreach (var kind in kinds) { - // Oh god I butchered this poor method in the name of my Ryzen CPU. - - // Run inheritance pushing concurrently to the rest of prototype loading. - // Use some basic tasks and .Wait() to make sure it's done by the time we get to the prototypes in question. - // The biggest prototypes by far in SS14 are entity prototypes. - // Entity prototypes have priority -1 right now, so they have to be read last. This works out great! - // We'll already be done pushing inheritance for them by the time we get to reading them. - var inheritanceTasks = new Dictionary(); - foreach (var (k, v) in _kinds) - { - if (v.Inheritance == null) - continue; + kind.Freeze(); + } - var task = Task.Run(() => PushKindInheritance(k, v)); - inheritanceTasks.Add(k, task); - } + // fun fact: Sawmill can be null in tests???? + Sawmill?.Verbose($"Freezing prototype instances took {st.Elapsed.TotalMilliseconds:f2}ms"); + } - var priorities = _kinds.Keys - .GroupBy(k => _kindPriorities[k]) - .OrderByDescending(k => k.Key); + /// + /// Resolves the mappings stored in memory to actual prototypeinstances. + /// + public void ResolveResults() + { + // Oh god I butchered this poor method in the name of my Ryzen CPU. - foreach (var group in priorities) - { - var kinds = group.Select(k => _kinds[k]).ToArray(); - InstantiateKinds(kinds, inheritanceTasks); - } + // Run inheritance pushing concurrently to the rest of prototype loading. + // Use some basic tasks and .Wait() to make sure it's done by the time we get to the prototypes in question. + // The biggest prototypes by far in SS14 are entity prototypes. + // Entity prototypes have priority -1 right now, so they have to be read last. This works out great! + // We'll already be done pushing inheritance for them by the time we get to reading them. + var inheritanceTasks = new Dictionary(); + foreach (var (k, v) in _kinds) + { + if (v.Inheritance == null) + continue; - UpdateCategories(); + var task = Task.Run(() => PushKindInheritance(k, v)); + inheritanceTasks.Add(k, task); } - private void InstantiateKinds(KindData[] kinds, Dictionary inheritanceTasks) + var priorities = _kinds.Keys + .GroupBy(k => _kindPriorities[k]) + .OrderByDescending(k => k.Key); + + foreach (var group in priorities) { - // Wait for all inheritance pushing in this group to finish. - // This isn't ideal, but since entity prototypes are the big ones in SS14 it's fine. - foreach (var kind in kinds) - { - if (inheritanceTasks.TryGetValue(kind.Type, out var task)) - task.Wait(); - } + var kinds = group.Select(k => _kinds[k]).ToArray(); + InstantiateKinds(kinds, inheritanceTasks); + } + + UpdateCategories(); + } + + private void InstantiateKinds(KindData[] kinds, Dictionary inheritanceTasks) + { + // Wait for all inheritance pushing in this group to finish. + // This isn't ideal, but since entity prototypes are the big ones in SS14 it's fine. + foreach (var kind in kinds) + { + if (inheritanceTasks.TryGetValue(kind.Type, out var task)) + task.Wait(); + } - // Process all prototypes in this group in a single parallel operation. - var results = kinds - .SelectMany(data => data.Results, - (data, results) => (KindData: data, Id: results.Key, Mapping: results.Value, Instance: (IPrototype?)null)) - .ToArray(); + // Process all prototypes in this group in a single parallel operation. + var results = kinds + .SelectMany(data => data.Results, + (data, results) => (KindData: data, Id: results.Key, Mapping: results.Value, Instance: (IPrototype?)null)) + .ToArray(); - // Randomize to remove any patterns that could cause uneven load. - _random.Shuffle(results.AsSpan()); + // Randomize to remove any patterns that could cause uneven load. + _random.Shuffle(results.AsSpan()); - // Create channel that all AfterDeserialization hooks in this group will be sent into. - var hooksChannelOptions = new UnboundedChannelOptions - { - SingleReader = true, - SingleWriter = false, - // Don't use an async job to unblock the read task. - AllowSynchronousContinuations = true - }; + // Create channel that all AfterDeserialization hooks in this group will be sent into. + var hooksChannelOptions = new UnboundedChannelOptions + { + SingleReader = true, + SingleWriter = false, + // Don't use an async job to unblock the read task. + AllowSynchronousContinuations = true + }; - var hooksChannel = Channel.CreateUnbounded(hooksChannelOptions); - var instantiateTask = Task.Run(() => InstantiatePrototypes(kinds, results, hooksChannel)); + var hooksChannel = Channel.CreateUnbounded(hooksChannelOptions); + var instantiateTask = Task.Run(() => InstantiatePrototypes(kinds, results, hooksChannel)); - // On the game thread: process AfterDeserialization hooks from the channel. - var channelReader = hooksChannel.Reader; + // On the game thread: process AfterDeserialization hooks from the channel. + var channelReader = hooksChannel.Reader; #pragma warning disable RA0004 - while (channelReader.WaitToReadAsync().AsTask().Result) + while (channelReader.WaitToReadAsync().AsTask().Result) #pragma warning restore RA0004 + { + while (channelReader.TryRead(out var hooks)) { - while (channelReader.TryRead(out var hooks)) - { - hooks.AfterDeserialization(); - } + hooks.AfterDeserialization(); } - - // Join task in case an exception was raised. - instantiateTask.Wait(); } - private void InstantiatePrototypes( - KindData[] kinds, - (KindData KindData, string Id, MappingDataNode Mapping, IPrototype? Instance)[] results, - Channel hooks) - { - var hookCtx = new SerializationHookContext(hooks.Writer, false); - try - { - Parallel.For(0, - results.Length, - i => - { - ref var item = ref results[i]; - item.Instance = TryReadPrototype(item.KindData.Type, item.Id, item.Mapping, hookCtx); - }); + // Join task in case an exception was raised. + instantiateTask.Wait(); + } - foreach (var item in results) + private void InstantiatePrototypes( + KindData[] kinds, + (KindData KindData, string Id, MappingDataNode Mapping, IPrototype? Instance)[] results, + Channel hooks) + { + var hookCtx = new SerializationHookContext(hooks.Writer, false); + try + { + Parallel.For(0, + results.Length, + i => { - if (item.Instance == null) - continue; - item.KindData.UnfrozenInstances ??= item.KindData.Instances.ToDictionary(); - item.KindData.UnfrozenInstances[item.Id] = item.Instance; - } + ref var item = ref results[i]; + item.Instance = TryReadPrototype(item.KindData.Type, item.Id, item.Mapping, hookCtx); + }); - Freeze(kinds.Where(data => data.UnfrozenInstances != null)); - } - finally + foreach (var item in results) { - // Mark the hooks channel as complete so the game thread unblocks. - hooks.Writer.Complete(); + if (item.Instance == null) + continue; + item.KindData.UnfrozenInstances ??= item.KindData.Instances.ToDictionary(); + item.KindData.UnfrozenInstances[item.Id] = item.Instance; } - } - private IPrototype? TryReadPrototype( - Type kind, - string id, - MappingDataNode mapping, - SerializationHookContext hookCtx) + Freeze(kinds.Where(data => data.UnfrozenInstances != null)); + } + finally { - if (mapping.TryGet(AbstractDataFieldAttribute.Name, out var abstractNode) && - abstractNode.AsBool()) - return null; - - try - { - return (IPrototype)_serializationManager.Read(kind, mapping, hookCtx)!; - } - catch (Exception e) - { - Sawmill.Error($"Reading {kind}({id}) threw the following exception: {e}"); - return null; - } + // Mark the hooks channel as complete so the game thread unblocks. + hooks.Writer.Complete(); } + } + + private IPrototype? TryReadPrototype( + Type kind, + string id, + MappingDataNode mapping, + SerializationHookContext hookCtx) + { + if (mapping.TryGet(AbstractDataFieldAttribute.Name, out var abstractNode) && + abstractNode.AsBool()) + return null; - private async Task PushKindInheritance(Type kind, KindData data) + try + { + return (IPrototype)_serializationManager.Read(kind, mapping, hookCtx)!; + } + catch (Exception e) { - if (data.Inheritance is not { } tree) - return; + Sawmill.Error($"Reading {kind}({id}) threw the following exception: {e}"); + return null; + } + } - // var sw = RStopwatch.StartNew(); + private async Task PushKindInheritance(Type kind, KindData data) + { + if (data.Inheritance is not { } tree) + return; - var results = data.RawResults.ToDictionary( - k => k.Key, - k => new InheritancePushDatum(k.Value, tree.GetParentsCount(k.Key))); + // var sw = RStopwatch.StartNew(); - using var countDown = new CountdownEvent(results.Count); + var results = data.RawResults.ToDictionary( + k => k.Key, + k => new InheritancePushDatum(k.Value, tree.GetParentsCount(k.Key))); - foreach (var root in tree.RootNodes) - { - ThreadPool.QueueUserWorkItem(_ => { ProcessItem(root, results[root]); }); - } + using var countDown = new CountdownEvent(results.Count); - void ProcessItem(string id, InheritancePushDatum datum) + foreach (var root in tree.RootNodes) + { + ThreadPool.QueueUserWorkItem(_ => { ProcessItem(root, results[root]); }); + } + + void ProcessItem(string id, InheritancePushDatum datum) + { + try { - try + if (tree.TryGetParents(id, out var parents)) { - if (tree.TryGetParents(id, out var parents)) + if (parents.Length == 1) { - if (parents.Length == 1) + datum.Result = _serializationManager.PushCompositionWithGenericNode( + kind, + results[parents[0]].Result, + datum.Result); + } + else + { + var parentNodes = new MappingDataNode[parents.Length]; + for (var i = 0; i < parents.Length; i++) { - datum.Result = _serializationManager.PushCompositionWithGenericNode( - kind, - results[parents[0]].Result, - datum.Result); + parentNodes[i] = results[parents[i]].Result; } - else - { - var parentNodes = new MappingDataNode[parents.Length]; - for (var i = 0; i < parents.Length; i++) - { - parentNodes[i] = results[parents[i]].Result; - } - datum.Result = _serializationManager.PushCompositionWithGenericNode( - kind, - parentNodes, - datum.Result); - } + datum.Result = _serializationManager.PushCompositionWithGenericNode( + kind, + parentNodes, + datum.Result); } + } - if (tree.TryGetChildren(id, out var children)) + if (tree.TryGetChildren(id, out var children)) + { + foreach (var child in children) { - foreach (var child in children) + var childDatum = results[child]; + var val = Interlocked.Decrement(ref childDatum.CountParentsRemaining); + if (val == 0) { - var childDatum = results[child]; - var val = Interlocked.Decrement(ref childDatum.CountParentsRemaining); - if (val == 0) - { - ThreadPool.QueueUserWorkItem(_ => { ProcessItem(child, childDatum); }); - } + ThreadPool.QueueUserWorkItem(_ => { ProcessItem(child, childDatum); }); } } - - // ReSharper disable once AccessToDisposedClosure - countDown.Signal(); - } - catch (Exception e) - { - Sawmill.Error($"Failed to push composition for {kind.Name} prototype with id: {id}. Exception: {e}"); - throw; } - } - await WaitHandleHelpers.WaitOneAsync(countDown.WaitHandle); - - data.Results.Clear(); - foreach (var (k, v) in results) - { - data.Results[k] = v.Result; + // ReSharper disable once AccessToDisposedClosure + countDown.Signal(); } - - // _sawmill.Debug($"Inheritance {kind}: {sw.Elapsed}"); - } - - private sealed class InheritancePushDatum - { - public MappingDataNode Result; - public int CountParentsRemaining; - - public InheritancePushDatum(MappingDataNode result, int countParentsRemaining) + catch (Exception e) { - Result = result; - CountParentsRemaining = countParentsRemaining; + Sawmill.Error($"Failed to push composition for {kind.Name} prototype with id: {id}. Exception: {e}"); + throw; } } - #endregion IPrototypeManager members + await WaitHandleHelpers.WaitOneAsync(countDown.WaitHandle); - /// - public void ReloadPrototypeKinds() + data.Results.Clear(); + foreach (var (k, v) in results) { - Clear(); - var dict = new Dictionary(); - foreach (var type in _reflectionManager.GetAllChildren()) - { - RegisterKind(type, dict); - } - Freeze(dict); + data.Results[k] = v.Result; } - /// - public bool HasIndex(string id) where T : class, IPrototype - { - if (!_kinds.TryGetValue(typeof(T), out var index)) - { - throw new UnknownPrototypeException(id, typeof(T)); - } - - return index.Instances.ContainsKey(id); - } + // _sawmill.Debug($"Inheritance {kind}: {sw.Elapsed}"); + } - /// - public bool HasIndex(EntProtoId id) - { - return HasIndex(id.Id); - } + private sealed class InheritancePushDatum + { + public MappingDataNode Result; + public int CountParentsRemaining; - /// - public bool HasIndex(ProtoId id) where T : class, IPrototype + public InheritancePushDatum(MappingDataNode result, int countParentsRemaining) { - return HasIndex(id.Id); + Result = result; + CountParentsRemaining = countParentsRemaining; } + } - /// - public bool HasIndex(EntProtoId? id) - { - if (id == null) - return false; - - return HasIndex(id.Value); - } + #endregion IPrototypeManager members - /// - public bool HasIndex(ProtoId? id) where T : class, IPrototype + /// + public void ReloadPrototypeKinds() + { + Clear(); + var dict = new Dictionary(); + foreach (var type in _reflectionManager.GetAllChildren()) { - if (id == null) - return false; - - return HasIndex(id.Value); + RegisterKind(type, dict); } + Freeze(dict); + } - /// - public bool TryIndex(string id, [NotNullWhen(true)] out T? prototype) where T : class, IPrototype + /// + public bool HasIndex(string id) where T : class, IPrototype + { + if (!_kinds.TryGetValue(typeof(T), out var index)) { - var returned = TryIndex(typeof(T), id, out var proto); - prototype = (proto ?? null) as T; - return returned; + throw new UnknownPrototypeException(id, typeof(T)); } - /// - public bool TryIndex(Type kind, string id, [NotNullWhen(true)] out IPrototype? prototype) - { - if (!_kinds.TryGetValue(kind, out var index)) - { - throw new UnknownPrototypeException(id, kind); - } + return index.Instances.ContainsKey(id); + } - return index.Instances.TryGetValue(id, out prototype); - } + /// + public bool HasIndex(EntProtoId id) + { + return HasIndex(id.Id); + } + /// + public bool HasIndex(ProtoId id) where T : class, IPrototype + { + return HasIndex(id.Id); + } - // For obsolete APIs. - // ReSharper disable MethodOverloadWithOptionalParameter + /// + public bool HasIndex(EntProtoId? id) + { + if (id == null) + return false; - public bool Resolve(EntProtoId id, [NotNullWhen(true)] out EntityPrototype? prototype) - { - if (TryIndex(id.Id, out prototype)) - return true; + return HasIndex(id.Value); + } - Sawmill.Error($"Attempted to resolve invalid {nameof(EntProtoId)}: {id.Id}\n{Environment.StackTrace}"); + /// + public bool HasIndex(ProtoId? id) where T : class, IPrototype + { + if (id == null) return false; - } - [Obsolete("Use Resolve() if you want to get a prototype without throwing but while still logging an error.")] - public bool TryIndex(EntProtoId id, [NotNullWhen(true)] out EntityPrototype? prototype, bool logError = true) - { - if (logError) - return Resolve(id, out prototype); - return TryIndex(id, out prototype); - } + return HasIndex(id.Value); + } - public bool TryIndex([ForbidLiteral] EntProtoId id, [NotNullWhen(true)] out EntityPrototype? prototype) + /// + public bool TryIndex(string id, [NotNullWhen(true)] out T? prototype) where T : class, IPrototype + { + var returned = TryIndex(typeof(T), id, out var proto); + prototype = (proto ?? null) as T; + return returned; + } + + /// + public bool TryIndex(Type kind, string id, [NotNullWhen(true)] out IPrototype? prototype) + { + if (!_kinds.TryGetValue(kind, out var index)) { - return TryIndex(id.Id, out prototype); + throw new UnknownPrototypeException(id, kind); } - public bool Resolve(ProtoId id, [NotNullWhen(true)] out T? prototype) where T : class, IPrototype - { - if (TryIndex(id.Id, out prototype)) - return true; + return index.Instances.TryGetValue(id, out prototype); + } - Sawmill.Error($"Attempted to resolve invalid ProtoId<{typeof(T).Name}>: {id.Id}\n{Environment.StackTrace}"); - return false; - } - [Obsolete("Use Resolve() if you want to get a prototype without throwing but while still logging an error.")] - public bool TryIndex(ProtoId id, [NotNullWhen(true)] out T? prototype, bool logError = true) - where T : class, IPrototype - { - if (logError) - return Resolve(id, out prototype); - return TryIndex(id, out prototype); - } + // For obsolete APIs. + // ReSharper disable MethodOverloadWithOptionalParameter - public bool TryIndex(ProtoId id, [NotNullWhen(true)] out T? prototype) - where T : class, IPrototype - { - return TryIndex(id.Id, out prototype); - } + public bool Resolve(EntProtoId id, [NotNullWhen(true)] out EntityPrototype? prototype) + { + if (TryIndex(id.Id, out prototype)) + return true; - public bool Resolve(EntProtoId? id, [NotNullWhen(true)] out EntityPrototype? prototype) - { - if (id == null) - { - prototype = null; - return false; - } + Sawmill.Error($"Attempted to resolve invalid {nameof(EntProtoId)}: {id.Id}\n{Environment.StackTrace}"); + return false; + } - return Resolve(id.Value, out prototype); - } + [Obsolete("Use Resolve() if you want to get a prototype without throwing but while still logging an error.")] + public bool TryIndex(EntProtoId id, [NotNullWhen(true)] out EntityPrototype? prototype, bool logError = true) + { + if (logError) + return Resolve(id, out prototype); + return TryIndex(id, out prototype); + } - [Obsolete("Use Resolve() if you want to get a prototype without throwing but while still logging an error.")] - public bool TryIndex(EntProtoId? id, [NotNullWhen(true)] out EntityPrototype? prototype, bool logError = true) - { - if (logError) - return Resolve(id, out prototype); - return TryIndex(id, out prototype); - } + public bool TryIndex([ForbidLiteral] EntProtoId id, [NotNullWhen(true)] out EntityPrototype? prototype) + { + return TryIndex(id.Id, out prototype); + } - public bool TryIndex(EntProtoId? id, [NotNullWhen(true)] out EntityPrototype? prototype) - { - if (id == null) - { - prototype = null; - return false; - } + public bool Resolve(ProtoId id, [NotNullWhen(true)] out T? prototype) where T : class, IPrototype + { + if (TryIndex(id.Id, out prototype)) + return true; - return TryIndex(id.Value, out prototype); - } + Sawmill.Error($"Attempted to resolve invalid ProtoId<{typeof(T).Name}>: {id.Id}\n{Environment.StackTrace}"); + return false; + } - public bool Resolve(ProtoId? id, [NotNullWhen(true)] out T? prototype) where T : class, IPrototype - { - if (id == null) - { - prototype = null; - return false; - } + [Obsolete("Use Resolve() if you want to get a prototype without throwing but while still logging an error.")] + public bool TryIndex(ProtoId id, [NotNullWhen(true)] out T? prototype, bool logError = true) + where T : class, IPrototype + { + if (logError) + return Resolve(id, out prototype); + return TryIndex(id, out prototype); + } - return Resolve(id.Value, out prototype); - } + public bool TryIndex(ProtoId id, [NotNullWhen(true)] out T? prototype) + where T : class, IPrototype + { + return TryIndex(id.Id, out prototype); + } - [Obsolete("Use Resolve() if you want to get a prototype without throwing but while still logging an error.")] - public bool TryIndex(ProtoId? id, [NotNullWhen(true)] out T? prototype, bool logError = true) - where T : class, IPrototype + public bool Resolve(EntProtoId? id, [NotNullWhen(true)] out EntityPrototype? prototype) + { + if (id == null) { - if (logError) - return Resolve(id, out prototype); - return TryIndex(id, out prototype); + prototype = null; + return false; } - public bool TryIndex(ProtoId? id, [NotNullWhen(true)] out T? prototype) - where T : class, IPrototype - { - if (id == null) - { - prototype = null; - return false; - } - - return TryIndex(id.Value, out prototype); - } + return Resolve(id.Value, out prototype); + } - // ReSharper restore MethodOverloadWithOptionalParameter + [Obsolete("Use Resolve() if you want to get a prototype without throwing but while still logging an error.")] + public bool TryIndex(EntProtoId? id, [NotNullWhen(true)] out EntityPrototype? prototype, bool logError = true) + { + if (logError) + return Resolve(id, out prototype); + return TryIndex(id, out prototype); + } - /// - public bool HasMapping(string id) + public bool TryIndex(EntProtoId? id, [NotNullWhen(true)] out EntityPrototype? prototype) + { + if (id == null) { - if (!_kinds.TryGetValue(typeof(T), out var index)) - { - throw new UnknownPrototypeException(id, typeof(T)); - } - - return index.Results.ContainsKey(id); + prototype = null; + return false; } - /// - public bool TryGetMapping(Type kind, string id, [NotNullWhen(true)] out MappingDataNode? mappings) - { - return _kinds[kind].Results.TryGetValue(id, out mappings); - } + return TryIndex(id.Value, out prototype); + } - public bool HasKind(string kind) + public bool Resolve(ProtoId? id, [NotNullWhen(true)] out T? prototype) where T : class, IPrototype + { + if (id == null) { - return _kindNames.ContainsKey(kind); + prototype = null; + return false; } - public Type GetKindType(string kind) - { - return _kindNames[kind]; - } + return Resolve(id.Value, out prototype); + } - public bool TryGetKindType(string kind, [NotNullWhen(true)] out Type? prototype) + [Obsolete("Use Resolve() if you want to get a prototype without throwing but while still logging an error.")] + public bool TryIndex(ProtoId? id, [NotNullWhen(true)] out T? prototype, bool logError = true) + where T : class, IPrototype + { + if (logError) + return Resolve(id, out prototype); + return TryIndex(id, out prototype); + } + + public bool TryIndex(ProtoId? id, [NotNullWhen(true)] out T? prototype) + where T : class, IPrototype + { + if (id == null) { - return _kindNames.TryGetValue(kind, out prototype); + prototype = null; + return false; } - public bool TryGetKindFrom(Type type, [NotNullWhen(true)] out string? kind) - { - kind = null; - if (!_kinds.TryGetValue(type, out var kindData)) - return false; + return TryIndex(id.Value, out prototype); + } - kind = kindData.Name; - return true; - } + // ReSharper restore MethodOverloadWithOptionalParameter - public FrozenDictionary GetInstances() where T : IPrototype + /// + public bool HasMapping(string id) + { + if (!_kinds.TryGetValue(typeof(T), out var index)) { - if (TryGetInstances(out var dict)) - return dict; - - throw new Exception($"Failed to fetch instances for kind {nameof(T)}"); + throw new UnknownPrototypeException(id, typeof(T)); } - public bool TryGetInstances([NotNullWhen(true)] out FrozenDictionary? instances) - where T : IPrototype - { - if (!TryGetInstances(typeof(T), out var dict)) - { - instances = null; - return false; - } + return index.Results.ContainsKey(id); + } - DebugTools.Assert(dict is FrozenDictionary || dict == null); - instances = dict as FrozenDictionary; + /// + public bool TryGetMapping(Type kind, string id, [NotNullWhen(true)] out MappingDataNode? mappings) + { + return _kinds[kind].Results.TryGetValue(id, out mappings); + } - // Prototypes with no loaded instances never get frozen. - instances ??= FrozenDictionary.Empty; - return true; - } + public bool HasKind(string kind) + { + return _kindNames.ContainsKey(kind); + } - private bool TryGetInstances(Type kind, [NotNullWhen(true)] out object? instances) - { - if (!_hasEverBeenReloaded) - throw new InvalidOperationException("No prototypes have been loaded yet."); + public Type GetKindType(string kind) + { + return _kindNames[kind]; + } - DebugTools.Assert(kind.IsAssignableTo(typeof(IPrototype))); - if (!_kinds.TryGetValue(kind, out var kindData)) - { - instances = null; - return false; - } + public bool TryGetKindType(string kind, [NotNullWhen(true)] out Type? prototype) + { + return _kindNames.TryGetValue(kind, out prototype); + } - instances = kindData.InstancesDirect; - return true; - } + public bool TryGetKindFrom(Type type, [NotNullWhen(true)] out string? kind) + { + kind = null; + if (!_kinds.TryGetValue(type, out var kindData)) + return false; - public bool TryGetKindFrom(IPrototype prototype, [NotNullWhen(true)] out string? kind) - { - return TryGetKindFrom(prototype.GetType(), out kind); - } + kind = kindData.Name; + return true; + } - public bool TryGetKindFrom([NotNullWhen(true)] out string? kind) where T : class, IPrototype - { - return TryGetKindFrom(typeof(T), out kind); - } + public FrozenDictionary GetInstances() where T : IPrototype + { + if (TryGetInstances(out var dict)) + return dict; - public bool IsIgnored(string name) => _ignoredPrototypeTypes.Contains(name); + throw new Exception($"Failed to fetch instances for kind {nameof(T)}"); + } - /// - public void RegisterIgnore(string name) + public bool TryGetInstances([NotNullWhen(true)] out FrozenDictionary? instances) + where T : IPrototype + { + if (!TryGetInstances(typeof(T), out var dict)) { - _ignoredPrototypeTypes.Add(name); + instances = null; + return false; } - static string CalculatePrototypeName(Type type) + DebugTools.Assert(dict is FrozenDictionary || dict == null); + instances = dict as FrozenDictionary; + + // Prototypes with no loaded instances never get frozen. + instances ??= FrozenDictionary.Empty; + return true; + } + + private bool TryGetInstances(Type kind, [NotNullWhen(true)] out object? instances) + { + if (!_hasEverBeenReloaded) + throw new InvalidOperationException("No prototypes have been loaded yet."); + + DebugTools.Assert(kind.IsAssignableTo(typeof(IPrototype))); + if (!_kinds.TryGetValue(kind, out var kindData)) { - return PrototypeUtility.CalculatePrototypeName(type.Name); + instances = null; + return false; } - /// - public void RegisterKind(params Type[] kinds) - { - var dict = _kinds.ToDictionary(); - foreach (var kind in kinds) - { - RegisterKind(kind, dict); - } + instances = kindData.InstancesDirect; + return true; + } - Freeze(dict); - } + public bool TryGetKindFrom(IPrototype prototype, [NotNullWhen(true)] out string? kind) + { + return TryGetKindFrom(prototype.GetType(), out kind); + } - private void Freeze(Dictionary dict) - { - var st = RStopwatch.StartNew(); - _kinds = dict.ToFrozenDictionary(); + public bool TryGetKindFrom([NotNullWhen(true)] out string? kind) where T : class, IPrototype + { + return TryGetKindFrom(typeof(T), out kind); + } - // fun fact: Sawmill can be null in tests???? - Sawmill?.Verbose($"Freezing prototype kinds took {st.Elapsed.TotalMilliseconds:f2}ms"); - } + public bool IsIgnored(string name) => _ignoredPrototypeTypes.Contains(name); - private void RegisterKind(Type kind, Dictionary kinds) + /// + public void RegisterIgnore(string name) + { + _ignoredPrototypeTypes.Add(name); + } + + static string CalculatePrototypeName(Type type) + { + return PrototypeUtility.CalculatePrototypeName(type.Name); + } + + /// + public void RegisterKind(params Type[] kinds) + { + var dict = _kinds.ToDictionary(); + foreach (var kind in kinds) { - if (!(typeof(IPrototype).IsAssignableFrom(kind))) - throw new InvalidOperationException("Type must implement IPrototype."); + RegisterKind(kind, dict); + } - var attribute = (PrototypeAttribute?)Attribute.GetCustomAttribute(kind, typeof(PrototypeAttribute)); + Freeze(dict); + } - if (attribute == null) - { - throw new InvalidImplementationException(kind, - typeof(IPrototype), - "No " + nameof(PrototypeAttribute) + " to give it a type string."); - } + private void Freeze(Dictionary dict) + { + var st = RStopwatch.StartNew(); + _kinds = dict.ToFrozenDictionary(); - var name = attribute.Type ?? CalculatePrototypeName(kind); + // fun fact: Sawmill can be null in tests???? + Sawmill?.Verbose($"Freezing prototype kinds took {st.Elapsed.TotalMilliseconds:f2}ms"); + } - if (_ignoredPrototypeTypes.Contains(name)) - { - // For whatever reason, we are registering a prototype despite it having been marked as ignored. - // This often happens when someone is moving a server or client prototype to shared. Maybe this should - // log an error, but I want to avoid breaking changes and maaaaybe there some weird instance where you - // want the client to know that a prototype kind exists, without having the client load information - // about the individual prototypes? So for now lets just log a warning instead of introducing breaking - // changes. - Sawmill.Warning($"Registering an ignored prototype {kind}"); - } + private void RegisterKind(Type kind, Dictionary kinds) + { + if (!(typeof(IPrototype).IsAssignableFrom(kind))) + throw new InvalidOperationException("Type must implement IPrototype."); - if (_kindNames.TryGetValue(name, out var existing)) - { - throw new InvalidImplementationException(kind, - typeof(IPrototype), - $"Duplicate prototype type ID: {attribute.Type}. Current: {existing}"); - } + var attribute = (PrototypeAttribute?)Attribute.GetCustomAttribute(kind, typeof(PrototypeAttribute)); - var foundIdAttribute = false; - var foundParentAttribute = false; - var foundAbstractAttribute = false; - foreach (var info in kind.GetAllPropertiesAndFields()) - { - var hasId = info.HasAttribute(); - var hasParent = info.HasAttribute(); - if (hasId) - { - if (foundIdAttribute) - { - throw new InvalidImplementationException(kind, - typeof(IPrototype), - $"Found two {nameof(IdDataFieldAttribute)}"); - } + if (attribute == null) + { + throw new InvalidImplementationException(kind, + typeof(IPrototype), + "No " + nameof(PrototypeAttribute) + " to give it a type string."); + } - foundIdAttribute = true; - } + var name = attribute.Type ?? CalculatePrototypeName(kind); - if (hasParent) - { - if (foundParentAttribute) - { - throw new InvalidImplementationException(kind, - typeof(IInheritingPrototype), - $"Found two {nameof(ParentDataFieldAttribute)}"); - } + if (_ignoredPrototypeTypes.Contains(name)) + { + // For whatever reason, we are registering a prototype despite it having been marked as ignored. + // This often happens when someone is moving a server or client prototype to shared. Maybe this should + // log an error, but I want to avoid breaking changes and maaaaybe there some weird instance where you + // want the client to know that a prototype kind exists, without having the client load information + // about the individual prototypes? So for now lets just log a warning instead of introducing breaking + // changes. + Sawmill.Warning($"Registering an ignored prototype {kind}"); + } - foundParentAttribute = true; - } + if (_kindNames.TryGetValue(name, out var existing)) + { + throw new InvalidImplementationException(kind, + typeof(IPrototype), + $"Duplicate prototype type ID: {attribute.Type}. Current: {existing}"); + } - if (hasId && hasParent) + var foundIdAttribute = false; + var foundParentAttribute = false; + var foundAbstractAttribute = false; + foreach (var info in kind.GetAllPropertiesAndFields()) + { + var hasId = info.HasAttribute(); + var hasParent = info.HasAttribute(); + if (hasId) + { + if (foundIdAttribute) { throw new InvalidImplementationException(kind, typeof(IPrototype), - $"Prototype {kind} has the Id- & ParentDatafield on single member {info.Name}"); + $"Found two {nameof(IdDataFieldAttribute)}"); } - if (info.HasAttribute()) - { - if (foundAbstractAttribute) - { - throw new InvalidImplementationException(kind, - typeof(IInheritingPrototype), - $"Found two {nameof(AbstractDataFieldAttribute)}"); - } + foundIdAttribute = true; + } - foundAbstractAttribute = true; + if (hasParent) + { + if (foundParentAttribute) + { + throw new InvalidImplementationException(kind, + typeof(IInheritingPrototype), + $"Found two {nameof(ParentDataFieldAttribute)}"); } + + foundParentAttribute = true; } - if (!foundIdAttribute) + if (hasId && hasParent) { throw new InvalidImplementationException(kind, typeof(IPrototype), - $"Did not find any member annotated with the {nameof(IdDataFieldAttribute)}"); + $"Prototype {kind} has the Id- & ParentDatafield on single member {info.Name}"); } - if (kind.IsAssignableTo(typeof(IInheritingPrototype)) && (!foundParentAttribute || !foundAbstractAttribute)) + if (info.HasAttribute()) { - throw new InvalidImplementationException(kind, - typeof(IInheritingPrototype), - $"Did not find any member annotated with the {nameof(ParentDataFieldAttribute)} and/or {nameof(AbstractDataFieldAttribute)}"); - } + if (foundAbstractAttribute) + { + throw new InvalidImplementationException(kind, + typeof(IInheritingPrototype), + $"Found two {nameof(AbstractDataFieldAttribute)}"); + } - _kindNames[name] = kind; - _kindPriorities[kind] = attribute.LoadPriority; + foundAbstractAttribute = true; + } + } - var kindData = new KindData(kind, name); - kinds[kind] = kindData; + if (!foundIdAttribute) + { + throw new InvalidImplementationException(kind, + typeof(IPrototype), + $"Did not find any member annotated with the {nameof(IdDataFieldAttribute)}"); + } - if (kind.IsAssignableTo(typeof(IInheritingPrototype))) - kindData.Inheritance = new MultiRootInheritanceGraph(); - else - kindData.Results = kindData.RawResults; + if (kind.IsAssignableTo(typeof(IInheritingPrototype)) && (!foundParentAttribute || !foundAbstractAttribute)) + { + throw new InvalidImplementationException(kind, + typeof(IInheritingPrototype), + $"Did not find any member annotated with the {nameof(ParentDataFieldAttribute)} and/or {nameof(AbstractDataFieldAttribute)}"); } - /// - public event Action? PrototypesReloaded; + _kindNames[name] = kind; + _kindPriorities[kind] = attribute.LoadPriority; - private sealed class KindData(Type kind, string name) - { - public Dictionary? UnfrozenInstances; + var kindData = new KindData(kind, name); + kinds[kind] = kindData; - public FrozenDictionary Instances = FrozenDictionary.Empty; + if (kind.IsAssignableTo(typeof(IInheritingPrototype))) + kindData.Inheritance = new MultiRootInheritanceGraph(); + else + kindData.Results = kindData.RawResults; + } - public Dictionary Results = new(); + /// + public event Action? PrototypesReloaded; - /// - /// Variant of prior to inheritance pushing. If the kind does not have inheritance, - /// then this is just the same dictionary. - /// - public readonly Dictionary RawResults = new(); + private sealed class KindData(Type kind, string name) + { + public Dictionary? UnfrozenInstances; - public readonly Type Type = kind; - public readonly string Name = name; + public FrozenDictionary Instances = FrozenDictionary.Empty; - // Only initialized if prototype is inheriting. - public MultiRootInheritanceGraph? Inheritance; + public Dictionary Results = new(); - /// - /// Variant of that has a direct mapping to the prototype kind. I.e., no IPrototype interface. - /// - public object InstancesDirect = default!; + /// + /// Variant of prior to inheritance pushing. If the kind does not have inheritance, + /// then this is just the same dictionary. + /// + public readonly Dictionary RawResults = new(); - private MethodInfo _freezeDirectInfo = typeof(KindData) - .GetMethod(nameof(FreezeDirect), BindingFlags.Instance | BindingFlags.NonPublic)! - .MakeGenericMethod(kind); + public readonly Type Type = kind; + public readonly string Name = name; - private void FreezeDirect() - { - var dict = new Dictionary(); - foreach (var (id, instance) in Instances) - { - dict.Add(id, (T) instance); - } - InstancesDirect = dict.ToFrozenDictionary(); - } + // Only initialized if prototype is inheriting. + public MultiRootInheritanceGraph? Inheritance; - public void Freeze() - { - DebugTools.AssertNotNull(UnfrozenInstances); - Instances = UnfrozenInstances?.ToFrozenDictionary() ?? FrozenDictionary.Empty; - UnfrozenInstances = null; - _freezeDirectInfo.Invoke(this, null); - } - } + /// + /// Variant of that has a direct mapping to the prototype kind. I.e., no IPrototype interface. + /// + public object InstancesDirect = default!; + + private MethodInfo _freezeDirectInfo = typeof(KindData) + .GetMethod(nameof(FreezeDirect), BindingFlags.Instance | BindingFlags.NonPublic)! + .MakeGenericMethod(kind); - private void OnReload(PrototypesReloadedEventArgs args) + private void FreezeDirect() { - if (args.ByType.TryGetValue(typeof(EntityPrototype), out var modified)) + var dict = new Dictionary(); + foreach (var (id, instance) in Instances) { - foreach (var id in modified.Modified.Keys) - { - _prototypeDataCache.Remove(id); - } + dict.Add(id, (T) instance); } + InstancesDirect = dict.ToFrozenDictionary(); + } - if (args.Removed == null || !args.Removed.TryGetValue(typeof(EntityPrototype), out var removed)) - return; + public void Freeze() + { + DebugTools.AssertNotNull(UnfrozenInstances); + Instances = UnfrozenInstances?.ToFrozenDictionary() ?? FrozenDictionary.Empty; + UnfrozenInstances = null; + _freezeDirectInfo.Invoke(this, null); + } + } - foreach (var id in removed) + private void OnReload(PrototypesReloadedEventArgs args) + { + if (args.ByType.TryGetValue(typeof(EntityPrototype), out var modified)) + { + foreach (var id in modified.Modified.Keys) { _prototypeDataCache.Remove(id); } } - public IReadOnlyDictionary GetPrototypeData(EntityPrototype prototype) + if (args.Removed == null || !args.Removed.TryGetValue(typeof(EntityPrototype), out var removed)) + return; + + foreach (var id in removed) { - if (_prototypeDataCache.TryGetValue(prototype.ID, out var data)) - return data; + _prototypeDataCache.Remove(id); + } + } - _context.WritingReadingPrototypes = true; - data = new(); + public IReadOnlyDictionary GetPrototypeData(EntityPrototype prototype) + { + if (_prototypeDataCache.TryGetValue(prototype.ID, out var data)) + return data; - var xform = _factory.GetRegistration(typeof(TransformComponent)).Name; - try - { - foreach (var (compType, comp) in prototype.Components) - { - if (compType == xform) - continue; + _context.WritingReadingPrototypes = true; + data = new(); - var node = _serializationManager.WriteValueAs(comp.Component.GetType(), comp.Component, - alwaysWrite: true, context: _context); - data.Add(compType, node); - } - } - catch (Exception e) + var xform = _factory.GetRegistration(typeof(TransformComponent)).Name; + try + { + foreach (var (compType, comp) in prototype.Components) { - Sawmill.Error($"Failed to convert prototype {prototype.ID} into yaml. Exception: {e.Message}"); - } + if (compType == xform) + continue; - _context.WritingReadingPrototypes = false; - _prototypeDataCache[prototype.ID] = data; - return data; + var node = _serializationManager.WriteValueAs(comp.Component.GetType(), comp.Component, + alwaysWrite: true, context: _context); + data.Add(compType, node); + } } - - public bool TryGetRandom(IRobustRandom random, [NotNullWhen(true)] out IPrototype? prototype) where T : class, IPrototype + catch (Exception e) { - var count = Count(); + Sawmill.Error($"Failed to convert prototype {prototype.ID} into yaml. Exception: {e.Message}"); + } - if (count == 0) - { - prototype = null; - return false; - } + _context.WritingReadingPrototypes = false; + _prototypeDataCache[prototype.ID] = data; + return data; + } - var index = 0; + public bool TryGetRandom(IRobustRandom random, [NotNullWhen(true)] out IPrototype? prototype) where T : class, IPrototype + { + var count = Count(); - var picked = random.Next(count); + if (count == 0) + { + prototype = null; + return false; + } - foreach (var proto in EnumeratePrototypes()) - { - if (index == picked) - { - prototype = proto; - return true; - } + var index = 0; + + var picked = random.Next(count); - index++; + foreach (var proto in EnumeratePrototypes()) + { + if (index == picked) + { + prototype = proto; + return true; } - throw new ArgumentOutOfRangeException($"Unable to pick valid prototype for {typeof(T)}?"); + index++; } + + throw new ArgumentOutOfRangeException($"Unable to pick valid prototype for {typeof(T)}?"); } }