using System; using System.Collections.Generic; using Svelto.DataStructures; using Svelto.ECS; using Svelto.ECS.Hybrid; using Svelto.ECS.Internal; using TechbloxModdingAPI.Common.Engines; using TechbloxModdingAPI.Common.Utils; using TechbloxModdingAPI.Utility; namespace TechbloxModdingAPI.Common; public abstract class EcsObjectBase : EcsObjectBase where TDescriptor : IEntityDescriptor, new() { protected EcsObjectBase(EGID id) : base(id, typeof(TDescriptor)) { } protected EcsObjectBase(EntityReference reference) : base(reference, typeof(TDescriptor)) { } protected bool RemoveEntity() { if (!Exists) return false; _engine.Functions.RemoveEntity(Id); return true; } } public abstract class EcsObjectBase { public EGID Id => _engine.GetEgid(Reference); /// /// A reference to a specific entity that persists through group swaps and such. /// May be an invalid reference, in that case operations do not have any effect. /// public EntityReference Reference { get; } /// /// Whether the entity reference is still valid. Returns false if this object no longer exists. /// public bool Exists => Id != default; // TODO: Might need extra code to support IDs during init public readonly Type EntityDescriptorType; public readonly Type[] AllowedEntityComponents; private static readonly Dictionary> _instances = new(); internal static readonly EcsObjectBaseEngine _engine = new(); private static WeakDictionary GetInstances(Type type) { return _instances.TryGetValue(type, out var dict) ? dict : null; } /// /// Returns a cached instance if there's an actively used instance of the object already. /// Objects still get garbage collected and then they will be removed from the cache.
/// Only use for existing entities! Use the other overload for newly created entities. ///
/// The EGID of the entity /// The constructor to construct the object /// The object type /// internal static T GetInstanceExisting(EGID egid, Func constructor, Type type = null) where T : EcsObjectBase { var instances = GetInstances(type ?? typeof(T)); if (instances == null || !instances.TryGetValue(_engine.GetEntityReference(egid), out var instance)) return constructor(egid); // It will be added by the constructor return (T)instance; } /// /// Returns a cached instance if there's an actively used instance of the object already. /// Objects still get garbage collected and then they will be removed from the cache.
/// Only use for newly created entities! Use the other overload for existing entities. ///
/// The EGID of the entity /// The constructor to construct the object /// The object type /// internal static T GetInstanceNew(EcsInitData initData, Func constructor, Type type = null) where T : EcsObjectBase { var instances = GetInstances(type ?? typeof(T)); if (instances == null || !instances.TryGetValue(initData.Reference, out var instance)) { var ret = constructor(initData.EGID); ret.InitData = initData; return ret; // It will be added by the constructor } return (T)instance; } protected static V CreateEntity(EGID egid, Func constructor, Type type = null) where U : IEntityDescriptor, new() where V : EcsObjectBase { return GetInstanceNew(_engine.Factory.BuildEntity(egid), constructor, type); } protected EcsObjectBase(EGID id, Type entityDescriptorType) : this(_engine.GetEntityReference(id), entityDescriptorType) { } protected EcsObjectBase(EntityReference reference, Type entityDescriptorType) { if (!_instances.TryGetValue(GetType(), out var dict)) { dict = new(); _instances.Add(GetType(), dict); } if (!dict.ContainsKey(reference)) // Multiple instances may be created dict.Add(reference, this); Reference = reference; EntityDescriptorType = entityDescriptorType; AllowedEntityComponents = EcsUtils.GetValidEntityComponents(entityDescriptorType); // Remove init data once the entity gets submitted so that it won't be used again once the entity is removed if (InitData != default) _engine.TrackNewEntity(this, obj => obj.InitData = default); } protected internal OptionalRef GetComponentOptional() where T : unmanaged, IEntityComponent { return _engine.GetComponentOptional(this); } protected internal ref T GetComponent() where T : unmanaged, IEntityComponent { return ref _engine.GetComponent(this); } protected internal ref T GetViewComponent() where T : struct, IEntityViewComponent { return ref _engine.GetViewComponent(this); } protected internal object GetComponent(Type type, string name) { return _engine.GetComponent(this, type, name); } protected internal void SetComponent(Type type, string name, object value) { _engine.SetComponent(this, type, name, value); } #region ECS initializer stuff internal EcsInitData InitData { get; private set; } /// /// Holds information needed to construct a component initializer. /// Necessary because the initializer is a ref struct which cannot be assigned to a field. /// protected internal readonly record struct EcsInitData(FasterDictionary group, EntityReference Reference, EGID EGID) { public static implicit operator EcsInitData(EntityInitializer initializer) => new(GetInitGroup(initializer), initializer.reference, initializer.EGID); private readonly FasterDictionary group = group; public readonly EntityReference Reference = Reference; public readonly EGID EGID = EGID; public EntityInitializer Initializer(EGID id = default) => new(id == default ? EGID : id, group, Reference); public bool Valid => group != null; } private delegate FasterDictionary GetInitGroupFunc( EntityInitializer initializer); /// /// Accesses the group field of the initializer /// private static readonly GetInitGroupFunc GetInitGroup = Reflections.CreateAccessor("_group"); #endregion public static void Init() { EngineManager.AddEngine(_engine, ApiEngineType.Build, ApiEngineType.Menu, ApiEngineType.PlayClient, ApiEngineType.PlayServer); } }