// ---------------------------------------------------------------------------- // The MIT License // Simple Entity Component System framework https://github.com/Leopotam/ecs // Copyright (c) 2017-2020 Leopotam // ---------------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; // ReSharper disable ClassNeverInstantiated.Global namespace Leopotam.Ecs { /// /// Marks component type to be not auto-filled as GetX in filter. /// public interface IEcsIgnoreInFilter { } /// /// Marks component type as resettable with custom logic. /// public interface IEcsAutoReset { void Reset (); } /// /// Marks field of IEcsSystem class to be ignored during dependency injection. /// public sealed class EcsIgnoreInjectAttribute : Attribute { } /// /// Marks field of component to be not checked for null on component removing. /// Works only in DEBUG mode! /// [System.Diagnostics.Conditional ("DEBUG")] [AttributeUsage (AttributeTargets.Field)] public sealed class EcsIgnoreNullCheckAttribute : Attribute { } /// /// Global descriptor of used component type. /// /// Component type. public static class EcsComponentType where T : class { // ReSharper disable StaticMemberInGenericType public static readonly int TypeIndex; public static readonly Type Type; public static readonly bool IsAutoReset; public static readonly bool IsIgnoreInFilter; // ReSharper restore StaticMemberInGenericType static EcsComponentType () { TypeIndex = Interlocked.Increment (ref EcsComponentPool.ComponentTypesCount); Type = typeof (T); IsAutoReset = typeof (IEcsAutoReset).IsAssignableFrom (Type); IsIgnoreInFilter = typeof (IEcsIgnoreInFilter).IsAssignableFrom (Type); } } #if ENABLE_IL2CPP [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] #endif public sealed class EcsComponentPool { /// /// Global component type counter. /// First component will be "1" for correct filters updating (add component on positive and remove on negative). /// internal static int ComponentTypesCount; #if DEBUG readonly List _nullableFields = new List (8); #endif public object[] Items = new Object[128]; Func _customCtor; readonly Type _type; readonly bool _isAutoReset; int[] _reservedItems = new int[128]; int _itemsCount; int _reservedItemsCount; internal EcsComponentPool (Type cType, bool isAutoReset) { _type = cType; _isAutoReset = isAutoReset; #if DEBUG // collect all marshal-by-reference fields. var fields = _type.GetFields (); for (var i = 0; i < fields.Length; i++) { var field = fields[i]; if (!Attribute.IsDefined (field, typeof (EcsIgnoreNullCheckAttribute))) { var type = field.FieldType; var underlyingType = Nullable.GetUnderlyingType (type); if (!type.IsValueType || (underlyingType != null && !underlyingType.IsValueType)) { if (type != typeof (string)) { _nullableFields.Add (field); } } if (type == typeof (EcsEntity)) { _nullableFields.Add (field); } } } #endif } /// /// Sets custom constructor for component instances. /// /// public void SetCustomCtor (Func ctor) { #if DEBUG // ReSharper disable once JoinNullCheckWithUsage if (ctor == null) { throw new Exception ("Ctor is null."); } #endif _customCtor = ctor; } /// /// Sets new capacity (if more than current amount). /// /// New value. public void SetCapacity (int capacity) { if (capacity > Items.Length) { Array.Resize (ref Items, capacity); } } [MethodImpl (MethodImplOptions.AggressiveInlining)] public int New () { int id; if (_reservedItemsCount > 0) { id = _reservedItems[--_reservedItemsCount]; } else { id = _itemsCount; if (_itemsCount == Items.Length) { Array.Resize (ref Items, _itemsCount << 1); } var instance = _customCtor != null ? _customCtor () : Activator.CreateInstance (_type); // reset brand new instance if component implements IEcsAutoReset. if (_isAutoReset) { ((IEcsAutoReset) instance).Reset (); } Items[_itemsCount++] = instance; } return id; } [MethodImpl (MethodImplOptions.AggressiveInlining)] public object GetItem (int idx) { return Items[idx]; } [MethodImpl (MethodImplOptions.AggressiveInlining)] public void Recycle (int idx) { if (_isAutoReset) { ((IEcsAutoReset) Items[idx]).Reset (); } #if DEBUG // check all marshal-by-reference typed fields for nulls. var obj = Items[idx]; for (int i = 0, iMax = _nullableFields.Count; i < iMax; i++) { if (_nullableFields[i].FieldType.IsValueType) { if (_nullableFields[i].FieldType == typeof (EcsEntity) && ((EcsEntity) _nullableFields[i].GetValue (obj)).Owner != null) { throw new Exception ( $"Memory leak for \"{_type.Name}\" component: \"{_nullableFields[i].Name}\" field not null-ed with EcsEntity.Null. If you are sure that it's not - mark field with [EcsIgnoreNullCheck] attribute."); } } else { if (_nullableFields[i].GetValue (obj) != null) { throw new Exception ( $"Memory leak for \"{_type.Name}\" component: \"{_nullableFields[i].Name}\" field not null-ed. If you are sure that it's not - mark field with [EcsIgnoreNullCheck] attribute."); } } } #endif if (_reservedItemsCount == _reservedItems.Length) { Array.Resize (ref _reservedItems, _reservedItemsCount << 1); } _reservedItems[_reservedItemsCount++] = idx; } } }