├── .gitignore ├── LICENSE.md ├── LICENSE.md.meta ├── Leopotam.Ecs.asmdef ├── Leopotam.Ecs.asmdef.meta ├── README.md ├── README.md.meta ├── package.json ├── package.json.meta ├── src.meta └── src ├── EcsComponent.cs ├── EcsComponent.cs.meta ├── EcsEntity.cs ├── EcsEntity.cs.meta ├── EcsFilter.cs ├── EcsFilter.cs.meta ├── EcsHelpers.cs ├── EcsHelpers.cs.meta ├── EcsSystem.cs ├── EcsSystem.cs.meta ├── EcsWorld.cs └── EcsWorld.cs.meta /.gitignore: -------------------------------------------------------------------------------- 1 | *.sln 2 | .vscode 3 | .idea 4 | Library 5 | bin 6 | obj 7 | Temp 8 | .DS_Store 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 - 2023 leopotam@yandex.ru 2 | 3 | Данное программное обеспечение и сопутствующая документация (далее - Продукт) 4 | выпускается на условиях двойного лицензирования - под собственнической/коммерческой 5 | и MIT-Red лицензиями. 6 | 7 | Условия использования под собственнической/коммерческой лицензии обсуждаются 8 | индивидуально, для подробностей следует писать на электронную почту. 9 | 10 | MIT-Red регулируется совокупностью следующих правил, если хотя бы 11 | одно из них невыполнимо - использование Продукта запрещено: 12 | 13 | Если вы за применение opensource программного обеспечения в 14 | военной сфере - вы не можете использовать этот Продукт. 15 | 16 | Если вы испытываете ненависть к русским или поддерживаете 17 | любые нападки на них - вы не можете использовать этот Продукт. 18 | 19 | Данная лицензия разрешает лицам, получившим копию данного Продукта, 20 | безвозмездно использовать Программное обеспечение без ограничений, включая 21 | неограниченное право на использование, копирование, изменение, слияние, 22 | публикацию и распространение копий Продукта. 23 | 24 | Указанное выше уведомление об авторском праве и данные условия должны быть 25 | включены во все копии или значимые части данного Продукта. 26 | 27 | ДАННЫЙ ПРОДУКТ ПРЕДОСТАВЛЯЕТСЯ «КАК ЕСТЬ», БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ЯВНО 28 | ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ ГАРАНТИИ ТОВАРНОЙ ПРИГОДНОСТИ, 29 | СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ НАЗНАЧЕНИЮ И ОТСУТСТВИЯ НАРУШЕНИЙ, НО 30 | НЕ ОГРАНИЧИВАЯСЬ ИМИ. НИ В КАКОМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ НЕСУТ 31 | ОТВЕТСТВЕННОСТИ ПО КАКИМ-ЛИБО ИСКАМ, ЗА УЩЕРБ ИЛИ ПО ИНЫМ ТРЕБОВАНИЯМ, 32 | В ТОМ ЧИСЛЕ, ПРИ ДЕЙСТВИИ КОНТРАКТА, ДЕЛИКТЕ ИЛИ ИНОЙ СИТУАЦИИ, ВОЗНИКШИМ 33 | ИЗ-ЗА ИСПОЛЬЗОВАНИЯ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ ИЛИ ИНЫХ ДЕЙСТВИЙ С ПРОДУКТОМ. -------------------------------------------------------------------------------- /LICENSE.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2a91ec8cda9a14e9eb716d58db684448 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Leopotam.Ecs.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Leopotam.Ecs", 3 | "references": [], 4 | "includePlatforms": [], 5 | "excludePlatforms": [], 6 | "allowUnsafeCode": false, 7 | "overrideReferences": false, 8 | "precompiledReferences": [], 9 | "autoReferenced": true, 10 | "defineConstraints": [], 11 | "versionDefines": [], 12 | "noEngineReferences": false 13 | } -------------------------------------------------------------------------------- /Leopotam.Ecs.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 656c2c43d3cfe4855bd40e058874b0be 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LeoECS - Легковесный C# Entity Component System фреймворк 2 | Производительность, нулевые или минимальные аллокации, минимизация использования памяти, отсутствие зависимостей от любого игрового движка - это основные цели данного фреймворка. 3 | 4 | > **ВАЖНО!** РАЗРАБОТКА ДАННОГО ПРОЕКТА ПРЕКРАЩЕНА, ВМЕСТО НЕГО СЛЕДУЕТ ИСПОЛЬЗОВАТЬ [EcsProto](https://leopotam.com/28/) или [EcsLite](https://github.com/Leopotam/ecslite). 5 | 6 | > **ВАЖНО!** Не забывайте использовать `DEBUG`-версии билдов для разработки и `RELEASE`-версии билдов для релизов: все внутренние проверки/исключения будут работать только в `DEBUG`-версиях и удалены для увеличения производительности в `RELEASE`-версиях. 7 | 8 | > **ВАЖНО!** LeoEcs-фрейморк **не потокобезопасен** и никогда не будет таким! Если вам нужна многопоточность - вы должны реализовать ее самостоятельно и интегрировать синхронизацию в виде ecs-системы. 9 | 10 | 11 | # Содержание 12 | * [Социальные ресурсы](#Социальные-ресурсы) 13 | * [Установка](#Установка) 14 | * [В виде unity модуля](#В-виде-unity-модуля) 15 | * [В виде исходников](#В-виде-исходников) 16 | * [Основные типы](#Основные-типы) 17 | * [Компонент](#Компонент) 18 | * [Сущность](#Сущность) 19 | * [Система](#Система) 20 | * [Инъекция данных](#Инъекция-данных) 21 | * [Специальные типы](#Специальные-типы) 22 | * [EcsFilter](#EcsFilterT) 23 | * [EcsWorld](#EcsWorld) 24 | * [EcsSystems](#EcsSystems) 25 | * [Интеграция с движками](#Интеграция-с-движками) 26 | * [Unity](#Unity) 27 | * [Кастомный движок](#Кастомный-движок) 28 | * [Статьи](#Статьи) 29 | * [Проекты, использующие LeoECS](#Проекты-использующие-LeoECS) 30 | * [С исходниками](#С-исходниками) 31 | * [Расширения](#Расширения) 32 | * [Лицензия](#Лицензия) 33 | * [ЧаВо](#ЧаВо) 34 | 35 | # Социальные ресурсы 36 | [![discord](https://img.shields.io/discord/404358247621853185.svg?label=enter%20to%20discord%20server&style=for-the-badge&logo=discord)](https://discord.gg/5GZVde6) 37 | 38 | # Установка 39 | 40 | ## В виде unity модуля 41 | Поддерживается установка в виде unity-модуля через git-ссылку в PackageManager или прямое редактирование `Packages/manifest.json`: 42 | ``` 43 | "com.leopotam.ecs": "https://github.com/Leopotam/ecs.git", 44 | ``` 45 | По умолчанию используется последняя релизная версия. Если требуется версия "в разработке" с актуальными изменениями - следует переключиться на ветку `develop`: 46 | ``` 47 | "com.leopotam.ecs": "https://github.com/Leopotam/ecs.git#develop", 48 | ``` 49 | 50 | ## В виде исходников 51 | Код так же может быть склонирован или получен в виде архива со страницы релизов. 52 | 53 | # Основные типы 54 | 55 | ## Компонент 56 | Является контейнером для данных пользователя и не должен содержать логику (допускаются минимальные хелперы, но не куски основной логики): 57 | ```c# 58 | struct WeaponComponent { 59 | public int Ammo; 60 | public string GunName; 61 | } 62 | ``` 63 | 64 | ## Сущность 65 | Сама по себе ничего не значит и не существует, является исключительно контейнером для компонентов. Реализована как `EcsEntity`: 66 | ```c# 67 | // NewEntity() используется для создания новых сущностей в контексте мира. 68 | EcsEntity entity = _world.NewEntity (); 69 | 70 | // Get() возвращает существующий на сущности компонент. Если компонент не существовал - он будет добавлен автоматически. 71 | // Следует обратить внимание на "ref" - компоненты должны обрабатываться по ссылке. 72 | ref Component1 c1 = ref entity.Get (); 73 | ref Component2 c2 = ref entity.Get (); 74 | 75 | // Del() удаляет компонент с сущности. Если это был последний компонент - сущность будет удалена автоматически. Если компонент не существовал - ошибки не будет. 76 | entity.Del (); 77 | 78 | // Replace() выполняет замену компонента новым экземпляром. Если старый компонент не существовал - новый будет добавлен без ошибки. 79 | WeaponComponent weapon = new WeaponComponent () { Ammo = 10, GunName = "Handgun" }; 80 | entity.Replace (weapon); 81 | 82 | // Replace() позволяет выполнять "чейнинг" создания компонентов: 83 | EcsEntity entity2 = world.NewEntity (); 84 | entity2.Replace (new Component1 { Id = 10 }).Replace (new Component2 { Name = "Username" }); 85 | 86 | // Любая сущность может быть скопирована вместе с компонентами: 87 | EcsEntity entity2Copy = entity2.Copy (); 88 | 89 | // Любая сущность может "передать" свои компоненты другой сущности (сама будет уничтожена): 90 | var newEntity = world.NewEntity (); 91 | entity2Copy.MoveTo (newEntity); // все компоненты с "entity2Copy" переместятся на "newEntity", а "entity2Copy" будет удалена. 92 | 93 | // Любая сущность может быть удалена, при этом сначала все компоненты будут автоматически удалены и только потом энтити будет считаться уничтоженной. 94 | entity.Destroy (); 95 | ``` 96 | 97 | > **ВАЖНО!** Сущности не могут существовать без компонентов и будут автоматически уничтожаться при удалении последнего компонента на них. 98 | 99 | ## Система 100 | Является контейнером для основной логики для обработки отфильтрованных сущностей. Существует в виде пользовательского класса, реализующего как минимум один из `IEcsInitSystem`, `IEcsDestroySystem`, `IEcsRunSystem` (и прочих поддерживаемых) интерфейсов: 101 | ```c# 102 | class UserSystem : IEcsPreInitSystem, IEcsInitSystem, IEcsRunSystem, IEcsDestroySystem, IEcsPostDestroySystem { 103 | public void PreInit () { 104 | // Будет вызван один раз в момент работы EcsSystems.Init() и до срабатывания IEcsInitSystem.Init(). 105 | } 106 | 107 | public void Init () { 108 | // Будет вызван один раз в момент работы EcsSystems.Init() и после срабатывания IEcsPreInitSystem.PreInit(). 109 | } 110 | 111 | public void Run () { 112 | // Будет вызван один раз в момент работы EcsSystems.Run(). 113 | } 114 | 115 | public void Destroy () { 116 | // Будет вызван один раз в момент работы EcsSystems.Destroy() и до срабатывания IEcsPostDestroySystem.PostDestroy(). 117 | } 118 | 119 | public void PostDestroy () { 120 | // Будет вызван один раз в момент работы EcsSystems.Destroy() и после срабатывания IEcsDestroySystem.Destroy(). 121 | } 122 | } 123 | ``` 124 | 125 | # Инъекция данных 126 | Все поля **ecs-систем**, совместимые c `EcsWorld` и `EcsFilter` будут автоматически инициализированы валидными экземплярами соответствующих типов: 127 | ```c# 128 | class HealthSystem : IEcsSystem { 129 | // Поля с авто-инъекцией. 130 | EcsWorld _world; 131 | EcsFilter _weaponFilter; 132 | } 133 | ``` 134 | 135 | Экземпляр любого кастомного типа (класса) может быть инъецирован с помощью метода `EcsSystems.Inject()`: 136 | ```c# 137 | class SharedData { 138 | public string PrefabsPath; 139 | } 140 | ... 141 | SharedData sharedData = new SharedData { PrefabsPath = "Items/{0}" }; 142 | EcsSystems systems = new EcsSystems (world); 143 | systems 144 | .Add (new TestSystem1 ()) 145 | .Inject (sharedData) 146 | .Init (); 147 | ``` 148 | 149 | Каждая система будет просканирована на наличие полей, совместимых по типу с последующей инъекцией: 150 | ```c# 151 | class TestSystem1 : IEcsInitSystem { 152 | // Поле с авто-инъекцией. 153 | SharedData _sharedData; 154 | 155 | public void Init() { 156 | var prefabPath = string.Format (_sharedData.Prefabspath, 123); 157 | // prefabPath = "Items/123" к этому моменту. 158 | } 159 | } 160 | ``` 161 | > **ВАЖНО!** Для инъекции подходят только нестатичные public/private-поля конечного класса системы, либо public/protected-поля базовых классов. Все остальные поля будут проигнорированы! 162 | 163 | # Специальные типы 164 | 165 | ## EcsFilter 166 | Является контейнером для хранения отфильтрованных сущностей по наличию или отсутствию определенных компонентов: 167 | ```c# 168 | class WeaponSystem : IEcsInitSystem, IEcsRunSystem { 169 | // Поля с авто-инъекцией. 170 | EcsWorld _world; 171 | // Мы хотим получить все сущности с компонентом "WeaponComponent" 172 | // и без компонента "HealthComponent". 173 | EcsFilter.Exclude _filter; 174 | 175 | public void Init () { 176 | _world.NewEntity ().Get (); 177 | } 178 | 179 | public void Run () { 180 | foreach (int i in _filter) { 181 | // Сущность, которая точно содержит компонент "WeaponComponent". 182 | ref EcsEntity entity = ref _filter.GetEntity (i); 183 | 184 | // Get1() позволяет получить доступ по ссылке на компонент, 185 | // указанный первым в списке ограничений фильтра ("WeaponComponent"). 186 | ref WeaponComponent weapon = ref _filter.Get1 (i); 187 | weapon.Ammo = System.Math.Max (0, weapon.Ammo - 1); 188 | } 189 | } 190 | } 191 | ``` 192 | 193 | Любые компоненты из `Include`-списка ограничений фильтра могут быть получены через вызовы `EcsFilter.Get1()`, `EcsFilter.Get2()` и т.д - нумерация идет в том же порядке, что и в списке ограничений. 194 | 195 | Если в компоненте нет данных и он используется исключительно как флаг-признак для фильтрации, то компонент может реализовать интерфейс `IEcsIgnoreInFilter` - это поможет уменьшить потребление памяти фильтром и немного увеличить производительность: 196 | ```c# 197 | struct Component1 { } 198 | 199 | struct Component2 : IEcsIgnoreInFilter { } 200 | 201 | class TestSystem : IEcsRunSystem { 202 | EcsFilter _filter; 203 | 204 | public void Run () { 205 | foreach (var i in _filter) { 206 | // Мы можем получить компонент "Component1". 207 | ref var component1 = ref _filter.Get1 (i); 208 | 209 | // Мы не можем получить "Component2" - кеш внутри фильтра не существует и будет выкинуто исключение. 210 | ref var component2 = ref _filter.Get2 (i); 211 | } 212 | } 213 | } 214 | ``` 215 | 216 | > **ВАЖНО!**: Фильтры поддерживают до 6 `Include`-ограничений и 2 `Exclude`-ограничений. Чем меньше ограничений в фильтре - тем он быстрее работает. 217 | 218 | > **ВАЖНО!** Нельзя использовать несколько фильтров с одинаковым списком ограничений, но выставленных в разном порядке - в `DEBUG`-версии будет выкинуто исключение с описанием конфликтующих фильтров. 219 | 220 | > **ВАЖНО!** Один и тот же компонент не может быть в списках "Include" и "Exclude" одного фильтра одновременно. 221 | 222 | ## EcsWorld 223 | Является контейнером для всех сущностей и фильтров, данные каждого экземпляра уникальны и изолированы от других миров. 224 | 225 | > **ВАЖНО!** Необходимо вызывать `EcsWorld.Destroy()` у экземпляра мира если он больше не нужен. 226 | 227 | ## EcsSystems 228 | Является контейнером для систем, которыми будет обрабатываться `EcsWorld`-экземпляр мира: 229 | ```c# 230 | class Startup : MonoBehaviour { 231 | EcsWorld _world; 232 | EcsSystems _systems; 233 | 234 | void Start () { 235 | // Создаем окружение, подключаем системы. 236 | _world = new EcsWorld (); 237 | _systems = new EcsSystems (_world) 238 | .Add (new WeaponSystem ()); 239 | _systems.Init (); 240 | } 241 | 242 | void Update () { 243 | // Выполняем все подключенные системы. 244 | _systems.Run (); 245 | } 246 | 247 | void OnDestroy () { 248 | // Уничтожаем подключенные системы. 249 | _systems.Destroy (); 250 | // Очищаем окружение. 251 | _world.Destroy (); 252 | } 253 | } 254 | ``` 255 | 256 | Экземпляр `EcsSystems` может быть использован как обычная ecs-система (вложена в другую `EcsSystems`): 257 | ```c# 258 | // Инициализация. 259 | EcsSystems nestedSystems = new EcsSystems (_world).Add (new NestedSystem ()); 260 | 261 | // Нельзя вызывать nestedSystems.Init() здесь, 262 | // "rootSystems" выполнит этот вызов автоматически. 263 | EcsSystems rootSystems = new EcsSystems (_world).Add (nestedSystems); 264 | rootSystems.Init (); 265 | 266 | // В цикле обновления нельзя вызывать nestedSystems.Run(), 267 | // "rootSystems" выполнит этот вызов автоматически. 268 | rootSystems.Run (); 269 | 270 | // Очистка. 271 | // Нельзя вызывать nestedSystems.Destroy() здесь, 272 | // "rootSystems" выполнит этот вызов автоматически. 273 | rootSystems.Destroy (); 274 | ``` 275 | 276 | Любая `IEcsRunSystem` система (включая вложенные `EcsSystems`) может быть включен или выключен из списка обработки: 277 | ```c# 278 | class TestSystem : IEcsRunSystem { 279 | public void Run () { } 280 | } 281 | EcsSystems systems = new EcsSystems (_world); 282 | systems.Add (new TestSystem (), "my special system"); 283 | systems.Init (); 284 | var idx = systems.GetNamedRunSystem ("my special system"); 285 | 286 | // "state" будет иметь значение "true", все системы включены по умолчанию. 287 | var state = systems.GetRunSystemState (idx); 288 | 289 | // Выключаем систему по ее индексу. 290 | systems.SetRunSystemState (idx, false); 291 | ``` 292 | 293 | # Интеграция с движками 294 | 295 | ## Unity 296 | > Проверено на Unity 2020.3 (не зависит от нее) и содержит asmdef-описания для компиляции в виде отдельных сборок и уменьшения времени рекомпиляции основного проекта. 297 | 298 | [Интеграция в Unity editor](https://github.com/Leopotam/ecs-unityintegration) содержит шаблоны кода, а так же предоставляет мониторинг состояния мира. 299 | 300 | 301 | ## Кастомный движок 302 | > Для использования фреймворка требуется C#7.3 или выше. 303 | 304 | Каждая часть примера ниже должна быть корректно интегрирована в правильное место выполнения кода движком: 305 | ```c# 306 | using Leopotam.Ecs; 307 | 308 | class EcsStartup { 309 | EcsWorld _world; 310 | EcsSystems _systems; 311 | 312 | // Инициализация окружения. 313 | void Init () { 314 | _world = new EcsWorld (); 315 | _systems = new EcsSystems (_world); 316 | _systems 317 | // Системы с основной логикой должны 318 | // быть зарегистрированы здесь, порядок важен: 319 | // .Add (new TestSystem1 ()) 320 | // .Add (new TestSystem2 ()) 321 | 322 | // OneFrame-компоненты должны быть зарегистрированы 323 | // в общем списке систем, порядок важен: 324 | // .OneFrame () 325 | // .OneFrame () 326 | 327 | // Инъекция должна быть произведена здесь, 328 | // порядок не важен: 329 | // .Inject (new CameraService ()) 330 | // .Inject (new NavMeshSupport ()) 331 | .Init (); 332 | } 333 | 334 | // Метод должен быть вызван из 335 | // основного update-цикла движка. 336 | void UpdateLoop () { 337 | _systems?.Run (); 338 | } 339 | 340 | // Очистка. 341 | void Destroy () { 342 | if (_systems != null) { 343 | _systems.Destroy (); 344 | _systems = null; 345 | _world.Destroy (); 346 | _world = null; 347 | } 348 | } 349 | } 350 | ``` 351 | 352 | # Статьи 353 | 354 | * ["Создание шутера с LeoECS. Часть 1"](https://habr.com/ru/post/573028/) 355 | 356 | [![](https://habrastorage.org/getpro/habr/upload_files/f77/53f/bd5/f7753fbd5e6d9ad0fd1a6e734750277a.png)](https://habr.com/ru/post/573028/) 357 | 358 | * ["Создание шутера с LeoECS. Часть 2"](https://habr.com/ru/post/578054/) 359 | 360 | [![](https://habrastorage.org/getpro/habr/upload_files/b76/595/263/b76595263c00346640f27d1e52c66323.png)](https://habr.com/ru/post/578054/) 361 | 362 | * ["Создание шутера с LeoECS. Часть 3"](https://habr.com/ru/post/585058/) 363 | 364 | [![](https://habrastorage.org/getpro/habr/upload_files/d06/a68/587/d06a68587d3893edbcde2bb346f08abc.png)](https://habr.com/ru/post/585058/) 365 | 366 | * ["Создание шутера с LeoECS. Часть 4"](https://habr.com/ru/post/647233/) 367 | 368 | [![](https://habrastorage.org/getpro/habr/upload_files/25a/747/725/25a747725de3b33c2964ce728490bb71.png)](https://habr.com/ru/post/647233/) 369 | 370 | * ["Всё что нужно знать про ECS"](https://habr.com/ru/post/665276/) 371 | 372 | [![](https://habrastorage.org/r/w1560/getpro/habr/upload_files/3fd/5bc/544/3fd5bc5442b03a20d52a8003576056d4.png)](https://habr.com/ru/post/665276/) 373 | 374 | 375 | # Проекты, использующие LeoECS 376 | ## С исходниками 377 | * ["MatchTwo"](https://github.com/cadfoot/unity-ecs-match-two) 378 | 379 | [![](https://img.youtube.com/vi/Y3DwZmPCPSk/0.jpg)](https://www.youtube.com/watch?v=Y3DwZmPCPSk) 380 | 381 | 382 | * ["Bubble shooter"](https://github.com/cadfoot/unity-ecs-bubble-shooter) 383 | 384 | [![](https://img.youtube.com/vi/l19wREGUf1k/0.jpg)](https://www.youtube.com/watch?v=l19wREGUf1k) 385 | 386 | 387 | * ["Frantic Architect Remake"](https://github.com/cadfoot/unity-ecs-fran-arch) 388 | 389 | [![](https://img.youtube.com/vi/YAfHDyBl7Fg/0.jpg)](https://www.youtube.com/watch?v=YAfHDyBl7Fg) 390 | 391 | 392 | * ["Mahjong Solitaire"](https://github.com/cadfoot/unity-ecs-mahjong-solitaire) 393 | 394 | [![](https://img.youtube.com/vi/FxOcqVwue9g/0.jpg)](https://www.youtube.com/watch?v=FxOcqVwue9g) 395 | 396 | 397 | * ["Tetris"](https://github.com/fomgleb/tetris) 398 | 399 | [![](https://user-images.githubusercontent.com/60964034/198828588-288efc77-30da-4b54-8879-920327ffb24d.png)](https://github.com/fomgleb/tetris) 400 | 401 | 402 | * ["3D Platformer"](https://github.com/supremestranger/3D-Platformer) 403 | 404 | [![](https://camo.githubusercontent.com/dcd2f525130d73f4688c1f1cfb12f6e37d166dae23a1c6fac70e5b7873c3ab21/68747470733a2f2f692e6962622e636f2f686d374c726d342f506c6174666f726d65722e706e67)](https://github.com/supremestranger/3D-Platformer) 405 | 406 | 407 | * ["SpaceInvaders (Guns&Bullets variation)"](https://github.com/GoodCatGames/SpaceInvadersEcs) 408 | 409 | [![](https://github.com/GoodCatGames/SpaceInvadersEcs/raw/master/docs/SpaceInvadersImage.png)](https://github.com/GoodCatGames/SpaceInvadersEcs) 410 | 411 | 412 | * ["Pacman"](https://github.com/SH42913/pacmanecs) 413 | 414 | [![](https://github.com/SH42913/pacmanecs/raw/master/Screenshots/PacManEcs_fZyXscSovk.png)](https://github.com/SH42913/pacmanecs) 415 | 416 | 417 | * ["Runner"](https://github.com/t1az2z/RunnerECS) 418 | 419 | 420 | # Расширения 421 | * [Интеграция в редактор Unity](https://github.com/Leopotam/ecs-unityintegration) 422 | * [Поддержка Unity uGui](https://github.com/Leopotam/ecs-ui) 423 | * [Поддержка многопоточности](https://github.com/Leopotam/ecs-threads) 424 | * [Unity Physx events support](https://github.com/supremestranger/leoecs-physics) 425 | 426 | # Лицензия 427 | Фреймворк выпускается под двумя лицензиями, [подробности тут](./LICENSE.md). 428 | 429 | В случаях лицензирования по условиям MIT-Red не стоит расчитывать на 430 | персональные консультации или какие-либо гарантии. 431 | 432 | # ЧаВо 433 | 434 | ### Я хочу знать - существовал ли компонент на сущности до вызова Get() для разной инициализации полученных данных. Как я могу это сделать? 435 | 436 | Если не важно - существовал компонент ранее и просто нужна уверенность, что он теперь существует достаточно вызова `EcsEntity.Get()`. 437 | 438 | Если нужно понимание, что компонент существовал ранее - это можно проверить с помощью вызова `EcsEntity.Has()`. 439 | 440 | ### Я хочу одну систему вызвать в `MonoBehaviour.Update()`, а другую - в `MonoBehaviour.FixedUpdate()`. Как я могу это сделать? 441 | 442 | Для разделения систем на основе разных методов из `MonoBehaviour` необходимо создать под каждый метод отдельную `EcsSystems`-группу: 443 | ```c# 444 | EcsSystems _update; 445 | EcsSystems _fixedUpdate; 446 | 447 | void Start () { 448 | var world = new EcsWorld (); 449 | _update = new EcsSystems (world).Add (new UpdateSystem ()); 450 | _update.Init (); 451 | _fixedUpdate = new EcsSystems (world).Add (new FixedUpdateSystem ()); 452 | _fixedUpdate.Init (); 453 | } 454 | 455 | void Update () { 456 | _update.Run (); 457 | } 458 | 459 | void FixedUpdate () { 460 | _fixedUpdate.Run (); 461 | } 462 | ``` 463 | 464 | ### Мне нравится как работает автоматическая инъекция данных, но хотелось бы часть полей исключить. Как я могу сделать это? 465 | 466 | Для этого достаточно пометить поле системы атрибутом `[EcsIgnoreInject]`: 467 | ```c# 468 | // Это поле будет обработано инъекцией. 469 | EcsFilter _filter1; 470 | 471 | // Это поле будет проигнорировано инъекцией. 472 | [EcsIgnoreInject] EcsFilter _filter2; 473 | ``` 474 | 475 | ### Меня не устраивают значения по умолчанию для полей компонентов. Как я могу это настроить? 476 | 477 | Компоненты поддерживают кастомную настройку значений через реализацию интерфейса `IEcsAutoReset<>`: 478 | ```c# 479 | struct MyComponent : IEcsAutoReset { 480 | public int Id; 481 | public object LinkToAnotherComponent; 482 | 483 | public void AutoReset (ref MyComponent c) { 484 | c.Id = 2; 485 | c.LinkToAnotherComponent = null; 486 | } 487 | } 488 | ``` 489 | Этот метод будет автоматически вызываться для всех новых компонентов, а так же для всех только что удаленных, до помещения их в пул. 490 | > **ВАЖНО!** В случае применения `IEcsAutoReset` все дополнительные очистки/проверки полей компонента отключаются, что может привести к утечкам памяти. Ответственность лежит на пользователе! 491 | 492 | > **ВАЖНО!**: Компоненты, реализующие `IEcsAutoReset` не совместимы с вызовами `entity.Replace()`. Рекомендуется не использовать `entity.Replace()` или любые другие способы полной перезаписи компонентов. 493 | 494 | ### Я использую компоненты как "события", которые живут 1 цикл, а потом удаляются в конце отдельной системой. Получается много лишнего кода, есть ли более простой способ? 495 | 496 | Для автоматической очистки компонентов, которые должны жить один цикл, место их очистки может быть зарегистрировано в общем списке систем внутри `EcsSystems`: 497 | ```c# 498 | struct MyOneFrameComponent { } 499 | 500 | EcsSystems _update; 501 | 502 | void Start () { 503 | var world = new EcsWorld (); 504 | _update = new EcsSystems (world); 505 | _update 506 | .Add (new CalculateSystem ()) 507 | // Все "MyOneFrameComponent" компоненты будут 508 | // удалены здесь. 509 | .OneFrame () 510 | // Здесь можно быть уверенным, что ни один 511 | // "MyOneFrameComponent" не существует. 512 | .Add (new UpdateSystem ()) 513 | .Init (); 514 | } 515 | 516 | void Update () { 517 | _update.Run (); 518 | } 519 | ``` 520 | 521 | ### Мне нужен больший контроль над размерами кешей, которые мир использует в момент создания. Как я могу сделать это? 522 | 523 | Мир может быть создан с явным указанием `EcsWorldConfig`-конфигурации: 524 | ```c# 525 | var config = new EcsWorldConfig () { 526 | // Размер по умолчанию для World.Entities. 527 | WorldEntitiesCacheSize = 1024, 528 | // Размер по умолчанию для World.Filters. 529 | WorldFiltersCacheSize = 128, 530 | // Размер по умолчанию для World.ComponentPools. 531 | WorldComponentPoolsCacheSize = 512, 532 | // Размер по умолчанию для Entity.Components (до удвоения). 533 | EntityComponentsCacheSize = 8, 534 | // Размер по умолчанию для Filter.Entities. 535 | FilterEntitiesCacheSize = 256, 536 | }; 537 | var world = new EcsWorld(config); 538 | ``` 539 | 540 | ### Мне нужно больше чем 6-"Include" и 2-"Exclude" ограничений для компонентов в фильтре. Как я могу сделать это? 541 | 542 | > **ВАЖНО!** Это приведет к модификации исходного кода фреймворка и несовместимости с обновлениями. 543 | > 544 | Необходимо воспользоваться [кодогенерацией EcsFilter](https://leopotam.github.io/ecs/filter-gen.html) классов и заменить содержимое файла `EcsFilter.cs`. 545 | 546 | ### Я хочу добавить реактивщины и обрабатывать события изменения фильтров. Как я могу это сделать? 547 | 548 | > **ВАЖНО!** Так делать не рекомендуется из-за падения производительности. 549 | 550 | Для активации этого функционала следует добавить `LEOECS_FILTER_EVENTS` в список директив комплятора, а затем - добавить слушатель событий: 551 | ```c# 552 | class CustomListener: IEcsFilterListener { 553 | public void OnEntityAdded (in EcsEntity entity) { 554 | // Сущность добавлена в фильтр. 555 | } 556 | 557 | public void OnEntityRemoved (in EcsEntity entity) { 558 | // Сущность удалена из фильтра. 559 | } 560 | } 561 | 562 | class MySystem : IEcsInitSystem, IEcsDestroySystem { 563 | readonly EcsFilter _filter = null; 564 | readonly CustomListener _listener = new CustomListener (); 565 | public void Init () { 566 | // Подписка слушателя на события фильтра. 567 | _filter.AddListener (_listener); 568 | } 569 | public void Destroy () { 570 | // Отписка слушателя от событий фильтра. 571 | _filter.RemoveListener (_listener); 572 | } 573 | } 574 | ``` -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 25c3bcf351a444fba847d6a57f031293 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.leopotam.ecs", 3 | "author": "Leopotam", 4 | "displayName": "LeoECS", 5 | "description": "LeoECS - легковесный ECS-фреймворк, основанный на структурах. Производительность, нулевые или минимальные аллокации, минимизация использования памяти, отсутствие зависимостей от любого игрового движка - это основные цели данного фреймворка.", 6 | "unity": "2020.3", 7 | "version": "2023.6.22", 8 | "keywords": [ 9 | "leoecs", 10 | "ecs", 11 | "performance" 12 | ], 13 | "dependencies": {}, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/Leopotam/ecs.git" 17 | } 18 | } -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 358c4ba84637a40bc908d2aafea29a6d 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /src.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0c9b69b81724e412dbaba789f7f87e8f 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /src/EcsComponent.cs: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // The Proprietary or MIT-Red License 3 | // Copyright (c) 2012-2023 Leopotam 4 | // ---------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Runtime.CompilerServices; 8 | using System.Threading; 9 | 10 | // ReSharper disable ClassNeverInstantiated.Global 11 | 12 | namespace Leopotam.Ecs { 13 | /// 14 | /// Marks component type to be not auto-filled as GetX in filter. 15 | /// 16 | public interface IEcsIgnoreInFilter { } 17 | 18 | /// 19 | /// Marks component type for custom reset behaviour. 20 | /// 21 | /// Type of component, should be the same as main component! 22 | public interface IEcsAutoReset where T : struct { 23 | void AutoReset (ref T c); 24 | } 25 | 26 | /// 27 | /// Marks field of IEcsSystem class to be ignored during dependency injection. 28 | /// 29 | public sealed class EcsIgnoreInjectAttribute : Attribute { } 30 | 31 | /// 32 | /// Global descriptor of used component type. 33 | /// 34 | /// Component type. 35 | public static class EcsComponentType where T : struct { 36 | // ReSharper disable StaticMemberInGenericType 37 | public static readonly int TypeIndex; 38 | public static readonly Type Type; 39 | public static readonly bool IsIgnoreInFilter; 40 | public static readonly bool IsAutoReset; 41 | // ReSharper restore StaticMemberInGenericType 42 | 43 | static EcsComponentType () { 44 | TypeIndex = Interlocked.Increment (ref EcsComponentPool.ComponentTypesCount); 45 | Type = typeof (T); 46 | IsIgnoreInFilter = typeof (IEcsIgnoreInFilter).IsAssignableFrom (Type); 47 | IsAutoReset = typeof (IEcsAutoReset).IsAssignableFrom (Type); 48 | #if DEBUG 49 | if (!IsAutoReset && Type.GetInterface ("IEcsAutoReset`1") != null) { 50 | throw new Exception ($"IEcsAutoReset should have <{typeof (T).Name}> constraint for component \"{typeof (T).Name}\"."); 51 | } 52 | #endif 53 | } 54 | } 55 | 56 | public sealed class EcsComponentPool { 57 | /// 58 | /// Global component type counter. 59 | /// First component will be "1" for correct filters updating (add component on positive and remove on negative). 60 | /// 61 | internal static int ComponentTypesCount; 62 | } 63 | 64 | public interface IEcsComponentPool { 65 | Type ItemType { get; } 66 | object GetItem (int idx); 67 | void Recycle (int idx); 68 | int New (); 69 | void CopyData (int srcIdx, int dstIdx); 70 | } 71 | 72 | /// 73 | /// Helper for save reference to component. 74 | /// 75 | /// Type of component. 76 | public struct EcsComponentRef where T : struct { 77 | internal EcsComponentPool Pool; 78 | internal int Idx; 79 | 80 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 81 | public static bool AreEquals (in EcsComponentRef lhs, in EcsComponentRef rhs) { 82 | return lhs.Idx == rhs.Idx && lhs.Pool == rhs.Pool; 83 | } 84 | } 85 | 86 | #if ENABLE_IL2CPP 87 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] 88 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] 89 | #endif 90 | public static class EcsComponentRefExtensions { 91 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 92 | public static ref T Unref (in this EcsComponentRef wrapper) where T : struct { 93 | return ref wrapper.Pool.Items[wrapper.Idx]; 94 | } 95 | 96 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 97 | public static bool IsNull (in this EcsComponentRef wrapper) where T : struct { 98 | return wrapper.Pool == null; 99 | } 100 | } 101 | 102 | public interface IEcsComponentPoolResizeListener { 103 | void OnComponentPoolResize (); 104 | } 105 | 106 | #if ENABLE_IL2CPP 107 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] 108 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] 109 | #endif 110 | public sealed class EcsComponentPool : IEcsComponentPool where T : struct { 111 | delegate void AutoResetHandler (ref T component); 112 | 113 | public Type ItemType { get; } 114 | public T[] Items = new T[128]; 115 | int[] _reservedItems = new int[128]; 116 | int _itemsCount; 117 | int _reservedItemsCount; 118 | readonly AutoResetHandler _autoReset; 119 | #if ENABLE_IL2CPP && !UNITY_EDITOR 120 | T _autoresetFakeInstance; 121 | #endif 122 | IEcsComponentPoolResizeListener[] _resizeListeners; 123 | int _resizeListenersCount; 124 | 125 | internal EcsComponentPool () { 126 | ItemType = typeof (T); 127 | if (EcsComponentType.IsAutoReset) { 128 | var autoResetMethod = typeof (T).GetMethod (nameof (IEcsAutoReset.AutoReset)); 129 | #if DEBUG 130 | 131 | if (autoResetMethod == null) { 132 | throw new Exception ( 133 | $"IEcsAutoReset<{typeof (T).Name}> explicit implementation not supported, use implicit instead."); 134 | } 135 | #endif 136 | _autoReset = (AutoResetHandler) Delegate.CreateDelegate ( 137 | typeof (AutoResetHandler), 138 | #if ENABLE_IL2CPP && !UNITY_EDITOR 139 | _autoresetFakeInstance, 140 | #else 141 | null, 142 | #endif 143 | autoResetMethod); 144 | } 145 | _resizeListeners = new IEcsComponentPoolResizeListener[128]; 146 | _reservedItemsCount = 0; 147 | } 148 | 149 | void RaiseOnResizeEvent () { 150 | for (int i = 0, iMax = _resizeListenersCount; i < iMax; i++) { 151 | _resizeListeners[i].OnComponentPoolResize (); 152 | } 153 | } 154 | 155 | public void AddResizeListener (IEcsComponentPoolResizeListener listener) { 156 | #if DEBUG 157 | if (listener == null) { throw new Exception ("Listener is null."); } 158 | #endif 159 | if (_resizeListeners.Length == _resizeListenersCount) { 160 | Array.Resize (ref _resizeListeners, _resizeListenersCount << 1); 161 | } 162 | _resizeListeners[_resizeListenersCount++] = listener; 163 | } 164 | 165 | public void RemoveResizeListener (IEcsComponentPoolResizeListener listener) { 166 | #if DEBUG 167 | if (listener == null) { throw new Exception ("Listener is null."); } 168 | #endif 169 | for (int i = 0, iMax = _resizeListenersCount; i < iMax; i++) { 170 | if (_resizeListeners[i] == listener) { 171 | _resizeListenersCount--; 172 | if (i < _resizeListenersCount) { 173 | _resizeListeners[i] = _resizeListeners[_resizeListenersCount]; 174 | } 175 | _resizeListeners[_resizeListenersCount] = null; 176 | break; 177 | } 178 | } 179 | } 180 | 181 | /// 182 | /// Sets new capacity (if more than current amount). 183 | /// 184 | /// New value. 185 | public void SetCapacity (int capacity) { 186 | if (capacity > Items.Length) { 187 | Array.Resize (ref Items, capacity); 188 | RaiseOnResizeEvent (); 189 | } 190 | } 191 | 192 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 193 | public int New () { 194 | int id; 195 | if (_reservedItemsCount > 0) { 196 | id = _reservedItems[--_reservedItemsCount]; 197 | } else { 198 | id = _itemsCount; 199 | if (_itemsCount == Items.Length) { 200 | Array.Resize (ref Items, _itemsCount << 1); 201 | RaiseOnResizeEvent (); 202 | } 203 | // reset brand new instance if custom AutoReset was registered. 204 | _autoReset?.Invoke (ref Items[_itemsCount]); 205 | _itemsCount++; 206 | } 207 | return id; 208 | } 209 | 210 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 211 | public ref T GetItem (int idx) { 212 | return ref Items[idx]; 213 | } 214 | 215 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 216 | public void Recycle (int idx) { 217 | if (_autoReset != null) { 218 | _autoReset (ref Items[idx]); 219 | } else { 220 | Items[idx] = default; 221 | } 222 | if (_reservedItemsCount == _reservedItems.Length) { 223 | Array.Resize (ref _reservedItems, _reservedItemsCount << 1); 224 | } 225 | _reservedItems[_reservedItemsCount++] = idx; 226 | } 227 | 228 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 229 | public void CopyData (int srcIdx, int dstIdx) { 230 | Items[dstIdx] = Items[srcIdx]; 231 | } 232 | 233 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 234 | public EcsComponentRef Ref (int idx) { 235 | EcsComponentRef componentRef; 236 | componentRef.Pool = this; 237 | componentRef.Idx = idx; 238 | return componentRef; 239 | } 240 | 241 | object IEcsComponentPool.GetItem (int idx) { 242 | return Items[idx]; 243 | } 244 | } 245 | } -------------------------------------------------------------------------------- /src/EcsComponent.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fd5d9a7ced9ce452e84675f2d9e4a299 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /src/EcsEntity.cs: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // The Proprietary or MIT-Red License 3 | // Copyright (c) 2012-2023 Leopotam 4 | // ---------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Runtime.CompilerServices; 8 | 9 | namespace Leopotam.Ecs { 10 | /// 11 | /// Entity descriptor. 12 | /// 13 | public struct EcsEntity : IEquatable { 14 | internal int Id; 15 | internal ushort Gen; 16 | internal EcsWorld Owner; 17 | #if DEBUG 18 | // For using in IDE debugger. 19 | internal object[] Components { 20 | get { 21 | object[] list = null; 22 | if (this.IsAlive ()) { 23 | this.GetComponentValues (ref list); 24 | } 25 | return list; 26 | } 27 | } 28 | #endif 29 | 30 | public static readonly EcsEntity Null = new EcsEntity (); 31 | 32 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 33 | public static bool operator == (in EcsEntity lhs, in EcsEntity rhs) { 34 | return lhs.Id == rhs.Id && lhs.Gen == rhs.Gen; 35 | } 36 | 37 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 38 | public static bool operator != (in EcsEntity lhs, in EcsEntity rhs) { 39 | return lhs.Id != rhs.Id || lhs.Gen != rhs.Gen; 40 | } 41 | 42 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 43 | public override int GetHashCode () { 44 | unchecked { 45 | // ReSharper disable NonReadonlyMemberInGetHashCode 46 | var hashCode = (Id * 397) ^ Gen.GetHashCode (); 47 | hashCode = (hashCode * 397) ^ (Owner != null ? Owner.GetHashCode () : 0); 48 | // ReSharper restore NonReadonlyMemberInGetHashCode 49 | return hashCode; 50 | } 51 | } 52 | 53 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 54 | public override bool Equals (object other) { 55 | return other is EcsEntity otherEntity && Equals (otherEntity); 56 | } 57 | 58 | #if DEBUG 59 | public override string ToString () { 60 | if (this.IsNull ()) { return "Entity-Null"; } 61 | if (!this.IsAlive ()) { return "Entity-NonAlive"; } 62 | Type[] types = null; 63 | this.GetComponentTypes (ref types); 64 | var sb = new System.Text.StringBuilder (512); 65 | foreach (var type in types) { 66 | if (sb.Length > 0) { sb.Append (","); } 67 | sb.Append (type.Name); 68 | } 69 | return $"Entity-{Id}:{Gen} [{sb}]"; 70 | } 71 | #endif 72 | public bool Equals (EcsEntity other) { 73 | return Id == other.Id && Gen == other.Gen && Owner == other.Owner; 74 | } 75 | } 76 | 77 | #if ENABLE_IL2CPP 78 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] 79 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] 80 | #endif 81 | public static class EcsEntityExtensions { 82 | /// 83 | /// Replaces or adds new one component to entity. 84 | /// 85 | /// Type of component. 86 | /// Entity. 87 | /// New value of component. 88 | #if ENABLE_IL2CPP 89 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] 90 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] 91 | #endif 92 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 93 | public static EcsEntity Replace (in this EcsEntity entity, in T item) where T : struct { 94 | ref var entityData = ref entity.Owner.GetEntityData (entity); 95 | #if DEBUG 96 | if (entityData.Gen != entity.Gen) { throw new Exception ("Cant add component to destroyed entity."); } 97 | #endif 98 | var typeIdx = EcsComponentType.TypeIndex; 99 | // check already attached components. 100 | for (int i = 0, iiMax = entityData.ComponentsCountX2; i < iiMax; i += 2) { 101 | if (entityData.Components[i] == typeIdx) { 102 | ((EcsComponentPool) entity.Owner.ComponentPools[typeIdx]).Items[entityData.Components[i + 1]] = item; 103 | return entity; 104 | } 105 | } 106 | // attach new component. 107 | if (entityData.Components.Length == entityData.ComponentsCountX2) { 108 | Array.Resize (ref entityData.Components, entityData.ComponentsCountX2 << 1); 109 | } 110 | entityData.Components[entityData.ComponentsCountX2++] = typeIdx; 111 | 112 | var pool = entity.Owner.GetPool (); 113 | 114 | var idx = pool.New (); 115 | entityData.Components[entityData.ComponentsCountX2++] = idx; 116 | pool.Items[idx] = item; 117 | #if DEBUG 118 | for (var ii = 0; ii < entity.Owner.DebugListeners.Count; ii++) { 119 | entity.Owner.DebugListeners[ii].OnComponentListChanged (entity); 120 | } 121 | #endif 122 | entity.Owner.UpdateFilters (typeIdx, entity, entityData); 123 | return entity; 124 | } 125 | 126 | /// 127 | /// Returns exist component on entity or adds new one otherwise. 128 | /// 129 | /// Type of component. 130 | #if ENABLE_IL2CPP 131 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] 132 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] 133 | #endif 134 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 135 | public static ref T Get (in this EcsEntity entity) where T : struct { 136 | ref var entityData = ref entity.Owner.GetEntityData (entity); 137 | #if DEBUG 138 | if (entityData.Gen != entity.Gen) { throw new Exception ("Cant add component to destroyed entity."); } 139 | #endif 140 | var typeIdx = EcsComponentType.TypeIndex; 141 | // check already attached components. 142 | for (int i = 0, iiMax = entityData.ComponentsCountX2; i < iiMax; i += 2) { 143 | if (entityData.Components[i] == typeIdx) { 144 | return ref ((EcsComponentPool) entity.Owner.ComponentPools[typeIdx]).Items[entityData.Components[i + 1]]; 145 | } 146 | } 147 | // attach new component. 148 | if (entityData.Components.Length == entityData.ComponentsCountX2) { 149 | Array.Resize (ref entityData.Components, entityData.ComponentsCountX2 << 1); 150 | } 151 | entityData.Components[entityData.ComponentsCountX2++] = typeIdx; 152 | 153 | var pool = entity.Owner.GetPool (); 154 | 155 | var idx = pool.New (); 156 | entityData.Components[entityData.ComponentsCountX2++] = idx; 157 | #if DEBUG 158 | for (var ii = 0; ii < entity.Owner.DebugListeners.Count; ii++) { 159 | entity.Owner.DebugListeners[ii].OnComponentListChanged (entity); 160 | } 161 | #endif 162 | entity.Owner.UpdateFilters (typeIdx, entity, entityData); 163 | return ref pool.Items[idx]; 164 | } 165 | 166 | /// 167 | /// Checks that component is attached to entity. 168 | /// 169 | /// Type of component. 170 | #if ENABLE_IL2CPP 171 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] 172 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] 173 | #endif 174 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 175 | public static bool Has (in this EcsEntity entity) where T : struct { 176 | ref var entityData = ref entity.Owner.GetEntityData (entity); 177 | #if DEBUG 178 | if (entityData.Gen != entity.Gen) { throw new Exception ("Cant check component on destroyed entity."); } 179 | #endif 180 | var typeIdx = EcsComponentType.TypeIndex; 181 | for (int i = 0, iMax = entityData.ComponentsCountX2; i < iMax; i += 2) { 182 | if (entityData.Components[i] == typeIdx) { 183 | return true; 184 | } 185 | } 186 | return false; 187 | } 188 | 189 | /// 190 | /// Removes component from entity. 191 | /// 192 | /// Type of component. 193 | #if ENABLE_IL2CPP 194 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] 195 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] 196 | #endif 197 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 198 | public static void Del (in this EcsEntity entity) where T : struct { 199 | var typeIndex = EcsComponentType.TypeIndex; 200 | ref var entityData = ref entity.Owner.GetEntityData (entity); 201 | // save copy to local var for protect from cleanup fields outside. 202 | var owner = entity.Owner; 203 | #if DEBUG 204 | if (entityData.Gen != entity.Gen) { throw new Exception ("Cant touch destroyed entity."); } 205 | #endif 206 | for (int i = 0, iMax = entityData.ComponentsCountX2; i < iMax; i += 2) { 207 | if (entityData.Components[i] == typeIndex) { 208 | owner.UpdateFilters (-typeIndex, entity, entityData); 209 | #if DEBUG 210 | // var removedComponent = owner.ComponentPools[typeIndex].GetItem (entityData.Components[i + 1]); 211 | #endif 212 | owner.ComponentPools[typeIndex].Recycle (entityData.Components[i + 1]); 213 | // remove current item and move last component to this gap. 214 | entityData.ComponentsCountX2 -= 2; 215 | if (i < entityData.ComponentsCountX2) { 216 | entityData.Components[i] = entityData.Components[entityData.ComponentsCountX2]; 217 | entityData.Components[i + 1] = entityData.Components[entityData.ComponentsCountX2 + 1]; 218 | } 219 | #if DEBUG 220 | for (var ii = 0; ii < entity.Owner.DebugListeners.Count; ii++) { 221 | entity.Owner.DebugListeners[ii].OnComponentListChanged (entity); 222 | } 223 | #endif 224 | break; 225 | } 226 | } 227 | // unrolled and inlined Destroy() call. 228 | if (entityData.ComponentsCountX2 == 0) { 229 | owner.RecycleEntityData (entity.Id, ref entityData); 230 | #if DEBUG 231 | for (var ii = 0; ii < entity.Owner.DebugListeners.Count; ii++) { 232 | owner.DebugListeners[ii].OnEntityDestroyed (entity); 233 | } 234 | #endif 235 | } 236 | } 237 | 238 | /// 239 | /// Creates copy of entity with all components. 240 | /// 241 | #if ENABLE_IL2CPP 242 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] 243 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] 244 | #endif 245 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 246 | public static EcsEntity Copy (in this EcsEntity entity) { 247 | var owner = entity.Owner; 248 | #if DEBUG 249 | if (owner == null) { throw new Exception ("Cant copy invalid entity."); } 250 | #endif 251 | ref var srcData = ref owner.GetEntityData (entity); 252 | #if DEBUG 253 | if (srcData.Gen != entity.Gen) { throw new Exception ("Cant copy destroyed entity."); } 254 | #endif 255 | var dstEntity = owner.NewEntity (); 256 | ref var dstData = ref owner.GetEntityData (dstEntity); 257 | if (dstData.Components.Length < srcData.ComponentsCountX2) { 258 | dstData.Components = new int[srcData.Components.Length]; 259 | } 260 | dstData.ComponentsCountX2 = 0; 261 | for (int i = 0, iiMax = srcData.ComponentsCountX2; i < iiMax; i += 2) { 262 | var typeIdx = srcData.Components[i]; 263 | var pool = owner.ComponentPools[typeIdx]; 264 | var dstItemIdx = pool.New (); 265 | dstData.Components[i] = typeIdx; 266 | dstData.Components[i + 1] = dstItemIdx; 267 | pool.CopyData (srcData.Components[i + 1], dstItemIdx); 268 | dstData.ComponentsCountX2 += 2; 269 | owner.UpdateFilters (typeIdx, dstEntity, dstData); 270 | } 271 | #if DEBUG 272 | for (var ii = 0; ii < owner.DebugListeners.Count; ii++) { 273 | owner.DebugListeners[ii].OnComponentListChanged (entity); 274 | } 275 | #endif 276 | return dstEntity; 277 | } 278 | 279 | /// 280 | /// Adds copies of source entity components 281 | /// on target entity (overwrite exists) and 282 | /// removes source entity. 283 | /// 284 | #if ENABLE_IL2CPP 285 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] 286 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] 287 | #endif 288 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 289 | public static void MoveTo (in this EcsEntity source, in EcsEntity target) { 290 | #if DEBUG 291 | if (!source.IsAlive ()) { throw new Exception ("Cant move from invalid entity."); } 292 | if (!target.IsAlive ()) { throw new Exception ("Cant move to invalid entity."); } 293 | if (source.Owner != target.Owner) { throw new Exception ("Cant move data between worlds."); } 294 | if (source.AreEquals (target)) { throw new Exception ("Source and target entities are same."); } 295 | var componentsListChanged = false; 296 | #endif 297 | var owner = source.Owner; 298 | ref var srcData = ref owner.GetEntityData (source); 299 | ref var dstData = ref owner.GetEntityData (target); 300 | if (dstData.Components.Length < srcData.ComponentsCountX2) { 301 | dstData.Components = new int[srcData.Components.Length]; 302 | } 303 | for (int i = 0, iiMax = srcData.ComponentsCountX2; i < iiMax; i += 2) { 304 | var typeIdx = srcData.Components[i]; 305 | var pool = owner.ComponentPools[typeIdx]; 306 | var j = dstData.ComponentsCountX2 - 2; 307 | // search exist component on target. 308 | for (; j >= 0; j -= 2) { 309 | if (dstData.Components[j] == typeIdx) { break; } 310 | } 311 | if (j >= 0) { 312 | // found, copy data. 313 | pool.CopyData (srcData.Components[i + 1], dstData.Components[j + 1]); 314 | } else { 315 | // add new one. 316 | if (dstData.Components.Length == dstData.ComponentsCountX2) { 317 | Array.Resize (ref dstData.Components, dstData.ComponentsCountX2 << 1); 318 | } 319 | dstData.Components[dstData.ComponentsCountX2] = typeIdx; 320 | var idx = pool.New (); 321 | dstData.Components[dstData.ComponentsCountX2 + 1] = idx; 322 | dstData.ComponentsCountX2 += 2; 323 | pool.CopyData (srcData.Components[i + 1], idx); 324 | owner.UpdateFilters (typeIdx, target, dstData); 325 | #if DEBUG 326 | componentsListChanged = true; 327 | #endif 328 | } 329 | } 330 | #if DEBUG 331 | if (componentsListChanged) { 332 | for (var ii = 0; ii < owner.DebugListeners.Count; ii++) { 333 | owner.DebugListeners[ii].OnComponentListChanged (target); 334 | } 335 | } 336 | #endif 337 | source.Destroy (); 338 | } 339 | 340 | /// 341 | /// Gets component index at component pool. 342 | /// If component doesn't exists "-1" will be returned. 343 | /// 344 | /// Type of component. 345 | #if ENABLE_IL2CPP 346 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] 347 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] 348 | #endif 349 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 350 | public static int GetComponentIndexInPool (in this EcsEntity entity) where T : struct { 351 | ref var entityData = ref entity.Owner.GetEntityData (entity); 352 | #if DEBUG 353 | if (entityData.Gen != entity.Gen) { throw new Exception ("Cant check component on destroyed entity."); } 354 | #endif 355 | var typeIdx = EcsComponentType.TypeIndex; 356 | for (int i = 0, iMax = entityData.ComponentsCountX2; i < iMax; i += 2) { 357 | if (entityData.Components[i] == typeIdx) { 358 | return entityData.Components[i + 1]; 359 | } 360 | } 361 | return -1; 362 | } 363 | 364 | /// 365 | /// Compares entities. 366 | /// 367 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 368 | public static bool AreEquals (in this EcsEntity lhs, in EcsEntity rhs) { 369 | return lhs.Id == rhs.Id && lhs.Gen == rhs.Gen; 370 | } 371 | 372 | /// 373 | /// Compares internal Ids without Gens check. Use carefully! 374 | /// 375 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 376 | public static bool AreIdEquals (in this EcsEntity lhs, in EcsEntity rhs) { 377 | return lhs.Id == rhs.Id; 378 | } 379 | 380 | /// 381 | /// Gets internal identifier. 382 | /// 383 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 384 | public static int GetInternalId (in this EcsEntity entity) { 385 | return entity.Id; 386 | } 387 | 388 | /// 389 | /// Gets internal generation. 390 | /// 391 | public static int GetInternalGen (in this EcsEntity entity) { 392 | return entity.Gen; 393 | } 394 | 395 | /// 396 | /// Gets internal world. 397 | /// 398 | public static EcsWorld GetInternalWorld (in this EcsEntity entity) { 399 | return entity.Owner; 400 | } 401 | 402 | /// 403 | /// Gets ComponentRef wrapper to keep direct reference to component. 404 | /// 405 | /// Entity. 406 | /// Component type. 407 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 408 | public static EcsComponentRef Ref (in this EcsEntity entity) where T : struct { 409 | ref var entityData = ref entity.Owner.GetEntityData (entity); 410 | #if DEBUG 411 | if (entityData.Gen != entity.Gen) { throw new Exception ("Cant wrap component on destroyed entity."); } 412 | #endif 413 | var typeIdx = EcsComponentType.TypeIndex; 414 | for (int i = 0, iMax = entityData.ComponentsCountX2; i < iMax; i += 2) { 415 | if (entityData.Components[i] == typeIdx) { 416 | return ((EcsComponentPool) entity.Owner.ComponentPools[entityData.Components[i]]).Ref (entityData.Components[i + 1]); 417 | } 418 | } 419 | #if DEBUG 420 | throw new Exception ($"\"{typeof (T).Name}\" component not exists on entity for wrapping."); 421 | #else 422 | return default; 423 | #endif 424 | } 425 | 426 | /// 427 | /// Removes components from entity and destroys it. 428 | /// 429 | #if ENABLE_IL2CPP 430 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] 431 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] 432 | #endif 433 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 434 | public static void Destroy (in this EcsEntity entity) { 435 | ref var entityData = ref entity.Owner.GetEntityData (entity); 436 | // save copy to local var for protect from cleanup fields outside. 437 | EcsEntity savedEntity; 438 | savedEntity.Id = entity.Id; 439 | savedEntity.Gen = entity.Gen; 440 | savedEntity.Owner = entity.Owner; 441 | #if DEBUG 442 | if (entityData.Gen != entity.Gen) { throw new Exception ("Cant touch destroyed entity."); } 443 | #endif 444 | // remove components first. 445 | for (var i = entityData.ComponentsCountX2 - 2; i >= 0; i -= 2) { 446 | savedEntity.Owner.UpdateFilters (-entityData.Components[i], savedEntity, entityData); 447 | savedEntity.Owner.ComponentPools[entityData.Components[i]].Recycle (entityData.Components[i + 1]); 448 | entityData.ComponentsCountX2 -= 2; 449 | #if DEBUG 450 | for (var ii = 0; ii < savedEntity.Owner.DebugListeners.Count; ii++) { 451 | savedEntity.Owner.DebugListeners[ii].OnComponentListChanged (savedEntity); 452 | } 453 | #endif 454 | } 455 | entityData.ComponentsCountX2 = 0; 456 | savedEntity.Owner.RecycleEntityData (savedEntity.Id, ref entityData); 457 | #if DEBUG 458 | for (var ii = 0; ii < savedEntity.Owner.DebugListeners.Count; ii++) { 459 | savedEntity.Owner.DebugListeners[ii].OnEntityDestroyed (savedEntity); 460 | } 461 | #endif 462 | } 463 | 464 | /// 465 | /// Is entity null-ed. 466 | /// 467 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 468 | public static bool IsNull (in this EcsEntity entity) { 469 | return entity.Id == 0 && entity.Gen == 0; 470 | } 471 | 472 | /// 473 | /// Is entity alive. If world was destroyed - false will be returned. 474 | /// 475 | #if ENABLE_IL2CPP 476 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] 477 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] 478 | #endif 479 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 480 | public static bool IsAlive (in this EcsEntity entity) { 481 | if (!IsWorldAlive (entity)) { return false; } 482 | ref var entityData = ref entity.Owner.GetEntityData (entity); 483 | return entityData.Gen == entity.Gen && entityData.ComponentsCountX2 >= 0; 484 | } 485 | 486 | /// 487 | /// Is world alive. 488 | /// 489 | #if ENABLE_IL2CPP 490 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] 491 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] 492 | #endif 493 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 494 | public static bool IsWorldAlive (in this EcsEntity entity) { 495 | return entity.Owner != null && entity.Owner.IsAlive (); 496 | } 497 | 498 | /// 499 | /// Gets components count on entity. 500 | /// 501 | #if ENABLE_IL2CPP 502 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] 503 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] 504 | #endif 505 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 506 | public static int GetComponentsCount (in this EcsEntity entity) { 507 | ref var entityData = ref entity.Owner.GetEntityData (entity); 508 | #if DEBUG 509 | if (entityData.Gen != entity.Gen) { throw new Exception ("Cant touch destroyed entity."); } 510 | #endif 511 | return entityData.ComponentsCountX2 <= 0 ? 0 : (entityData.ComponentsCountX2 >> 1); 512 | } 513 | 514 | /// 515 | /// Gets types of all attached components. 516 | /// 517 | /// Entity. 518 | /// List to put results in it. if null - will be created. If not enough space - will be resized. 519 | /// Amount of components in list. 520 | public static int GetComponentTypes (in this EcsEntity entity, ref Type[] list) { 521 | ref var entityData = ref entity.Owner.GetEntityData (entity); 522 | #if DEBUG 523 | if (entityData.Gen != entity.Gen) { throw new Exception ("Cant touch destroyed entity."); } 524 | #endif 525 | var itemsCount = entityData.ComponentsCountX2 >> 1; 526 | if (list == null || list.Length < itemsCount) { 527 | list = new Type[itemsCount]; 528 | } 529 | for (int i = 0, j = 0, iMax = entityData.ComponentsCountX2; i < iMax; i += 2, j++) { 530 | list[j] = entity.Owner.ComponentPools[entityData.Components[i]].ItemType; 531 | } 532 | return itemsCount; 533 | } 534 | 535 | /// 536 | /// Gets values of all attached components as copies. Important: force boxing / unboxing! 537 | /// 538 | /// Entity. 539 | /// List to put results in it. if null - will be created. If not enough space - will be resized. 540 | /// Amount of components in list. 541 | public static int GetComponentValues (in this EcsEntity entity, ref object[] list) { 542 | ref var entityData = ref entity.Owner.GetEntityData (entity); 543 | #if DEBUG 544 | if (entityData.Gen != entity.Gen) { throw new Exception ("Cant touch destroyed entity."); } 545 | #endif 546 | var itemsCount = entityData.ComponentsCountX2 >> 1; 547 | if (list == null || list.Length < itemsCount) { 548 | list = new object[itemsCount]; 549 | } 550 | for (int i = 0, j = 0, iMax = entityData.ComponentsCountX2; i < iMax; i += 2, j++) { 551 | list[j] = entity.Owner.ComponentPools[entityData.Components[i]].GetItem (entityData.Components[i + 1]); 552 | } 553 | return itemsCount; 554 | } 555 | } 556 | } -------------------------------------------------------------------------------- /src/EcsEntity.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7ee02f754a6bb4d998b1d36b2eb61f58 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /src/EcsFilter.cs: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // The Proprietary or MIT-Red License 3 | // Copyright (c) 2012-2023 Leopotam 4 | // ---------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Runtime.CompilerServices; 9 | 10 | // ReSharper disable InconsistentNaming 11 | // ReSharper disable ClassNeverInstantiated.Global 12 | 13 | namespace Leopotam.Ecs { 14 | #if LEOECS_FILTER_EVENTS 15 | /// 16 | /// Common interface for all filter listeners. 17 | /// 18 | public interface IEcsFilterListener { 19 | void OnEntityAdded (in EcsEntity entity); 20 | void OnEntityRemoved (in EcsEntity entity); 21 | } 22 | #endif 23 | /// 24 | /// Container for filtered entities based on specified constraints. 25 | /// 26 | #if ENABLE_IL2CPP 27 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] 28 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] 29 | #endif 30 | #if UNITY_2019_1_OR_NEWER 31 | [UnityEngine.Scripting.Preserve] 32 | #endif 33 | public abstract class EcsFilter { 34 | protected EcsEntity[] Entities; 35 | protected readonly Dictionary EntitiesMap; 36 | protected int EntitiesCount; 37 | protected int LockCount; 38 | protected readonly int EntitiesCacheSize; 39 | 40 | DelayedOp[] _delayedOps; 41 | int _delayedOpsCount; 42 | #if LEOECS_FILTER_EVENTS 43 | protected IEcsFilterListener[] Listeners = new IEcsFilterListener[4]; 44 | protected int ListenersCount; 45 | #endif 46 | protected internal int[] IncludedTypeIndices; 47 | protected internal int[] ExcludedTypeIndices; 48 | 49 | public Type[] IncludedTypes; 50 | public Type[] ExcludedTypes; 51 | #if UNITY_2019_1_OR_NEWER 52 | [UnityEngine.Scripting.Preserve] 53 | #endif 54 | protected EcsFilter (EcsWorld world) { 55 | EntitiesCacheSize = world.Config.FilterEntitiesCacheSize; 56 | Entities = new EcsEntity[EntitiesCacheSize]; 57 | EntitiesMap = new Dictionary (EntitiesCacheSize); 58 | _delayedOps = new DelayedOp[EntitiesCacheSize]; 59 | } 60 | 61 | /// 62 | /// Remove subscription from component pools. 63 | /// 64 | public abstract void Destroy (); 65 | 66 | #if DEBUG 67 | public Dictionary GetInternalEntitiesMap () { 68 | return EntitiesMap; 69 | } 70 | #endif 71 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 72 | public Enumerator GetEnumerator () { 73 | return new Enumerator (this); 74 | } 75 | 76 | /// 77 | /// Gets entity by index. 78 | /// 79 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 80 | public ref EcsEntity GetEntity (in int idx) { 81 | return ref Entities[idx]; 82 | } 83 | 84 | /// 85 | /// Gets entities count. 86 | /// 87 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 88 | public int GetEntitiesCount () { 89 | return EntitiesCount; 90 | } 91 | 92 | /// 93 | /// Is filter not contains entities. 94 | /// 95 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 96 | public bool IsEmpty () { 97 | return EntitiesCount == 0; 98 | } 99 | #if LEOECS_FILTER_EVENTS 100 | /// 101 | /// Subscribes listener to filter events. 102 | /// 103 | /// Listener. 104 | public void AddListener (IEcsFilterListener listener) { 105 | #if DEBUG 106 | for (int i = 0, iMax = ListenersCount; i < iMax; i++) { 107 | if (Listeners[i] == listener) { 108 | throw new Exception ("Listener already subscribed."); 109 | } 110 | } 111 | #endif 112 | if (Listeners.Length == ListenersCount) { 113 | Array.Resize (ref Listeners, ListenersCount << 1); 114 | } 115 | Listeners[ListenersCount++] = listener; 116 | } 117 | 118 | // ReSharper disable once CommentTypo 119 | /// 120 | /// Unsubscribes listener from filter events. 121 | /// 122 | /// Listener. 123 | public void RemoveListener (IEcsFilterListener listener) { 124 | for (int i = 0, iMax = ListenersCount; i < iMax; i++) { 125 | if (Listeners[i] == listener) { 126 | ListenersCount--; 127 | // cant fill gap with last element due listeners order is important. 128 | Array.Copy (Listeners, i + 1, Listeners, i, ListenersCount - i); 129 | break; 130 | } 131 | } 132 | } 133 | #endif 134 | /// 135 | /// Is filter compatible with components on entity with optional added / removed component. 136 | /// 137 | /// Entity data. 138 | /// Optional added (greater 0) or removed (less 0) component. Will be ignored if zero. 139 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 140 | internal bool IsCompatible (in EcsWorld.EcsEntityData entityData, int addedRemovedTypeIndex) { 141 | var incIdx = IncludedTypeIndices.Length - 1; 142 | for (; incIdx >= 0; incIdx--) { 143 | var typeIdx = IncludedTypeIndices[incIdx]; 144 | var idx = entityData.ComponentsCountX2 - 2; 145 | for (; idx >= 0; idx -= 2) { 146 | var typeIdx2 = entityData.Components[idx]; 147 | if (typeIdx2 == -addedRemovedTypeIndex) { 148 | continue; 149 | } 150 | if (typeIdx2 == addedRemovedTypeIndex || typeIdx2 == typeIdx) { 151 | break; 152 | } 153 | } 154 | // not found. 155 | if (idx == -2) { 156 | break; 157 | } 158 | } 159 | // one of required component not found. 160 | if (incIdx != -1) { 161 | return false; 162 | } 163 | // check for excluded components. 164 | if (ExcludedTypeIndices != null) { 165 | for (var excIdx = 0; excIdx < ExcludedTypeIndices.Length; excIdx++) { 166 | var typeIdx = ExcludedTypeIndices[excIdx]; 167 | for (var idx = entityData.ComponentsCountX2 - 2; idx >= 0; idx -= 2) { 168 | var typeIdx2 = entityData.Components[idx]; 169 | if (typeIdx2 == -addedRemovedTypeIndex) { 170 | continue; 171 | } 172 | if (typeIdx2 == addedRemovedTypeIndex || typeIdx2 == typeIdx) { 173 | return false; 174 | } 175 | } 176 | } 177 | } 178 | return true; 179 | } 180 | 181 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 182 | protected bool AddDelayedOp (bool isAdd, in EcsEntity entity) { 183 | if (LockCount <= 0) { 184 | return false; 185 | } 186 | if (_delayedOps.Length == _delayedOpsCount) { 187 | Array.Resize (ref _delayedOps, _delayedOpsCount << 1); 188 | } 189 | ref var op = ref _delayedOps[_delayedOpsCount++]; 190 | op.IsAdd = isAdd; 191 | op.Entity = entity; 192 | return true; 193 | } 194 | #if LEOECS_FILTER_EVENTS 195 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 196 | protected void ProcessListeners (bool isAdd, in EcsEntity entity) { 197 | if (isAdd) { 198 | for (int i = 0, iMax = ListenersCount; i < iMax; i++) { 199 | Listeners[i].OnEntityAdded (entity); 200 | } 201 | } else { 202 | for (int i = 0, iMax = ListenersCount; i < iMax; i++) { 203 | Listeners[i].OnEntityRemoved (entity); 204 | } 205 | } 206 | } 207 | #endif 208 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 209 | void Lock () { 210 | LockCount++; 211 | } 212 | 213 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 214 | void Unlock () { 215 | #if DEBUG 216 | if (LockCount <= 0) { 217 | throw new Exception ($"Invalid lock-unlock balance for \"{GetType ().Name}\"."); 218 | } 219 | #endif 220 | LockCount--; 221 | if (LockCount == 0 && _delayedOpsCount > 0) { 222 | // process delayed operations. 223 | for (int i = 0, iMax = _delayedOpsCount; i < iMax; i++) { 224 | ref var op = ref _delayedOps[i]; 225 | if (op.IsAdd) { 226 | OnAddEntity (op.Entity); 227 | } else { 228 | OnRemoveEntity (op.Entity); 229 | } 230 | } 231 | _delayedOpsCount = 0; 232 | } 233 | } 234 | 235 | #if DEBUG 236 | /// 237 | /// For debug purposes. Check filters equality by included / excluded components. 238 | /// 239 | /// Filter to compare. 240 | internal bool AreComponentsSame (EcsFilter filter) { 241 | if (IncludedTypeIndices.Length != filter.IncludedTypeIndices.Length) { 242 | return false; 243 | } 244 | for (var i = 0; i < IncludedTypeIndices.Length; i++) { 245 | if (Array.IndexOf (filter.IncludedTypeIndices, IncludedTypeIndices[i]) == -1) { 246 | return false; 247 | } 248 | } 249 | if ((ExcludedTypeIndices == null && filter.ExcludedTypeIndices != null) || 250 | (ExcludedTypeIndices != null && filter.ExcludedTypeIndices == null)) { 251 | return false; 252 | } 253 | if (ExcludedTypeIndices != null) { 254 | if (filter.ExcludedTypeIndices == null || ExcludedTypeIndices.Length != filter.ExcludedTypeIndices.Length) { 255 | return false; 256 | } 257 | for (var i = 0; i < ExcludedTypeIndices.Length; i++) { 258 | if (Array.IndexOf (filter.ExcludedTypeIndices, ExcludedTypeIndices[i]) == -1) { 259 | return false; 260 | } 261 | } 262 | } 263 | return true; 264 | } 265 | #endif 266 | 267 | /// 268 | /// Event for adding compatible entity to filter. 269 | /// Warning: Don't call manually! 270 | /// 271 | /// Entity. 272 | public abstract void OnAddEntity (in EcsEntity entity); 273 | 274 | /// 275 | /// Event for removing non-compatible entity to filter. 276 | /// Warning: Don't call manually! 277 | /// 278 | /// Entity. 279 | public abstract void OnRemoveEntity (in EcsEntity entity); 280 | 281 | public struct Enumerator : IDisposable { 282 | readonly EcsFilter _filter; 283 | readonly int _count; 284 | int _idx; 285 | 286 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 287 | internal Enumerator (EcsFilter filter) { 288 | _filter = filter; 289 | _count = _filter.GetEntitiesCount (); 290 | _idx = -1; 291 | _filter.Lock (); 292 | } 293 | 294 | public int Current { 295 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 296 | get => _idx; 297 | } 298 | 299 | #if ENABLE_IL2CPP 300 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] 301 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] 302 | #endif 303 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 304 | public void Dispose () { 305 | _filter.Unlock (); 306 | } 307 | 308 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 309 | public bool MoveNext () { 310 | return ++_idx < _count; 311 | } 312 | } 313 | 314 | struct DelayedOp { 315 | public bool IsAdd; 316 | public EcsEntity Entity; 317 | } 318 | } 319 | 320 | #if ENABLE_IL2CPP 321 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] 322 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] 323 | #endif 324 | #if UNITY_2019_1_OR_NEWER 325 | [UnityEngine.Scripting.Preserve] 326 | #endif 327 | public class EcsFilter : EcsFilter, IEcsComponentPoolResizeListener 328 | where Inc1 : struct { 329 | int[] _get1; 330 | 331 | readonly bool _allow1; 332 | 333 | readonly EcsComponentPool _pool1; 334 | Inc1[] _pool1Items; 335 | 336 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 337 | public ref Inc1 Get1 (in int idx) { 338 | return ref _pool1Items[_get1[idx]]; 339 | } 340 | 341 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 342 | public EcsComponentRef Get1Ref (in int idx) { 343 | return _pool1.Ref (_get1[idx]); 344 | } 345 | #if UNITY_2019_1_OR_NEWER 346 | [UnityEngine.Scripting.Preserve] 347 | #endif 348 | protected EcsFilter (EcsWorld world) : base (world) { 349 | _allow1 = !EcsComponentType.IsIgnoreInFilter; 350 | _pool1 = world.GetPool (); 351 | _pool1.AddResizeListener (this); 352 | _pool1Items = _pool1.Items; 353 | _get1 = _allow1 ? new int[EntitiesCacheSize] : null; 354 | IncludedTypeIndices = new[] { 355 | EcsComponentType.TypeIndex 356 | }; 357 | IncludedTypes = new[] { 358 | EcsComponentType.Type 359 | }; 360 | } 361 | 362 | /// 363 | /// For internal use. 364 | /// 365 | public override void Destroy () { 366 | _pool1.RemoveResizeListener (this); 367 | } 368 | 369 | /// 370 | /// For internal use. 371 | /// 372 | public void OnComponentPoolResize () { 373 | _pool1Items = _pool1.Items; 374 | } 375 | 376 | /// 377 | /// For internal use. 378 | /// 379 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 380 | public override void OnAddEntity (in EcsEntity entity) { 381 | if (AddDelayedOp (true, entity)) { return; } 382 | if (Entities.Length == EntitiesCount) { 383 | var newSize = EntitiesCount << 1; 384 | Array.Resize (ref Entities, newSize); 385 | if (_allow1) { Array.Resize (ref _get1, newSize); } 386 | } 387 | // inlined and optimized EcsEntity.Get() call. 388 | ref var entityData = ref entity.Owner.GetEntityData (entity); 389 | var allow1 = _allow1; 390 | for (int i = 0, iMax = entityData.ComponentsCountX2, left = 1; left > 0 && i < iMax; i += 2) { 391 | var typeIdx = entityData.Components[i]; 392 | var itemIdx = entityData.Components[i + 1]; 393 | if (allow1 && typeIdx == EcsComponentType.TypeIndex) { 394 | _get1[EntitiesCount] = itemIdx; 395 | allow1 = false; 396 | left--; 397 | } 398 | } 399 | EntitiesMap[entity.GetInternalId ()] = EntitiesCount; 400 | Entities[EntitiesCount++] = entity; 401 | #if LEOECS_FILTER_EVENTS 402 | ProcessListeners (true, entity); 403 | #endif 404 | } 405 | 406 | /// 407 | /// For internal use. 408 | /// 409 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 410 | public override void OnRemoveEntity (in EcsEntity entity) { 411 | if (AddDelayedOp (false, entity)) { return; } 412 | var entityId = entity.GetInternalId (); 413 | var idx = EntitiesMap[entityId]; 414 | EntitiesMap.Remove (entityId); 415 | EntitiesCount--; 416 | if (idx < EntitiesCount) { 417 | Entities[idx] = Entities[EntitiesCount]; 418 | EntitiesMap[Entities[idx].GetInternalId ()] = idx; 419 | if (_allow1) { _get1[idx] = _get1[EntitiesCount]; } 420 | } 421 | #if LEOECS_FILTER_EVENTS 422 | ProcessListeners (false, entity); 423 | #endif 424 | } 425 | 426 | public class Exclude : EcsFilter 427 | where Exc1 : struct { 428 | #if UNITY_2019_1_OR_NEWER 429 | [UnityEngine.Scripting.Preserve] 430 | #endif 431 | protected Exclude (EcsWorld world) : base (world) { 432 | ExcludedTypeIndices = new[] { 433 | EcsComponentType.TypeIndex 434 | }; 435 | ExcludedTypes = new[] { 436 | EcsComponentType.Type 437 | }; 438 | } 439 | } 440 | 441 | public class Exclude : EcsFilter 442 | where Exc1 : struct 443 | where Exc2 : struct { 444 | #if UNITY_2019_1_OR_NEWER 445 | [UnityEngine.Scripting.Preserve] 446 | #endif 447 | protected Exclude (EcsWorld world) : base (world) { 448 | ExcludedTypeIndices = new[] { 449 | EcsComponentType.TypeIndex, 450 | EcsComponentType.TypeIndex 451 | }; 452 | ExcludedTypes = new[] { 453 | EcsComponentType.Type, 454 | EcsComponentType.Type 455 | }; 456 | } 457 | } 458 | } 459 | 460 | #if ENABLE_IL2CPP 461 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] 462 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] 463 | #endif 464 | #if UNITY_2019_1_OR_NEWER 465 | [UnityEngine.Scripting.Preserve] 466 | #endif 467 | public class EcsFilter : EcsFilter, IEcsComponentPoolResizeListener 468 | where Inc1 : struct 469 | where Inc2 : struct { 470 | int[] _get1; 471 | int[] _get2; 472 | 473 | readonly bool _allow1; 474 | readonly bool _allow2; 475 | 476 | readonly EcsComponentPool _pool1; 477 | Inc1[] _pool1Items; 478 | readonly EcsComponentPool _pool2; 479 | Inc2[] _pool2Items; 480 | 481 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 482 | public ref Inc1 Get1 (in int idx) { 483 | return ref _pool1Items[_get1[idx]]; 484 | } 485 | 486 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 487 | public ref Inc2 Get2 (in int idx) { 488 | return ref _pool2Items[_get2[idx]]; 489 | } 490 | 491 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 492 | public EcsComponentRef Get1Ref (in int idx) { 493 | return _pool1.Ref (_get1[idx]); 494 | } 495 | 496 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 497 | public EcsComponentRef Get2Ref (in int idx) { 498 | return _pool2.Ref (_get2[idx]); 499 | } 500 | #if UNITY_2019_1_OR_NEWER 501 | [UnityEngine.Scripting.Preserve] 502 | #endif 503 | protected EcsFilter (EcsWorld world) : base (world) { 504 | _allow1 = !EcsComponentType.IsIgnoreInFilter; 505 | _allow2 = !EcsComponentType.IsIgnoreInFilter; 506 | _pool1 = world.GetPool (); 507 | _pool1.AddResizeListener (this); 508 | _pool1Items = _pool1.Items; 509 | _pool2 = world.GetPool (); 510 | _pool2.AddResizeListener (this); 511 | _pool2Items = _pool2.Items; 512 | _get1 = _allow1 ? new int[EntitiesCacheSize] : null; 513 | _get2 = _allow2 ? new int[EntitiesCacheSize] : null; 514 | IncludedTypeIndices = new[] { 515 | EcsComponentType.TypeIndex, 516 | EcsComponentType.TypeIndex 517 | }; 518 | IncludedTypes = new[] { 519 | EcsComponentType.Type, 520 | EcsComponentType.Type 521 | }; 522 | } 523 | 524 | /// 525 | /// For internal use. 526 | /// 527 | public override void Destroy () { 528 | _pool1.RemoveResizeListener (this); 529 | _pool2.RemoveResizeListener (this); 530 | } 531 | 532 | /// 533 | /// For internal use. 534 | /// 535 | public void OnComponentPoolResize () { 536 | _pool1Items = _pool1.Items; 537 | _pool2Items = _pool2.Items; 538 | } 539 | 540 | /// 541 | /// For internal use. 542 | /// 543 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 544 | public override void OnAddEntity (in EcsEntity entity) { 545 | if (AddDelayedOp (true, entity)) { return; } 546 | if (Entities.Length == EntitiesCount) { 547 | var newSize = EntitiesCount << 1; 548 | Array.Resize (ref Entities, newSize); 549 | if (_allow1) { Array.Resize (ref _get1, newSize); } 550 | if (_allow2) { Array.Resize (ref _get2, newSize); } 551 | } 552 | // inlined and optimized EcsEntity.Get() call. 553 | ref var entityData = ref entity.Owner.GetEntityData (entity); 554 | var allow1 = _allow1; 555 | var allow2 = _allow2; 556 | for (int i = 0, iMax = entityData.ComponentsCountX2, left = 2; left > 0 && i < iMax; i += 2) { 557 | var typeIdx = entityData.Components[i]; 558 | var itemIdx = entityData.Components[i + 1]; 559 | if (allow1 && typeIdx == EcsComponentType.TypeIndex) { 560 | _get1[EntitiesCount] = itemIdx; 561 | allow1 = false; 562 | left--; 563 | continue; 564 | } 565 | if (allow2 && typeIdx == EcsComponentType.TypeIndex) { 566 | _get2[EntitiesCount] = itemIdx; 567 | allow2 = false; 568 | left--; 569 | } 570 | } 571 | EntitiesMap[entity.GetInternalId ()] = EntitiesCount; 572 | Entities[EntitiesCount++] = entity; 573 | #if LEOECS_FILTER_EVENTS 574 | ProcessListeners (true, entity); 575 | #endif 576 | } 577 | 578 | /// 579 | /// For internal use. 580 | /// 581 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 582 | public override void OnRemoveEntity (in EcsEntity entity) { 583 | if (AddDelayedOp (false, entity)) { return; } 584 | var entityId = entity.GetInternalId (); 585 | var idx = EntitiesMap[entityId]; 586 | EntitiesMap.Remove (entityId); 587 | EntitiesCount--; 588 | if (idx < EntitiesCount) { 589 | Entities[idx] = Entities[EntitiesCount]; 590 | EntitiesMap[Entities[idx].GetInternalId ()] = idx; 591 | if (_allow1) { _get1[idx] = _get1[EntitiesCount]; } 592 | if (_allow2) { _get2[idx] = _get2[EntitiesCount]; } 593 | } 594 | #if LEOECS_FILTER_EVENTS 595 | ProcessListeners (false, entity); 596 | #endif 597 | } 598 | 599 | public class Exclude : EcsFilter 600 | where Exc1 : struct { 601 | #if UNITY_2019_1_OR_NEWER 602 | [UnityEngine.Scripting.Preserve] 603 | #endif 604 | protected Exclude (EcsWorld world) : base (world) { 605 | ExcludedTypeIndices = new[] { 606 | EcsComponentType.TypeIndex 607 | }; 608 | ExcludedTypes = new[] { 609 | EcsComponentType.Type 610 | }; 611 | } 612 | } 613 | 614 | public class Exclude : EcsFilter 615 | where Exc1 : struct 616 | where Exc2 : struct { 617 | #if UNITY_2019_1_OR_NEWER 618 | [UnityEngine.Scripting.Preserve] 619 | #endif 620 | protected Exclude (EcsWorld world) : base (world) { 621 | ExcludedTypeIndices = new[] { 622 | EcsComponentType.TypeIndex, 623 | EcsComponentType.TypeIndex 624 | }; 625 | ExcludedTypes = new[] { 626 | EcsComponentType.Type, 627 | EcsComponentType.Type 628 | }; 629 | } 630 | } 631 | } 632 | 633 | #if ENABLE_IL2CPP 634 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] 635 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] 636 | #endif 637 | #if UNITY_2019_1_OR_NEWER 638 | [UnityEngine.Scripting.Preserve] 639 | #endif 640 | public class EcsFilter : EcsFilter, IEcsComponentPoolResizeListener 641 | where Inc1 : struct 642 | where Inc2 : struct 643 | where Inc3 : struct { 644 | int[] _get1; 645 | int[] _get2; 646 | int[] _get3; 647 | 648 | readonly bool _allow1; 649 | readonly bool _allow2; 650 | readonly bool _allow3; 651 | 652 | readonly EcsComponentPool _pool1; 653 | Inc1[] _pool1Items; 654 | readonly EcsComponentPool _pool2; 655 | Inc2[] _pool2Items; 656 | readonly EcsComponentPool _pool3; 657 | Inc3[] _pool3Items; 658 | 659 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 660 | public ref Inc1 Get1 (in int idx) { 661 | return ref _pool1Items[_get1[idx]]; 662 | } 663 | 664 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 665 | public ref Inc2 Get2 (in int idx) { 666 | return ref _pool2Items[_get2[idx]]; 667 | } 668 | 669 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 670 | public ref Inc3 Get3 (in int idx) { 671 | return ref _pool3Items[_get3[idx]]; 672 | } 673 | 674 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 675 | public EcsComponentRef Get1Ref (in int idx) { 676 | return _pool1.Ref (_get1[idx]); 677 | } 678 | 679 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 680 | public EcsComponentRef Get2Ref (in int idx) { 681 | return _pool2.Ref (_get2[idx]); 682 | } 683 | 684 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 685 | public EcsComponentRef Get3Ref (in int idx) { 686 | return _pool3.Ref (_get3[idx]); 687 | } 688 | #if UNITY_2019_1_OR_NEWER 689 | [UnityEngine.Scripting.Preserve] 690 | #endif 691 | protected EcsFilter (EcsWorld world) : base (world) { 692 | _allow1 = !EcsComponentType.IsIgnoreInFilter; 693 | _allow2 = !EcsComponentType.IsIgnoreInFilter; 694 | _allow3 = !EcsComponentType.IsIgnoreInFilter; 695 | _pool1 = world.GetPool (); 696 | _pool1.AddResizeListener (this); 697 | _pool1Items = _pool1.Items; 698 | _pool2 = world.GetPool (); 699 | _pool2.AddResizeListener (this); 700 | _pool2Items = _pool2.Items; 701 | _pool3 = world.GetPool (); 702 | _pool3.AddResizeListener (this); 703 | _pool3Items = _pool3.Items; 704 | _get1 = _allow1 ? new int[EntitiesCacheSize] : null; 705 | _get2 = _allow2 ? new int[EntitiesCacheSize] : null; 706 | _get3 = _allow3 ? new int[EntitiesCacheSize] : null; 707 | IncludedTypeIndices = new[] { 708 | EcsComponentType.TypeIndex, 709 | EcsComponentType.TypeIndex, 710 | EcsComponentType.TypeIndex 711 | }; 712 | IncludedTypes = new[] { 713 | EcsComponentType.Type, 714 | EcsComponentType.Type, 715 | EcsComponentType.Type 716 | }; 717 | } 718 | 719 | /// 720 | /// For internal use. 721 | /// 722 | public override void Destroy () { 723 | _pool1.RemoveResizeListener (this); 724 | _pool2.RemoveResizeListener (this); 725 | _pool3.RemoveResizeListener (this); 726 | } 727 | 728 | /// 729 | /// For internal use. 730 | /// 731 | public void OnComponentPoolResize () { 732 | _pool1Items = _pool1.Items; 733 | _pool2Items = _pool2.Items; 734 | _pool3Items = _pool3.Items; 735 | } 736 | 737 | /// 738 | /// For internal use. 739 | /// 740 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 741 | public override void OnAddEntity (in EcsEntity entity) { 742 | if (AddDelayedOp (true, entity)) { return; } 743 | if (Entities.Length == EntitiesCount) { 744 | var newSize = EntitiesCount << 1; 745 | Array.Resize (ref Entities, newSize); 746 | if (_allow1) { Array.Resize (ref _get1, newSize); } 747 | if (_allow2) { Array.Resize (ref _get2, newSize); } 748 | if (_allow3) { Array.Resize (ref _get3, newSize); } 749 | } 750 | // inlined and optimized EcsEntity.Get() call. 751 | ref var entityData = ref entity.Owner.GetEntityData (entity); 752 | var allow1 = _allow1; 753 | var allow2 = _allow2; 754 | var allow3 = _allow3; 755 | for (int i = 0, iMax = entityData.ComponentsCountX2, left = 3; left > 0 && i < iMax; i += 2) { 756 | var typeIdx = entityData.Components[i]; 757 | var itemIdx = entityData.Components[i + 1]; 758 | if (allow1 && typeIdx == EcsComponentType.TypeIndex) { 759 | _get1[EntitiesCount] = itemIdx; 760 | allow1 = false; 761 | left--; 762 | continue; 763 | } 764 | if (allow2 && typeIdx == EcsComponentType.TypeIndex) { 765 | _get2[EntitiesCount] = itemIdx; 766 | allow2 = false; 767 | left--; 768 | continue; 769 | } 770 | if (allow3 && typeIdx == EcsComponentType.TypeIndex) { 771 | _get3[EntitiesCount] = itemIdx; 772 | allow3 = false; 773 | left--; 774 | } 775 | } 776 | EntitiesMap[entity.GetInternalId ()] = EntitiesCount; 777 | Entities[EntitiesCount++] = entity; 778 | #if LEOECS_FILTER_EVENTS 779 | ProcessListeners (true, entity); 780 | #endif 781 | } 782 | 783 | /// 784 | /// For internal use. 785 | /// 786 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 787 | public override void OnRemoveEntity (in EcsEntity entity) { 788 | if (AddDelayedOp (false, entity)) { return; } 789 | var entityId = entity.GetInternalId (); 790 | var idx = EntitiesMap[entityId]; 791 | EntitiesMap.Remove (entityId); 792 | EntitiesCount--; 793 | if (idx < EntitiesCount) { 794 | Entities[idx] = Entities[EntitiesCount]; 795 | EntitiesMap[Entities[idx].GetInternalId ()] = idx; 796 | if (_allow1) { _get1[idx] = _get1[EntitiesCount]; } 797 | if (_allow2) { _get2[idx] = _get2[EntitiesCount]; } 798 | if (_allow3) { _get3[idx] = _get3[EntitiesCount]; } 799 | } 800 | #if LEOECS_FILTER_EVENTS 801 | ProcessListeners (false, entity); 802 | #endif 803 | } 804 | 805 | public class Exclude : EcsFilter 806 | where Exc1 : struct { 807 | #if UNITY_2019_1_OR_NEWER 808 | [UnityEngine.Scripting.Preserve] 809 | #endif 810 | protected Exclude (EcsWorld world) : base (world) { 811 | ExcludedTypeIndices = new[] { 812 | EcsComponentType.TypeIndex 813 | }; 814 | ExcludedTypes = new[] { 815 | EcsComponentType.Type 816 | }; 817 | } 818 | } 819 | 820 | public class Exclude : EcsFilter 821 | where Exc1 : struct 822 | where Exc2 : struct { 823 | #if UNITY_2019_1_OR_NEWER 824 | [UnityEngine.Scripting.Preserve] 825 | #endif 826 | protected Exclude (EcsWorld world) : base (world) { 827 | ExcludedTypeIndices = new[] { 828 | EcsComponentType.TypeIndex, 829 | EcsComponentType.TypeIndex 830 | }; 831 | ExcludedTypes = new[] { 832 | EcsComponentType.Type, 833 | EcsComponentType.Type 834 | }; 835 | } 836 | } 837 | } 838 | 839 | #if ENABLE_IL2CPP 840 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] 841 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] 842 | #endif 843 | #if UNITY_2019_1_OR_NEWER 844 | [UnityEngine.Scripting.Preserve] 845 | #endif 846 | public class EcsFilter : EcsFilter, IEcsComponentPoolResizeListener 847 | where Inc1 : struct 848 | where Inc2 : struct 849 | where Inc3 : struct 850 | where Inc4 : struct { 851 | int[] _get1; 852 | int[] _get2; 853 | int[] _get3; 854 | int[] _get4; 855 | 856 | readonly bool _allow1; 857 | readonly bool _allow2; 858 | readonly bool _allow3; 859 | readonly bool _allow4; 860 | 861 | readonly EcsComponentPool _pool1; 862 | Inc1[] _pool1Items; 863 | readonly EcsComponentPool _pool2; 864 | Inc2[] _pool2Items; 865 | readonly EcsComponentPool _pool3; 866 | Inc3[] _pool3Items; 867 | readonly EcsComponentPool _pool4; 868 | Inc4[] _pool4Items; 869 | 870 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 871 | public ref Inc1 Get1 (in int idx) { 872 | return ref _pool1Items[_get1[idx]]; 873 | } 874 | 875 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 876 | public ref Inc2 Get2 (in int idx) { 877 | return ref _pool2Items[_get2[idx]]; 878 | } 879 | 880 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 881 | public ref Inc3 Get3 (in int idx) { 882 | return ref _pool3Items[_get3[idx]]; 883 | } 884 | 885 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 886 | public ref Inc4 Get4 (in int idx) { 887 | return ref _pool4Items[_get4[idx]]; 888 | } 889 | 890 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 891 | public EcsComponentRef Get1Ref (in int idx) { 892 | return _pool1.Ref (_get1[idx]); 893 | } 894 | 895 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 896 | public EcsComponentRef Get2Ref (in int idx) { 897 | return _pool2.Ref (_get2[idx]); 898 | } 899 | 900 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 901 | public EcsComponentRef Get3Ref (in int idx) { 902 | return _pool3.Ref (_get3[idx]); 903 | } 904 | 905 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 906 | public EcsComponentRef Get4Ref (in int idx) { 907 | return _pool4.Ref (_get4[idx]); 908 | } 909 | #if UNITY_2019_1_OR_NEWER 910 | [UnityEngine.Scripting.Preserve] 911 | #endif 912 | protected EcsFilter (EcsWorld world) : base (world) { 913 | _allow1 = !EcsComponentType.IsIgnoreInFilter; 914 | _allow2 = !EcsComponentType.IsIgnoreInFilter; 915 | _allow3 = !EcsComponentType.IsIgnoreInFilter; 916 | _allow4 = !EcsComponentType.IsIgnoreInFilter; 917 | _pool1 = world.GetPool (); 918 | _pool1.AddResizeListener (this); 919 | _pool1Items = _pool1.Items; 920 | _pool2 = world.GetPool (); 921 | _pool2.AddResizeListener (this); 922 | _pool2Items = _pool2.Items; 923 | _pool3 = world.GetPool (); 924 | _pool3.AddResizeListener (this); 925 | _pool3Items = _pool3.Items; 926 | _pool4 = world.GetPool (); 927 | _pool4.AddResizeListener (this); 928 | _pool4Items = _pool4.Items; 929 | _get1 = _allow1 ? new int[EntitiesCacheSize] : null; 930 | _get2 = _allow2 ? new int[EntitiesCacheSize] : null; 931 | _get3 = _allow3 ? new int[EntitiesCacheSize] : null; 932 | _get4 = _allow4 ? new int[EntitiesCacheSize] : null; 933 | IncludedTypeIndices = new[] { 934 | EcsComponentType.TypeIndex, 935 | EcsComponentType.TypeIndex, 936 | EcsComponentType.TypeIndex, 937 | EcsComponentType.TypeIndex 938 | }; 939 | IncludedTypes = new[] { 940 | EcsComponentType.Type, 941 | EcsComponentType.Type, 942 | EcsComponentType.Type, 943 | EcsComponentType.Type 944 | }; 945 | } 946 | 947 | /// 948 | /// For internal use. 949 | /// 950 | public override void Destroy () { 951 | _pool1.RemoveResizeListener (this); 952 | _pool2.RemoveResizeListener (this); 953 | _pool3.RemoveResizeListener (this); 954 | _pool4.RemoveResizeListener (this); 955 | } 956 | 957 | /// 958 | /// For internal use. 959 | /// 960 | public void OnComponentPoolResize () { 961 | _pool1Items = _pool1.Items; 962 | _pool2Items = _pool2.Items; 963 | _pool3Items = _pool3.Items; 964 | _pool4Items = _pool4.Items; 965 | } 966 | 967 | /// 968 | /// For internal use. 969 | /// 970 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 971 | public override void OnAddEntity (in EcsEntity entity) { 972 | if (AddDelayedOp (true, entity)) { return; } 973 | if (Entities.Length == EntitiesCount) { 974 | var newSize = EntitiesCount << 1; 975 | Array.Resize (ref Entities, newSize); 976 | if (_allow1) { Array.Resize (ref _get1, newSize); } 977 | if (_allow2) { Array.Resize (ref _get2, newSize); } 978 | if (_allow3) { Array.Resize (ref _get3, newSize); } 979 | if (_allow4) { Array.Resize (ref _get4, newSize); } 980 | } 981 | // inlined and optimized EcsEntity.Get() call. 982 | ref var entityData = ref entity.Owner.GetEntityData (entity); 983 | var allow1 = _allow1; 984 | var allow2 = _allow2; 985 | var allow3 = _allow3; 986 | var allow4 = _allow4; 987 | for (int i = 0, iMax = entityData.ComponentsCountX2, left = 4; left > 0 && i < iMax; i += 2) { 988 | var typeIdx = entityData.Components[i]; 989 | var itemIdx = entityData.Components[i + 1]; 990 | if (allow1 && typeIdx == EcsComponentType.TypeIndex) { 991 | _get1[EntitiesCount] = itemIdx; 992 | allow1 = false; 993 | left--; 994 | continue; 995 | } 996 | if (allow2 && typeIdx == EcsComponentType.TypeIndex) { 997 | _get2[EntitiesCount] = itemIdx; 998 | allow2 = false; 999 | left--; 1000 | continue; 1001 | } 1002 | if (allow3 && typeIdx == EcsComponentType.TypeIndex) { 1003 | _get3[EntitiesCount] = itemIdx; 1004 | allow3 = false; 1005 | left--; 1006 | continue; 1007 | } 1008 | if (allow4 && typeIdx == EcsComponentType.TypeIndex) { 1009 | _get4[EntitiesCount] = itemIdx; 1010 | allow4 = false; 1011 | left--; 1012 | } 1013 | } 1014 | EntitiesMap[entity.GetInternalId ()] = EntitiesCount; 1015 | Entities[EntitiesCount++] = entity; 1016 | #if LEOECS_FILTER_EVENTS 1017 | ProcessListeners (true, entity); 1018 | #endif 1019 | } 1020 | 1021 | /// 1022 | /// For internal use. 1023 | /// 1024 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1025 | public override void OnRemoveEntity (in EcsEntity entity) { 1026 | if (AddDelayedOp (false, entity)) { return; } 1027 | var entityId = entity.GetInternalId (); 1028 | var idx = EntitiesMap[entityId]; 1029 | EntitiesMap.Remove (entityId); 1030 | EntitiesCount--; 1031 | if (idx < EntitiesCount) { 1032 | Entities[idx] = Entities[EntitiesCount]; 1033 | EntitiesMap[Entities[idx].GetInternalId ()] = idx; 1034 | if (_allow1) { _get1[idx] = _get1[EntitiesCount]; } 1035 | if (_allow2) { _get2[idx] = _get2[EntitiesCount]; } 1036 | if (_allow3) { _get3[idx] = _get3[EntitiesCount]; } 1037 | if (_allow4) { _get4[idx] = _get4[EntitiesCount]; } 1038 | } 1039 | #if LEOECS_FILTER_EVENTS 1040 | ProcessListeners (false, entity); 1041 | #endif 1042 | } 1043 | 1044 | public class Exclude : EcsFilter 1045 | where Exc1 : struct { 1046 | #if UNITY_2019_1_OR_NEWER 1047 | [UnityEngine.Scripting.Preserve] 1048 | #endif 1049 | protected Exclude (EcsWorld world) : base (world) { 1050 | ExcludedTypeIndices = new[] { 1051 | EcsComponentType.TypeIndex 1052 | }; 1053 | ExcludedTypes = new[] { 1054 | EcsComponentType.Type 1055 | }; 1056 | } 1057 | } 1058 | 1059 | public class Exclude : EcsFilter 1060 | where Exc1 : struct 1061 | where Exc2 : struct { 1062 | #if UNITY_2019_1_OR_NEWER 1063 | [UnityEngine.Scripting.Preserve] 1064 | #endif 1065 | protected Exclude (EcsWorld world) : base (world) { 1066 | ExcludedTypeIndices = new[] { 1067 | EcsComponentType.TypeIndex, 1068 | EcsComponentType.TypeIndex 1069 | }; 1070 | ExcludedTypes = new[] { 1071 | EcsComponentType.Type, 1072 | EcsComponentType.Type 1073 | }; 1074 | } 1075 | } 1076 | } 1077 | 1078 | #if ENABLE_IL2CPP 1079 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] 1080 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] 1081 | #endif 1082 | #if UNITY_2019_1_OR_NEWER 1083 | [UnityEngine.Scripting.Preserve] 1084 | #endif 1085 | public class EcsFilter : EcsFilter, IEcsComponentPoolResizeListener 1086 | where Inc1 : struct 1087 | where Inc2 : struct 1088 | where Inc3 : struct 1089 | where Inc4 : struct 1090 | where Inc5 : struct { 1091 | int[] _get1; 1092 | int[] _get2; 1093 | int[] _get3; 1094 | int[] _get4; 1095 | int[] _get5; 1096 | 1097 | readonly bool _allow1; 1098 | readonly bool _allow2; 1099 | readonly bool _allow3; 1100 | readonly bool _allow4; 1101 | readonly bool _allow5; 1102 | 1103 | readonly EcsComponentPool _pool1; 1104 | Inc1[] _pool1Items; 1105 | readonly EcsComponentPool _pool2; 1106 | Inc2[] _pool2Items; 1107 | readonly EcsComponentPool _pool3; 1108 | Inc3[] _pool3Items; 1109 | readonly EcsComponentPool _pool4; 1110 | Inc4[] _pool4Items; 1111 | readonly EcsComponentPool _pool5; 1112 | Inc5[] _pool5Items; 1113 | 1114 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1115 | public ref Inc1 Get1 (in int idx) { 1116 | return ref _pool1Items[_get1[idx]]; 1117 | } 1118 | 1119 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1120 | public ref Inc2 Get2 (in int idx) { 1121 | return ref _pool2Items[_get2[idx]]; 1122 | } 1123 | 1124 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1125 | public ref Inc3 Get3 (in int idx) { 1126 | return ref _pool3Items[_get3[idx]]; 1127 | } 1128 | 1129 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1130 | public ref Inc4 Get4 (in int idx) { 1131 | return ref _pool4Items[_get4[idx]]; 1132 | } 1133 | 1134 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1135 | public ref Inc5 Get5 (in int idx) { 1136 | return ref _pool5Items[_get5[idx]]; 1137 | } 1138 | 1139 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1140 | public EcsComponentRef Get1Ref (in int idx) { 1141 | return _pool1.Ref (_get1[idx]); 1142 | } 1143 | 1144 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1145 | public EcsComponentRef Get2Ref (in int idx) { 1146 | return _pool2.Ref (_get2[idx]); 1147 | } 1148 | 1149 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1150 | public EcsComponentRef Get3Ref (in int idx) { 1151 | return _pool3.Ref (_get3[idx]); 1152 | } 1153 | 1154 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1155 | public EcsComponentRef Get4Ref (in int idx) { 1156 | return _pool4.Ref (_get4[idx]); 1157 | } 1158 | 1159 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1160 | public EcsComponentRef Get5Ref (in int idx) { 1161 | return _pool5.Ref (_get5[idx]); 1162 | } 1163 | #if UNITY_2019_1_OR_NEWER 1164 | [UnityEngine.Scripting.Preserve] 1165 | #endif 1166 | protected EcsFilter (EcsWorld world) : base (world) { 1167 | _allow1 = !EcsComponentType.IsIgnoreInFilter; 1168 | _allow2 = !EcsComponentType.IsIgnoreInFilter; 1169 | _allow3 = !EcsComponentType.IsIgnoreInFilter; 1170 | _allow4 = !EcsComponentType.IsIgnoreInFilter; 1171 | _allow5 = !EcsComponentType.IsIgnoreInFilter; 1172 | _pool1 = world.GetPool (); 1173 | _pool1.AddResizeListener (this); 1174 | _pool1Items = _pool1.Items; 1175 | _pool2 = world.GetPool (); 1176 | _pool2.AddResizeListener (this); 1177 | _pool2Items = _pool2.Items; 1178 | _pool3 = world.GetPool (); 1179 | _pool3.AddResizeListener (this); 1180 | _pool3Items = _pool3.Items; 1181 | _pool4 = world.GetPool (); 1182 | _pool4.AddResizeListener (this); 1183 | _pool4Items = _pool4.Items; 1184 | _pool5 = world.GetPool (); 1185 | _pool5.AddResizeListener (this); 1186 | _pool5Items = _pool5.Items; 1187 | _get1 = _allow1 ? new int[EntitiesCacheSize] : null; 1188 | _get2 = _allow2 ? new int[EntitiesCacheSize] : null; 1189 | _get3 = _allow3 ? new int[EntitiesCacheSize] : null; 1190 | _get4 = _allow4 ? new int[EntitiesCacheSize] : null; 1191 | _get5 = _allow5 ? new int[EntitiesCacheSize] : null; 1192 | IncludedTypeIndices = new[] { 1193 | EcsComponentType.TypeIndex, 1194 | EcsComponentType.TypeIndex, 1195 | EcsComponentType.TypeIndex, 1196 | EcsComponentType.TypeIndex, 1197 | EcsComponentType.TypeIndex 1198 | }; 1199 | IncludedTypes = new[] { 1200 | EcsComponentType.Type, 1201 | EcsComponentType.Type, 1202 | EcsComponentType.Type, 1203 | EcsComponentType.Type, 1204 | EcsComponentType.Type 1205 | }; 1206 | } 1207 | 1208 | /// 1209 | /// For internal use. 1210 | /// 1211 | public override void Destroy () { 1212 | _pool1.RemoveResizeListener (this); 1213 | _pool2.RemoveResizeListener (this); 1214 | _pool3.RemoveResizeListener (this); 1215 | _pool4.RemoveResizeListener (this); 1216 | _pool5.RemoveResizeListener (this); 1217 | } 1218 | 1219 | /// 1220 | /// For internal use. 1221 | /// 1222 | public void OnComponentPoolResize () { 1223 | _pool1Items = _pool1.Items; 1224 | _pool2Items = _pool2.Items; 1225 | _pool3Items = _pool3.Items; 1226 | _pool4Items = _pool4.Items; 1227 | _pool5Items = _pool5.Items; 1228 | } 1229 | 1230 | /// 1231 | /// For internal use. 1232 | /// 1233 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1234 | public override void OnAddEntity (in EcsEntity entity) { 1235 | if (AddDelayedOp (true, entity)) { return; } 1236 | if (Entities.Length == EntitiesCount) { 1237 | var newSize = EntitiesCount << 1; 1238 | Array.Resize (ref Entities, newSize); 1239 | if (_allow1) { Array.Resize (ref _get1, newSize); } 1240 | if (_allow2) { Array.Resize (ref _get2, newSize); } 1241 | if (_allow3) { Array.Resize (ref _get3, newSize); } 1242 | if (_allow4) { Array.Resize (ref _get4, newSize); } 1243 | if (_allow5) { Array.Resize (ref _get5, newSize); } 1244 | } 1245 | // inlined and optimized EcsEntity.Get() call. 1246 | ref var entityData = ref entity.Owner.GetEntityData (entity); 1247 | var allow1 = _allow1; 1248 | var allow2 = _allow2; 1249 | var allow3 = _allow3; 1250 | var allow4 = _allow4; 1251 | var allow5 = _allow5; 1252 | for (int i = 0, iMax = entityData.ComponentsCountX2, left = 5; left > 0 && i < iMax; i += 2) { 1253 | var typeIdx = entityData.Components[i]; 1254 | var itemIdx = entityData.Components[i + 1]; 1255 | if (allow1 && typeIdx == EcsComponentType.TypeIndex) { 1256 | _get1[EntitiesCount] = itemIdx; 1257 | allow1 = false; 1258 | left--; 1259 | continue; 1260 | } 1261 | if (allow2 && typeIdx == EcsComponentType.TypeIndex) { 1262 | _get2[EntitiesCount] = itemIdx; 1263 | allow2 = false; 1264 | left--; 1265 | continue; 1266 | } 1267 | if (allow3 && typeIdx == EcsComponentType.TypeIndex) { 1268 | _get3[EntitiesCount] = itemIdx; 1269 | allow3 = false; 1270 | left--; 1271 | continue; 1272 | } 1273 | if (allow4 && typeIdx == EcsComponentType.TypeIndex) { 1274 | _get4[EntitiesCount] = itemIdx; 1275 | allow4 = false; 1276 | left--; 1277 | continue; 1278 | } 1279 | if (allow5 && typeIdx == EcsComponentType.TypeIndex) { 1280 | _get5[EntitiesCount] = itemIdx; 1281 | allow5 = false; 1282 | left--; 1283 | } 1284 | } 1285 | EntitiesMap[entity.GetInternalId ()] = EntitiesCount; 1286 | Entities[EntitiesCount++] = entity; 1287 | #if LEOECS_FILTER_EVENTS 1288 | ProcessListeners (true, entity); 1289 | #endif 1290 | } 1291 | 1292 | /// 1293 | /// For internal use. 1294 | /// 1295 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1296 | public override void OnRemoveEntity (in EcsEntity entity) { 1297 | if (AddDelayedOp (false, entity)) { return; } 1298 | var entityId = entity.GetInternalId (); 1299 | var idx = EntitiesMap[entityId]; 1300 | EntitiesMap.Remove (entityId); 1301 | EntitiesCount--; 1302 | if (idx < EntitiesCount) { 1303 | Entities[idx] = Entities[EntitiesCount]; 1304 | EntitiesMap[Entities[idx].GetInternalId ()] = idx; 1305 | if (_allow1) { _get1[idx] = _get1[EntitiesCount]; } 1306 | if (_allow2) { _get2[idx] = _get2[EntitiesCount]; } 1307 | if (_allow3) { _get3[idx] = _get3[EntitiesCount]; } 1308 | if (_allow4) { _get4[idx] = _get4[EntitiesCount]; } 1309 | if (_allow5) { _get5[idx] = _get5[EntitiesCount]; } 1310 | } 1311 | #if LEOECS_FILTER_EVENTS 1312 | ProcessListeners (false, entity); 1313 | #endif 1314 | } 1315 | 1316 | public class Exclude : EcsFilter 1317 | where Exc1 : struct { 1318 | #if UNITY_2019_1_OR_NEWER 1319 | [UnityEngine.Scripting.Preserve] 1320 | #endif 1321 | protected Exclude (EcsWorld world) : base (world) { 1322 | ExcludedTypeIndices = new[] { 1323 | EcsComponentType.TypeIndex 1324 | }; 1325 | ExcludedTypes = new[] { 1326 | EcsComponentType.Type 1327 | }; 1328 | } 1329 | } 1330 | 1331 | public class Exclude : EcsFilter 1332 | where Exc1 : struct 1333 | where Exc2 : struct { 1334 | #if UNITY_2019_1_OR_NEWER 1335 | [UnityEngine.Scripting.Preserve] 1336 | #endif 1337 | protected Exclude (EcsWorld world) : base (world) { 1338 | ExcludedTypeIndices = new[] { 1339 | EcsComponentType.TypeIndex, 1340 | EcsComponentType.TypeIndex 1341 | }; 1342 | ExcludedTypes = new[] { 1343 | EcsComponentType.Type, 1344 | EcsComponentType.Type 1345 | }; 1346 | } 1347 | } 1348 | } 1349 | 1350 | #if ENABLE_IL2CPP 1351 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] 1352 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] 1353 | #endif 1354 | #if UNITY_2019_1_OR_NEWER 1355 | [UnityEngine.Scripting.Preserve] 1356 | #endif 1357 | public class EcsFilter : EcsFilter, IEcsComponentPoolResizeListener 1358 | where Inc1 : struct 1359 | where Inc2 : struct 1360 | where Inc3 : struct 1361 | where Inc4 : struct 1362 | where Inc5 : struct 1363 | where Inc6 : struct { 1364 | int[] _get1; 1365 | int[] _get2; 1366 | int[] _get3; 1367 | int[] _get4; 1368 | int[] _get5; 1369 | int[] _get6; 1370 | 1371 | readonly bool _allow1; 1372 | readonly bool _allow2; 1373 | readonly bool _allow3; 1374 | readonly bool _allow4; 1375 | readonly bool _allow5; 1376 | readonly bool _allow6; 1377 | 1378 | readonly EcsComponentPool _pool1; 1379 | Inc1[] _pool1Items; 1380 | readonly EcsComponentPool _pool2; 1381 | Inc2[] _pool2Items; 1382 | readonly EcsComponentPool _pool3; 1383 | Inc3[] _pool3Items; 1384 | readonly EcsComponentPool _pool4; 1385 | Inc4[] _pool4Items; 1386 | readonly EcsComponentPool _pool5; 1387 | Inc5[] _pool5Items; 1388 | readonly EcsComponentPool _pool6; 1389 | Inc6[] _pool6Items; 1390 | 1391 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1392 | public ref Inc1 Get1 (in int idx) { 1393 | return ref _pool1Items[_get1[idx]]; 1394 | } 1395 | 1396 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1397 | public ref Inc2 Get2 (in int idx) { 1398 | return ref _pool2Items[_get2[idx]]; 1399 | } 1400 | 1401 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1402 | public ref Inc3 Get3 (in int idx) { 1403 | return ref _pool3Items[_get3[idx]]; 1404 | } 1405 | 1406 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1407 | public ref Inc4 Get4 (in int idx) { 1408 | return ref _pool4Items[_get4[idx]]; 1409 | } 1410 | 1411 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1412 | public ref Inc5 Get5 (in int idx) { 1413 | return ref _pool5Items[_get5[idx]]; 1414 | } 1415 | 1416 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1417 | public ref Inc6 Get6 (in int idx) { 1418 | return ref _pool6Items[_get6[idx]]; 1419 | } 1420 | 1421 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1422 | public EcsComponentRef Get1Ref (in int idx) { 1423 | return _pool1.Ref (_get1[idx]); 1424 | } 1425 | 1426 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1427 | public EcsComponentRef Get2Ref (in int idx) { 1428 | return _pool2.Ref (_get2[idx]); 1429 | } 1430 | 1431 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1432 | public EcsComponentRef Get3Ref (in int idx) { 1433 | return _pool3.Ref (_get3[idx]); 1434 | } 1435 | 1436 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1437 | public EcsComponentRef Get4Ref (in int idx) { 1438 | return _pool4.Ref (_get4[idx]); 1439 | } 1440 | 1441 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1442 | public EcsComponentRef Get5Ref (in int idx) { 1443 | return _pool5.Ref (_get5[idx]); 1444 | } 1445 | 1446 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1447 | public EcsComponentRef Get6Ref (in int idx) { 1448 | return _pool6.Ref (_get6[idx]); 1449 | } 1450 | #if UNITY_2019_1_OR_NEWER 1451 | [UnityEngine.Scripting.Preserve] 1452 | #endif 1453 | protected EcsFilter (EcsWorld world) : base (world) { 1454 | _allow1 = !EcsComponentType.IsIgnoreInFilter; 1455 | _allow2 = !EcsComponentType.IsIgnoreInFilter; 1456 | _allow3 = !EcsComponentType.IsIgnoreInFilter; 1457 | _allow4 = !EcsComponentType.IsIgnoreInFilter; 1458 | _allow5 = !EcsComponentType.IsIgnoreInFilter; 1459 | _allow6 = !EcsComponentType.IsIgnoreInFilter; 1460 | _pool1 = world.GetPool (); 1461 | _pool1.AddResizeListener (this); 1462 | _pool1Items = _pool1.Items; 1463 | _pool2 = world.GetPool (); 1464 | _pool2.AddResizeListener (this); 1465 | _pool2Items = _pool2.Items; 1466 | _pool3 = world.GetPool (); 1467 | _pool3.AddResizeListener (this); 1468 | _pool3Items = _pool3.Items; 1469 | _pool4 = world.GetPool (); 1470 | _pool4.AddResizeListener (this); 1471 | _pool4Items = _pool4.Items; 1472 | _pool5 = world.GetPool (); 1473 | _pool5.AddResizeListener (this); 1474 | _pool5Items = _pool5.Items; 1475 | _pool6 = world.GetPool (); 1476 | _pool6.AddResizeListener (this); 1477 | _pool6Items = _pool6.Items; 1478 | _get1 = _allow1 ? new int[EntitiesCacheSize] : null; 1479 | _get2 = _allow2 ? new int[EntitiesCacheSize] : null; 1480 | _get3 = _allow3 ? new int[EntitiesCacheSize] : null; 1481 | _get4 = _allow4 ? new int[EntitiesCacheSize] : null; 1482 | _get5 = _allow5 ? new int[EntitiesCacheSize] : null; 1483 | _get6 = _allow6 ? new int[EntitiesCacheSize] : null; 1484 | IncludedTypeIndices = new[] { 1485 | EcsComponentType.TypeIndex, 1486 | EcsComponentType.TypeIndex, 1487 | EcsComponentType.TypeIndex, 1488 | EcsComponentType.TypeIndex, 1489 | EcsComponentType.TypeIndex, 1490 | EcsComponentType.TypeIndex 1491 | }; 1492 | IncludedTypes = new[] { 1493 | EcsComponentType.Type, 1494 | EcsComponentType.Type, 1495 | EcsComponentType.Type, 1496 | EcsComponentType.Type, 1497 | EcsComponentType.Type, 1498 | EcsComponentType.Type 1499 | }; 1500 | } 1501 | 1502 | /// 1503 | /// For internal use. 1504 | /// 1505 | public override void Destroy () { 1506 | _pool1.RemoveResizeListener (this); 1507 | _pool2.RemoveResizeListener (this); 1508 | _pool3.RemoveResizeListener (this); 1509 | _pool4.RemoveResizeListener (this); 1510 | _pool5.RemoveResizeListener (this); 1511 | _pool6.RemoveResizeListener (this); 1512 | } 1513 | 1514 | /// 1515 | /// For internal use. 1516 | /// 1517 | public void OnComponentPoolResize () { 1518 | _pool1Items = _pool1.Items; 1519 | _pool2Items = _pool2.Items; 1520 | _pool3Items = _pool3.Items; 1521 | _pool4Items = _pool4.Items; 1522 | _pool5Items = _pool5.Items; 1523 | _pool6Items = _pool6.Items; 1524 | } 1525 | 1526 | /// 1527 | /// For internal use. 1528 | /// 1529 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1530 | public override void OnAddEntity (in EcsEntity entity) { 1531 | if (AddDelayedOp (true, entity)) { return; } 1532 | if (Entities.Length == EntitiesCount) { 1533 | var newSize = EntitiesCount << 1; 1534 | Array.Resize (ref Entities, newSize); 1535 | if (_allow1) { Array.Resize (ref _get1, newSize); } 1536 | if (_allow2) { Array.Resize (ref _get2, newSize); } 1537 | if (_allow3) { Array.Resize (ref _get3, newSize); } 1538 | if (_allow4) { Array.Resize (ref _get4, newSize); } 1539 | if (_allow5) { Array.Resize (ref _get5, newSize); } 1540 | if (_allow6) { Array.Resize (ref _get6, newSize); } 1541 | } 1542 | // inlined and optimized EcsEntity.Get() call. 1543 | ref var entityData = ref entity.Owner.GetEntityData (entity); 1544 | var allow1 = _allow1; 1545 | var allow2 = _allow2; 1546 | var allow3 = _allow3; 1547 | var allow4 = _allow4; 1548 | var allow5 = _allow5; 1549 | var allow6 = _allow6; 1550 | for (int i = 0, iMax = entityData.ComponentsCountX2, left = 6; left > 0 && i < iMax; i += 2) { 1551 | var typeIdx = entityData.Components[i]; 1552 | var itemIdx = entityData.Components[i + 1]; 1553 | if (allow1 && typeIdx == EcsComponentType.TypeIndex) { 1554 | _get1[EntitiesCount] = itemIdx; 1555 | allow1 = false; 1556 | left--; 1557 | continue; 1558 | } 1559 | if (allow2 && typeIdx == EcsComponentType.TypeIndex) { 1560 | _get2[EntitiesCount] = itemIdx; 1561 | allow2 = false; 1562 | left--; 1563 | continue; 1564 | } 1565 | if (allow3 && typeIdx == EcsComponentType.TypeIndex) { 1566 | _get3[EntitiesCount] = itemIdx; 1567 | allow3 = false; 1568 | left--; 1569 | continue; 1570 | } 1571 | if (allow4 && typeIdx == EcsComponentType.TypeIndex) { 1572 | _get4[EntitiesCount] = itemIdx; 1573 | allow4 = false; 1574 | left--; 1575 | continue; 1576 | } 1577 | if (allow5 && typeIdx == EcsComponentType.TypeIndex) { 1578 | _get5[EntitiesCount] = itemIdx; 1579 | allow5 = false; 1580 | left--; 1581 | continue; 1582 | } 1583 | if (allow6 && typeIdx == EcsComponentType.TypeIndex) { 1584 | _get6[EntitiesCount] = itemIdx; 1585 | allow6 = false; 1586 | left--; 1587 | } 1588 | } 1589 | EntitiesMap[entity.GetInternalId ()] = EntitiesCount; 1590 | Entities[EntitiesCount++] = entity; 1591 | #if LEOECS_FILTER_EVENTS 1592 | ProcessListeners (true, entity); 1593 | #endif 1594 | } 1595 | 1596 | /// 1597 | /// For internal use. 1598 | /// 1599 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 1600 | public override void OnRemoveEntity (in EcsEntity entity) { 1601 | if (AddDelayedOp (false, entity)) { return; } 1602 | var entityId = entity.GetInternalId (); 1603 | var idx = EntitiesMap[entityId]; 1604 | EntitiesMap.Remove (entityId); 1605 | EntitiesCount--; 1606 | if (idx < EntitiesCount) { 1607 | Entities[idx] = Entities[EntitiesCount]; 1608 | EntitiesMap[Entities[idx].GetInternalId ()] = idx; 1609 | if (_allow1) { _get1[idx] = _get1[EntitiesCount]; } 1610 | if (_allow2) { _get2[idx] = _get2[EntitiesCount]; } 1611 | if (_allow3) { _get3[idx] = _get3[EntitiesCount]; } 1612 | if (_allow4) { _get4[idx] = _get4[EntitiesCount]; } 1613 | if (_allow5) { _get5[idx] = _get5[EntitiesCount]; } 1614 | if (_allow6) { _get6[idx] = _get6[EntitiesCount]; } 1615 | } 1616 | #if LEOECS_FILTER_EVENTS 1617 | ProcessListeners (false, entity); 1618 | #endif 1619 | } 1620 | 1621 | public class Exclude : EcsFilter 1622 | where Exc1 : struct { 1623 | #if UNITY_2019_1_OR_NEWER 1624 | [UnityEngine.Scripting.Preserve] 1625 | #endif 1626 | protected Exclude (EcsWorld world) : base (world) { 1627 | ExcludedTypeIndices = new[] { 1628 | EcsComponentType.TypeIndex 1629 | }; 1630 | ExcludedTypes = new[] { 1631 | EcsComponentType.Type 1632 | }; 1633 | } 1634 | } 1635 | 1636 | public class Exclude : EcsFilter 1637 | where Exc1 : struct 1638 | where Exc2 : struct { 1639 | #if UNITY_2019_1_OR_NEWER 1640 | [UnityEngine.Scripting.Preserve] 1641 | #endif 1642 | protected Exclude (EcsWorld world) : base (world) { 1643 | ExcludedTypeIndices = new[] { 1644 | EcsComponentType.TypeIndex, 1645 | EcsComponentType.TypeIndex 1646 | }; 1647 | ExcludedTypes = new[] { 1648 | EcsComponentType.Type, 1649 | EcsComponentType.Type 1650 | }; 1651 | } 1652 | } 1653 | } 1654 | } -------------------------------------------------------------------------------- /src/EcsFilter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3c4be8af30e0c4a93ab1bce7ad98ef68 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /src/EcsHelpers.cs: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // The Proprietary or MIT-Red License 3 | // Copyright (c) 2012-2023 Leopotam 4 | // ---------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Runtime.CompilerServices; 8 | 9 | namespace Leopotam.Ecs { 10 | /// 11 | /// Fast List replacement for growing only collections. 12 | /// 13 | /// Type of item. 14 | public class EcsGrowList { 15 | public T[] Items; 16 | public int Count; 17 | 18 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 19 | public EcsGrowList (int capacity) { 20 | #if DEBUG 21 | if (capacity <= 0) { throw new Exception ("Capacity should be greater than zero."); } 22 | #endif 23 | Items = new T[capacity]; 24 | Count = 0; 25 | } 26 | 27 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 28 | public void Add (T item) { 29 | if (Items.Length == Count) { 30 | Array.Resize (ref Items, Items.Length << 1); 31 | } 32 | Items[Count++] = item; 33 | } 34 | 35 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 36 | public void EnsureCapacity (int count) { 37 | if (Items.Length < count) { 38 | var len = Items.Length << 1; 39 | while (len <= count) { 40 | len <<= 1; 41 | } 42 | Array.Resize (ref Items, len); 43 | } 44 | } 45 | } 46 | } 47 | 48 | #if ENABLE_IL2CPP 49 | // Unity IL2CPP performance optimization attribute. 50 | namespace Unity.IL2CPP.CompilerServices { 51 | enum Option { 52 | NullChecks = 1, 53 | ArrayBoundsChecks = 2 54 | } 55 | 56 | [AttributeUsage (AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] 57 | class Il2CppSetOptionAttribute : Attribute { 58 | public Option Option { get; private set; } 59 | public object Value { get; private set; } 60 | 61 | public Il2CppSetOptionAttribute (Option option, object value) { Option = option; Value = value; } 62 | } 63 | } 64 | #endif -------------------------------------------------------------------------------- /src/EcsHelpers.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9cfb8a67aeb254dcfbc6b6c54677c316 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /src/EcsSystem.cs: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // The Proprietary or MIT-Red License 3 | // Copyright (c) 2012-2023 Leopotam 4 | // ---------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Reflection; 9 | 10 | namespace Leopotam.Ecs { 11 | /// 12 | /// Base interface for all systems. 13 | /// 14 | public interface IEcsSystem { } 15 | 16 | /// 17 | /// Interface for PreInit systems. PreInit() will be called before Init(). 18 | /// 19 | public interface IEcsPreInitSystem : IEcsSystem { 20 | void PreInit (); 21 | } 22 | 23 | /// 24 | /// Interface for Init systems. Init() will be called before Run(). 25 | /// 26 | public interface IEcsInitSystem : IEcsSystem { 27 | void Init (); 28 | } 29 | 30 | /// 31 | /// Interface for PostDestroy systems. PostDestroy() will be called after Destroy(). 32 | /// 33 | public interface IEcsPostDestroySystem : IEcsSystem { 34 | void PostDestroy (); 35 | } 36 | 37 | /// 38 | /// Interface for Destroy systems. Destroy() will be called last in system lifetime cycle. 39 | /// 40 | public interface IEcsDestroySystem : IEcsSystem { 41 | void Destroy (); 42 | } 43 | 44 | /// 45 | /// Interface for Run systems. 46 | /// 47 | public interface IEcsRunSystem : IEcsSystem { 48 | void Run (); 49 | } 50 | 51 | #if DEBUG 52 | /// 53 | /// Debug interface for systems events processing. 54 | /// 55 | public interface IEcsSystemsDebugListener { 56 | void OnSystemsDestroyed (EcsSystems systems); 57 | } 58 | #endif 59 | 60 | /// 61 | /// Logical group of systems. 62 | /// 63 | #if ENABLE_IL2CPP 64 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] 65 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] 66 | #endif 67 | public sealed class EcsSystems : IEcsInitSystem, IEcsDestroySystem, IEcsRunSystem { 68 | public readonly string Name; 69 | public readonly EcsWorld World; 70 | readonly EcsGrowList _allSystems = new EcsGrowList (64); 71 | readonly EcsGrowList _runSystems = new EcsGrowList (64); 72 | readonly Dictionary _namedRunSystems = new Dictionary (64); 73 | readonly Dictionary _injections = new Dictionary (32); 74 | bool _injected; 75 | #if DEBUG 76 | bool _initialized; 77 | bool _destroyed; 78 | readonly List _debugListeners = new List (4); 79 | 80 | /// 81 | /// Adds external event listener. 82 | /// 83 | /// Event listener. 84 | public void AddDebugListener (IEcsSystemsDebugListener listener) { 85 | if (listener == null) { throw new Exception ("listener is null"); } 86 | _debugListeners.Add (listener); 87 | } 88 | 89 | /// 90 | /// Removes external event listener. 91 | /// 92 | /// Event listener. 93 | public void RemoveDebugListener (IEcsSystemsDebugListener listener) { 94 | if (listener == null) { throw new Exception ("listener is null"); } 95 | _debugListeners.Remove (listener); 96 | } 97 | #endif 98 | 99 | /// 100 | /// Creates new instance of EcsSystems group. 101 | /// 102 | /// EcsWorld instance. 103 | /// Custom name for this group. 104 | public EcsSystems (EcsWorld world, string name = null) { 105 | World = world; 106 | Name = name; 107 | } 108 | 109 | /// 110 | /// Adds new system to processing. 111 | /// 112 | /// System instance. 113 | /// Optional name of system. 114 | public EcsSystems Add (IEcsSystem system, string namedRunSystem = null) { 115 | #if DEBUG 116 | if (system == null) { throw new Exception ("System is null."); } 117 | if (_initialized) { throw new Exception ("Cant add system after initialization."); } 118 | if (_destroyed) { throw new Exception ("Cant touch after destroy."); } 119 | if (!string.IsNullOrEmpty (namedRunSystem) && !(system is IEcsRunSystem)) { throw new Exception ("Cant name non-IEcsRunSystem."); } 120 | #endif 121 | _allSystems.Add (system); 122 | if (system is IEcsRunSystem) { 123 | if (namedRunSystem == null && system is EcsSystems ecsSystems) { 124 | namedRunSystem = ecsSystems.Name; 125 | } 126 | if (namedRunSystem != null) { 127 | #if DEBUG 128 | if (_namedRunSystems.ContainsKey (namedRunSystem.GetHashCode ())) { 129 | throw new Exception ($"Cant add named system - \"{namedRunSystem}\" name already exists."); 130 | } 131 | #endif 132 | _namedRunSystems[namedRunSystem.GetHashCode ()] = _runSystems.Count; 133 | } 134 | _runSystems.Add (new EcsSystemsRunItem { Active = true, System = (IEcsRunSystem) system }); 135 | } 136 | return this; 137 | } 138 | 139 | public int GetNamedRunSystem (string name) { 140 | return _namedRunSystems.TryGetValue (name.GetHashCode (), out var idx) ? idx : -1; 141 | } 142 | 143 | /// 144 | /// Sets IEcsRunSystem active state. 145 | /// 146 | /// Index of system. 147 | /// New state of system. 148 | public void SetRunSystemState (int idx, bool state) { 149 | #if DEBUG 150 | if (idx < 0 || idx >= _runSystems.Count) { throw new Exception ("Invalid index"); } 151 | #endif 152 | _runSystems.Items[idx].Active = state; 153 | } 154 | 155 | /// 156 | /// Gets IEcsRunSystem active state. 157 | /// 158 | /// Index of system. 159 | public bool GetRunSystemState (int idx) { 160 | #if DEBUG 161 | if (idx < 0 || idx >= _runSystems.Count) { throw new Exception ("Invalid index"); } 162 | #endif 163 | return _runSystems.Items[idx].Active; 164 | } 165 | 166 | /// 167 | /// Get all systems. Important: Don't change collection! 168 | /// 169 | public EcsGrowList GetAllSystems () { 170 | return _allSystems; 171 | } 172 | 173 | /// 174 | /// Gets all run systems. Important: Don't change collection! 175 | /// 176 | public EcsGrowList GetRunSystems () { 177 | return _runSystems; 178 | } 179 | 180 | /// 181 | /// Injects instance of object type to all compatible fields of added systems. 182 | /// 183 | /// Instance. 184 | /// Overriden type, if null - typeof(obj) will be used. 185 | public EcsSystems Inject (object obj, Type overridenType = null) { 186 | #if DEBUG 187 | if (_initialized) { throw new Exception ("Cant inject after initialization."); } 188 | if (obj == null) { throw new Exception ("Cant inject null instance."); } 189 | if (overridenType != null && !overridenType.IsInstanceOfType (obj)) { throw new Exception ("Invalid overriden type."); } 190 | #endif 191 | if (overridenType == null) { 192 | overridenType = obj.GetType (); 193 | } 194 | _injections[overridenType] = obj; 195 | return this; 196 | } 197 | 198 | /// 199 | /// Processes injections immediately. 200 | /// Can be used to DI before Init() call. 201 | /// 202 | public EcsSystems ProcessInjects () { 203 | #if DEBUG 204 | if (_initialized) { throw new Exception ("Cant inject after initialization."); } 205 | if (_destroyed) { throw new Exception ("Cant touch after destroy."); } 206 | #endif 207 | if (!_injected) { 208 | _injected = true; 209 | for (int i = 0, iMax = _allSystems.Count; i < iMax; i++) { 210 | if (_allSystems.Items[i] is EcsSystems nestedSystems) { 211 | foreach (var pair in _injections) { 212 | nestedSystems._injections[pair.Key] = pair.Value; 213 | } 214 | nestedSystems.ProcessInjects (); 215 | } else { 216 | InjectDataToSystem (_allSystems.Items[i], World, _injections); 217 | } 218 | } 219 | } 220 | return this; 221 | } 222 | 223 | /// 224 | /// Registers component type as one-frame for auto-removing at this point in execution sequence. 225 | /// 226 | public EcsSystems OneFrame () where T : struct { 227 | return Add (new RemoveOneFrame ()); 228 | } 229 | 230 | /// 231 | /// Closes registration for new systems, initialize all registered. 232 | /// 233 | public void Init () { 234 | #if DEBUG 235 | if (_initialized) { throw new Exception ("Already initialized."); } 236 | if (_destroyed) { throw new Exception ("Cant touch after destroy."); } 237 | #endif 238 | ProcessInjects (); 239 | // IEcsPreInitSystem processing. 240 | for (int i = 0, iMax = _allSystems.Count; i < iMax; i++) { 241 | var system = _allSystems.Items[i]; 242 | if (system is IEcsPreInitSystem preInitSystem) { 243 | preInitSystem.PreInit (); 244 | #if DEBUG 245 | World.CheckForLeakedEntities ($"{preInitSystem.GetType ().Name}.PreInit()"); 246 | #endif 247 | } 248 | } 249 | // IEcsInitSystem processing. 250 | for (int i = 0, iMax = _allSystems.Count; i < iMax; i++) { 251 | var system = _allSystems.Items[i]; 252 | if (system is IEcsInitSystem initSystem) { 253 | initSystem.Init (); 254 | #if DEBUG 255 | World.CheckForLeakedEntities ($"{initSystem.GetType ().Name}.Init()"); 256 | #endif 257 | } 258 | } 259 | #if DEBUG 260 | _initialized = true; 261 | #endif 262 | } 263 | 264 | /// 265 | /// Processes all IEcsRunSystem systems. 266 | /// 267 | public void Run () { 268 | #if DEBUG 269 | if (!_initialized) { throw new Exception ($"[{Name ?? "NONAME"}] EcsSystems should be initialized before."); } 270 | if (_destroyed) { throw new Exception ("Cant touch after destroy."); } 271 | #endif 272 | for (int i = 0, iMax = _runSystems.Count; i < iMax; i++) { 273 | var runItem = _runSystems.Items[i]; 274 | if (runItem.Active) { 275 | runItem.System.Run (); 276 | } 277 | #if DEBUG 278 | if (World.CheckForLeakedEntities (null)) { 279 | throw new Exception ($"Empty entity detected, possible memory leak in {_runSystems.Items[i].GetType ().Name}.Run ()"); 280 | } 281 | #endif 282 | } 283 | } 284 | 285 | /// 286 | /// Destroys registered data. 287 | /// 288 | public void Destroy () { 289 | #if DEBUG 290 | if (_destroyed) { throw new Exception ("Already destroyed."); } 291 | _destroyed = true; 292 | #endif 293 | // IEcsDestroySystem processing. 294 | for (var i = _allSystems.Count - 1; i >= 0; i--) { 295 | var system = _allSystems.Items[i]; 296 | if (system is IEcsDestroySystem destroySystem) { 297 | destroySystem.Destroy (); 298 | #if DEBUG 299 | World.CheckForLeakedEntities ($"{destroySystem.GetType ().Name}.Destroy ()"); 300 | #endif 301 | } 302 | } 303 | // IEcsPostDestroySystem processing. 304 | for (var i = _allSystems.Count - 1; i >= 0; i--) { 305 | var system = _allSystems.Items[i]; 306 | if (system is IEcsPostDestroySystem postDestroySystem) { 307 | postDestroySystem.PostDestroy (); 308 | #if DEBUG 309 | World.CheckForLeakedEntities ($"{postDestroySystem.GetType ().Name}.PostDestroy ()"); 310 | #endif 311 | } 312 | } 313 | #if DEBUG 314 | for (int i = 0, iMax = _debugListeners.Count; i < iMax; i++) { 315 | _debugListeners[i].OnSystemsDestroyed (this); 316 | } 317 | #endif 318 | } 319 | 320 | /// 321 | /// Injects custom data to fields of ISystem instance. 322 | /// 323 | /// ISystem instance. 324 | /// EcsWorld instance. 325 | /// Additional instances for injection. 326 | public static void InjectDataToSystem (IEcsSystem system, EcsWorld world, Dictionary injections) { 327 | var systemType = system.GetType (); 328 | var worldType = world.GetType (); 329 | var filterType = typeof (EcsFilter); 330 | var ignoreType = typeof (EcsIgnoreInjectAttribute); 331 | 332 | foreach (var f in systemType.GetFields (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { 333 | // skip statics or fields with [EcsIgnoreInject] attribute. 334 | if (f.IsStatic || Attribute.IsDefined (f, ignoreType)) { 335 | continue; 336 | } 337 | // EcsWorld 338 | if (f.FieldType.IsAssignableFrom (worldType)) { 339 | f.SetValue (system, world); 340 | continue; 341 | } 342 | // EcsFilter 343 | #if DEBUG 344 | if (f.FieldType == filterType) { 345 | throw new Exception ($"Cant use EcsFilter type at \"{system}\" system for dependency injection, use generic version instead"); 346 | } 347 | #endif 348 | if (f.FieldType.IsSubclassOf (filterType)) { 349 | f.SetValue (system, world.GetFilter (f.FieldType)); 350 | continue; 351 | } 352 | // Other injections. 353 | foreach (var pair in injections) { 354 | if (f.FieldType.IsAssignableFrom (pair.Key)) { 355 | f.SetValue (system, pair.Value); 356 | break; 357 | } 358 | } 359 | } 360 | } 361 | } 362 | 363 | /// 364 | /// System for removing OneFrame component. 365 | /// 366 | /// OneFrame component type. 367 | sealed class RemoveOneFrame : IEcsRunSystem where T : struct { 368 | readonly EcsFilter _oneFrames = null; 369 | 370 | void IEcsRunSystem.Run () { 371 | for (var idx = _oneFrames.GetEntitiesCount () - 1; idx >= 0; idx--) { 372 | _oneFrames.GetEntity (idx).Del (); 373 | } 374 | } 375 | } 376 | 377 | /// 378 | /// IEcsRunSystem instance with active state. 379 | /// 380 | public sealed class EcsSystemsRunItem { 381 | public bool Active; 382 | public IEcsRunSystem System; 383 | } 384 | } -------------------------------------------------------------------------------- /src/EcsSystem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0a3385433fb6a4b948a3f15a7c829025 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /src/EcsWorld.cs: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // The Proprietary or MIT-Red License 3 | // Copyright (c) 2012-2023 Leopotam 4 | // ---------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Globalization; 9 | using System.Reflection; 10 | using System.Runtime.CompilerServices; 11 | using System.Runtime.InteropServices; 12 | 13 | namespace Leopotam.Ecs { 14 | /// 15 | /// Ecs data context. 16 | /// 17 | #if ENABLE_IL2CPP 18 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] 19 | [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] 20 | #endif 21 | // ReSharper disable once ClassWithVirtualMembersNeverInherited.Global 22 | public class EcsWorld { 23 | protected EcsEntityData[] Entities; 24 | protected int EntitiesCount; 25 | protected readonly EcsGrowList FreeEntities; 26 | protected readonly EcsGrowList Filters; 27 | protected readonly Dictionary> FilterByIncludedComponents; 28 | protected readonly Dictionary> FilterByExcludedComponents; 29 | 30 | // just for world stats. 31 | int _usedComponentsCount; 32 | 33 | internal readonly EcsWorldConfig Config; 34 | readonly object[] _filterCtor; 35 | 36 | /// 37 | /// Creates new ecs-world instance. 38 | /// 39 | /// Optional config for default cache sizes. On zero or negative value - default value will be used. 40 | public EcsWorld (EcsWorldConfig config = default) { 41 | var finalConfig = new EcsWorldConfig { 42 | EntityComponentsCacheSize = config.EntityComponentsCacheSize <= 0 43 | ? EcsWorldConfig.DefaultEntityComponentsCacheSize 44 | : config.EntityComponentsCacheSize, 45 | FilterEntitiesCacheSize = config.FilterEntitiesCacheSize <= 0 46 | ? EcsWorldConfig.DefaultFilterEntitiesCacheSize 47 | : config.FilterEntitiesCacheSize, 48 | WorldEntitiesCacheSize = config.WorldEntitiesCacheSize <= 0 49 | ? EcsWorldConfig.DefaultWorldEntitiesCacheSize 50 | : config.WorldEntitiesCacheSize, 51 | WorldFiltersCacheSize = config.WorldFiltersCacheSize <= 0 52 | ? EcsWorldConfig.DefaultWorldFiltersCacheSize 53 | : config.WorldFiltersCacheSize, 54 | WorldComponentPoolsCacheSize = config.WorldComponentPoolsCacheSize <= 0 55 | ? EcsWorldConfig.DefaultWorldComponentPoolsCacheSize 56 | : config.WorldComponentPoolsCacheSize 57 | }; 58 | Config = finalConfig; 59 | Entities = new EcsEntityData[Config.WorldEntitiesCacheSize]; 60 | FreeEntities = new EcsGrowList (Config.WorldEntitiesCacheSize); 61 | Filters = new EcsGrowList (Config.WorldFiltersCacheSize); 62 | FilterByIncludedComponents = new Dictionary> (Config.WorldFiltersCacheSize); 63 | FilterByExcludedComponents = new Dictionary> (Config.WorldFiltersCacheSize); 64 | ComponentPools = new IEcsComponentPool[Config.WorldComponentPoolsCacheSize]; 65 | _filterCtor = new object[] { this }; 66 | } 67 | 68 | /// 69 | /// Component pools cache. 70 | /// 71 | public IEcsComponentPool[] ComponentPools; 72 | 73 | protected bool IsDestroyed; 74 | #if DEBUG 75 | internal readonly List DebugListeners = new List (4); 76 | readonly EcsGrowList _leakedEntities = new EcsGrowList (256); 77 | bool _inDestroying; 78 | 79 | /// 80 | /// Adds external event listener. 81 | /// 82 | /// Event listener. 83 | public void AddDebugListener (IEcsWorldDebugListener listener) { 84 | if (listener == null) { throw new Exception ("Listener is null."); } 85 | DebugListeners.Add (listener); 86 | } 87 | 88 | /// 89 | /// Removes external event listener. 90 | /// 91 | /// Event listener. 92 | public void RemoveDebugListener (IEcsWorldDebugListener listener) { 93 | if (listener == null) { throw new Exception ("Listener is null."); } 94 | DebugListeners.Remove (listener); 95 | } 96 | #endif 97 | 98 | /// 99 | /// Destroys world and exist entities. 100 | /// 101 | public virtual void Destroy () { 102 | #if DEBUG 103 | if (IsDestroyed || _inDestroying) { throw new Exception ("EcsWorld already destroyed."); } 104 | _inDestroying = true; 105 | CheckForLeakedEntities ("Destroy"); 106 | #endif 107 | EcsEntity entity; 108 | entity.Owner = this; 109 | for (var i = EntitiesCount - 1; i >= 0; i--) { 110 | ref var entityData = ref Entities[i]; 111 | if (entityData.ComponentsCountX2 > 0) { 112 | entity.Id = i; 113 | entity.Gen = entityData.Gen; 114 | entity.Destroy (); 115 | } 116 | } 117 | for (int i = 0, iMax = Filters.Count; i < iMax; i++) { 118 | Filters.Items[i].Destroy (); 119 | } 120 | 121 | IsDestroyed = true; 122 | #if DEBUG 123 | for (var i = DebugListeners.Count - 1; i >= 0; i--) { 124 | DebugListeners[i].OnWorldDestroyed (this); 125 | } 126 | #endif 127 | } 128 | 129 | /// 130 | /// Is world not destroyed. 131 | /// 132 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 133 | public bool IsAlive () { 134 | return !IsDestroyed; 135 | } 136 | 137 | /// 138 | /// Creates new entity. 139 | /// 140 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 141 | public EcsEntity NewEntity () { 142 | #if DEBUG 143 | if (IsDestroyed) { throw new Exception ("EcsWorld already destroyed."); } 144 | #endif 145 | EcsEntity entity; 146 | entity.Owner = this; 147 | // try to reuse entity from pool. 148 | if (FreeEntities.Count > 0) { 149 | entity.Id = FreeEntities.Items[--FreeEntities.Count]; 150 | ref var entityData = ref Entities[entity.Id]; 151 | entity.Gen = entityData.Gen; 152 | entityData.ComponentsCountX2 = 0; 153 | } else { 154 | // create new entity. 155 | if (EntitiesCount == Entities.Length) { 156 | Array.Resize (ref Entities, EntitiesCount << 1); 157 | } 158 | entity.Id = EntitiesCount++; 159 | ref var entityData = ref Entities[entity.Id]; 160 | entityData.Components = new int[Config.EntityComponentsCacheSize * 2]; 161 | entityData.Gen = 1; 162 | entity.Gen = entityData.Gen; 163 | entityData.ComponentsCountX2 = 0; 164 | } 165 | #if DEBUG 166 | _leakedEntities.Add (entity); 167 | foreach (var debugListener in DebugListeners) { 168 | debugListener.OnEntityCreated (entity); 169 | } 170 | #endif 171 | return entity; 172 | } 173 | 174 | /// 175 | /// Restores EcsEntity from internal id and gen. For internal use only! 176 | /// 177 | /// Internal id. 178 | /// Generation. If less than 0 - will be filled from current generation value. 179 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 180 | public EcsEntity RestoreEntityFromInternalId (int id, int gen = -1) { 181 | EcsEntity entity; 182 | entity.Owner = this; 183 | entity.Id = id; 184 | if (gen < 0) { 185 | entity.Gen = 0; 186 | ref var entityData = ref GetEntityData (entity); 187 | entity.Gen = entityData.Gen; 188 | } else { 189 | entity.Gen = (ushort) gen; 190 | } 191 | return entity; 192 | } 193 | 194 | /// 195 | /// Request exist filter or create new one. For internal use only! 196 | /// 197 | /// Filter type. 198 | /// Create filter if not exists. 199 | public EcsFilter GetFilter (Type filterType, bool createIfNotExists = true) { 200 | #if DEBUG 201 | if (filterType == null) { throw new Exception ("FilterType is null."); } 202 | if (!filterType.IsSubclassOf (typeof (EcsFilter))) { throw new Exception ($"Invalid filter type: {filterType}."); } 203 | if (IsDestroyed) { throw new Exception ("EcsWorld already destroyed."); } 204 | #endif 205 | // check already exist filters. 206 | for (int i = 0, iMax = Filters.Count; i < iMax; i++) { 207 | if (Filters.Items[i].GetType () == filterType) { 208 | return Filters.Items[i]; 209 | } 210 | } 211 | if (!createIfNotExists) { 212 | return null; 213 | } 214 | // create new filter. 215 | var filter = (EcsFilter) Activator.CreateInstance (filterType, BindingFlags.NonPublic | BindingFlags.Instance, null, _filterCtor, CultureInfo.InvariantCulture); 216 | #if DEBUG 217 | for (var filterIdx = 0; filterIdx < Filters.Count; filterIdx++) { 218 | if (filter.AreComponentsSame (Filters.Items[filterIdx])) { 219 | throw new Exception ( 220 | $"Invalid filter \"{filter.GetType ()}\": Another filter \"{Filters.Items[filterIdx].GetType ()}\" already has same components, but in different order."); 221 | } 222 | } 223 | #endif 224 | Filters.Add (filter); 225 | // add to component dictionaries for fast compatibility scan. 226 | for (int i = 0, iMax = filter.IncludedTypeIndices.Length; i < iMax; i++) { 227 | if (!FilterByIncludedComponents.TryGetValue (filter.IncludedTypeIndices[i], out var filtersList)) { 228 | filtersList = new EcsGrowList (8); 229 | FilterByIncludedComponents[filter.IncludedTypeIndices[i]] = filtersList; 230 | } 231 | filtersList.Add (filter); 232 | } 233 | if (filter.ExcludedTypeIndices != null) { 234 | for (int i = 0, iMax = filter.ExcludedTypeIndices.Length; i < iMax; i++) { 235 | if (!FilterByExcludedComponents.TryGetValue (filter.ExcludedTypeIndices[i], out var filtersList)) { 236 | filtersList = new EcsGrowList (8); 237 | FilterByExcludedComponents[filter.ExcludedTypeIndices[i]] = filtersList; 238 | } 239 | filtersList.Add (filter); 240 | } 241 | } 242 | #if DEBUG 243 | foreach (var debugListener in DebugListeners) { 244 | debugListener.OnFilterCreated (filter); 245 | } 246 | #endif 247 | // scan exist entities for compatibility with new filter. 248 | EcsEntity entity; 249 | entity.Owner = this; 250 | for (int i = 0, iMax = EntitiesCount; i < iMax; i++) { 251 | ref var entityData = ref Entities[i]; 252 | if (entityData.ComponentsCountX2 > 0 && filter.IsCompatible (entityData, 0)) { 253 | entity.Id = i; 254 | entity.Gen = entityData.Gen; 255 | filter.OnAddEntity (entity); 256 | } 257 | } 258 | return filter; 259 | } 260 | 261 | /// 262 | /// Gets stats of internal data. 263 | /// 264 | public EcsWorldStats GetStats () { 265 | var stats = new EcsWorldStats () { 266 | ActiveEntities = EntitiesCount - FreeEntities.Count, 267 | ReservedEntities = FreeEntities.Count, 268 | Filters = Filters.Count, 269 | Components = _usedComponentsCount 270 | }; 271 | return stats; 272 | } 273 | 274 | /// 275 | /// Recycles internal entity data to pool. 276 | /// 277 | /// Entity id. 278 | /// Entity internal data. 279 | protected internal void RecycleEntityData (int id, ref EcsEntityData entityData) { 280 | #if DEBUG 281 | if (entityData.ComponentsCountX2 != 0) { throw new Exception ("Cant recycle invalid entity."); } 282 | #endif 283 | entityData.ComponentsCountX2 = -2; 284 | entityData.Gen++; 285 | if (entityData.Gen == 0) { entityData.Gen = 1; } 286 | FreeEntities.Add (id); 287 | } 288 | 289 | #if DEBUG 290 | /// 291 | /// Checks exist entities but without components. 292 | /// 293 | /// Prefix for error message. 294 | public bool CheckForLeakedEntities (string errorMsg) { 295 | if (_leakedEntities.Count > 0) { 296 | for (int i = 0, iMax = _leakedEntities.Count; i < iMax; i++) { 297 | if (GetEntityData (_leakedEntities.Items[i]).ComponentsCountX2 == 0) { 298 | if (errorMsg != null) { 299 | throw new Exception ($"{errorMsg}: Empty entity detected, possible memory leak."); 300 | } 301 | return true; 302 | } 303 | } 304 | _leakedEntities.Count = 0; 305 | } 306 | return false; 307 | } 308 | #endif 309 | 310 | /// 311 | /// Updates filters. 312 | /// 313 | /// Component type index.abstract Positive for add operation, negative for remove operation. 314 | /// Target entity. 315 | /// Target entity data. 316 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 317 | protected internal void UpdateFilters (int typeIdx, in EcsEntity entity, in EcsEntityData entityData) { 318 | #if DEBUG 319 | if (IsDestroyed) { throw new Exception ("EcsWorld already destroyed."); } 320 | #endif 321 | EcsGrowList filters; 322 | if (typeIdx < 0) { 323 | // remove component. 324 | if (FilterByIncludedComponents.TryGetValue (-typeIdx, out filters)) { 325 | for (int i = 0, iMax = filters.Count; i < iMax; i++) { 326 | if (filters.Items[i].IsCompatible (entityData, 0)) { 327 | #if DEBUG 328 | if (!filters.Items[i].GetInternalEntitiesMap ().TryGetValue (entity.GetInternalId (), out var filterIdx)) { filterIdx = -1; } 329 | if (filterIdx < 0) { throw new Exception ("Entity not in filter."); } 330 | #endif 331 | filters.Items[i].OnRemoveEntity (entity); 332 | } 333 | } 334 | } 335 | if (FilterByExcludedComponents.TryGetValue (-typeIdx, out filters)) { 336 | for (int i = 0, iMax = filters.Count; i < iMax; i++) { 337 | if (filters.Items[i].IsCompatible (entityData, typeIdx)) { 338 | #if DEBUG 339 | if (!filters.Items[i].GetInternalEntitiesMap ().TryGetValue (entity.GetInternalId (), out var filterIdx)) { filterIdx = -1; } 340 | if (filterIdx >= 0) { throw new Exception ("Entity already in filter."); } 341 | #endif 342 | filters.Items[i].OnAddEntity (entity); 343 | } 344 | } 345 | } 346 | } else { 347 | // add component. 348 | if (FilterByIncludedComponents.TryGetValue (typeIdx, out filters)) { 349 | for (int i = 0, iMax = filters.Count; i < iMax; i++) { 350 | if (filters.Items[i].IsCompatible (entityData, 0)) { 351 | #if DEBUG 352 | if (!filters.Items[i].GetInternalEntitiesMap ().TryGetValue (entity.GetInternalId (), out var filterIdx)) { filterIdx = -1; } 353 | if (filterIdx >= 0) { throw new Exception ("Entity already in filter."); } 354 | #endif 355 | filters.Items[i].OnAddEntity (entity); 356 | } 357 | } 358 | } 359 | if (FilterByExcludedComponents.TryGetValue (typeIdx, out filters)) { 360 | for (int i = 0, iMax = filters.Count; i < iMax; i++) { 361 | if (filters.Items[i].IsCompatible (entityData, -typeIdx)) { 362 | #if DEBUG 363 | if (!filters.Items[i].GetInternalEntitiesMap ().TryGetValue (entity.GetInternalId (), out var filterIdx)) { filterIdx = -1; } 364 | if (filterIdx < 0) { throw new Exception ("Entity not in filter."); } 365 | #endif 366 | filters.Items[i].OnRemoveEntity (entity); 367 | } 368 | } 369 | } 370 | } 371 | } 372 | 373 | /// 374 | /// Returns internal state of entity. For internal use! 375 | /// 376 | /// Entity. 377 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 378 | public ref EcsEntityData GetEntityData (in EcsEntity entity) { 379 | #if DEBUG 380 | if (IsDestroyed) { throw new Exception ("EcsWorld already destroyed."); } 381 | if (entity.Id < 0 || entity.Id > EntitiesCount) { throw new Exception ($"Invalid entity {entity.Id}"); } 382 | #endif 383 | return ref Entities[entity.Id]; 384 | } 385 | 386 | /// 387 | /// Internal state of entity. 388 | /// 389 | [StructLayout (LayoutKind.Sequential, Pack = 2)] 390 | public struct EcsEntityData { 391 | public ushort Gen; 392 | public short ComponentsCountX2; 393 | public int[] Components; 394 | } 395 | 396 | [MethodImpl (MethodImplOptions.AggressiveInlining)] 397 | public EcsComponentPool GetPool () where T : struct { 398 | var typeIdx = EcsComponentType.TypeIndex; 399 | if (ComponentPools.Length <= typeIdx) { 400 | var len = ComponentPools.Length << 1; 401 | while (len <= typeIdx) { 402 | len <<= 1; 403 | } 404 | Array.Resize (ref ComponentPools, len); 405 | } 406 | var pool = (EcsComponentPool) ComponentPools[typeIdx]; 407 | if (pool == null) { 408 | pool = new EcsComponentPool (); 409 | ComponentPools[typeIdx] = pool; 410 | _usedComponentsCount++; 411 | } 412 | return pool; 413 | } 414 | 415 | /// 416 | /// Gets all alive entities. 417 | /// 418 | /// List to put results in it. if null - will be created. If not enough space - will be resized. 419 | /// Amount of alive entities. 420 | public int GetAllEntities (ref EcsEntity[] entities) { 421 | var count = EntitiesCount - FreeEntities.Count; 422 | if (entities == null || entities.Length < count) { 423 | entities = new EcsEntity[count]; 424 | } 425 | EcsEntity e; 426 | e.Owner = this; 427 | var id = 0; 428 | for (int i = 0, iMax = EntitiesCount; i < iMax; i++) { 429 | ref var entityData = ref Entities[i]; 430 | // should we skip empty entities here? 431 | if (entityData.ComponentsCountX2 >= 0) { 432 | e.Id = i; 433 | e.Gen = entityData.Gen; 434 | entities[id++] = e; 435 | } 436 | } 437 | return count; 438 | } 439 | } 440 | 441 | /// 442 | /// Stats of EcsWorld instance. 443 | /// 444 | public struct EcsWorldStats { 445 | /// 446 | /// Amount of active entities. 447 | /// 448 | public int ActiveEntities; 449 | 450 | /// 451 | /// Amount of cached (not in use) entities. 452 | /// 453 | public int ReservedEntities; 454 | 455 | /// 456 | /// Amount of registered filters. 457 | /// 458 | public int Filters; 459 | 460 | /// 461 | /// Amount of registered component types. 462 | /// 463 | public int Components; 464 | } 465 | 466 | /// 467 | /// World config to setup default caches. 468 | /// 469 | public struct EcsWorldConfig { 470 | /// 471 | /// World.Entities cache size. 472 | /// 473 | public int WorldEntitiesCacheSize; 474 | /// 475 | /// World.Filters cache size. 476 | /// 477 | public int WorldFiltersCacheSize; 478 | /// 479 | /// World.ComponentPools cache size. 480 | /// 481 | public int WorldComponentPoolsCacheSize; 482 | /// 483 | /// Entity.Components cache size (not doubled). 484 | /// 485 | public int EntityComponentsCacheSize; 486 | /// 487 | /// Filter.Entities cache size. 488 | /// 489 | public int FilterEntitiesCacheSize; 490 | /// 491 | /// World.Entities default cache size. 492 | /// 493 | public const int DefaultWorldEntitiesCacheSize = 1024; 494 | /// 495 | /// World.Filters default cache size. 496 | /// 497 | public const int DefaultWorldFiltersCacheSize = 128; 498 | /// 499 | /// World.ComponentPools default cache size. 500 | /// 501 | public const int DefaultWorldComponentPoolsCacheSize = 512; 502 | /// 503 | /// Entity.Components default cache size (not doubled). 504 | /// 505 | public const int DefaultEntityComponentsCacheSize = 8; 506 | /// 507 | /// Filter.Entities default cache size. 508 | /// 509 | public const int DefaultFilterEntitiesCacheSize = 256; 510 | } 511 | 512 | #if DEBUG 513 | /// 514 | /// Debug interface for world events processing. 515 | /// 516 | public interface IEcsWorldDebugListener { 517 | void OnEntityCreated (EcsEntity entity); 518 | void OnEntityDestroyed (EcsEntity entity); 519 | void OnFilterCreated (EcsFilter filter); 520 | void OnComponentListChanged (EcsEntity entity); 521 | void OnWorldDestroyed (EcsWorld world); 522 | } 523 | #endif 524 | } -------------------------------------------------------------------------------- /src/EcsWorld.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 15cce7e7960f14f1b89fd3f1f13e9d13 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | --------------------------------------------------------------------------------