├── .gitignore ├── src ├── components.cs.meta ├── entities.cs.meta ├── filters.cs.meta ├── systems.cs.meta ├── worlds.cs.meta ├── entities.cs ├── systems.cs ├── filters.cs ├── components.cs └── worlds.cs ├── Leopotam.EcsLite.asmdef.meta ├── LICENSE.md.meta ├── README.md.meta ├── package.json.meta ├── src.meta ├── Leopotam.EcsLite.asmdef ├── package.json ├── LICENSE.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.sln 2 | .vscode 3 | .idea 4 | .DS_Store 5 | bin 6 | obj 7 | Library 8 | Temp 9 | -------------------------------------------------------------------------------- /src/components.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: de072e90f7ae4e0e970b30c5c9b5aa71 3 | timeCreated: 1618064138 -------------------------------------------------------------------------------- /src/entities.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c74142197318412c9a7f80aa16c5abac 3 | timeCreated: 1619359720 -------------------------------------------------------------------------------- /src/filters.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6460a91dd76b4fa2a8ac079767e094fb 3 | timeCreated: 1619130516 -------------------------------------------------------------------------------- /src/systems.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7ce0c346e4024f069419496fd77aadcb 3 | timeCreated: 1618698420 -------------------------------------------------------------------------------- /Leopotam.EcsLite.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e59bf9fc7d5a44b18a819a92edd46163 3 | timeCreated: 1595884462 -------------------------------------------------------------------------------- /LICENSE.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f123f63dd77c407d8383719e587f37f9 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2bdb45a10caf4764bbfefcd766607194 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 911105c007df4622bd82dc6c57e4b0b8 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /src.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8acc83891df8b4cdab9d3833a3a6e543 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /src/worlds.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e4f8e32372cdd49a899e9ffe82841a20 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Leopotam.EcsLite.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Leopotam.EcsLite", 3 | "rootNamespace": "Leopotam.EcsLite", 4 | "references": [], 5 | "includePlatforms": [], 6 | "excludePlatforms": [], 7 | "allowUnsafeCode": false, 8 | "overrideReferences": false, 9 | "precompiledReferences": [], 10 | "autoReferenced": true, 11 | "defineConstraints": [], 12 | "versionDefines": [], 13 | "noEngineReferences": false 14 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.leopotam.ecslite", 3 | "author": "Leopotam", 4 | "displayName": "LeoECS Lite", 5 | "description": "LeoECS Lite is lightweight C# Entity Component System framework based on structs. Performance, zero/small memory allocations/footprint, no dependency on fucking Unity engine - main goals of this project.", 6 | "unity": "2020.3", 7 | "version": "2022.3.22-preview", 8 | "keywords": [ 9 | "leoecslite", 10 | "leoecs", 11 | "ecs", 12 | "performance" 13 | ], 14 | "dependencies": {}, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/Leopotam/ecslite.git" 18 | } 19 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 - 2022 Leopotam 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/entities.cs: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // The MIT License 3 | // Lightweight ECS framework https://github.com/Leopotam/ecslite 4 | // Copyright (c) 2021-2022 Leopotam 5 | // ---------------------------------------------------------------------------- 6 | 7 | using System.Runtime.CompilerServices; 8 | 9 | #if ENABLE_IL2CPP 10 | using Unity.IL2CPP.CompilerServices; 11 | #endif 12 | 13 | namespace Leopotam.EcsLite { 14 | public struct EcsPackedEntity { 15 | internal int Id; 16 | internal int Gen; 17 | } 18 | 19 | public struct EcsPackedEntityWithWorld { 20 | internal int Id; 21 | internal int Gen; 22 | internal EcsWorld World; 23 | #if DEBUG 24 | // For using in IDE debugger. 25 | internal object[] DebugComponentsView { 26 | get { 27 | object[] list = null; 28 | if (World != null && World.IsAlive () && World.IsEntityAliveInternal (Id) && World.GetEntityGen (Id) == Gen) { 29 | World.GetComponents (Id, ref list); 30 | } 31 | return list; 32 | } 33 | } 34 | // For using in IDE debugger. 35 | internal int DebugComponentsCount { 36 | get { 37 | if (World != null && World.IsAlive () && World.IsEntityAliveInternal (Id) && World.GetEntityGen (Id) == Gen) { 38 | return World.GetComponentsCount (Id); 39 | } 40 | return 0; 41 | } 42 | } 43 | 44 | // For using in IDE debugger. 45 | public override string ToString () { 46 | if (Id == 0 && Gen == 0) { return "Entity-Null"; } 47 | if (World == null || !World.IsAlive () || !World.IsEntityAliveInternal (Id) || World.GetEntityGen (Id) != Gen) { return "Entity-NonAlive"; } 48 | System.Type[] types = null; 49 | var count = World.GetComponentTypes (Id, ref types); 50 | System.Text.StringBuilder sb = null; 51 | if (count > 0) { 52 | sb = new System.Text.StringBuilder (512); 53 | for (var i = 0; i < count; i++) { 54 | if (sb.Length > 0) { sb.Append (","); } 55 | sb.Append (types[i].Name); 56 | } 57 | } 58 | return $"Entity-{Id}:{Gen} [{sb}]"; 59 | } 60 | #endif 61 | } 62 | 63 | #if ENABLE_IL2CPP 64 | [Il2CppSetOption (Option.NullChecks, false)] 65 | [Il2CppSetOption (Option.ArrayBoundsChecks, false)] 66 | #endif 67 | public static class EcsEntityExtensions { 68 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 69 | public static EcsPackedEntity PackEntity (this EcsWorld world, int entity) { 70 | EcsPackedEntity packed; 71 | packed.Id = entity; 72 | packed.Gen = world.GetEntityGen (entity); 73 | return packed; 74 | } 75 | 76 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 77 | public static bool Unpack (this in EcsPackedEntity packed, EcsWorld world, out int entity) { 78 | if (!world.IsAlive () || !world.IsEntityAliveInternal (packed.Id) || world.GetEntityGen (packed.Id) != packed.Gen) { 79 | entity = -1; 80 | return false; 81 | } 82 | entity = packed.Id; 83 | return true; 84 | } 85 | 86 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 87 | public static bool EqualsTo (this in EcsPackedEntity a, in EcsPackedEntity b) { 88 | return a.Id == b.Id && a.Gen == b.Gen; 89 | } 90 | 91 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 92 | public static EcsPackedEntityWithWorld PackEntityWithWorld (this EcsWorld world, int entity) { 93 | EcsPackedEntityWithWorld packedEntity; 94 | packedEntity.World = world; 95 | packedEntity.Id = entity; 96 | packedEntity.Gen = world.GetEntityGen (entity); 97 | return packedEntity; 98 | } 99 | 100 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 101 | public static bool Unpack (this in EcsPackedEntityWithWorld packedEntity, out EcsWorld world, out int entity) { 102 | if (packedEntity.World == null || !packedEntity.World.IsAlive () || !packedEntity.World.IsEntityAliveInternal (packedEntity.Id) || packedEntity.World.GetEntityGen (packedEntity.Id) != packedEntity.Gen) { 103 | world = null; 104 | entity = -1; 105 | return false; 106 | } 107 | world = packedEntity.World; 108 | entity = packedEntity.Id; 109 | return true; 110 | } 111 | 112 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 113 | public static bool EqualsTo (this in EcsPackedEntityWithWorld a, in EcsPackedEntityWithWorld b) { 114 | return a.Id == b.Id && a.Gen == b.Gen && a.World == b.World; 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /src/systems.cs: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // The MIT License 3 | // Lightweight ECS framework https://github.com/Leopotam/ecslite 4 | // Copyright (c) 2021-2022 Leopotam 5 | // ---------------------------------------------------------------------------- 6 | 7 | using System.Collections.Generic; 8 | using System.Runtime.CompilerServices; 9 | 10 | #if ENABLE_IL2CPP 11 | using Unity.IL2CPP.CompilerServices; 12 | #endif 13 | 14 | namespace Leopotam.EcsLite { 15 | public interface IEcsSystem { } 16 | 17 | public interface IEcsPreInitSystem : IEcsSystem { 18 | void PreInit (EcsSystems systems); 19 | } 20 | 21 | public interface IEcsInitSystem : IEcsSystem { 22 | void Init (EcsSystems systems); 23 | } 24 | 25 | public interface IEcsRunSystem : IEcsSystem { 26 | void Run (EcsSystems systems); 27 | } 28 | 29 | public interface IEcsDestroySystem : IEcsSystem { 30 | void Destroy (EcsSystems systems); 31 | } 32 | 33 | public interface IEcsPostDestroySystem : IEcsSystem { 34 | void PostDestroy (EcsSystems systems); 35 | } 36 | 37 | #if ENABLE_IL2CPP 38 | [Il2CppSetOption (Option.NullChecks, false)] 39 | [Il2CppSetOption (Option.ArrayBoundsChecks, false)] 40 | #endif 41 | public class EcsSystems { 42 | readonly EcsWorld _defaultWorld; 43 | readonly Dictionary _worlds; 44 | readonly List _allSystems; 45 | readonly object _shared; 46 | IEcsRunSystem[] _runSystems; 47 | int _runSystemsCount; 48 | 49 | public EcsSystems (EcsWorld defaultWorld, object shared = null) { 50 | _defaultWorld = defaultWorld; 51 | _shared = shared; 52 | _worlds = new Dictionary (32); 53 | _allSystems = new List (128); 54 | } 55 | 56 | public Dictionary GetAllNamedWorlds () { 57 | return _worlds; 58 | } 59 | 60 | public int GetAllSystems (ref IEcsSystem[] list) { 61 | var itemsCount = _allSystems.Count; 62 | if (itemsCount == 0) { return 0; } 63 | if (list == null || list.Length < itemsCount) { 64 | list = new IEcsSystem[_allSystems.Capacity]; 65 | } 66 | for (int i = 0, iMax = itemsCount; i < iMax; i++) { 67 | list[i] = _allSystems[i]; 68 | } 69 | return itemsCount; 70 | } 71 | 72 | public int GetRunSystems (ref IEcsRunSystem[] list) { 73 | var itemsCount = _runSystemsCount; 74 | if (itemsCount == 0) { return 0; } 75 | if (list == null || list.Length < itemsCount) { 76 | list = new IEcsRunSystem[_runSystems.Length]; 77 | } 78 | for (int i = 0, iMax = itemsCount; i < iMax; i++) { 79 | list[i] = _runSystems[i]; 80 | } 81 | return itemsCount; 82 | } 83 | 84 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 85 | public T GetShared () where T : class { 86 | return _shared as T; 87 | } 88 | 89 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 90 | public EcsWorld GetWorld (string name = null) { 91 | if (name == null) { 92 | return _defaultWorld; 93 | } 94 | _worlds.TryGetValue (name, out var world); 95 | return world; 96 | } 97 | 98 | public void Destroy () { 99 | for (var i = _allSystems.Count - 1; i >= 0; i--) { 100 | if (_allSystems[i] is IEcsDestroySystem destroySystem) { 101 | destroySystem.Destroy (this); 102 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 103 | var worldName = CheckForLeakedEntities (); 104 | if (worldName != null) { throw new System.Exception ($"Empty entity detected in world \"{worldName}\" after {destroySystem.GetType ().Name}.Destroy()."); } 105 | #endif 106 | } 107 | } 108 | for (var i = _allSystems.Count - 1; i >= 0; i--) { 109 | if (_allSystems[i] is IEcsPostDestroySystem postDestroySystem) { 110 | postDestroySystem.PostDestroy (this); 111 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 112 | var worldName = CheckForLeakedEntities (); 113 | if (worldName != null) { throw new System.Exception ($"Empty entity detected in world \"{worldName}\" after {postDestroySystem.GetType ().Name}.PostDestroy()."); } 114 | #endif 115 | } 116 | } 117 | _allSystems.Clear (); 118 | _runSystems = null; 119 | } 120 | 121 | public EcsSystems AddWorld (EcsWorld world, string name) { 122 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 123 | if (string.IsNullOrEmpty (name)) { throw new System.Exception ("World name cant be null or empty."); } 124 | #endif 125 | _worlds[name] = world; 126 | return this; 127 | } 128 | 129 | public EcsSystems Add (IEcsSystem system) { 130 | _allSystems.Add (system); 131 | if (system is IEcsRunSystem) { 132 | _runSystemsCount++; 133 | } 134 | return this; 135 | } 136 | 137 | public void Init () { 138 | if (_runSystemsCount > 0) { 139 | _runSystems = new IEcsRunSystem[_runSystemsCount]; 140 | } 141 | foreach (var system in _allSystems) { 142 | if (system is IEcsPreInitSystem initSystem) { 143 | initSystem.PreInit (this); 144 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 145 | var worldName = CheckForLeakedEntities (); 146 | if (worldName != null) { throw new System.Exception ($"Empty entity detected in world \"{worldName}\" after {initSystem.GetType ().Name}.PreInit()."); } 147 | #endif 148 | } 149 | } 150 | var runIdx = 0; 151 | foreach (var system in _allSystems) { 152 | if (system is IEcsInitSystem initSystem) { 153 | initSystem.Init (this); 154 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 155 | var worldName = CheckForLeakedEntities (); 156 | if (worldName != null) { throw new System.Exception ($"Empty entity detected in world \"{worldName}\" after {initSystem.GetType ().Name}.Init()."); } 157 | #endif 158 | } 159 | if (system is IEcsRunSystem runSystem) { 160 | _runSystems[runIdx++] = runSystem; 161 | } 162 | } 163 | } 164 | 165 | public void Run () { 166 | for (int i = 0, iMax = _runSystemsCount; i < iMax; i++) { 167 | _runSystems[i].Run (this); 168 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 169 | var worldName = CheckForLeakedEntities (); 170 | if (worldName != null) { throw new System.Exception ($"Empty entity detected in world \"{worldName}\" after {_runSystems[i].GetType ().Name}.Run()."); } 171 | #endif 172 | } 173 | } 174 | 175 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 176 | public string CheckForLeakedEntities () { 177 | if (_defaultWorld.CheckForLeakedEntities ()) { return "default"; } 178 | foreach (var pair in _worlds) { 179 | if (pair.Value.CheckForLeakedEntities ()) { 180 | return pair.Key; 181 | } 182 | } 183 | return null; 184 | } 185 | #endif 186 | } 187 | } -------------------------------------------------------------------------------- /src/filters.cs: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // The MIT License 3 | // Lightweight ECS framework https://github.com/Leopotam/ecslite 4 | // Copyright (c) 2021-2022 Leopotam 5 | // ---------------------------------------------------------------------------- 6 | 7 | using System; 8 | using System.Runtime.CompilerServices; 9 | 10 | #if ENABLE_IL2CPP 11 | using Unity.IL2CPP.CompilerServices; 12 | #endif 13 | 14 | namespace Leopotam.EcsLite { 15 | #if LEOECSLITE_FILTER_EVENTS 16 | public interface IEcsFilterEventListener { 17 | void OnEntityAdded (int entity); 18 | void OnEntityRemoved (int entity); 19 | } 20 | #endif 21 | #if ENABLE_IL2CPP 22 | [Il2CppSetOption (Option.NullChecks, false)] 23 | [Il2CppSetOption (Option.ArrayBoundsChecks, false)] 24 | #endif 25 | public sealed class EcsFilter { 26 | readonly EcsWorld _world; 27 | readonly EcsWorld.Mask _mask; 28 | int[] _denseEntities; 29 | int _entitiesCount; 30 | internal int[] SparseEntities; 31 | int _lockCount; 32 | DelayedOp[] _delayedOps; 33 | int _delayedOpsCount; 34 | #if LEOECSLITE_FILTER_EVENTS 35 | IEcsFilterEventListener[] _eventListeners = new IEcsFilterEventListener[4]; 36 | int _eventListenersCount; 37 | #endif 38 | 39 | internal EcsFilter (EcsWorld world, EcsWorld.Mask mask, int denseCapacity, int sparseCapacity) { 40 | _world = world; 41 | _mask = mask; 42 | _denseEntities = new int[denseCapacity]; 43 | SparseEntities = new int[sparseCapacity]; 44 | _entitiesCount = 0; 45 | _delayedOps = new DelayedOp[512]; 46 | _delayedOpsCount = 0; 47 | _lockCount = 0; 48 | } 49 | 50 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 51 | public EcsWorld GetWorld () { 52 | return _world; 53 | } 54 | 55 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 56 | public int GetEntitiesCount () { 57 | return _entitiesCount; 58 | } 59 | 60 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 61 | public int[] GetRawEntities () { 62 | return _denseEntities; 63 | } 64 | 65 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 66 | public int[] GetSparseIndex () { 67 | return SparseEntities; 68 | } 69 | 70 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 71 | public Enumerator GetEnumerator () { 72 | _lockCount++; 73 | return new Enumerator (this); 74 | } 75 | 76 | #if LEOECSLITE_FILTER_EVENTS 77 | public void AddEventListener (IEcsFilterEventListener eventListener) { 78 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 79 | if (eventListener == null) { throw new Exception ("Listener is null."); } 80 | #endif 81 | if (_eventListeners.Length == _eventListenersCount) { 82 | Array.Resize (ref _eventListeners, _eventListenersCount << 1); 83 | } 84 | _eventListeners[_eventListenersCount++] = eventListener; 85 | } 86 | 87 | public void RemoveEventListener (IEcsFilterEventListener eventListener) { 88 | for (var i = 0; i < _eventListenersCount; i++) { 89 | if (_eventListeners[i] == eventListener) { 90 | _eventListenersCount--; 91 | // cant fill gap with last element due listeners order is important. 92 | Array.Copy (_eventListeners, i + 1, _eventListeners, i, _eventListenersCount - i); 93 | break; 94 | } 95 | } 96 | } 97 | #endif 98 | 99 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 100 | internal void ResizeSparseIndex (int capacity) { 101 | Array.Resize (ref SparseEntities, capacity); 102 | } 103 | 104 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 105 | internal EcsWorld.Mask GetMask () { 106 | return _mask; 107 | } 108 | 109 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 110 | internal void AddEntity (int entity) { 111 | if (AddDelayedOp (true, entity)) { return; } 112 | if (_entitiesCount == _denseEntities.Length) { 113 | Array.Resize (ref _denseEntities, _entitiesCount << 1); 114 | } 115 | _denseEntities[_entitiesCount++] = entity; 116 | SparseEntities[entity] = _entitiesCount; 117 | #if LEOECSLITE_FILTER_EVENTS 118 | ProcessEventListeners (true, entity); 119 | #endif 120 | } 121 | 122 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 123 | internal void RemoveEntity (int entity) { 124 | if (AddDelayedOp (false, entity)) { return; } 125 | var idx = SparseEntities[entity] - 1; 126 | SparseEntities[entity] = 0; 127 | _entitiesCount--; 128 | if (idx < _entitiesCount) { 129 | _denseEntities[idx] = _denseEntities[_entitiesCount]; 130 | SparseEntities[_denseEntities[idx]] = idx + 1; 131 | } 132 | #if LEOECSLITE_FILTER_EVENTS 133 | ProcessEventListeners (false, entity); 134 | #endif 135 | } 136 | 137 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 138 | bool AddDelayedOp (bool added, int entity) { 139 | if (_lockCount <= 0) { return false; } 140 | if (_delayedOpsCount == _delayedOps.Length) { 141 | Array.Resize (ref _delayedOps, _delayedOpsCount << 1); 142 | } 143 | ref var op = ref _delayedOps[_delayedOpsCount++]; 144 | op.Added = added; 145 | op.Entity = entity; 146 | return true; 147 | } 148 | 149 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 150 | void Unlock () { 151 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 152 | if (_lockCount <= 0) { throw new Exception ($"Invalid lock-unlock balance for \"{GetType ().Name}\"."); } 153 | #endif 154 | _lockCount--; 155 | if (_lockCount == 0 && _delayedOpsCount > 0) { 156 | for (int i = 0, iMax = _delayedOpsCount; i < iMax; i++) { 157 | ref var op = ref _delayedOps[i]; 158 | if (op.Added) { 159 | AddEntity (op.Entity); 160 | } else { 161 | RemoveEntity (op.Entity); 162 | } 163 | } 164 | _delayedOpsCount = 0; 165 | } 166 | } 167 | 168 | #if LEOECSLITE_FILTER_EVENTS 169 | void ProcessEventListeners (bool isAdd, int entity) { 170 | if (isAdd) { 171 | for (var i = 0; i < _eventListenersCount; i++) { 172 | _eventListeners[i].OnEntityAdded (entity); 173 | } 174 | } else { 175 | for (var i = 0; i < _eventListenersCount; i++) { 176 | _eventListeners[i].OnEntityRemoved (entity); 177 | } 178 | } 179 | } 180 | #endif 181 | 182 | public struct Enumerator : IDisposable { 183 | readonly EcsFilter _filter; 184 | readonly int[] _entities; 185 | readonly int _count; 186 | int _idx; 187 | 188 | public Enumerator (EcsFilter filter) { 189 | _filter = filter; 190 | _entities = filter._denseEntities; 191 | _count = filter._entitiesCount; 192 | _idx = -1; 193 | } 194 | 195 | public int Current { 196 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 197 | get => _entities[_idx]; 198 | } 199 | 200 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 201 | public bool MoveNext () { 202 | return ++_idx < _count; 203 | } 204 | 205 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 206 | public void Dispose () { 207 | _filter.Unlock (); 208 | } 209 | } 210 | 211 | struct DelayedOp { 212 | public bool Added; 213 | public int Entity; 214 | } 215 | } 216 | } -------------------------------------------------------------------------------- /src/components.cs: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // The MIT License 3 | // Lightweight ECS framework https://github.com/Leopotam/ecslite 4 | // Copyright (c) 2021-2022 Leopotam 5 | // ---------------------------------------------------------------------------- 6 | 7 | using System; 8 | using System.Runtime.CompilerServices; 9 | 10 | #if ENABLE_IL2CPP 11 | using Unity.IL2CPP.CompilerServices; 12 | #endif 13 | 14 | namespace Leopotam.EcsLite { 15 | public interface IEcsPool { 16 | void Resize (int capacity); 17 | bool Has (int entity); 18 | void Del (int entity); 19 | void AddRaw (int entity, object dataRaw); 20 | object GetRaw (int entity); 21 | void SetRaw (int entity, object dataRaw); 22 | int GetId (); 23 | Type GetComponentType (); 24 | } 25 | 26 | public interface IEcsAutoReset where T : struct { 27 | void AutoReset (ref T c); 28 | } 29 | 30 | #if ENABLE_IL2CPP 31 | [Il2CppSetOption (Option.NullChecks, false)] 32 | [Il2CppSetOption (Option.ArrayBoundsChecks, false)] 33 | #endif 34 | public sealed class EcsPool : IEcsPool where T : struct { 35 | readonly Type _type; 36 | readonly EcsWorld _world; 37 | readonly int _id; 38 | readonly AutoResetHandler _autoReset; 39 | // 1-based index. 40 | T[] _denseItems; 41 | int[] _sparseItems; 42 | int _denseItemsCount; 43 | int[] _recycledItems; 44 | int _recycledItemsCount; 45 | #if ENABLE_IL2CPP && !UNITY_EDITOR 46 | T _autoresetFakeInstance; 47 | #endif 48 | 49 | internal EcsPool (EcsWorld world, int id, int denseCapacity, int sparseCapacity, int recycledCapacity) { 50 | _type = typeof (T); 51 | _world = world; 52 | _id = id; 53 | _denseItems = new T[denseCapacity + 1]; 54 | _sparseItems = new int[sparseCapacity]; 55 | _denseItemsCount = 1; 56 | _recycledItems = new int[recycledCapacity]; 57 | _recycledItemsCount = 0; 58 | var isAutoReset = typeof (IEcsAutoReset).IsAssignableFrom (_type); 59 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 60 | if (!isAutoReset && _type.GetInterface ("IEcsAutoReset`1") != null) { 61 | throw new Exception ($"IEcsAutoReset should have <{typeof (T).Name}> constraint for component \"{typeof (T).Name}\"."); 62 | } 63 | #endif 64 | if (isAutoReset) { 65 | var autoResetMethod = typeof (T).GetMethod (nameof (IEcsAutoReset.AutoReset)); 66 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 67 | if (autoResetMethod == null) { 68 | throw new Exception ( 69 | $"IEcsAutoReset<{typeof (T).Name}> explicit implementation not supported, use implicit instead."); 70 | } 71 | #endif 72 | _autoReset = (AutoResetHandler) Delegate.CreateDelegate ( 73 | typeof (AutoResetHandler), 74 | #if ENABLE_IL2CPP && !UNITY_EDITOR 75 | _autoresetFakeInstance, 76 | #else 77 | null, 78 | #endif 79 | autoResetMethod); 80 | } 81 | } 82 | 83 | #if UNITY_2020_3_OR_NEWER 84 | [UnityEngine.Scripting.Preserve] 85 | #endif 86 | void ReflectionSupportHack () { 87 | _world.GetPool (); 88 | _world.Filter ().Exc ().End (); 89 | } 90 | 91 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 92 | public EcsWorld GetWorld () { 93 | return _world; 94 | } 95 | 96 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 97 | public int GetId () { 98 | return _id; 99 | } 100 | 101 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 102 | public Type GetComponentType () { 103 | return _type; 104 | } 105 | 106 | void IEcsPool.Resize (int capacity) { 107 | Array.Resize (ref _sparseItems, capacity); 108 | } 109 | 110 | object IEcsPool.GetRaw (int entity) { 111 | return Get (entity); 112 | } 113 | 114 | void IEcsPool.SetRaw (int entity, object dataRaw) { 115 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 116 | if (dataRaw == null || dataRaw.GetType () != _type) { throw new Exception ("Invalid component data, valid \"{typeof (T).Name}\" instance required."); } 117 | if (_sparseItems[entity] <= 0) { throw new Exception ($"Component \"{typeof (T).Name}\" not attached to entity."); } 118 | #endif 119 | _denseItems[_sparseItems[entity]] = (T) dataRaw; 120 | } 121 | 122 | void IEcsPool.AddRaw (int entity, object dataRaw) { 123 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 124 | if (dataRaw == null || dataRaw.GetType () != _type) { throw new Exception ("Invalid component data, valid \"{typeof (T).Name}\" instance required."); } 125 | #endif 126 | ref var data = ref Add (entity); 127 | data = (T) dataRaw; 128 | } 129 | 130 | public T[] GetRawDenseItems () { 131 | return _denseItems; 132 | } 133 | 134 | public ref int GetRawDenseItemsCount () { 135 | return ref _denseItemsCount; 136 | } 137 | 138 | public int[] GetRawSparseItems () { 139 | return _sparseItems; 140 | } 141 | 142 | public int[] GetRawRecycledItems () { 143 | return _recycledItems; 144 | } 145 | 146 | public ref int GetRawRecycledItemsCount () { 147 | return ref _recycledItemsCount; 148 | } 149 | 150 | public ref T Add (int entity) { 151 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 152 | if (!_world.IsEntityAliveInternal (entity)) { throw new Exception ("Cant touch destroyed entity."); } 153 | if (_sparseItems[entity] > 0) { throw new Exception ($"Component \"{typeof (T).Name}\" already attached to entity."); } 154 | #endif 155 | int idx; 156 | if (_recycledItemsCount > 0) { 157 | idx = _recycledItems[--_recycledItemsCount]; 158 | } else { 159 | idx = _denseItemsCount; 160 | if (_denseItemsCount == _denseItems.Length) { 161 | Array.Resize (ref _denseItems, _denseItemsCount << 1); 162 | } 163 | _denseItemsCount++; 164 | _autoReset?.Invoke (ref _denseItems[idx]); 165 | } 166 | _sparseItems[entity] = idx; 167 | _world.OnEntityChangeInternal (entity, _id, true); 168 | _world.Entities[entity].ComponentsCount++; 169 | #if DEBUG || LEOECSLITE_WORLD_EVENTS 170 | _world.RaiseEntityChangeEvent (entity); 171 | #endif 172 | return ref _denseItems[idx]; 173 | } 174 | 175 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 176 | public ref T Get (int entity) { 177 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 178 | if (!_world.IsEntityAliveInternal (entity)) { throw new Exception ("Cant touch destroyed entity."); } 179 | if (_sparseItems[entity] == 0) { throw new Exception ($"Cant get \"{typeof (T).Name}\" component - not attached."); } 180 | #endif 181 | return ref _denseItems[_sparseItems[entity]]; 182 | } 183 | 184 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 185 | public bool Has (int entity) { 186 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 187 | if (!_world.IsEntityAliveInternal (entity)) { throw new Exception ("Cant touch destroyed entity."); } 188 | #endif 189 | return _sparseItems[entity] > 0; 190 | } 191 | 192 | public void Del (int entity) { 193 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 194 | if (!_world.IsEntityAliveInternal (entity)) { throw new Exception ("Cant touch destroyed entity."); } 195 | #endif 196 | ref var sparseData = ref _sparseItems[entity]; 197 | if (sparseData > 0) { 198 | _world.OnEntityChangeInternal (entity, _id, false); 199 | if (_recycledItemsCount == _recycledItems.Length) { 200 | Array.Resize (ref _recycledItems, _recycledItemsCount << 1); 201 | } 202 | _recycledItems[_recycledItemsCount++] = sparseData; 203 | if (_autoReset != null) { 204 | _autoReset.Invoke (ref _denseItems[sparseData]); 205 | } else { 206 | _denseItems[sparseData] = default; 207 | } 208 | sparseData = 0; 209 | ref var entityData = ref _world.Entities[entity]; 210 | entityData.ComponentsCount--; 211 | #if DEBUG || LEOECSLITE_WORLD_EVENTS 212 | _world.RaiseEntityChangeEvent (entity); 213 | #endif 214 | if (entityData.ComponentsCount == 0) { 215 | _world.DelEntity (entity); 216 | } 217 | } 218 | } 219 | 220 | delegate void AutoResetHandler (ref T component); 221 | } 222 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LeoEcsLite - Lightweight C# Entity Component System framework 2 | Performance, zero/small memory allocations/footprint, no dependencies on any game engine - are the main goals of this project. 3 | 4 | > **Important!** Don't forget to use `DEBUG` builds for development and `RELEASE` builds in production: all internal error checks / exception throwing work only in `DEBUG` builds and removed for performance reasons in `RELEASE`. 5 | 6 | > **Important!** LeoEcsLite API **is not thread safe** and will never be! If you need multithread-processing - you should implement it on your side as part of ecs-system. 7 | 8 | # Table of content 9 | * [Socials](#socials) 10 | * [Installation](#installation) 11 | * [As unity module](#as-unity-module) 12 | * [As source](#as-source) 13 | * [Main parts of ecs](#main-parts-of-ecs) 14 | * [Entity](#entity) 15 | * [Component](#component) 16 | * [System](#system) 17 | * [Data sharing](#data-sharing) 18 | * [Special classes](#special-classes) 19 | * [EcsPool](#ecspool) 20 | * [EcsFilter](#ecsfilter) 21 | * [EcsWorld](#ecsworld) 22 | * [EcsSystems](#ecssystems) 23 | * [Engine integration](#engine-integration) 24 | * [Unity](#unity) 25 | * [Custom engine](#custom-engine) 26 | * [Projects powered by LeoECS Lite](#projects-powered-by-leoecs-lite) 27 | * [With sources](#with-sources) 28 | * [Extensions](#extensions) 29 | * [License](#license) 30 | * [FAQ](#faq) 31 | 32 | # Socials 33 | [![discord](https://img.shields.io/discord/404358247621853185.svg?label=enter%20to%20discord%20server&style=for-the-badge&logo=discord)](https://discord.gg/5GZVde6) 34 | 35 | # Installation 36 | 37 | ## As unity module 38 | This repository can be installed as unity module directly from git url. In this way new line should be added to `Packages/manifest.json`: 39 | ``` 40 | "com.leopotam.ecslite": "https://github.com/Leopotam/ecslite.git", 41 | ``` 42 | By default last released version will be used. If you need trunk / developing version then `develop` name of branch should be added after hash: 43 | ``` 44 | "com.leopotam.ecslite": "https://github.com/Leopotam/ecslite.git#develop", 45 | ``` 46 | 47 | ## As source 48 | If you can't / don't want to use unity modules, code can be cloned or downloaded as archive from `releases` page. 49 | 50 | # Main parts of ecs 51 | 52 | ## Entity 53 | Сontainer for components. Implemented as `int`: 54 | ```csharp 55 | // Creates new entity in world context. 56 | int entity = _world.NewEntity (); 57 | 58 | // Any entity can be destroyed. All components will be removed first, then entity will be destroyed. 59 | world.DelEntity (entity); 60 | ``` 61 | 62 | > **Important!** Entities can't live without components and will be killed automatically after last component is removed. 63 | 64 | ## Component 65 | Container for user data without / with small logic inside: 66 | ```csharp 67 | struct Component1 { 68 | public int Id; 69 | public string Name; 70 | } 71 | ``` 72 | Components can be added / requested / removed through [component pools](#ecspool). 73 | 74 | ## System 75 | Сontainer for logic for processing filtered entities. User class should implement at least one of `IEcsInitSystem`, `IEcsDestroySystem`, `IEcsRunSystem` (or other supported) interfaces: 76 | ```csharp 77 | class UserSystem : IEcsPreInitSystem, IEcsInitSystem, IEcsRunSystem, IEcsDestroySystem, IEcsPostDestroySystem { 78 | public void PreInit (EcsSystems systems) { 79 | // Will be called once during EcsSystems.Init() call and before IEcsInitSystem.Init(). 80 | } 81 | 82 | public void Init (EcsSystems systems) { 83 | // Will be called once during EcsSystems.Init() call and after IEcsPreInitSystem.PreInit(). 84 | } 85 | 86 | public void Run (EcsSystems systems) { 87 | // Will be called on each EcsSystems.Run() call. 88 | } 89 | 90 | public void Destroy (EcsSystems systems) { 91 | // Will be called once during EcsSystems.Destroy() call and before IEcsPostDestroySystem.PostDestroy(). 92 | } 93 | 94 | public void PostDestroy (EcsSystems systems) { 95 | // Will be called once during EcsSystems.Destroy() call and after IEcsDestroySystem.Destroy(). 96 | } 97 | } 98 | ``` 99 | 100 | # Data sharing 101 | Instance of any custom type can be shared between all systems: 102 | ```csharp 103 | class SharedData { 104 | public string PrefabsPath; 105 | } 106 | ... 107 | SharedData sharedData = new SharedData { PrefabsPath = "Items/{0}" }; 108 | EcsSystems systems = new EcsSystems (world, sharedData); 109 | systems 110 | .Add (new TestSystem1 ()) 111 | .Init (); 112 | ... 113 | class TestSystem1 : IEcsInitSystem { 114 | public void Init(EcsSystems systems) { 115 | SharedData shared = systems.GetShared (); 116 | string prefabPath = string.Format (shared.PrefabsPath, 123); 117 | // prefabPath = "Items/123" here. 118 | } 119 | } 120 | ``` 121 | 122 | # Special classes 123 | 124 | ## EcsPool 125 | Container for components, provides api for adding / requesting / removing components on entity: 126 | ```csharp 127 | int entity = world.NewEntity (); 128 | EcsPool pool = world.GetPool (); 129 | 130 | // Add() adds component to entity. If component already exists - exception will be raised in DEBUG. 131 | ref Component1 c1 = ref pool.Add (entity); 132 | 133 | // Get() returns exist component on entity. If component does not exists - exception will be raised in DEBUG. 134 | ref Component1 c1 = ref pool.Get (entity); 135 | 136 | // Del() removes component from entity. If it was last component - entity will be removed automatically too. 137 | pool.Del (entity); 138 | ``` 139 | 140 | > **Important!** After removing component will be pooled and can be reused later. All fields will be reset to default values automatically. 141 | 142 | ## EcsFilter 143 | Container for keeping filtered entities with specified component list: 144 | ```csharp 145 | class WeaponSystem : IEcsInitSystem, IEcsRunSystem { 146 | public void Init (EcsSystems systems) { 147 | // We want to get default world instance... 148 | EcsWorld world = systems.GetWorld (); 149 | 150 | // and create test entity... 151 | int entity = world.NewEntity (); 152 | 153 | // with "Weapon" component on it. 154 | var weapons = world.GetPool(); 155 | weapons.Add (entity); 156 | } 157 | 158 | public void Run (EcsSystems systems) { 159 | EcsWorld world = systems.GetWorld (); 160 | // We want to get entities with "Weapon" and without "Health". 161 | // You can cache this filter somehow if you want. 162 | var filter = world.Filter ().Exc ().End (); 163 | 164 | // We want to get pool of "Weapon" components. 165 | // You can cache this pool somehow if you want. 166 | var weapons = world.GetPool(); 167 | 168 | foreach (int entity in filter) { 169 | ref Weapon weapon = ref weapons.Get (entity); 170 | weapon.Ammo = System.Math.Max (0, weapon.Ammo - 1); 171 | } 172 | } 173 | } 174 | ``` 175 | 176 | Additional constraints can be added with `Inc<>()` / `Exc<>()` methods. 177 | 178 | > Important: Filters support any amount of components, include and exclude lists should be unique. Filters can't both include and exclude the same component. 179 | 180 | ## EcsWorld 181 | Root level container for all entities / components, works like isolated environment. 182 | 183 | > Important: Do not forget to call `EcsWorld.Destroy()` method if instance will not be used anymore. 184 | 185 | ## EcsSystems 186 | Group of systems to process `EcsWorld` instance: 187 | ```csharp 188 | class Startup : MonoBehaviour { 189 | EcsWorld _world; 190 | EcsSystems _systems; 191 | 192 | void Start () { 193 | // create ecs environment. 194 | _world = new EcsWorld (); 195 | _systems = new EcsSystems (_world) 196 | .Add (new WeaponSystem ()); 197 | _systems.Init (); 198 | } 199 | 200 | void Update () { 201 | // process all dependent systems. 202 | _systems?.Run (); 203 | } 204 | 205 | void OnDestroy () { 206 | // destroy systems logical group. 207 | if (_systems != null) { 208 | _systems.Destroy (); 209 | _systems = null; 210 | } 211 | // destroy world. 212 | if (_world != null) { 213 | _world.Destroy (); 214 | _world = null; 215 | } 216 | } 217 | } 218 | ``` 219 | 220 | > Important: Do not forget to call `EcsSystems.Destroy()` method if instance will not be used anymore. 221 | 222 | # Engine integration 223 | 224 | ## Unity 225 | > Tested on unity 2020.3 (but not dependent on it) and contains assembly definition for compiling to separate assembly file for performance reason. 226 | 227 | [Unity editor integration](https://github.com/Leopotam/ecslite-unityeditor) contains code templates and world debug viewer. 228 | 229 | ## Custom engine 230 | > C#7.3 or above required for this framework. 231 | 232 | Code example - each part should be integrated in proper place of engine execution flow. 233 | ```csharp 234 | using Leopotam.EcsLite; 235 | 236 | class EcsStartup { 237 | EcsWorld _world; 238 | EcsSystems _systems; 239 | 240 | // Initialization of ecs world and systems. 241 | void Init () { 242 | _world = new EcsWorld (); 243 | _systems = new EcsSystems (_world); 244 | _systems 245 | // register additional worlds here. 246 | // .AddWorld (customWorldInstance, "events") 247 | // register your systems here, for example: 248 | // .Add (new TestSystem1 ()) 249 | // .Add (new TestSystem2 ()) 250 | .Init (); 251 | } 252 | 253 | // Engine update loop. 254 | void UpdateLoop () { 255 | _systems?.Run (); 256 | } 257 | 258 | // Cleanup. 259 | void Destroy () { 260 | if (_systems != null) { 261 | _systems.Destroy (); 262 | _systems = null; 263 | } 264 | if (_world != null) { 265 | _world.Destroy (); 266 | _world = null; 267 | } 268 | } 269 | } 270 | ``` 271 | 272 | # Projects powered by LeoECS Lite 273 | ## With sources 274 | * ["3D Platformer"](https://github.com/supremestranger/3D-Platformer-Lite) 275 | [![](https://camo.githubusercontent.com/dcd2f525130d73f4688c1f1cfb12f6e37d166dae23a1c6fac70e5b7873c3ab21/68747470733a2f2f692e6962622e636f2f686d374c726d342f506c6174666f726d65722e706e67)](https://github.com/supremestranger/3D-Platformer-Lite) 276 | 277 | 278 | * ["SharpPhysics2D"](https://github.com/7Bpencil/sharpPhysics) 279 | [![](https://github.com/7Bpencil/sharpPhysics/raw/master/pictures/preview.png)](https://github.com/7Bpencil/sharpPhysics) 280 | 281 | 282 | * ["Busy ECS - extremely nice (and most likely slow) ECS framework"](https://github.com/kkolyan/busyecs) 283 | 284 | # Extensions 285 | * [Dependency injection](https://github.com/Leopotam/ecslite-di) 286 | * [Extended filters](https://github.com/Leopotam/ecslite-extendedfilters) 287 | * [Extended systems](https://github.com/Leopotam/ecslite-extendedsystems) 288 | * [Threads support](https://github.com/Leopotam/ecslite-threads) 289 | * [Unity editor integration](https://github.com/Leopotam/ecslite-unityeditor) 290 | * [Unity uGui bindings](https://github.com/Leopotam/ecslite-unity-ugui) 291 | * [Unity jobs support](https://github.com/Leopotam/ecslite-threads-unity) 292 | * [UniLeo - Unity scene data converter](https://github.com/voody2506/UniLeo-Lite) 293 | * [Unity Physx events support](https://github.com/supremestranger/leoecs-lite-physics) 294 | * [Multiple Shared injection](https://github.com/GoodCatGames/ecslite-multiple-shared) 295 | * [Unity native collections support](https://github.com/odingamesdev/native-ecslite) 296 | 297 | # License 298 | The software is released under the terms of the [MIT license](./LICENSE.md). 299 | 300 | No personal support or any guarantees. 301 | 302 | # FAQ 303 | 304 | ### What is the difference from Ecs-full? 305 | 306 | I prefer to name them `lite` (ecs-lite) and `classic` (ecs-full). Main differences between them (based on `lite`): 307 | * Codebase decreased by 50% (easier to maintain and extend). 308 | * Zero static data in core. 309 | * No caches for components in filter (less memory consuming). 310 | * Fast access to any component on any entity (with performance of cached filter components in `classic`). 311 | * No limits to amount of filter contraints (filter is not generic class anymore). 312 | * Performance is similar to `classic`, maybe slightly better in some cases (worse in some corner cases on very huge amount of data). 313 | * Is aimed at using multiple worlds at same time (can be useful to keep memory consumption low on huge amount of short living components like "events"). 314 | * No reflection at runtime (can be used with aggressive code stripping). 315 | * No data injection through reflection by default (you can use custom shared class between systems with required data or `ecslite-di` from extension's list). 316 | * Entities switched back to `int` (memory consuming decreased). Saving entity as component field supported through packing to `classic` `EcsEntity`-similar struct. 317 | * Small core, all new features can be added through extension repos. 318 | * All new features will be added to `lite` only, `classic` looks stable and mature enough - no new features, bugfixes only. 319 | 320 | ### I want to process one system at MonoBehaviour.Update() and another - at MonoBehaviour.FixedUpdate(). How can I do it? 321 | 322 | For splitting systems by `MonoBehaviour`-method multiple `EcsSystems` logical groups should be used: 323 | ```csharp 324 | EcsSystems _update; 325 | EcsSystems _fixedUpdate; 326 | 327 | void Start () { 328 | EcsWorld world = new EcsWorld (); 329 | _update = new EcsSystems (world).Add (new UpdateSystem ()); 330 | _update.Init (); 331 | _fixedUpdate = new EcsSystems (world).Add (new FixedUpdateSystem ()); 332 | _fixedUpdate.Init (); 333 | } 334 | 335 | void Update () { 336 | _update.Run (); 337 | } 338 | 339 | void FixedUpdate () { 340 | _fixedUpdate.Run (); 341 | } 342 | ``` 343 | 344 | ### I copy&paste my reset components code again and again. How can I do it in other manner? 345 | 346 | If you want to simplify your code and keep reset/init code at one place, you can setup custom handler to process cleanup / initialization for component: 347 | ```csharp 348 | struct MyComponent : IEcsAutoReset { 349 | public int Id; 350 | public object LinkToAnotherComponent; 351 | 352 | public void AutoReset (ref MyComponent c) { 353 | c.Id = 2; 354 | c.LinkToAnotherComponent = null; 355 | } 356 | } 357 | ``` 358 | This method will be automatically called for brand new component instance and after component removing from entity and before recycling to component pool. 359 | > Important: With custom `AutoReset` behaviour there are no any additional checks for reference-type fields, you should provide custom correct cleanup/init behaviour to avoid possible memory leaks. 360 | 361 | ### I want to keep references to entities in components, but entity can be killed at any system and I need protection from reusing the same ID. How can I do it? 362 | 363 | For keeping entity somewhere you should pack it to special `EcsPackedEntity` or `EcsPackedEntityWithWorld` types: 364 | ```csharp 365 | EcsWorld world = new EcsWorld (); 366 | int entity = world.NewEntity (); 367 | EcsPackedEntity packed = world.PackEntity (entity); 368 | EcsPackedEntityWithWorld packedWithWorld = world.PackEntityWithWorld (entity); 369 | ... 370 | if (packed.Unpack (world, out int unpacked)) { 371 | // unpacked is valid and can be used. 372 | } 373 | if (packedWithWorld.Unpack (out EcsWorld unpackedWorld, out int unpackedWithWorld)) { 374 | // unpackedWithWorld is valid and can be used. 375 | } 376 | ``` 377 | 378 | ### I want to add some reactive behaviour on world changes, how I can do it? 379 | 380 | You can use `LEOECSLITE_WORLD_EVENTS` definition to enable custom event listeners support on worlds: 381 | 382 | ```csharp 383 | class TestWorldEventListener : IEcsWorldEventListener { 384 | public void OnEntityCreated (int entity) { 385 | // entity created - raises on world.NewEntity(). 386 | } 387 | 388 | public void OnEntityChanged (int entity) { 389 | // entity changed - raises on pool.Add() / pool.Del(). 390 | } 391 | 392 | public void OnEntityDestroyed (int entity) { 393 | // entity destroyed - raises on world.DelEntity() or last component removing. 394 | } 395 | 396 | public void OnFilterCreated (EcsFilter filter) { 397 | // filter created - raises on world.Filter().End() for brand new filter. 398 | } 399 | 400 | public void OnWorldResized (int newSize) { 401 | // world resized - raises on world/pools resizing when no room for entity at world.NewEntity() call. 402 | } 403 | 404 | public void OnWorldDestroyed (EcsWorld world) { 405 | // world destroyed - raises on world.Destroy(). 406 | } 407 | } 408 | ... 409 | var world = new EcsWorld (); 410 | var listener = new TestWorldEventListener (); 411 | world.AddEventListener (listener); 412 | ``` 413 | 414 | ### I want to add some reactive behaviour on filter changes, how I can do it? 415 | 416 | You can use `LEOECSLITE_FILTER_EVENTS` definition to enable custom event listeners support on filters: 417 | 418 | ```csharp 419 | class TestFilterEventListener : IEcsFilterEventListener { 420 | public void OnEntityAdded (int entity) { 421 | // entity added to filter. 422 | } 423 | 424 | public void OnEntityRemoved (int entity) { 425 | // entity removed from filter. 426 | } 427 | } 428 | ... 429 | var world = new EcsWorld (); 430 | var filter = world.Filter ().End (); 431 | var listener = new TestFilterEventListener (); 432 | filter.AddEventListener (listener); 433 | ``` -------------------------------------------------------------------------------- /src/worlds.cs: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // The MIT License 3 | // Lightweight ECS framework https://github.com/Leopotam/ecslite 4 | // Copyright (c) 2021-2022 Leopotam 5 | // ---------------------------------------------------------------------------- 6 | 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Runtime.CompilerServices; 10 | 11 | #if ENABLE_IL2CPP 12 | using Unity.IL2CPP.CompilerServices; 13 | #endif 14 | 15 | namespace Leopotam.EcsLite { 16 | #if ENABLE_IL2CPP 17 | [Il2CppSetOption (Option.NullChecks, false)] 18 | [Il2CppSetOption (Option.ArrayBoundsChecks, false)] 19 | #endif 20 | public class EcsWorld { 21 | internal EntityData[] Entities; 22 | int _entitiesCount; 23 | int[] _recycledEntities; 24 | int _recycledEntitiesCount; 25 | IEcsPool[] _pools; 26 | int _poolsCount; 27 | readonly int _poolDenseSize; 28 | readonly int _poolRecycledSize; 29 | readonly Dictionary _poolHashes; 30 | readonly Dictionary _hashedFilters; 31 | readonly List _allFilters; 32 | List[] _filtersByIncludedComponents; 33 | List[] _filtersByExcludedComponents; 34 | Mask[] _masks; 35 | int _masksCount; 36 | 37 | bool _destroyed; 38 | #if DEBUG || LEOECSLITE_WORLD_EVENTS 39 | List _eventListeners; 40 | 41 | public void AddEventListener (IEcsWorldEventListener listener) { 42 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 43 | if (listener == null) { throw new Exception ("Listener is null."); } 44 | #endif 45 | _eventListeners.Add (listener); 46 | } 47 | 48 | public void RemoveEventListener (IEcsWorldEventListener listener) { 49 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 50 | if (listener == null) { throw new Exception ("Listener is null."); } 51 | #endif 52 | _eventListeners.Remove (listener); 53 | } 54 | 55 | public void RaiseEntityChangeEvent (int entity) { 56 | for (int ii = 0, iMax = _eventListeners.Count; ii < iMax; ii++) { 57 | _eventListeners[ii].OnEntityChanged (entity); 58 | } 59 | } 60 | #endif 61 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 62 | readonly List _leakedEntities = new List (512); 63 | 64 | internal bool CheckForLeakedEntities () { 65 | if (_leakedEntities.Count > 0) { 66 | for (int i = 0, iMax = _leakedEntities.Count; i < iMax; i++) { 67 | ref var entityData = ref Entities[_leakedEntities[i]]; 68 | if (entityData.Gen > 0 && entityData.ComponentsCount == 0) { 69 | return true; 70 | } 71 | } 72 | _leakedEntities.Clear (); 73 | } 74 | return false; 75 | } 76 | #endif 77 | 78 | public EcsWorld (in Config cfg = default) { 79 | // entities. 80 | var capacity = cfg.Entities > 0 ? cfg.Entities : Config.EntitiesDefault; 81 | Entities = new EntityData[capacity]; 82 | capacity = cfg.RecycledEntities > 0 ? cfg.RecycledEntities : Config.RecycledEntitiesDefault; 83 | _recycledEntities = new int[capacity]; 84 | _entitiesCount = 0; 85 | _recycledEntitiesCount = 0; 86 | // pools. 87 | capacity = cfg.Pools > 0 ? cfg.Pools : Config.PoolsDefault; 88 | _pools = new IEcsPool[capacity]; 89 | _poolHashes = new Dictionary (capacity); 90 | _filtersByIncludedComponents = new List[capacity]; 91 | _filtersByExcludedComponents = new List[capacity]; 92 | _poolDenseSize = cfg.PoolDenseSize > 0 ? cfg.PoolDenseSize : Config.PoolDenseSizeDefault; 93 | _poolRecycledSize = cfg.PoolRecycledSize > 0 ? cfg.PoolRecycledSize : Config.PoolRecycledSizeDefault; 94 | _poolsCount = 0; 95 | // filters. 96 | capacity = cfg.Filters > 0 ? cfg.Filters : Config.FiltersDefault; 97 | _hashedFilters = new Dictionary (capacity); 98 | _allFilters = new List (capacity); 99 | // masks. 100 | _masks = new Mask[64]; 101 | _masksCount = 0; 102 | #if DEBUG || LEOECSLITE_WORLD_EVENTS 103 | _eventListeners = new List (4); 104 | #endif 105 | _destroyed = false; 106 | } 107 | 108 | public void Destroy () { 109 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 110 | if (CheckForLeakedEntities ()) { throw new Exception ($"Empty entity detected before EcsWorld.Destroy()."); } 111 | #endif 112 | _destroyed = true; 113 | for (var i = _entitiesCount - 1; i >= 0; i--) { 114 | ref var entityData = ref Entities[i]; 115 | if (entityData.ComponentsCount > 0) { 116 | DelEntity (i); 117 | } 118 | } 119 | _pools = Array.Empty (); 120 | _poolHashes.Clear (); 121 | _hashedFilters.Clear (); 122 | _allFilters.Clear (); 123 | _filtersByIncludedComponents = Array.Empty> (); 124 | _filtersByExcludedComponents = Array.Empty> (); 125 | #if DEBUG || LEOECSLITE_WORLD_EVENTS 126 | for (var ii = _eventListeners.Count - 1; ii >= 0; ii--) { 127 | _eventListeners[ii].OnWorldDestroyed (this); 128 | } 129 | #endif 130 | } 131 | 132 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 133 | public bool IsAlive () { 134 | return !_destroyed; 135 | } 136 | 137 | public int NewEntity () { 138 | int entity; 139 | if (_recycledEntitiesCount > 0) { 140 | entity = _recycledEntities[--_recycledEntitiesCount]; 141 | ref var entityData = ref Entities[entity]; 142 | entityData.Gen = (short) -entityData.Gen; 143 | } else { 144 | // new entity. 145 | if (_entitiesCount == Entities.Length) { 146 | // resize entities and component pools. 147 | var newSize = _entitiesCount << 1; 148 | Array.Resize (ref Entities, newSize); 149 | for (int i = 0, iMax = _poolsCount; i < iMax; i++) { 150 | _pools[i].Resize (newSize); 151 | } 152 | for (int i = 0, iMax = _allFilters.Count; i < iMax; i++) { 153 | _allFilters[i].ResizeSparseIndex (newSize); 154 | } 155 | #if DEBUG || LEOECSLITE_WORLD_EVENTS 156 | for (int ii = 0, iMax = _eventListeners.Count; ii < iMax; ii++) { 157 | _eventListeners[ii].OnWorldResized (newSize); 158 | } 159 | #endif 160 | } 161 | entity = _entitiesCount++; 162 | Entities[entity].Gen = 1; 163 | } 164 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 165 | _leakedEntities.Add (entity); 166 | #endif 167 | #if DEBUG || LEOECSLITE_WORLD_EVENTS 168 | for (int ii = 0, iMax = _eventListeners.Count; ii < iMax; ii++) { 169 | _eventListeners[ii].OnEntityCreated (entity); 170 | } 171 | #endif 172 | return entity; 173 | } 174 | 175 | public void DelEntity (int entity) { 176 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 177 | if (entity < 0 || entity >= _entitiesCount) { throw new Exception ("Cant touch destroyed entity."); } 178 | #endif 179 | ref var entityData = ref Entities[entity]; 180 | if (entityData.Gen < 0) { 181 | return; 182 | } 183 | // kill components. 184 | if (entityData.ComponentsCount > 0) { 185 | var idx = 0; 186 | while (entityData.ComponentsCount > 0 && idx < _poolsCount) { 187 | for (; idx < _poolsCount; idx++) { 188 | if (_pools[idx].Has (entity)) { 189 | _pools[idx++].Del (entity); 190 | break; 191 | } 192 | } 193 | } 194 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 195 | if (entityData.ComponentsCount != 0) { throw new Exception ($"Invalid components count on entity {entity} => {entityData.ComponentsCount}."); } 196 | #endif 197 | return; 198 | } 199 | entityData.Gen = (short) (entityData.Gen == short.MaxValue ? -1 : -(entityData.Gen + 1)); 200 | if (_recycledEntitiesCount == _recycledEntities.Length) { 201 | Array.Resize (ref _recycledEntities, _recycledEntitiesCount << 1); 202 | } 203 | _recycledEntities[_recycledEntitiesCount++] = entity; 204 | #if DEBUG || LEOECSLITE_WORLD_EVENTS 205 | for (int ii = 0, iMax = _eventListeners.Count; ii < iMax; ii++) { 206 | _eventListeners[ii].OnEntityDestroyed (entity); 207 | } 208 | #endif 209 | } 210 | 211 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 212 | public int GetComponentsCount (int entity) { 213 | return Entities[entity].ComponentsCount; 214 | } 215 | 216 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 217 | public short GetEntityGen (int entity) { 218 | return Entities[entity].Gen; 219 | } 220 | 221 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 222 | public int GetAllocatedEntitiesCount () { 223 | return _entitiesCount; 224 | } 225 | 226 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 227 | public int GetWorldSize () { 228 | return Entities.Length; 229 | } 230 | 231 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 232 | public EntityData[] GetRawEntities () { 233 | return Entities; 234 | } 235 | 236 | public EcsPool GetPool () where T : struct { 237 | var poolType = typeof (T); 238 | if (_poolHashes.TryGetValue (poolType, out var rawPool)) { 239 | return (EcsPool) rawPool; 240 | } 241 | var pool = new EcsPool (this, _poolsCount, _poolDenseSize, Entities.Length, _poolRecycledSize); 242 | _poolHashes[poolType] = pool; 243 | if (_poolsCount == _pools.Length) { 244 | var newSize = _poolsCount << 1; 245 | Array.Resize (ref _pools, newSize); 246 | Array.Resize (ref _filtersByIncludedComponents, newSize); 247 | Array.Resize (ref _filtersByExcludedComponents, newSize); 248 | } 249 | _pools[_poolsCount++] = pool; 250 | return pool; 251 | } 252 | 253 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 254 | public IEcsPool GetPoolById (int typeId) { 255 | return typeId >= 0 && typeId < _poolsCount ? _pools[typeId] : null; 256 | } 257 | 258 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 259 | public IEcsPool GetPoolByType (Type type) { 260 | return _poolHashes.TryGetValue (type, out var pool) ? pool : null; 261 | } 262 | 263 | public int GetAllEntities (ref int[] entities) { 264 | var count = _entitiesCount - _recycledEntitiesCount; 265 | if (entities == null || entities.Length < count) { 266 | entities = new int[count]; 267 | } 268 | var id = 0; 269 | for (int i = 0, iMax = _entitiesCount; i < iMax; i++) { 270 | ref var entityData = ref Entities[i]; 271 | // should we skip empty entities here? 272 | if (entityData.Gen > 0 && entityData.ComponentsCount >= 0) { 273 | entities[id++] = i; 274 | } 275 | } 276 | return count; 277 | } 278 | 279 | public int GetAllPools (ref IEcsPool[] pools) { 280 | var count = _poolsCount; 281 | if (pools == null || pools.Length < count) { 282 | pools = new IEcsPool[count]; 283 | } 284 | Array.Copy (_pools, 0, pools, 0, _poolsCount); 285 | return _poolsCount; 286 | } 287 | 288 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 289 | public Mask Filter () where T : struct { 290 | var mask = _masksCount > 0 ? _masks[--_masksCount] : new Mask (this); 291 | return mask.Inc (); 292 | } 293 | 294 | public int GetComponents (int entity, ref object[] list) { 295 | var itemsCount = Entities[entity].ComponentsCount; 296 | if (itemsCount == 0) { return 0; } 297 | if (list == null || list.Length < itemsCount) { 298 | list = new object[_pools.Length]; 299 | } 300 | for (int i = 0, j = 0, iMax = _poolsCount; i < iMax; i++) { 301 | if (_pools[i].Has (entity)) { 302 | list[j++] = _pools[i].GetRaw (entity); 303 | } 304 | } 305 | return itemsCount; 306 | } 307 | 308 | public int GetComponentTypes (int entity, ref Type[] list) { 309 | var itemsCount = Entities[entity].ComponentsCount; 310 | if (itemsCount == 0) { return 0; } 311 | if (list == null || list.Length < itemsCount) { 312 | list = new Type[_pools.Length]; 313 | } 314 | for (int i = 0, j = 0, iMax = _poolsCount; i < iMax; i++) { 315 | if (_pools[i].Has (entity)) { 316 | list[j++] = _pools[i].GetComponentType (); 317 | } 318 | } 319 | return itemsCount; 320 | } 321 | 322 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 323 | internal bool IsEntityAliveInternal (int entity) { 324 | return entity >= 0 && entity < _entitiesCount && Entities[entity].Gen > 0; 325 | } 326 | 327 | (EcsFilter, bool) GetFilterInternal (Mask mask, int capacity = 512) { 328 | var hash = mask.Hash; 329 | var exists = _hashedFilters.TryGetValue (hash, out var filter); 330 | if (exists) { return (filter, false); } 331 | filter = new EcsFilter (this, mask, capacity, Entities.Length); 332 | _hashedFilters[hash] = filter; 333 | _allFilters.Add (filter); 334 | // add to component dictionaries for fast compatibility scan. 335 | for (int i = 0, iMax = mask.IncludeCount; i < iMax; i++) { 336 | var list = _filtersByIncludedComponents[mask.Include[i]]; 337 | if (list == null) { 338 | list = new List (8); 339 | _filtersByIncludedComponents[mask.Include[i]] = list; 340 | } 341 | list.Add (filter); 342 | } 343 | for (int i = 0, iMax = mask.ExcludeCount; i < iMax; i++) { 344 | var list = _filtersByExcludedComponents[mask.Exclude[i]]; 345 | if (list == null) { 346 | list = new List (8); 347 | _filtersByExcludedComponents[mask.Exclude[i]] = list; 348 | } 349 | list.Add (filter); 350 | } 351 | // scan exist entities for compatibility with new filter. 352 | for (int i = 0, iMax = _entitiesCount; i < iMax; i++) { 353 | ref var entityData = ref Entities[i]; 354 | if (entityData.ComponentsCount > 0 && IsMaskCompatible (mask, i)) { 355 | filter.AddEntity (i); 356 | } 357 | } 358 | #if DEBUG || LEOECSLITE_WORLD_EVENTS 359 | for (int ii = 0, iMax = _eventListeners.Count; ii < iMax; ii++) { 360 | _eventListeners[ii].OnFilterCreated (filter); 361 | } 362 | #endif 363 | return (filter, true); 364 | } 365 | 366 | public void OnEntityChangeInternal (int entity, int componentType, bool added) { 367 | var includeList = _filtersByIncludedComponents[componentType]; 368 | var excludeList = _filtersByExcludedComponents[componentType]; 369 | if (added) { 370 | // add component. 371 | if (includeList != null) { 372 | foreach (var filter in includeList) { 373 | if (IsMaskCompatible (filter.GetMask (), entity)) { 374 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 375 | if (filter.SparseEntities[entity] > 0) { throw new Exception ("Entity already in filter."); } 376 | #endif 377 | filter.AddEntity (entity); 378 | } 379 | } 380 | } 381 | if (excludeList != null) { 382 | foreach (var filter in excludeList) { 383 | if (IsMaskCompatibleWithout (filter.GetMask (), entity, componentType)) { 384 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 385 | if (filter.SparseEntities[entity] == 0) { throw new Exception ("Entity not in filter."); } 386 | #endif 387 | filter.RemoveEntity (entity); 388 | } 389 | } 390 | } 391 | } else { 392 | // remove component. 393 | if (includeList != null) { 394 | foreach (var filter in includeList) { 395 | if (IsMaskCompatible (filter.GetMask (), entity)) { 396 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 397 | if (filter.SparseEntities[entity] == 0) { throw new Exception ("Entity not in filter."); } 398 | #endif 399 | filter.RemoveEntity (entity); 400 | } 401 | } 402 | } 403 | if (excludeList != null) { 404 | foreach (var filter in excludeList) { 405 | if (IsMaskCompatibleWithout (filter.GetMask (), entity, componentType)) { 406 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 407 | if (filter.SparseEntities[entity] > 0) { throw new Exception ("Entity already in filter."); } 408 | #endif 409 | filter.AddEntity (entity); 410 | } 411 | } 412 | } 413 | } 414 | } 415 | 416 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 417 | bool IsMaskCompatible (Mask filterMask, int entity) { 418 | for (int i = 0, iMax = filterMask.IncludeCount; i < iMax; i++) { 419 | if (!_pools[filterMask.Include[i]].Has (entity)) { 420 | return false; 421 | } 422 | } 423 | for (int i = 0, iMax = filterMask.ExcludeCount; i < iMax; i++) { 424 | if (_pools[filterMask.Exclude[i]].Has (entity)) { 425 | return false; 426 | } 427 | } 428 | return true; 429 | } 430 | 431 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 432 | bool IsMaskCompatibleWithout (Mask filterMask, int entity, int componentId) { 433 | for (int i = 0, iMax = filterMask.IncludeCount; i < iMax; i++) { 434 | var typeId = filterMask.Include[i]; 435 | if (typeId == componentId || !_pools[typeId].Has (entity)) { 436 | return false; 437 | } 438 | } 439 | for (int i = 0, iMax = filterMask.ExcludeCount; i < iMax; i++) { 440 | var typeId = filterMask.Exclude[i]; 441 | if (typeId != componentId && _pools[typeId].Has (entity)) { 442 | return false; 443 | } 444 | } 445 | return true; 446 | } 447 | 448 | public struct Config { 449 | public int Entities; 450 | public int RecycledEntities; 451 | public int Pools; 452 | public int Filters; 453 | public int PoolDenseSize; 454 | public int PoolRecycledSize; 455 | 456 | internal const int EntitiesDefault = 512; 457 | internal const int RecycledEntitiesDefault = 512; 458 | internal const int PoolsDefault = 512; 459 | internal const int FiltersDefault = 512; 460 | internal const int PoolDenseSizeDefault = 512; 461 | internal const int PoolRecycledSizeDefault = 512; 462 | } 463 | 464 | #if ENABLE_IL2CPP 465 | [Il2CppSetOption (Option.NullChecks, false)] 466 | [Il2CppSetOption (Option.ArrayBoundsChecks, false)] 467 | #endif 468 | public sealed class Mask { 469 | readonly EcsWorld _world; 470 | internal int[] Include; 471 | internal int[] Exclude; 472 | internal int IncludeCount; 473 | internal int ExcludeCount; 474 | internal int Hash; 475 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 476 | bool _built; 477 | #endif 478 | 479 | internal Mask (EcsWorld world) { 480 | _world = world; 481 | Include = new int[8]; 482 | Exclude = new int[2]; 483 | Reset (); 484 | } 485 | 486 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 487 | void Reset () { 488 | IncludeCount = 0; 489 | ExcludeCount = 0; 490 | Hash = 0; 491 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 492 | _built = false; 493 | #endif 494 | } 495 | 496 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 497 | public Mask Inc () where T : struct { 498 | var poolId = _world.GetPool ().GetId (); 499 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 500 | if (_built) { throw new Exception ("Cant change built mask."); } 501 | if (Array.IndexOf (Include, poolId, 0, IncludeCount) != -1) { throw new Exception ($"{typeof (T).Name} already in constraints list."); } 502 | if (Array.IndexOf (Exclude, poolId, 0, ExcludeCount) != -1) { throw new Exception ($"{typeof (T).Name} already in constraints list."); } 503 | #endif 504 | if (IncludeCount == Include.Length) { Array.Resize (ref Include, IncludeCount << 1); } 505 | Include[IncludeCount++] = poolId; 506 | return this; 507 | } 508 | 509 | #if UNITY_2020_3_OR_NEWER 510 | [UnityEngine.Scripting.Preserve] 511 | #endif 512 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 513 | public Mask Exc () where T : struct { 514 | var poolId = _world.GetPool ().GetId (); 515 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 516 | if (_built) { throw new Exception ("Cant change built mask."); } 517 | if (Array.IndexOf (Include, poolId, 0, IncludeCount) != -1) { throw new Exception ($"{typeof (T).Name} already in constraints list."); } 518 | if (Array.IndexOf (Exclude, poolId, 0, ExcludeCount) != -1) { throw new Exception ($"{typeof (T).Name} already in constraints list."); } 519 | #endif 520 | if (ExcludeCount == Exclude.Length) { Array.Resize (ref Exclude, ExcludeCount << 1); } 521 | Exclude[ExcludeCount++] = poolId; 522 | return this; 523 | } 524 | 525 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 526 | public EcsFilter End (int capacity = 512) { 527 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 528 | if (_built) { throw new Exception ("Cant change built mask."); } 529 | _built = true; 530 | #endif 531 | Array.Sort (Include, 0, IncludeCount); 532 | Array.Sort (Exclude, 0, ExcludeCount); 533 | // calculate hash. 534 | Hash = IncludeCount + ExcludeCount; 535 | for (int i = 0, iMax = IncludeCount; i < iMax; i++) { 536 | Hash = unchecked (Hash * 314159 + Include[i]); 537 | } 538 | for (int i = 0, iMax = ExcludeCount; i < iMax; i++) { 539 | Hash = unchecked (Hash * 314159 - Exclude[i]); 540 | } 541 | var (filter, isNew) = _world.GetFilterInternal (this, capacity); 542 | if (!isNew) { Recycle (); } 543 | return filter; 544 | } 545 | 546 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 547 | void Recycle () { 548 | Reset (); 549 | if (_world._masksCount == _world._masks.Length) { 550 | Array.Resize (ref _world._masks, _world._masksCount << 1); 551 | } 552 | _world._masks[_world._masksCount++] = this; 553 | } 554 | } 555 | 556 | public struct EntityData { 557 | public short Gen; 558 | public short ComponentsCount; 559 | } 560 | } 561 | 562 | #if DEBUG || LEOECSLITE_WORLD_EVENTS 563 | public interface IEcsWorldEventListener { 564 | void OnEntityCreated (int entity); 565 | void OnEntityChanged (int entity); 566 | void OnEntityDestroyed (int entity); 567 | void OnFilterCreated (EcsFilter filter); 568 | void OnWorldResized (int newSize); 569 | void OnWorldDestroyed (EcsWorld world); 570 | } 571 | #endif 572 | } 573 | 574 | #if ENABLE_IL2CPP 575 | // Unity IL2CPP performance optimization attribute. 576 | namespace Unity.IL2CPP.CompilerServices { 577 | enum Option { 578 | NullChecks = 1, 579 | ArrayBoundsChecks = 2 580 | } 581 | 582 | [AttributeUsage (AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] 583 | class Il2CppSetOptionAttribute : Attribute { 584 | public Option Option { get; private set; } 585 | public object Value { get; private set; } 586 | 587 | public Il2CppSetOptionAttribute (Option option, object value) { Option = option; Value = value; } 588 | } 589 | } 590 | #endif --------------------------------------------------------------------------------