|
- 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<TDescriptor> : 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<TDescriptor>(Id);
- return true;
- }
- }
-
- public abstract class EcsObjectBase {
- public EGID Id => _engine.GetEgid(Reference);
- /// <summary>
- /// 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.
- /// </summary>
- public EntityReference Reference { get; }
-
- /// <summary>
- /// Whether the entity reference is still valid. Returns false if this object no longer exists.
- /// </summary>
- 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<Type, WeakDictionary<EntityReference, EcsObjectBase>> _instances = new();
- internal static readonly EcsObjectBaseEngine _engine = new();
-
- private static WeakDictionary<EntityReference, EcsObjectBase> GetInstances(Type type)
- {
- return _instances.TryGetValue(type, out var dict) ? dict : null;
- }
-
- /// <summary>
- /// 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.<br />
- /// <b>Only use for existing entities!</b> Use the other overload for newly created entities.
- /// </summary>
- /// <param name="egid">The EGID of the entity</param>
- /// <param name="constructor">The constructor to construct the object</param>
- /// <typeparam name="T">The object type</typeparam>
- /// <returns></returns>
- internal static T GetInstanceExisting<T>(EGID egid, Func<EGID, T> 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;
- }
-
- /// <summary>
- /// 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.<br />
- /// <b>Only use for newly created entities!</b> Use the other overload for existing entities.
- /// </summary>
- /// <param name="egid">The EGID of the entity</param>
- /// <param name="constructor">The constructor to construct the object</param>
- /// <typeparam name="T">The object type</typeparam>
- /// <returns></returns>
- internal static T GetInstanceNew<T>(EcsInitData initData, Func<EGID, T> 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<U, V>(EGID egid, Func<EGID, V> constructor, Type type = null) where U : IEntityDescriptor, new() where V : EcsObjectBase<U>
- {
- return GetInstanceNew(_engine.Factory.BuildEntity<U>(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<T> GetComponentOptional<T>() where T : unmanaged, IEntityComponent
- {
- return _engine.GetComponentOptional<T>(this);
- }
-
- protected internal ref T GetComponent<T>() where T : unmanaged, IEntityComponent
- {
- return ref _engine.GetComponent<T>(this);
- }
-
- protected internal ref T GetViewComponent<T>() where T : struct, IEntityViewComponent
- {
- return ref _engine.GetViewComponent<T>(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; }
-
- /// <summary>
- /// Holds information needed to construct a component initializer.
- /// Necessary because the initializer is a ref struct which cannot be assigned to a field.
- /// </summary>
- protected internal readonly record struct EcsInitData(FasterDictionary<RefWrapperType, ITypeSafeDictionary> group, EntityReference Reference, EGID EGID)
- {
- public static implicit operator EcsInitData(EntityInitializer initializer) => new(GetInitGroup(initializer), initializer.reference, initializer.EGID);
-
- private readonly FasterDictionary<RefWrapperType, ITypeSafeDictionary> 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<RefWrapperType, ITypeSafeDictionary> GetInitGroupFunc(
- EntityInitializer initializer);
-
- /// <summary>
- /// Accesses the group field of the initializer
- /// </summary>
- private static readonly GetInitGroupFunc GetInitGroup = Reflections.CreateAccessor<GetInitGroupFunc>("_group");
-
- #endregion
-
- public static void Init()
- {
- EngineManager.AddEngine(_engine, ApiEngineType.Build, ApiEngineType.Menu, ApiEngineType.PlayClient, ApiEngineType.PlayServer);
- }
- }
|