using System; using System.Collections.Generic; using TEngine.Core; using System.Runtime.Serialization; using TEngine.DataStructure; using MongoDB.Bson.Serialization.Attributes; using Newtonsoft.Json; #pragma warning disable CS8618 #pragma warning disable CS8625 #pragma warning disable CS8601 #pragma warning disable CS8603 #pragma warning disable CS0436 // ReSharper disable SuspiciousTypeConversion.Global // ReSharper disable InconsistentNaming namespace TEngine { [Flags] public enum EntityStatus: byte { None = 0, IsFromPool = 1, IsComponent = 1 << 1, IsCreated = 1 << 2, IsNew = 1 << 3, } public abstract class Entity : IDisposable { #if UNITY_EDITOR [UnityEngine.HideInInspector] public UnityEngine.GameObject ViewGO; #endif #region Status [BsonIgnore] [IgnoreDataMember] private EntityStatus status = EntityStatus.None; [BsonIgnore] [IgnoreDataMember] public bool IsFromPool { get => (this.status & EntityStatus.IsFromPool) == EntityStatus.IsFromPool; set { if (value) { this.status |= EntityStatus.IsFromPool; } else { this.status &= ~EntityStatus.IsFromPool; } } } [BsonIgnore] [IgnoreDataMember] protected bool IsComponent { get => (this.status & EntityStatus.IsComponent) == EntityStatus.IsComponent; set { if (value) { this.status |= EntityStatus.IsComponent; } else { this.status &= ~EntityStatus.IsComponent; } } } [BsonIgnore] [IgnoreDataMember] protected bool IsCreated { get => (this.status & EntityStatus.IsCreated) == EntityStatus.IsCreated; set { if (value) { this.status |= EntityStatus.IsCreated; } else { this.status &= ~EntityStatus.IsCreated; } } } [BsonIgnore] [IgnoreDataMember] protected bool IsNew { get => (this.status & EntityStatus.IsNew) == EntityStatus.IsNew; set { if (value) { this.status |= EntityStatus.IsNew; } else { this.status &= ~EntityStatus.IsNew; } } } [BsonIgnore] [IgnoreDataMember] protected virtual string ViewName => this.GetType().Name; #endregion #region Entities private static readonly Dictionary Entities = new Dictionary(); private static readonly OneToManyQueue Pool = new OneToManyQueue(); public static Entity GetEntity(long runTimeId) { return Entities.TryGetValue(runTimeId, out var entity) ? entity : null; } public static bool TryGetEntity(long runTimeId, out Entity entity) { return Entities.TryGetValue(runTimeId, out entity); } public static T GetEntity(long runTimeId) where T : Entity, new() { if (!Entities.TryGetValue(runTimeId, out var entity)) { return default; } return (T) entity; } public static bool TryGetEntity(long runTimeId, out T outEntity) where T : Entity, new() { if (!Entities.TryGetValue(runTimeId, out var entity)) { outEntity = default; return false; } outEntity = (T) entity; return true; } private static T Rent(Type entityType) where T : Entity, new() { if (typeof(INotSupportedPool).IsAssignableFrom(entityType)) { return Activator.CreateInstance(); } T entity; if (Pool.TryDequeue(entityType, out var poolEntity)) { entity = (T) poolEntity; } else { entity = Activator.CreateInstance(); } entity.IsFromPool = true; return entity; } private static void Return(Entity entity) { entity.Id = 0; if (!entity.IsFromPool) { return; } entity.IsFromPool = false; Pool.Enqueue(entity.GetType(), entity); } #endregion #region Create public static T Create(Scene scene, bool isRunEvent = true) where T : Entity, new() { var entity = Create(scene.RouteId, isRunEvent); entity.Scene = scene; return entity; } public static T Create(Scene scene, long id, bool isRunEvent = true) where T : Entity, new() { var entity = Create(id, scene.RouteId, isRunEvent); entity.Scene = scene; return entity; } private static T Create(uint routeId, bool isRunEvent = true) where T : Entity, new() { var entity = Rent(typeof(T)); #if TENGINE_NET entity.Id = entity.RuntimeId = IdFactory.NextEntityId(routeId); #else entity.Id = entity.RuntimeId = IdFactory.NextRunTimeId(); #endif Entities.Add(entity.RuntimeId, entity); if (isRunEvent) { EntitiesSystem.Instance.Awake(entity); EntitiesSystem.Instance.StartUpdate(entity); } entity.IsCreated = true; entity.IsNew = true; entity.OnCreate(); #if UNITY_EDITOR entity.ViewGO = new UnityEngine.GameObject(entity.ViewName); entity.ViewGO.AddComponent().Component = entity; entity.ViewGO.transform.SetParent(entity.Parent == null? UnityEngine.GameObject.Find("[EntitySystem]").transform : entity.Parent.ViewGO.transform); #endif return entity; } private static T Create(long id, uint routeId, bool isRunEvent = true) where T : Entity, new() { return Create(id, IdFactory.NextEntityId(routeId), isRunEvent); } private static T Create(long id, long runtimeId, bool isRunEvent = true) where T : Entity, new() { var entity = Rent(typeof(T)); entity.Id = id; entity.RuntimeId = runtimeId; Entities.Add(entity.RuntimeId, entity); if (isRunEvent) { EntitiesSystem.Instance.Awake(entity); EntitiesSystem.Instance.StartUpdate(entity); } entity.IsCreated = true; entity.IsNew = true; entity.OnCreate(); #if UNITY_EDITOR entity.ViewGO = new UnityEngine.GameObject(entity.ViewName); entity.ViewGO.AddComponent().Component = entity; entity.ViewGO.transform.SetParent(entity.Parent == null? UnityEngine.GameObject.Find("[EntitySystem]").transform : entity.Parent.ViewGO.transform); #endif return entity; } protected static Scene CreateScene(long id, bool isRunEvent = true) { var entity = Create(id, id, isRunEvent); entity.Scene = entity; return entity; } #endregion #region Members [BsonId] [BsonElement] [BsonIgnoreIfDefault] [BsonDefaultValue(0L)] public long Id { get; private set; } [BsonIgnore] [IgnoreDataMember] public long RuntimeId { get; private set; } [BsonIgnore] [JsonIgnore] [IgnoreDataMember] public bool IsDisposed => RuntimeId == 0; [BsonIgnore] [JsonIgnore] [IgnoreDataMember] public Scene Scene { get; private set; } [BsonIgnore] [JsonIgnore] [IgnoreDataMember] public Entity Parent { get; private set; } [BsonElement("t")] [BsonIgnoreIfNull] private ListPool _treeDb; [BsonIgnore] [IgnoreDataMember] private DictionaryPool _tree; [BsonElement("m")] [BsonIgnoreIfNull] private ListPool _multiDb; [BsonIgnore] [IgnoreDataMember] private DictionaryPool _multi; #endregion #region AddComponent public T AddComponent() where T : Entity, new() { var entity = Create(Scene.RouteId, false); AddComponent(entity); EntitiesSystem.Instance.Awake(entity); EntitiesSystem.Instance.StartUpdate(entity); return entity; } public T AddComponent(long id) where T : Entity, new() { var entity = Create(id, Scene.RouteId, false); AddComponent(entity); EntitiesSystem.Instance.Awake(entity); EntitiesSystem.Instance.StartUpdate(entity); return entity; } public void AddComponent(Entity component) { if (this == component) { Log.Error("Cannot add oneself to one's own components"); return; } if (component.IsDisposed) { Log.Error($"component is Disposed {component.GetType().FullName}"); return; } var type = component.GetType(); component.Parent?.RemoveComponent(component, false); if (component is ISupportedMultiEntity multiEntity) { _multi ??= DictionaryPool.Create(); _multi.Add(component.Id, multiEntity); if (component is ISupportedDataBase) { _multiDb ??= ListPool.Create(); _multiDb.Add(component); } } else { if (_tree == null) { _tree = DictionaryPool.Create(); } else if(_tree.ContainsKey(type)) { Log.Error($"type:{type.FullName} If you want to add multiple components of the same type, please implement IMultiEntity"); return; } _tree.Add(type, component); if (component is ISupportedDataBase) { _treeDb ??= ListPool.Create(); _treeDb.Add(component); } } this.IsComponent = false; component.Parent = this; component.Scene = Scene; component.IsComponent = true; #if UNITY_EDITOR if (component.ViewGO == null) { Log.Error($"{component} ‘s component.ViewGO is null"); return; } component.ViewGO.transform.SetParent(component.Parent == null? UnityEngine.GameObject.Find("[EntitySystem]").transform : component.Parent.ViewGO.transform); #endif } #endregion #region GetComponent public T GetComponent() where T : Entity, new() { return GetComponent(typeof(T)) as T; } public Entity GetComponent(Type componentType) { if (_tree == null) { return default; } return _tree.TryGetValue(componentType, out var component) ? component : default; } public T GetComponent(long id) where T : ISupportedMultiEntity, new() { if (_multi == null) { return default; } return _multi.TryGetValue(id, out var entity) ? (T) entity : default; } #endregion #region RemoveComponent public void RemoveComponent(bool isDispose = true) where T : Entity, new() { if (_tree == null || !_tree.TryGetValue(typeof(T), out var component)) { return; } RemoveComponent(component, isDispose); } public void RemoveComponent(long id, bool isDispose = true) where T : ISupportedMultiEntity, new() { if (_multi == null || !_multi.TryGetValue(id, out var component)) { return; } RemoveComponent((Entity)component, isDispose); } public void RemoveComponent(Entity component, bool isDispose = true) { if (this == component) { return; } if (component is ISupportedMultiEntity) { if (_multi != null) { #if TENGINE_NET if (component is ISupportedDataBase && _multiDb != null) { _multiDb.Remove(component); if (_multiDb.Count == 0) { _multiDb.Dispose(); _multiDb = null; } } #endif _multi.Remove(component.Id); if (_multi.Count == 0) { _multi.Dispose(); _multi = null; } } } else if (_tree != null) { #if TENGINE_NET if (component is ISupportedDataBase && _treeDb != null) { _treeDb.Remove(component); if (_treeDb.Count == 0) { _treeDb.Dispose(); _treeDb = null; } } #endif _tree.Remove(component.GetType()); if (_tree.Count == 0) { _tree.Dispose(); _tree = null; } } if (isDispose) { component.Dispose(); } } #endregion #region Deserialize public void Deserialize(Scene scene, bool resetId = false) { if (RuntimeId != 0) { return; } try { #if TENGINE_NET RuntimeId = IdFactory.NextEntityId(scene.RouteId); #else RuntimeId = IdFactory.NextRunTimeId(); #endif if (resetId) { Id = RuntimeId; } Entities.Add(RuntimeId, this); if (_treeDb != null && _treeDb.Count > 0) { _tree = DictionaryPool.Create(); foreach (var entity in _treeDb) { entity.Parent = this; entity.Scene = scene; entity.Deserialize(scene, resetId); _tree.Add(entity.GetType(), entity); } } if (_multiDb != null && _multiDb.Count > 0) { _multi = DictionaryPool.Create(); foreach (var entity in _multiDb) { entity.Parent = this; entity.Scene = scene; entity.Deserialize(scene, resetId); _multi.Add(entity.Id, (ISupportedMultiEntity)entity); } } } catch (Exception e) { if (RuntimeId != 0) { Entities.Remove(RuntimeId); } Log.Error(e); } } #endregion #region Clone public Entity Clone() { #if TENGINE_NET var entity = MongoHelper.Instance.Clone(this); entity.Deserialize(Scene, true); return entity; #elif TENGINE_UNITY var entity = ProtoBufHelper.Clone(this); entity.Deserialize(Scene, true); return entity; #endif } #endregion #region OnCreate public virtual void OnCreate() { } #endregion #region Dispose public virtual void Dispose() { if (IsDisposed) { return; } var runtimeId = RuntimeId; RuntimeId = 0; if (_tree != null) { foreach (var (_, entity) in _tree) { entity.Dispose(); } _tree.Dispose(); _tree = null; } if (_multi != null) { foreach (var (_, entity) in _multi) { entity.Dispose(); } _multi.Dispose(); _multi = null; } #if TENGINE_NET if (_treeDb != null) { foreach (var entity in _treeDb) { entity.Dispose(); } _treeDb.Dispose(); _treeDb = null; } if (_multiDb != null) { foreach (var entity in _multiDb) { entity.Dispose(); } _multiDb.Dispose(); _multiDb = null; } #endif EntitiesSystem.Instance?.Destroy(this); if (Parent != null && Parent != this && !Parent.IsDisposed) { Parent.RemoveComponent(this, false); Parent = null; } Entities.Remove(runtimeId); Scene = null; Return(this); #if UNITY_EDITOR if (this.ViewGO == null) { Log.Error($"{this} ‘s ViewGO is null"); return; } UnityEngine.Object.Destroy(this.ViewGO);; #endif } #endregion } }