diff --git a/README-RU.md b/README-RU.md index 72987df..f172df3 100644 --- a/README-RU.md +++ b/README-RU.md @@ -13,41 +13,49 @@ | Languages: | [Русский](https://github.com/DCFApixels/DragonECS/blob/main/README-RU.md) | [English(WIP)](https://github.com/DCFApixels/DragonECS) | | :--- | :--- | :--- | -Данный [ECS](https://en.wikipedia.org/wiki/Entity_component_system) Фреймворк нацелен на максимальную удобность, модульность, расширяемость и производительность динамического изменения сущностей. Без генерации кода и зависимостей. -> [!Warning] +Данный [ECS](https://en.wikipedia.org/wiki/Entity_component_system) Фреймворк нацелен на максимальную удобность, модульность, расширяемость и производительность динамического изменения сущностей. Без генерации кода и зависимостей. Вднохновлен [LeoEcs](https://github.com/Leopotam/ecslite). + +> [!IMPORTANT] +> И с Новым Годом + +> [!WARNING] > Проект в стадии разработки. API может меняться. > Readme еще не завершен ## Оглавление -* [Установка](#Установка) - * [Unity-модуль](#Unity-модуль) - * [В виде иходников](#В-виде-иходников) - * [Версионирование](#Версионирование) -* [Основные концепции](#Основные-концепции) - * [Entity](#Entity) - * [Component](#Component) - * [System](#System) -* [Концепции фреймворка](#Концепции-фреймворка) - * [Пайплайн](#Пайплайн) - * [Построение](#Построение) - * [Внедрение зависимостей](#Внедрение-зависимостей) - * [Модули](#Модули) - * [Слои](#Слои) - * [Процессы](#Процессы) - * [Мир](#Мир) - * [Пул](#Пул) - * [Группа](#Группа) - * [Аспект](#Аспект) - * [Запрос](#Запрос) - * [Корень ECS](#Корень-ECS) - * [Пример для Unity](#Пример-для-Unity) - * [Общий пример](#Общий-пример) -* [Debug](#Debug) - * [Debug-Атрибуты](#Debug-Атрибуты) - * [EcsDebug](#EcsDebug) -* [Расширения](#Расширения) -* [FAQ](#FAQ) -* [Обратная связь](#Обратная-связь) +- [DragonECS - C# Entity Component System Framework](#dragonecs---c-entity-component-system-framework) + - [Оглавление](#оглавление) +- [Установка](#установка) + - [Версионирование](#версионирование) +- [Основные концепции](#основные-концепции) + - [Entity](#entity) + - [Component](#component) + - [System](#system) +- [Концепции фреймворка](#концепции-фреймворка) + - [Пайплайн](#пайплайн) + - [Построение](#построение) + - [Внедрение зависимостей](#внедрение-зависимостей) + - [Модули](#модули) + - [Слои](#слои) + - [Процессы](#процессы) + - [Мир](#мир) + - [Компоненты мира](#компоненты-мира) + - [Пул](#пул) + - [Аспект](#аспект) + - [Запросы](#запросы) + - [Группа](#группа) + - [Корень ECS](#корень-ecs) + - [Пример для Unity](#пример-для-unity) + - [Общий пример](#общий-пример) + - [Гибридность](#гибридность) +- [Debug](#debug) + - [Атрибуты](#атрибуты) + - [EcsDebug](#ecsdebug) + - [Профилирование](#профилирование) +- [Расширения](#расширения) +- [FAQ](#faq) + - ['ReadOnlySpan\<\>' could not be found](#readonlyspan-could-not-be-found) +- [Обратная связь](#обратная-связь)
@@ -72,7 +80,7 @@ https://github.com/DCFApixels/DragonECS.git * `entlong` - долговременный идентификатор, содержит в себе полный набор информации для однозначной идентификации; ``` csharp // Создание новой сущности в мире. -int entityID = _world.NewEmptyEntity(); +int entityID = _world.NewEntity(); // Удаление сущности. _world.DelEntity(entityID); @@ -119,7 +127,7 @@ struct PlayerTag : IEcsTagComponent {} Встроенные виды компонентов: * `IEcsComponent` - Компоненты с данными. * `IEcsTagComponent` - Компоненты-теги. Без данных. -> Компоненты-теги хоть и не имеют данных, само наличие или отсутствие компонента-тега у сущности уже несет информацию и может применяться для определения типа сущности. +* `IEcsHybridComponent` - Гибридные компоненты. Испольщуются для реализации [гибридности](#Гибридность). ## System **Системы** - это основная логика, тут задается поведение сущностей. Существуют в виде пользовательских классов, реализующих как минимум один из интерфейсов процессов. Основные процессы: @@ -127,16 +135,16 @@ struct PlayerTag : IEcsTagComponent {} class SomeSystem : IEcsPreInitProcess, IEcsInitProcess, IEcsRunProcess, IEcsDestroyProcess { // Будет вызван один раз в момент работы EcsPipeline.Init() и до срабатывания IEcsInitProcess.Init() - public void PreInit (EcsPipeline pipeline) { } + public void PreInit () { } // Будет вызван один раз в момент работы EcsPipeline.Init() и после срабатывания IEcsPreInitProcess.PreInit() - public void Init (EcsPipeline pipeline) { } + public void Init () { } // Будет вызван один раз в момент работы EcsPipeline.Run(). - public void Run (EcsPipeline pipeline) { } + public void Run () { } // Будет вызван один раз в момент работы EcsPipeline.Destroy() - public void Destroy (EcsPipeline pipeline) { } + public void Destroy () { } } ``` > Для реализации дополнительных процессов перейдите к разделу [Процессы](#Процессы). @@ -163,12 +171,45 @@ pipeline.Init(); // Инициализация пайплайна > Для одновременного построения и инициализации есть метод Builder.BuildAndInit(); ### Внедрение зависимостей Внедрение зависимостей - это процесс который запускается вместе с инициализацией пайплайна и внедряет данные переданные в Builder. + +> [!WARNING] +> Внедрение идет параллельно с PreInit, поэтому в PreInit инъекция - не гарантируется. +> [!WARNING] +> Экземпляр EcsPipeline автоматически внедряется до еще до PreInit. ``` c# +SomeData _someData; +//... EcsPipelone pipeline = EcsPipeline.New() //... .Inject(_someData) // Внедрит в системы экземпляр _someData //... .BuildAndInit(); + +//... + +class SomeSystem : IInject, IEcsRunProcess +{ + // Для внедрения используется интерфейс IInject и его метод Inject(T obj) + SomeData _someData + public void Inject(SomeData obj) => _someData = obj; + + public void PreInit () + { + // тут возможно еще не внедрен _someData + } + public void Init () + { + // тут можно пользовать _someData + } + public void Run () + { + // тут можно пользовать _someData + } + public void Destroy () + { + // тут можно пользовать _someData + } +} ``` ### Модули Группы систем реализующие общую фичу можно объединять в модули, и просто добавлять модули в Pipeline. @@ -198,13 +239,14 @@ const string SOME_LAYER = nameof(SOME_LAYER); EcsPipelone pipeline = EcsPipeline.New() //... .Layers.Insert(EcsConsts.END_LAYER, SOME_LAYER) // Вставляет новый слой перед конечным слоем EcsConsts.END_LAYER + .Add(New SomeSystem(), SOME_LAYER) // Система SomeSystem будет вставлена в слой SOME_LAYER //... .BuildAndInit(); ``` Встроенные слои расположены в следующем порядке: * `EcsConst.PRE_BEGIN_LAYER` * `EcsConst.BEGIN_LAYER` -* `EcsConst.BASIC_LAYER` (Если при добавблении системы не казать слой, то она будет доавблена сюда) +* `EcsConst.BASIC_LAYER` (Если при добавблении системы не указать слой, то она будет доавблена сюда) * `EcsConst.END_LAYER` * `EcsConst.POST_END_LAYER` @@ -224,8 +266,9 @@ EcsPipelone pipeline = EcsPipeline.New()
Пользовательские процессы -Для добавления нового процесса создайте интерфейс наследованный от `IEcsProcess` и создайте раннер для него. Раннер это класс реализующий интерфейс запускаемого процесса и наследуемый от EcsRunner. Пример: +Для добавления нового процесса создайте интерфейс наследованный от `IEcsProcess` и создайте раннер для него. Раннер это класс реализующий интерфейс запускаемого процесса и наследуемый от EcsRunner. А после к интерфейсу добавте атрибут `BindWithEcsRunner` для связи. Пример: ```c# +[BindWithEcsRunner(typeof(DoSomethingProcessRunner))] interface IDoSomethingProcess : IEcsProcess { void Do(); @@ -317,10 +360,11 @@ public struct WorldComponent : IEcsWorldComponent > Компоненты можно применять для создания расширений в связке с методами расширений. ## Пул -Является контейнером для компонентов, предоставляет методы для добавления/чтения/редактирования/удаления компонентов на сущности. Есть несколько видов пулов, для разных целей -* `EcsPool` - универсальный пул, хранит struct-компоненты реализующие интерфейс IEcsComponent; -* `EcsTagPool` - подходит для хранения пустых компонентов-тегов, в сравнении с EcsPool имеет лучше оптимизацию памяти и действий с пулом, хранит в себе struct-компоненты реализующие IEcsTagComponent; - +Является контейнером для компонентов, предоставляет методы для добавления/чтения/редактирования/удаления компонентов на сущности. Есть несколько видов пулов, для разных целей: +* `EcsPool` - универсальный пул, хранит struct-компоненты реализующие интерфейс `IEcsComponent`; +* `EcsTagPool` - подходит для хранения пустых компонентов-тегов, в сравнении с `EcsPool` имеет лучше оптимизацию памяти и скорости, хранит struct-компоненты `IEcsTagComponent`; +* `EcsHybridPool` - пул для гибридных компонентов. Испольщуются для реализации [гибридности](#Гибридность), хранит struct-компоненты `IEcsHybridComponent`; + Пулы имеют 5 основных метода и их разновидности: ``` csharp // Один из способов получить пул из мира. @@ -434,14 +478,7 @@ for (int i = 0; i < group.Count; i++) //... } ``` -Так как группы это множества, они содержат операции над множествами. Каждый метод имеет 2 варианта, с записью результата в groupA, либо с возвращением новой группы: - -
- Визуализация методов - -![Визуализация методов группы](https://github.com/DCFApixels/DragonECS/assets/99481254/f2c85a9f-949c-4908-9a02-acc3c883a22b) - -
+Так как группы это множества, они содержат методы аналогичные `ISet`. Редактирующие методы имеет 2 варианта, с записью результата в groupA, либо с возвращением новой группы: ``` c# // Объединение groupA и groupB @@ -563,40 +600,163 @@ public class EcsRoot } } ``` +## Гибридность +Для смешивания архитектурных подходов классического OOP и ECS используется специальный пул `EcsHybridPool`. Принцип работы этого пула несколько отличается от других и он упрощает поддержу наследования и полиморфизма. + +
+Как это работает? + +При добавлении элемента в пул, пул сканирует его иерархию наследования и реализуемые интерфейсы в поиске типов у которых есть интерфес `IEcsHybridComponent` и автоматически добавляет компонент в соответсвующие этим типам пулы. Таким же образом происходит удаление. Сканирвоание просиходит не для типа T а для типа экземпляра, поэтому в примере ниже строчка в `_world.GetPool().Add(entity, _rigidbody);` добавляет не только в пул `EcsHybridPool` но и в остальные. + +
+ +Пример использования: +``` csharp +public interface ITransform : IEcsHybridComponent +{ + Vector3 Position { get; set; } + // ... +} +public class Transform : ITransform +{ + public Vector3 Position { get; set; } + // ... +} +public class Rigidbody : Transform +{ + public Vector3 Position { get; set; } + public float Mass { get; set; } + // ... +} +public class Camera : ITransform +{ + Vector3 Position { get; set; } + // ... +} +public TransformAspect : EcsAspect +{ + public EcsHybridPool transforms; + public Aspect(Builder b) + { + transforms = b.Include(); + } +} +// ... + +EcsWorld _world; +Rigidbody _rigidbody; +// ... + +// Создадим пустую сущность. +int entity = _world.NewEmptyEntity(); +// Получаем пул EcsHybridPool и добавляем в него для сущности компонент _rigidbody. +// Если вместо ITransform подставить Transform или Rigidbody, то результат будет одинаковый +_world.GetPool().Add(entity, _rigidbody); +// ... + +//Все эти строчки вернут экземпляр _rigidbody. +ITransform iTransform = _world.GetPool().Get(entity); +Transform transform = _world.GetPool().Get(entity); +Rigidbody rigidbody = _world.GetPool().Get(entity); +//Исключение - отсутсвует компонент. Camera не является наследником или наследуемым классом для _rigidbody. +Camera camera = _world.GetPool().Get(entity); + +//Вернет True. Поэтому фишка гибридных пулов будет работать и в запросах сущностей +bool isMatches = _world.GetAspect().IsMatches(entity); + +//Все эти строчки вернут True. +bool isITransform = _world.GetPool().Has(entity); +bool isTransform = _world.GetPool().Has(entity); +bool isRigidbody = _world.GetPool().Has(entity); +//Эта строчка вернет False. +bool isCamera = _world.GetPool().Has(entity); +// ... + +// Удалим у сущности компонент. +_world.GetPool().Del(entity); +// ... +//Все эти строчки вернут False. +bool isITransform = _world.GetPool().Has(entity); +bool isTransform = _world.GetPool().Has(entity); +bool isRigidbody = _world.GetPool().Has(entity); +bool isCamera = _world.GetPool().Has(entity); +// ... +```
# Debug -Фреймворк предоставляет дополнительные инструменты для отладки и логирования, не зависящие от среды. -## Debug-Атрибуты -В чистом виде дебаг-атрибуты не имеют применения, но используются в интеграциях с движками для задания отображения в отладочных инструментах и редакторах. +Фреймворк предоставляет дополнительные инструменты для отладки и логирования, не зависящие от среды. Так же многие типы имеют свой DebuggerProxy для более информативного отображения в IDE. +## Атрибуты +В чистом виде мета-атрибуты не имеют применения, но могут быть использованы для генерации автоматической документации и используются в интеграциях с движками для задания отображения в отладочных инструментах и редакторах. ``` c# using DCFApixels.DragonECS; // Задает пользовательское название типа, по умолчанию используется имя типа. -[DebugName("SomeComponent")] - +[MetaName("SomeComponent")] + +// Используется для группировки типов. +[MetaGroup("Abilities/Passive/")] + // Задает цвет типа в системе rgb, где каждый канал принимает значение от 0 до 255, по умолчанию белый. -[DebugColor(DebugColor.Red)] // или [DebugColor(255, 0, 0)] +[MetaColor(MetaColor.Red)] // или [DebugColor(255, 0, 0)] // Добавляет описание типу. -[DebugDescription("The quick brown fox jumps over the lazy dog")] +[MetaDescription("The quick brown fox jumps over the lazy dog")] -// Скрывает тип. -[DebugHide] +// Добавляет строковые теги. +[MetaTags(...)] // [MetaTags(MetaTags.HIDDEN))] чтобы скрыть в редакторе public struct Component { } ``` ## EcsDebug -Имеет набор методов для отладки и логирования. Реализован как статический класс вызывающий методы Debug-сервисов. Debug-сервисы являются посредниками между системами отладки среды и EcsDebug. Это позволяет не изменяя отладочный код проекта, переносить его на другие движки, достаточно только реализовать специальный Debug-сервис. +Имеет набор методов для отладки и логирования. Реализован как статический класс вызывающий методы Debug-сервисов. Debug-сервисы - это посредники между системами отладки среды и EcsDebug. Это позволяет не изменяя отладочный код проекта, переносить проект на другие движки, достаточно только реализовать специальный Debug-сервис. По умолчанию используется `DefaultDebugService` который выводит логи в консоль. Для реализации пользовательского создайте класс наследуемый от `DebugService` и реализуйте абстрактные члены класса. -# Расширения -* [Автоматическое внедрение зависимостей](https://github.com/DCFApixels/DragonECS-AutoInjections) -* [Поддержка классической C# многопоточности](https://github.com/DCFApixels/DragonECS-ClassicThreads) -* Интеграция с движком Unity (Work in progress) + +``` csharp +// Логирование. +EcsDebug.Print("Message"); + +// Логирование с тегом. +EcsDebug.Print("Tag", "Message"); + +// Прерывание игры. +EcsDebug.Break(); + +// Установка другого Debug-Сервиса. +EcsDebug.Set(); +``` + +## Профилирование +``` csharp +// Создание маркера с именем SomeMarker. +private static readonly EcsProfilerMarker marker = new EcsProfilerMarker("SomeMarker"); + +... + +marker.Begin(); +// Код для которого замеряется скорость. +marker.End(); + +// или + +using (marker.Auto()) +{ + // Код для которого замеряется скорость. +} +```
+# Расширения +* [Автоматическое внедрение зависимостей](https://github.com/DCFApixels/DragonECS-AutoInjections) +* [Поддержка классической C# многопоточности](https://github.com/DCFApixels/DragonECS-ClassicThreads) +* Отношения (Work in progress) +* Интеграция с движком Unity (Work in progress) + + +
+ # FAQ ## 'ReadOnlySpan<>' could not be found В версии юнити 2020.1.х в консоли может выпадать ошибка: @@ -610,4 +770,4 @@ The type or namespace name 'ReadOnlySpan<>' could not be found (are you missing # Обратная связь Discord для дискуссий [https://discord.gg/kqmJjExuCf](https://discord.gg/kqmJjExuCf) -

+


diff --git a/README.md b/README.md index ad718ce..2c77255 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,14 @@ inspired by [LeoEcs](https://github.com/Leopotam/ecslite) | Languages: | [Русский](https://github.com/DCFApixels/DragonECS/blob/main/README-RU.md) | [English(WIP)](https://github.com/DCFApixels/DragonECS) | | :--- | :--- | :--- | -The [ECS](https://en.wikipedia.org/wiki/Entity_component_system) Framework aims to maximize usability, modularity, extensibility and performance of dynamic entity changes. Without code generation and dependencies -> **NOTICE:** The project is a work in progress, API may change. +The [ECS](https://en.wikipedia.org/wiki/Entity_component_system) Framework aims to maximize usability, modularity, extensibility and performance of dynamic entity changes. Without code generation and dependencies. Inspired by [LeoEcs](https://github.com/Leopotam/ecslite). + +> [!IMPORTANT] +> And a Happy New Year. + +> [!WARNING] +> The project is a work in progress, API may change. +> > While the English version of the README is incomplete, you can view the [Russian version](https://github.com/DCFApixels/DragonECS/blob/main/README-RU.md). # Versioning @@ -22,6 +28,7 @@ DragonECS uses this versioning semantics: [Open](https://gist.github.com/DCFApix # Extensions * [Dependency autoinjections](https://github.com/DCFApixels/DragonECS-AutoInjections) * [Classic C# multithreading support](https://github.com/DCFApixels/DragonECS-ClassicThreads) +* Relations (Work in progress) * Unity integration (Work in progress) # Feedback diff --git a/package.json b/package.json index d4e1240..3fe128b 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "displayName": "DragonECS", "description": "C# Entity Component System Framework", "unity": "2020.3", - "version": "0.7.8", + "version": "0.8.0", "repository": { "type": "git", "url": "https://github.com/DCFApixels/DragonECS.git" diff --git a/src/Builtin/Aspects.cs b/src/Builtin/Aspects.cs index 4844048..0e9c607 100644 --- a/src/Builtin/Aspects.cs +++ b/src/Builtin/Aspects.cs @@ -121,20 +121,20 @@ var combined = self.GetAspect>(); a0 = combined.a0; a1 = combined.a1; - return self.WhereFor>(sourceGroup); + return self.WhereToGroupFor>(sourceGroup); } public static EcsReadonlyGroup Where(this EcsWorld self) where A0 : EcsAspect where A1 : EcsAspect { - return self.Where>(); + return self.WhereToGroup>(); } public static EcsReadonlyGroup WhereFor(this EcsWorld self, EcsReadonlyGroup sourceGroup) where A0 : EcsAspect where A1 : EcsAspect { - return self.WhereFor>(sourceGroup); + return self.WhereToGroupFor>(sourceGroup); } #endregion @@ -155,7 +155,7 @@ a0 = combined.a0; a1 = combined.a1; a2 = combined.a2; - return self.WhereFor>(sourceGroup); + return self.WhereToGroupFor>(sourceGroup); } public static EcsReadonlyGroup Where(this EcsWorld self) @@ -163,14 +163,14 @@ where A1 : EcsAspect where A2 : EcsAspect { - return self.Where>(); + return self.WhereToGroup>(); } public static EcsReadonlyGroup WhereFor(this EcsWorld self, EcsReadonlyGroup sourceGroup) where A0 : EcsAspect where A1 : EcsAspect where A2 : EcsAspect { - return self.WhereFor>(sourceGroup); + return self.WhereToGroupFor>(sourceGroup); } #endregion @@ -194,7 +194,7 @@ a1 = combined.a1; a2 = combined.a2; a3 = combined.a3; - return self.WhereFor>(sourceGroup); + return self.WhereToGroupFor>(sourceGroup); } public static EcsReadonlyGroup Where(this EcsWorld self) @@ -203,7 +203,7 @@ where A2 : EcsAspect where A3 : EcsAspect { - return self.Where>(); + return self.WhereToGroup>(); } public static EcsReadonlyGroup WhereFor(this EcsWorld self, EcsReadonlyGroup sourceGroup) where A0 : EcsAspect @@ -211,7 +211,7 @@ where A2 : EcsAspect where A3 : EcsAspect { - return self.WhereFor>(sourceGroup); + return self.WhereToGroupFor>(sourceGroup); } #endregion @@ -238,7 +238,7 @@ a2 = combined.a2; a3 = combined.a3; a4 = combined.a4; - return self.WhereFor>(sourceGroup); + return self.WhereToGroupFor>(sourceGroup); } @@ -249,7 +249,7 @@ where A3 : EcsAspect where A4 : EcsAspect { - return self.Where>(); + return self.WhereToGroup>(); } public static EcsReadonlyGroup WhereFor(this EcsWorld self, EcsReadonlyGroup sourceGroup) where A0 : EcsAspect @@ -258,7 +258,7 @@ where A3 : EcsAspect where A4 : EcsAspect { - return self.WhereFor>(sourceGroup); + return self.WhereToGroupFor>(sourceGroup); } #endregion @@ -288,7 +288,7 @@ a3 = combined.a3; a4 = combined.a4; a5 = combined.a5; - return self.WhereFor>(sourceGroup); + return self.WhereToGroupFor>(sourceGroup); } @@ -300,7 +300,7 @@ where A4 : EcsAspect where A5 : EcsAspect { - return self.Where>(); + return self.WhereToGroup>(); } public static EcsReadonlyGroup WhereFor(this EcsWorld self, EcsReadonlyGroup sourceGroup) where A0 : EcsAspect @@ -310,7 +310,7 @@ where A4 : EcsAspect where A5 : EcsAspect { - return self.WhereFor>(sourceGroup); + return self.WhereToGroupFor>(sourceGroup); } #endregion } diff --git a/src/Builtin/BaseProcesses.cs b/src/Builtin/BaseProcesses.cs index d172d1b..8697b95 100644 --- a/src/Builtin/BaseProcesses.cs +++ b/src/Builtin/BaseProcesses.cs @@ -1,32 +1,45 @@ #pragma warning disable CS0162 // Обнаружен недостижимый код +using DCFApixels.DragonECS.Internal; using DCFApixels.DragonECS.RunnersCore; using System; namespace DCFApixels.DragonECS { #region Interfaces + [MetaName(nameof(PreInit))] + [MetaColor(MetaColor.Orange)] + [BindWithEcsRunner(typeof(EcsPreInitProcessRunner))] public interface IEcsPreInitProcess : IEcsProcess { - void PreInit(EcsPipeline pipeline); + void PreInit(); } + [MetaName(nameof(Init))] + [MetaColor(MetaColor.Orange)] + [BindWithEcsRunner(typeof(EcsInitProcessRunner))] public interface IEcsInitProcess : IEcsProcess { - void Init(EcsPipeline pipeline); + void Init(); } + [MetaName(nameof(Run))] + [MetaColor(MetaColor.Orange)] + [BindWithEcsRunner(typeof(EcsRunProcessRunner))] public interface IEcsRunProcess : IEcsProcess { - void Run(EcsPipeline pipeline); + void Run(); } + [MetaName(nameof(Destroy))] + [MetaColor(MetaColor.Orange)] + [BindWithEcsRunner(typeof(EcsDestroyProcessRunner))] public interface IEcsDestroyProcess : IEcsProcess { - void Destroy(EcsPipeline pipeline); + void Destroy(); } #endregion namespace Internal { - [DebugColor(DebugColor.Orange)] + [MetaColor(MetaColor.Orange)] public sealed class EcsPreInitProcessRunner : EcsRunner, IEcsPreInitProcess { #if DEBUG && !DISABLE_DEBUG @@ -40,33 +53,33 @@ namespace DCFApixels.DragonECS } } #endif - public void PreInit(EcsPipeline pipeline) + public void PreInit() { #if DEBUG && !DISABLE_DEBUG for (int i = 0; i < targets.Length && targets.Length <= _markers.Length; i++) { + _markers[i].Begin(); try { - _markers[i].Begin(); - targets[i].PreInit(pipeline); - _markers[i].End(); + targets[i].PreInit(); } catch (Exception e) { #if DISABLE_CATH_EXCEPTIONS - throw; + throw e; #endif EcsDebug.PrintError(e); } + _markers[i].End(); } #else foreach (var item in targets) { - try { item.PreInit(pipeline); } + try { item.PreInit(); } catch (Exception e) { #if DISABLE_CATH_EXCEPTIONS - throw; + throw e; #endif EcsDebug.PrintError(e); } @@ -74,7 +87,7 @@ namespace DCFApixels.DragonECS #endif } } - [DebugColor(DebugColor.Orange)] + [MetaColor(MetaColor.Orange)] public sealed class EcsInitProcessRunner : EcsRunner, IEcsInitProcess { #if DEBUG && !DISABLE_DEBUG @@ -88,33 +101,33 @@ namespace DCFApixels.DragonECS } } #endif - public void Init(EcsPipeline pipeline) + public void Init() { #if DEBUG && !DISABLE_DEBUG for (int i = 0; i < targets.Length && targets.Length <= _markers.Length; i++) { + _markers[i].Begin(); try { - _markers[i].Begin(); - targets[i].Init(pipeline); - _markers[i].End(); + targets[i].Init(); } catch (Exception e) { #if DISABLE_CATH_EXCEPTIONS - throw; + throw e; #endif EcsDebug.PrintError(e); } + _markers[i].End(); } #else foreach (var item in targets) { - try { item.Init(pipeline); } + try { item.Init(); } catch (Exception e) { #if DISABLE_CATH_EXCEPTIONS - throw; + throw e; #endif EcsDebug.PrintError(e); } @@ -122,7 +135,7 @@ namespace DCFApixels.DragonECS #endif } } - [DebugColor(DebugColor.Orange)] + [MetaColor(MetaColor.Orange)] public sealed class EcsRunProcessRunner : EcsRunner, IEcsRunProcess { #if DEBUG && !DISABLE_DEBUG @@ -136,33 +149,33 @@ namespace DCFApixels.DragonECS } } #endif - public void Run(EcsPipeline pipeline) + public void Run() { #if DEBUG && !DISABLE_DEBUG for (int i = 0; i < targets.Length && targets.Length <= _markers.Length; i++) { + _markers[i].Begin(); try { - _markers[i].Begin(); - targets[i].Run(pipeline); - _markers[i].End(); + targets[i].Run(); } catch (Exception e) { #if DISABLE_CATH_EXCEPTIONS - throw; + throw e; #endif EcsDebug.PrintError(e); } + _markers[i].End(); } #else foreach (var item in targets) { - try { item.Run(pipeline); } + try { item.Run(); } catch (Exception e) { #if DISABLE_CATH_EXCEPTIONS - throw; + throw e; #endif EcsDebug.PrintError(e); } @@ -170,7 +183,7 @@ namespace DCFApixels.DragonECS #endif } } - [DebugColor(DebugColor.Orange)] + [MetaColor(MetaColor.Orange)] public sealed class EcsDestroyProcessRunner : EcsRunner, IEcsDestroyProcess { #if DEBUG && !DISABLE_DEBUG @@ -180,37 +193,37 @@ namespace DCFApixels.DragonECS _markers = new EcsProfilerMarker[targets.Length]; for (int i = 0; i < targets.Length; i++) { - _markers[i] = new EcsProfilerMarker($"{targets[i].GetType().Name}.{nameof(Destroy)}"); + _markers[i] = new EcsProfilerMarker($"{targets[i].GetType().Name}.{nameof(IEcsDestroyProcess.Destroy)}"); } } #endif - public void Destroy(EcsPipeline pipeline) + void IEcsDestroyProcess.Destroy() { #if DEBUG && !DISABLE_DEBUG for (int i = 0; i < targets.Length && targets.Length <= _markers.Length; i++) { + _markers[i].Begin(); try { - _markers[i].Begin(); - targets[i].Destroy(pipeline); - _markers[i].End(); + targets[i].Destroy(); } catch (Exception e) { #if DISABLE_CATH_EXCEPTIONS - throw; + throw e; #endif EcsDebug.PrintError(e); } + _markers[i].End(); } #else foreach (var item in targets) { - try { item.Destroy(pipeline); } + try { item.Destroy(); } catch (Exception e) { #if DISABLE_CATH_EXCEPTIONS - throw; + throw e; #endif EcsDebug.PrintError(e); } diff --git a/src/Builtin/InjectSystem.cs b/src/Builtin/InjectSystem.cs index e618f57..b9ab4e1 100644 --- a/src/Builtin/InjectSystem.cs +++ b/src/Builtin/InjectSystem.cs @@ -2,18 +2,23 @@ using DCFApixels.DragonECS.RunnersCore; using System; using System.Linq; -using System.Runtime.CompilerServices; namespace DCFApixels.DragonECS { + [MetaName(nameof(PreInject))] + [BindWithEcsRunner(typeof(EcsPreInjectRunner))] public interface IEcsPreInject : IEcsProcess { void PreInject(object obj); } + [MetaName(nameof(Inject))] + [BindWithEcsRunner(typeof(EcsInjectRunner<>))] public interface IEcsInject : IEcsProcess { void Inject(T obj); } + [MetaName("PreInitInject")] + [BindWithEcsRunner(typeof(EcsPreInitInjectProcessRunner))] public interface IEcsPreInitInjectProcess : IEcsProcess { void OnPreInitInjectionBefore(); @@ -45,7 +50,8 @@ namespace DCFApixels.DragonECS _injectSystems = null; } } - [DebugHide, DebugColor(DebugColor.Gray)] + [MetaTags(MetaTags.HIDDEN)] + [MetaColor(MetaColor.Gray)] public sealed class EcsPreInjectRunner : EcsRunner, IEcsPreInject { void IEcsPreInject.PreInject(object obj) @@ -53,18 +59,17 @@ namespace DCFApixels.DragonECS foreach (var item in targets) item.PreInject(obj); } } - [DebugHide, DebugColor(DebugColor.Gray)] + [MetaTags(MetaTags.HIDDEN)] + [MetaColor(MetaColor.Gray)] public sealed class EcsInjectRunner : EcsRunner>, IEcsInject { private EcsBaseTypeInjectRunner _baseTypeInjectRunner; void IEcsInject.Inject(T obj) { - if (obj == null) ThrowArgumentNullException(); + if (obj == null) Throw.ArgumentNull(); _baseTypeInjectRunner.Inject(obj); foreach (var item in targets) item.Inject(obj); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void ThrowArgumentNullException() => throw new ArgumentNullException(); protected override void OnSetup() { Type baseType = typeof(T).BaseType; @@ -78,20 +83,21 @@ namespace DCFApixels.DragonECS { public abstract void Inject(object obj); } - internal class EcsBaseTypeInjectRunner : EcsBaseTypeInjectRunner + internal sealed class EcsBaseTypeInjectRunner : EcsBaseTypeInjectRunner { private IEcsInject _runner; public EcsBaseTypeInjectRunner(EcsPipeline pipeline) => _runner = pipeline.GetRunner>(); - public override void Inject(object obj) => _runner.Inject((T)obj); + public sealed override void Inject(object obj) => _runner.Inject((T)obj); } - internal class EcsObjectTypePreInjectRunner : EcsBaseTypeInjectRunner + internal sealed class EcsObjectTypePreInjectRunner : EcsBaseTypeInjectRunner { private IEcsPreInject _runner; public EcsObjectTypePreInjectRunner(EcsPipeline pipeline) => _runner = pipeline.GetRunner(); - public override void Inject(object obj) => _runner.PreInject(obj); + public sealed override void Inject(object obj) => _runner.PreInject(obj); } - [DebugHide, DebugColor(DebugColor.Gray)] + [MetaTags(MetaTags.HIDDEN)] + [MetaColor(MetaColor.Gray)] public sealed class EcsPreInitInjectProcessRunner : EcsRunner, IEcsPreInitInjectProcess { public void OnPreInitInjectionAfter() @@ -103,34 +109,40 @@ namespace DCFApixels.DragonECS foreach (var item in targets) item.OnPreInitInjectionBefore(); } } - public class InjectSystemBase { } - [DebugHide, DebugColor(DebugColor.Gray)] - public class InjectSystem : InjectSystemBase, IEcsPreInitProcess, IEcsInject, IEcsPreInitInjectProcess + public abstract class InjectSystemBase { } + + [MetaTags(MetaTags.HIDDEN)] + [MetaColor(MetaColor.Gray)] + public class InjectSystem : InjectSystemBase, IEcsInject, IEcsPreInitProcess, IEcsInject, IEcsPreInitInjectProcess { - private T _injectedData; + private EcsPipeline _pipeline; + void IEcsInject.Inject(EcsPipeline obj) => _pipeline = obj; private PreInitInjectController _injectController; void IEcsInject.Inject(PreInitInjectController obj) => _injectController = obj; + + private T _injectedData; + public InjectSystem(T injectedData) { - if (injectedData == null) throw new ArgumentNullException(); + if (injectedData == null) Throw.ArgumentNull(); _injectedData = injectedData; } - public void PreInit(EcsPipeline pipeline) + public void PreInit() { if (_injectedData == null) return; if (_injectController == null) { - _injectController = new PreInitInjectController(pipeline); - var injectMapRunner = pipeline.GetRunner>(); - pipeline.GetRunner().OnPreInitInjectionBefore(); + _injectController = new PreInitInjectController(_pipeline); + var injectMapRunner = _pipeline.GetRunner>(); + _pipeline.GetRunner().OnPreInitInjectionBefore(); injectMapRunner.Inject(_injectController); } - var injectRunnerGeneric = pipeline.GetRunner>(); + var injectRunnerGeneric = _pipeline.GetRunner>(); injectRunnerGeneric.Inject(_injectedData); if (_injectController.OnInject()) { _injectController.Destroy(); - var injectCallbacksRunner = pipeline.GetRunner(); + var injectCallbacksRunner = _pipeline.GetRunner(); injectCallbacksRunner.OnPreInitInjectionAfter(); EcsRunner.Destroy(injectCallbacksRunner); } @@ -149,8 +161,11 @@ namespace DCFApixels.DragonECS { public static EcsPipeline.Builder Inject(this EcsPipeline.Builder self, T data) { - if (data == null) throw new ArgumentNullException(); - return self.Add(new InjectSystem(data)); + if (data == null) Throw.ArgumentNull(); + self.Add(new InjectSystem(data)); + if (data is IEcsModule module) + self.AddModule(module); + return self; } public static EcsPipeline.Builder Inject(this EcsPipeline.Builder self, A a, B b) { diff --git a/src/Builtin/Systems.cs b/src/Builtin/Systems.cs index f478cf6..5428395 100644 --- a/src/Builtin/Systems.cs +++ b/src/Builtin/Systems.cs @@ -5,42 +5,49 @@ namespace DCFApixels.DragonECS { namespace Internal { - [DebugHide, DebugColor(DebugColor.Black)] + [MetaTags(MetaTags.HIDDEN)] + [MetaColor(MetaColor.Black)] public class SystemsLayerMarkerSystem : IEcsProcess { public readonly string name; public SystemsLayerMarkerSystem(string name) => this.name = name; } - [DebugHide, DebugColor(DebugColor.Grey)] - public class DeleteEmptyEntitesSystem : IEcsRunProcess, IEcsInject + [MetaTags(MetaTags.HIDDEN)] + [MetaColor(MetaColor.Grey)] + public class EndFrameSystem : IEcsRunProcess, IEcsInject { - private List _worlds = new List(); + private readonly List _worlds = new List(); public void Inject(EcsWorld obj) => _worlds.Add(obj); - public void Run(EcsPipeline pipeline) + public void Run() { foreach (var world in _worlds) + { world.DeleteEmptyEntites(); + world.ReleaseDelEntityBufferAll(); + } } } - [DebugHide, DebugColor(DebugColor.Grey)] + [MetaTags(MetaTags.HIDDEN)] + [MetaColor(MetaColor.Grey)] public class DeleteOneFrameComponentSystem : IEcsRunProcess, IEcsInject where TComponent : struct, IEcsComponent { + public EcsPipeline pipeline { get; set; } private sealed class Aspect : EcsAspect { public EcsPool pool; public Aspect(Builder b) => pool = b.Include(); } - List _worlds = new List(); + private readonly List _worlds = new List(); public void Inject(EcsWorld obj) => _worlds.Add(obj); - public void Run(EcsPipeline pipeline) + public void Run() { for (int i = 0, iMax = _worlds.Count; i < iMax; i++) { EcsWorld world = _worlds[i]; if (world.IsComponentTypeDeclared()) { - foreach (var e in world.Where(out Aspect a)) + foreach (var e in world.WhereToGroup(out Aspect a)) a.pool.Del(e); } } diff --git a/src/Collections.meta b/src/Collections.meta new file mode 100644 index 0000000..6d26a82 --- /dev/null +++ b/src/Collections.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2e026d1a6d4fd884ea7324b6097703c5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Collections/EcsGroup.cs b/src/Collections/EcsGroup.cs new file mode 100644 index 0000000..6323e1c --- /dev/null +++ b/src/Collections/EcsGroup.cs @@ -0,0 +1,812 @@ +using DCFApixels.DragonECS.Internal; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace DCFApixels.DragonECS +{ + //_dense заполняется с индекса 1 + //в операциях изменяющих состояние группы нельзя итерироваться по this, либо осторожно учитывать этот момент + [StructLayout(LayoutKind.Sequential, Pack = 0, Size = 8)] + [DebuggerTypeProxy(typeof(DebuggerProxy))] + public readonly ref struct EcsReadonlyGroup + { + private readonly EcsGroup _source; + + #region Constructors + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EcsReadonlyGroup(EcsGroup source) => _source = source; + #endregion + + #region Properties + public bool IsNull => _source == null; + public int WorldID + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _source.World.id; + } + public EcsWorld World + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _source.World; + } + public int Count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _source.Count; + } + public int CapacityDense + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _source.CapacityDense; + } + public int CapacitySparce + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _source.CapacitySparce; + } + public bool IsReleazed + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _source.IsReleased; + } + public int this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _source[index]; + } + #endregion + + #region Methods + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Has(int entityID) => _source.Has(entityID); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOf(int entityID) => _source.IndexOf(entityID); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EcsGroup.Enumerator GetEnumerator() => _source.GetEnumerator(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EcsGroup Clone() => _source.Clone(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int[] Bake() => _source.Bake(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Bake(ref int[] entities) => _source.Bake(ref entities); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Bake(List entities) => _source.Bake(entities); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EcsSpan ToSpan() => _source.ToSpan(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EcsSpan ToSpan(int start, int length) => _source.ToSpan(start, length); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EcsGroup.LongsIterator GetLongs() => _source.GetLongs(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int First() => _source.First(); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Last() => _source.Last(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool SetEquals(EcsReadonlyGroup group) => _source.SetEquals(group._source); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool SetEquals(EcsGroup group) => _source.SetEquals(group); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool SetEquals(EcsSpan span) => _source.SetEquals(span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Overlaps(EcsReadonlyGroup group) => _source.Overlaps(group._source); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Overlaps(EcsGroup group) => _source.Overlaps(group); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Overlaps(EcsSpan span) => _source.Overlaps(span); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsSubsetOf(EcsReadonlyGroup group) => _source.IsSubsetOf(group._source); + public bool IsSubsetOf(EcsGroup group) => _source.IsSubsetOf(group); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsSupersetOf(EcsReadonlyGroup group) => _source.IsSupersetOf(group._source); + public bool IsSupersetOf(EcsGroup group) => _source.IsSupersetOf(group); + #endregion + + #region Object + public override string ToString() => _source != null ? _source.ToString() : "NULL"; + #endregion + + #region Internal + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal EcsGroup GetGroupInternal() => _source; + + #endregion + + #region Other + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator EcsSpan(EcsReadonlyGroup a) => a.ToSpan(); + internal class DebuggerProxy : EcsGroup.DebuggerProxy + { + public DebuggerProxy(EcsReadonlyGroup group) : base(group._source) { } + } + #endregion + } + + [DebuggerTypeProxy(typeof(DebuggerProxy))] + public unsafe class EcsGroup : IDisposable, IEnumerable + { + private EcsWorld _source; + private int[] _dense; + private int[] _sparse; + private int _count; + internal bool _isReleased = true; + + #region Properties + public int WorldID + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _source.id; + } + public EcsWorld World + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _source; + } + public int Count + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _count; + } + public int CapacityDense + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _dense.Length; + } + public int CapacitySparce + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _sparse.Length; + } + public EcsReadonlyGroup Readonly + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => new EcsReadonlyGroup(this); + } + public bool IsReleased + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _isReleased; + } + public int this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (index < 0 || index >= Count) Throw.ArgumentOutOfRange(); +#endif + return _dense[++index]; + } + } + #endregion + + #region Constrcutors/Dispose + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EcsGroup New(EcsWorld world) + { + return world.GetFreeGroup(); + } + internal EcsGroup(EcsWorld world, int denseCapacity = 64) + { + _source = world; + _source.RegisterGroup(this); + _dense = new int[denseCapacity]; + _sparse = new int[world.Capacity]; + + _count = 0; + } + public void Dispose() => _source.ReleaseGroup(this); + #endregion + + #region Has/IndexOf + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Has(int entityID) + { + return _sparse[entityID] > 0; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int IndexOf(int entityID) + { + return _sparse[entityID]; + } + #endregion + + #region Add/Remove + public void AddUnchecked(int entityID) => AddInternal(entityID); + public bool Add(int entityID) + { + if (Has(entityID)) + return false; + AddInternal(entityID); + return true; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void AddInternal(int entityID) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (Has(entityID)) Throw.Group_AlreadyContains(entityID); +#endif + if (++_count >= _dense.Length) + Array.Resize(ref _dense, _dense.Length << 1); + _dense[_count] = entityID; + _sparse[entityID] = _count; + } + + public void RemoveUnchecked(int entityID) => RemoveInternal(entityID); + public bool Remove(int entityID) + { + if (!Has(entityID)) + return false; + RemoveInternal(entityID); + return true; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void RemoveInternal(int entityID) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (!Has(entityID)) Throw.Group_DoesNotContain(entityID); +#endif + _dense[_sparse[entityID]] = _dense[_count]; + _sparse[_dense[_count--]] = _sparse[entityID]; + _sparse[entityID] = 0; + } + + public void RemoveUnusedEntityIDs() + { + foreach (var e in this) + { + if (!_source.IsUsed(e)) + RemoveInternal(e); + } + } + #endregion + + #region Clear + public void Clear() + { + _count = 0; + //массив _dense нет смысла очищать + for (int i = 0; i < _sparse.Length; i++) + _sparse[i] = 0; + } + #endregion + + #region CopyFrom/Clone/Bake/ToSpan + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void CopyFrom(EcsReadonlyGroup group) => CopyFrom(group.GetGroupInternal()); + public void CopyFrom(EcsGroup group) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (group.World != _source) throw new ArgumentException("groupFilter.WorldIndex != WorldIndex"); +#endif + if (_count > 0) + Clear(); + foreach (var item in group) + AddInternal(item); + } + public EcsGroup Clone() + { + EcsGroup result = _source.GetFreeGroup(); + result.CopyFrom(this); + return result; + } + public int[] Bake() + { + int[] result = new int[_count]; + Array.Copy(_dense, 1, result, 0, _count); + return result; + } + public int Bake(ref int[] entities) + { + if (entities.Length < _count) + entities = new int[_count]; + Array.Copy(_dense, 1, entities, 0, _count); + return _count; + } + public void Bake(List entities) + { + entities.Clear(); + foreach (var e in this) + entities.Add(e); + } + public EcsSpan ToSpan() + { + return new EcsSpan(WorldID, _dense); + } + public EcsSpan ToSpan(int start, int length) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (start + length > _count) Throw.ArgumentOutOfRange(); +#endif + return new EcsSpan(WorldID, _dense, start, length); + } + #endregion + + #region Set operations + + #region UnionWith + /// as Union sets + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void UnionWith(EcsReadonlyGroup group) => UnionWith(group.GetGroupInternal()); + /// as Union sets + public void UnionWith(EcsGroup group) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (_source != group.World) Throw.Group_ArgumentDifferentWorldsException(); +#endif + foreach (var item in group) + if (!Has(item)) + AddInternal(item); + } + public void UnionWith(EcsSpan span) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (_source.id != span.WorldID) Throw.Group_ArgumentDifferentWorldsException(); +#endif + foreach (var item in span) + { + if (!Has(item)) + AddInternal(item); + } + } + #endregion + + #region ExceptWith + /// as Except sets + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ExceptWith(EcsReadonlyGroup group) => ExceptWith(group.GetGroupInternal()); + /// as Except sets + public void ExceptWith(EcsGroup group) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (_source != group.World) Throw.Group_ArgumentDifferentWorldsException(); +#endif + //if (group.Count > Count) // вариант 1. есть итерация по this + //{ + // foreach (var item in this) + // if (group.Has(item)) + // RemoveInternal(item); + //} + //else + //{ + // foreach (var item in group) + // if (Has(item)) + // RemoveInternal(item); + //} + + //foreach (var item in group) // вариант 2 + // if (Has(item)) + // RemoveInternal(item); + + if (group.Count > Count) + { + for (int i = _count; i > 0; i--)//итерация в обратном порядке исключает ошибки при удалении элементов + { + int item = _dense[i]; + if (group.Has(item)) + RemoveInternal(item); + } + } + else + { + foreach (var item in group) + { + if (Has(item)) + RemoveInternal(item); + } + } + } + public void ExceptWith(EcsSpan span) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (_source.id != span.WorldID) Throw.Group_ArgumentDifferentWorldsException(); +#endif + foreach (var item in span) + { + if (Has(item)) + RemoveInternal(item); + } + } + #endregion + + #region IntersectWith + /// as Intersect sets + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void IntersectWith(EcsReadonlyGroup group) => IntersectWith(group.GetGroupInternal()); + /// as Intersect sets + public void IntersectWith(EcsGroup group) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (World != group.World) Throw.Group_ArgumentDifferentWorldsException(); +#endif + for (int i = _count; i > 0; i--)//итерация в обратном порядке исключает ошибки при удалении элементов + { + int item = _dense[i]; + if (!group.Has(item)) + RemoveInternal(item); + } + } + #endregion + + #region SymmetricExceptWith + /// as Symmetric Except sets + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SymmetricExceptWith(EcsReadonlyGroup group) => SymmetricExceptWith(group.GetGroupInternal()); + /// as Symmetric Except sets + public void SymmetricExceptWith(EcsGroup group) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (_source != group.World) Throw.Group_ArgumentDifferentWorldsException(); +#endif + foreach (var item in group) + if (Has(item)) + RemoveInternal(item); + else + AddInternal(item); + } + #endregion + + #region Inverse + public void Inverse() + { + foreach (var item in _source.Entities) + if (Has(item)) + RemoveInternal(item); + else + AddInternal(item); + } + #endregion + + #region SetEquals + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool SetEquals(EcsReadonlyGroup group) => SetEquals(group.GetGroupInternal()); + public bool SetEquals(EcsGroup group) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (_source != group.World) Throw.Group_ArgumentDifferentWorldsException(); +#endif + if (group.Count != Count) + return false; + foreach (var item in group) + if (!Has(item)) + return false; + return true; + } + public bool SetEquals(EcsSpan span) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (_source.id != span.WorldID) Throw.Group_ArgumentDifferentWorldsException(); +#endif + if (span.Length != Count) + return false; + foreach (var item in span) + if (!Has(item)) + return false; + return true; + } + #endregion + + #region Overlaps + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Overlaps(EcsReadonlyGroup group) => Overlaps(group.GetGroupInternal()); + public bool Overlaps(EcsGroup group) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (_source != group.World) Throw.Group_ArgumentDifferentWorldsException(); +#endif + if(group.Count > Count) + { + foreach (var item in this) + if (group.Has(item)) + return true; + } + else + { + foreach (var item in group) + if (Has(item)) + return true; + } + return false; + } + public bool Overlaps(EcsSpan span) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (_source.id != span.WorldID) Throw.Group_ArgumentDifferentWorldsException(); +#endif + foreach (var item in span) + if (Has(item)) + return true; + return false; + } + #endregion + + #region IsSubsetOf + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsSubsetOf(EcsReadonlyGroup group) => IsSubsetOf(group.GetGroupInternal()); + public bool IsSubsetOf(EcsGroup group) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (_source != group.World) Throw.Group_ArgumentDifferentWorldsException(); +#endif + if (group.Count < Count) + return false; + foreach (var item in this) + if (!group.Has(item)) + return false; + return true; + } + #endregion + + #region IsSupersetOf + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsSupersetOf(EcsReadonlyGroup group) => IsSupersetOf(group.GetGroupInternal()); + public bool IsSupersetOf(EcsGroup group) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (_source != group.World) Throw.Group_ArgumentDifferentWorldsException(); +#endif + if (group.Count > Count) + return false; + foreach (var item in group) + if (!Has(item)) + return false; + return true; + } + #endregion + + #endregion + + #region Static Set operations + /// as Intersect sets + /// new group from pool + public static EcsGroup Union(EcsGroup a, EcsGroup b) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (a._source != b._source) Throw.Group_ArgumentDifferentWorldsException(); +#endif + EcsGroup result = a._source.GetFreeGroup(); + foreach (var item in a) + result.AddInternal(item); + foreach (var item in b) + result.Add(item); + return result; + } + /// as Except sets + /// new group from pool + public static EcsGroup Except(EcsGroup a, EcsGroup b) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (a._source != b._source) Throw.Group_ArgumentDifferentWorldsException(); +#endif + EcsGroup result = a._source.GetFreeGroup(); + foreach (var item in a) + if (!b.Has(item)) + result.AddInternal(item); + return result; + } + /// as Intersect sets + /// new group from pool + public static EcsGroup Intersect(EcsGroup a, EcsGroup b) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (a._source != b._source) Throw.Group_ArgumentDifferentWorldsException(); +#endif + EcsGroup result = a._source.GetFreeGroup(); + foreach (var item in a) + if (b.Has(item)) + result.AddInternal(item); + return result; + } + + /// as Symmetric Except sets + /// new group from pool + public static EcsGroup SymmetricExcept(EcsGroup a, EcsGroup b) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (a._source != b._source) Throw.Group_ArgumentDifferentWorldsException(); +#endif + EcsGroup result = a._source.GetFreeGroup(); + foreach (var item in a) + if (!b.Has(item)) + result.AddInternal(item); + foreach (var item in b) + if (!a.Has(item)) + result.AddInternal(item); + return result; + } + + public static EcsGroup Inverse(EcsGroup a) + { + EcsGroup result = a._source.GetFreeGroup(); + foreach (var item in a._source.Entities) + if (!a.Has(item)) + result.AddInternal(item); + return result; + } + #endregion + + #region Enumerator + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Enumerator GetEnumerator() => new Enumerator(this); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public LongsIterator GetLongs() => new LongsIterator(this); + public struct Enumerator : IEnumerator + { + private readonly int[] _dense; + private uint _index; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Enumerator(EcsGroup group) + { + _dense = group._dense; + _index = (uint)(group._count > _dense.Length ? _dense.Length : group._count) + 1; + } + public int Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _dense[_index]; + } + object IEnumerator.Current => Current; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => --_index > 0; // <= потму что отсчет начинается с индекса 1 //_count < _dense.Length дает среде понять что проверки на выход за границы не нужны + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() { } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Reset() { } + } + public readonly struct LongsIterator : IEnumerable + { + private readonly EcsGroup _group; + public LongsIterator(EcsGroup group) => _group = group; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Enumerator GetEnumerator() => new Enumerator(_group); + IEnumerator IEnumerable.GetEnumerator() + { + for (int i = 0; i < _group._count; i++) + yield return _group.World.GetEntityLong(_group._dense[i]); + } + IEnumerator IEnumerable.GetEnumerator() + { + for (int i = 0; i < _group._count; i++) + yield return _group.World.GetEntityLong(_group._dense[i]); + } + public struct Enumerator : IEnumerator + { + private readonly EcsWorld world; + private readonly int[] _dense; + private uint _index; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Enumerator(EcsGroup group) + { + world = group.World; + _dense = group._dense; + _index = (uint)(group._count > _dense.Length ? _dense.Length : group._count) + 1; + } + public entlong Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => world.GetEntityLong(_dense[_index]); + } + object IEnumerator.Current => Current; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool MoveNext() => --_index > 0; // <= потму что отсчет начинается с индекса 1 //_count < _dense.Length дает среде понять что проверки на выход за границы не нужны + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Dispose() { } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Reset() { } + } + } + #endregion + + #region Other + public override string ToString() + { + return $"group{{{string.Join(", ", _dense.Skip(1).Take(_count))}}}"; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int First() + { + return _dense[1]; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Last() + { + return _dense[_count]; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void OnWorldResize(int newSize) + { + Array.Resize(ref _sparse, newSize); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator EcsReadonlyGroup(EcsGroup a) => a.Readonly; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator EcsSpan(EcsGroup a) => a.ToSpan(); + internal class DebuggerProxy + { + private EcsGroup _group; + public EcsWorld World => _group.World; + public bool IsReleased => _group.IsReleased; + public entlong[] Entities + { + get + { + entlong[] result = new entlong[_group.Count]; + int i = 0; + foreach (var e in _group) + result[i++] = _group.World.GetEntityLong(e); + return result; + } + } + public int Count => _group.Count; + public int CapacityDense => _group.CapacityDense; + public int CapacitySparce => _group.CapacitySparce; + public override string ToString() => _group.ToString(); + public DebuggerProxy(EcsGroup group) => _group = group; + } + #endregion + } + +#if false + public static class EcsGroupAliases + { + /// Alias for UnionWith + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Add(this EcsGroup self, EcsGroup group) + { + self.UnionWith(group); + } + /// Alias for UnionWith + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Add(this EcsGroup self, EcsReadonlyGroup group) + { + self.UnionWith(group); + } + /// Alias for ExceptWith + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Remove(this EcsGroup self, EcsGroup group) + { + self.ExceptWith(group); + } + /// Alias for ExceptWith + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Remove(this EcsGroup self, EcsReadonlyGroup group) + { + self.ExceptWith(group); + } + /// Alias for SymmetricExceptWith + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Xor(this EcsGroup self, EcsGroup group) + { + self.SymmetricExceptWith(group); + } + /// Alias for SymmetricExceptWith + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Xor(this EcsGroup self, EcsReadonlyGroup group) + { + self.SymmetricExceptWith(group); + } + /// Alias for IntersectWith + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void And(this EcsGroup self, EcsGroup group) + { + self.IntersectWith(group); + } + /// Alias for IntersectWith + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void And(this EcsGroup self, EcsReadonlyGroup group) + { + self.IntersectWith(group); + } + } +#endif +} \ No newline at end of file diff --git a/src/EcsGroup.cs.meta b/src/Collections/EcsGroup.cs.meta similarity index 100% rename from src/EcsGroup.cs.meta rename to src/Collections/EcsGroup.cs.meta diff --git a/src/Collections/EcsSpan.cs b/src/Collections/EcsSpan.cs new file mode 100644 index 0000000..d95f773 --- /dev/null +++ b/src/Collections/EcsSpan.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace DCFApixels.DragonECS +{ + public readonly ref struct EcsSpan + { + private readonly int _worldID; + private readonly ReadOnlySpan _values; + + #region Properties + public int WorldID + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _worldID; + } + public EcsWorld World + { + get => EcsWorld.GetWorld(_worldID); + } + public int Length + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _values.Length; + } + public readonly int this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _values[index]; + } + public bool IsNull + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _values.IsEmpty; + } + #endregion + + #region Constructors + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal EcsSpan(int worldID, ReadOnlySpan span) + { + _worldID = worldID; + _values = span; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal EcsSpan(int worldID, int[] array) + { + _worldID = worldID; + _values = array; + } + internal EcsSpan(int worldID, int[] array, int length) + { + _worldID = worldID; + _values = new ReadOnlySpan(array, 0, length); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal EcsSpan(int worldID, int[] array, int start, int length) + { + _worldID = worldID; + _values = new ReadOnlySpan(array, start, length); + } + #endregion + + #region Methdos + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int[] Bake() + { + int[] result = new int[_values.Length]; + _values.CopyTo(result); + return result; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Bake(ref int[] entities) + { + if (entities.Length < _values.Length) + Array.Resize(ref entities, _values.Length); + int[] result = new int[_values.Length]; + _values.CopyTo(result); + return _values.Length; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Bake(List entities) + { + entities.Clear(); + foreach (var e in _values) + { + entities.Add(e); + } + } + #endregion + + #region Object +#pragma warning disable CS0809 // Устаревший член переопределяет неустаревший член + [Obsolete("Equals() on EcsSpan will always throw an exception. Use the equality operator instead.")] + [EditorBrowsable(EditorBrowsableState.Never)] + public override bool Equals(object obj) => throw new NotSupportedException(); + [Obsolete("GetHashCode() on EcsSpan will always throw an exception.")] + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => throw new NotSupportedException(); +#pragma warning restore CS0809 // Устаревший член переопределяет неустаревший член + public override string ToString() => _values.ToString(); + #endregion + + #region operators + public static bool operator ==(EcsSpan left, EcsSpan right) => left._values == right._values; + public static bool operator !=(EcsSpan left, EcsSpan right) => left._values != right._values; + #endregion + + #region Enumerator + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan.Enumerator GetEnumerator() => _values.GetEnumerator(); + #endregion + + #region Other + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EcsSpan Slice(int start) => new EcsSpan(_worldID, _values.Slice(start)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EcsSpan Slice(int start, int length) => new EcsSpan(_worldID, _values.Slice(start, length)); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int[] ToArray() => _values.ToArray(); + #endregion + } +} diff --git a/src/Utils/WorldMetaStorage.cs.meta b/src/Collections/EcsSpan.cs.meta similarity index 83% rename from src/Utils/WorldMetaStorage.cs.meta rename to src/Collections/EcsSpan.cs.meta index ac629af..0fc6d1c 100644 --- a/src/Utils/WorldMetaStorage.cs.meta +++ b/src/Collections/EcsSpan.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 5e06cf4352ab9414293b145eb27daba7 +guid: 55c6215b2c0f45849b191532a01e1dfe MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/src/Consts.cs b/src/Consts.cs index 41bfb71..8100e90 100644 --- a/src/Consts.cs +++ b/src/Consts.cs @@ -8,11 +8,14 @@ public const string DEBUG_PREFIX = "[DEBUG] "; public const string DEBUG_WARNING_TAG = "WARNING"; public const string DEBUG_ERROR_TAG = "ERROR"; + public const string DEBUG_PASS_TAG = "PASS"; public const string PRE_BEGIN_LAYER = nameof(PRE_BEGIN_LAYER); public const string BEGIN_LAYER = nameof(BEGIN_LAYER); public const string BASIC_LAYER = nameof(BASIC_LAYER); public const string END_LAYER = nameof(END_LAYER); public const string POST_END_LAYER = nameof(POST_END_LAYER); + + public const string META_HIDDEN_TAG = "HiddenInDebagging"; } } diff --git a/src/Debug/Attributes/DebugColorAttribute.cs b/src/Debug/Attributes/DebugColorAttribute.cs deleted file mode 100644 index fe603dc..0000000 --- a/src/Debug/Attributes/DebugColorAttribute.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace DCFApixels.DragonECS -{ - [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, Inherited = false, AllowMultiple = false)] - public sealed class DebugColorAttribute : Attribute - { - private Color color; - public byte r => color.r; - public byte g => color.g; - public byte b => color.b; - public DebugColorAttribute(byte r, byte g, byte b) => color = new Color(r, g, b); - public DebugColorAttribute(DebugColor color) => this.color = new Color((int)color); - - [StructLayout(LayoutKind.Explicit, Pack = 1, Size = 4)] - internal readonly struct Color - { - [FieldOffset(0)] public readonly int full; - [FieldOffset(3)] public readonly byte r; - [FieldOffset(2)] public readonly byte g; - [FieldOffset(1)] public readonly byte b; - public Color(byte r, byte g, byte b) : this() - { - this.r = r; - this.g = g; - this.b = b; - } - public Color(int full) : this() => this.full = full; - public (byte, byte, byte) ToTuple() => (r, g, b); - - public Color UpContrastColor() - { - byte minChannel = Math.Min(Math.Min(r, g), b); - byte maxChannel = Math.Max(Math.Max(r, g), b); - if (maxChannel == minChannel) - return default; - float factor = 255f / (maxChannel - minChannel); - return new Color((byte)((r - minChannel) * factor), (byte)((g - minChannel) * factor), (byte)((b - minChannel) * factor)); - } - - public static Color operator /(Color a, float b) - { - return new Color((byte)(a.r / b), (byte)(a.g / b), (byte)(a.b / b)); - } - } - } - public enum DebugColor - { - /// Red. RGB is (255, 0, 0) - Red = (255 << 24) + (000 << 16) + (000 << 8), - /// Green. RGB is (0, 255, 0) - Green = (000 << 24) + (255 << 16) + (000 << 8), - /// Blue. RGB is (0, 0, 255) - Blue = (000 << 24) + (000 << 16) + (255 << 8), - - /// Yellow. RGB is (255, 255, 0) - Yellow = (255 << 24) + (255 << 16) + (000 << 8), - /// Cyan. RGB is (0, 255, 255) - Cyan = (000 << 24) + (255 << 16) + (255 << 8), - /// Magenta. RGB is (255, 0, 255) - Magenta = (255 << 24) + (000 << 16) + (255 << 8), - - /// Yellow. RGB is (255, 165, 0) - Orange = (255 << 24) + (165 << 16) + (000 << 8), - /// Yellow. RGB is (255, 69, 0) - OrangeRed = (255 << 24) + (69 << 16) + (000 << 8), - /// Lime. RGB is (125, 255, 0) - Lime = (125 << 24) + (255 << 16) + (000 << 8), - /// Lime. RGB is (127, 255, 212) - Aquamarine = (127 << 24) + (255 << 16) + (212 << 8), - /// Lime. RGB is (218, 165, 32) - Goldenrod = (218 << 24) + (165 << 16) + (32 << 8), - /// Yellow. RGB is (255, 105, 180) - DeepPink = (255 << 24) + (105 << 16) + (180 << 8), - /// Yellow. RGB is (220, 20, 60) - Crimson = (220 << 24) + (20 << 16) + (60 << 8), - /// Yellow. RGB is (138, 43, 226) - BlueViolet = (138 << 24) + (43 << 16) + (226 << 8), - /// Yellow. RGB is (255, 3, 62) - AmericanRose = (255 << 24) + (3 << 16) + (62 << 8), - - /// Grey/Gray. RGB is (127, 127, 127) - Gray = (127 << 24) + (127 << 16) + (127 << 8), - /// Grey/Gray. RGB is (127, 127, 127) - Grey = Gray, - /// Grey/Gray. RGB is (192, 192, 192) - Silver = (192 << 24) + (192 << 16) + (192 << 8), - /// White. RGB is (255, 255, 255) - White = -1, - /// Black. RGB is (0, 0, 0) - Black = 0, - } -} \ No newline at end of file diff --git a/src/Debug/Attributes/DebugDescriptionAttribute.cs b/src/Debug/Attributes/DebugDescriptionAttribute.cs deleted file mode 100644 index 1a7aacd..0000000 --- a/src/Debug/Attributes/DebugDescriptionAttribute.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace DCFApixels.DragonECS -{ - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)] - public sealed class DebugDescriptionAttribute : Attribute - { - public readonly string description; - public DebugDescriptionAttribute(string description) => this.description = description; - } -} diff --git a/src/Debug/Attributes/DebugHideAttribute.cs b/src/Debug/Attributes/DebugHideAttribute.cs deleted file mode 100644 index 38d7419..0000000 --- a/src/Debug/Attributes/DebugHideAttribute.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System; - -namespace DCFApixels.DragonECS -{ - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)] - public sealed class DebugHideAttribute : Attribute { } -} diff --git a/src/Debug/Attributes/DebugNameAttribute.cs b/src/Debug/Attributes/DebugNameAttribute.cs deleted file mode 100644 index 01f6fda..0000000 --- a/src/Debug/Attributes/DebugNameAttribute.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace DCFApixels.DragonECS -{ - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)] - public sealed class DebugNameAttribute : Attribute - { - public readonly string name; - public DebugNameAttribute(string name) => this.name = name; - } -} diff --git a/src/Debug/EcsDebug.cs b/src/Debug/EcsDebug.cs index 6dd1ac9..de0dba5 100644 --- a/src/Debug/EcsDebug.cs +++ b/src/Debug/EcsDebug.cs @@ -31,12 +31,23 @@ namespace DCFApixels.DragonECS public static class EcsDebug { + public const string WARNING_TAG = EcsConsts.DEBUG_WARNING_TAG; + public const string ERROR_TAG = EcsConsts.DEBUG_ERROR_TAG; + public const string PASS_TAG = EcsConsts.DEBUG_PASS_TAG; + public static void Set() where T : DebugService, new() => DebugService.Set(); public static void Set(DebugService service) => DebugService.Set(service); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void PrintWarning(object v) => Print(EcsConsts.DEBUG_WARNING_TAG, v); public static void PrintError(object v) => Print(EcsConsts.DEBUG_ERROR_TAG, v); + public static void PrintPass(object v) => Print(EcsConsts.DEBUG_PASS_TAG, v); + public static void Print() + { +#if !DISABLE_DRAGONECS_DEBUGGER + DebugService.Instance.Print(""); +#endif + } public static void Print(object v) { #if !DISABLE_DRAGONECS_DEBUGGER @@ -50,6 +61,14 @@ namespace DCFApixels.DragonECS DebugService.Instance.Print(tag, v); #endif } + public static void Break() + { + { +#if !DISABLE_DRAGONECS_DEBUGGER + DebugService.Instance.Break(); +#endif + } + } } public abstract class DebugService @@ -80,7 +99,7 @@ namespace DCFApixels.DragonECS [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Print(object v) => Print(null, v); public abstract void Print(string tag, object v); - + public abstract void Break(); public int RegisterMark(string name) { int id; @@ -114,14 +133,46 @@ namespace DCFApixels.DragonECS private string[] _stopwatchsNames; public DefaultDebugService() { + Console.ForegroundColor = ConsoleColor.White; + Console.BackgroundColor = ConsoleColor.Black; #if !DISABLE_DRAGONECS_DEBUGGER _stopwatchs = new Stopwatch[64]; _stopwatchsNames = new string[64]; #endif } + public override void Print(string tag, object v) { - Console.WriteLine($"[{tag}] {v}"); + if (string.IsNullOrEmpty(tag)) + { + Console.WriteLine(v); + } + else + { + var color = Console.ForegroundColor; + switch (tag) + { + case EcsDebug.ERROR_TAG: + Console.ForegroundColor = ConsoleColor.Red; + break; + case EcsDebug.WARNING_TAG: + Console.ForegroundColor = ConsoleColor.Yellow; + break; + case EcsDebug.PASS_TAG: + Console.ForegroundColor = ConsoleColor.Green; + break; + } + Console.WriteLine($"[{tag}] {v}"); + Console.ForegroundColor = color; + } + } + public override void Break() + { + var color = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine("Press Enter to сontinue."); + Console.ReadKey(); + Console.ForegroundColor = color; } public override void ProfilerMarkBegin(int id) { @@ -129,10 +180,13 @@ namespace DCFApixels.DragonECS } public override void ProfilerMarkEnd(int id) { + var color = Console.ForegroundColor; + Console.ForegroundColor = ConsoleColor.DarkGray; _stopwatchs[id].Stop(); var time = _stopwatchs[id].Elapsed; _stopwatchs[id].Reset(); Print("ProfilerMark", _stopwatchsNames[id] + " s:" + time.TotalSeconds); + Console.ForegroundColor = color; } protected override void OnDelProfilerMark(int id) { diff --git a/src/Debug/EcsDebugUtility.cs b/src/Debug/EcsDebugUtility.cs index 7985286..af4b262 100644 --- a/src/Debug/EcsDebugUtility.cs +++ b/src/Debug/EcsDebugUtility.cs @@ -1,11 +1,15 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; namespace DCFApixels.DragonECS { public static class EcsDebugUtility { + private const BindingFlags RFL_FLAGS = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; + #region GetGenericTypeName public static string GetGenericTypeFullName(int maxDepth = 2) => GetGenericTypeFullName(typeof(T), maxDepth); public static string GetGenericTypeFullName(Type type, int maxDepth = 2) => GetGenericTypeNameInternal(type, maxDepth, true); @@ -45,7 +49,7 @@ namespace DCFApixels.DragonECS } private static string AutoToString(object target, Type type, bool isWriteName) { - var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + var fields = type.GetFields(RFL_FLAGS); string[] values = new string[fields.Length]; for (int i = 0; i < fields.Length; i++) values[i] = (fields[i].GetValue(target) ?? "NULL").ToString(); @@ -57,12 +61,24 @@ namespace DCFApixels.DragonECS #endregion #region GetName - public static string GetName() => GetName(typeof(T)); - public static string GetName(Type type) => type.TryGetCustomAttribute(out DebugNameAttribute atr) ? atr.name : GetGenericTypeName(type); - public static bool TryGetCustomName(out string name) => TryGetCustomName(typeof(T), out name); + public static string GetName(object obj, int maxGenericDepth = 2) + { + return obj is IEcsMetaProvider intr ? + GetName(intr.MetaSource, maxGenericDepth) : + GetName(type: obj.GetType(), maxGenericDepth); + } + public static string GetName(int maxGenericDepth = 2) => GetName(typeof(T), maxGenericDepth); + public static string GetName(Type type, int maxGenericDepth = 2) => type.TryGetCustomAttribute(out MetaNameAttribute atr) ? atr.name : GetGenericTypeName(type, maxGenericDepth); + public static bool TryGetCustomName(object obj, out string name) + { + return obj is IEcsMetaProvider intr ? + TryGetCustomName(intr.MetaSource, out name) : + TryGetCustomName(type: obj.GetType(), out name); + } + public static bool TryGetCustomName(out string name) => TryGetCustomName(type: typeof(T), out name); public static bool TryGetCustomName(Type type, out string name) { - if (type.TryGetCustomAttribute(out DebugNameAttribute atr)) + if (type.TryGetCustomAttribute(out MetaNameAttribute atr)) { name = atr.name; return true; @@ -72,13 +88,53 @@ namespace DCFApixels.DragonECS } #endregion + #region GetGroup + public static MetaGroup GetGroup(object obj) + { + return obj is IEcsMetaProvider intr ? + GetGroup(intr.MetaSource) : + GetGroup(type: obj.GetType()); + } + public static MetaGroup GetGroup() => GetGroup(typeof(T)); + public static MetaGroup GetGroup(Type type) => type.TryGetCustomAttribute(out MetaGroupAttribute atr) ? atr.GetData() : MetaGroup.Empty; + public static bool TryGetGroup(object obj, out MetaGroup group) + { + return obj is IEcsMetaProvider intr ? + TryGetGroup(intr.MetaSource, out group) : + TryGetGroup(type: obj.GetType(), out group); + } + public static bool TryGetGroup(out MetaGroup text) => TryGetGroup(typeof(T), out text); + public static bool TryGetGroup(Type type, out MetaGroup group) + { + if (type.TryGetCustomAttribute(out MetaGroupAttribute atr)) + { + group = atr.GetData(); + return true; + } + group = MetaGroup.Empty; + return false; + } + #endregion + #region GetDescription + public static string GetDescription(object obj) + { + return obj is IEcsMetaProvider intr ? + GetDescription(intr.MetaSource) : + GetDescription(type: obj.GetType()); + } public static string GetDescription() => GetDescription(typeof(T)); - public static string GetDescription(Type type) => type.TryGetCustomAttribute(out DebugDescriptionAttribute atr) ? atr.description : string.Empty; + public static string GetDescription(Type type) => type.TryGetCustomAttribute(out MetaDescriptionAttribute atr) ? atr.description : string.Empty; + public static bool TryGetDescription(object obj, out string text) + { + return obj is IEcsMetaProvider intr ? + TryGetDescription(intr.MetaSource, out text) : + TryGetDescription(type: obj.GetType(), out text); + } public static bool TryGetDescription(out string text) => TryGetDescription(typeof(T), out text); public static bool TryGetDescription(Type type, out string text) { - if (type.TryGetCustomAttribute(out DebugDescriptionAttribute atr)) + if (type.TryGetCustomAttribute(out MetaDescriptionAttribute atr)) { text = atr.description; return true; @@ -89,12 +145,12 @@ namespace DCFApixels.DragonECS #endregion #region GetColor - private static Random random = new Random(); + private static Random random = new Random(100100100); private static Dictionary _words = new Dictionary(); private class WordColor { public int wordsCount; - public DebugColorAttribute.Color color; + public MetaColor color; } private class NameColor { @@ -107,7 +163,7 @@ namespace DCFApixels.DragonECS { color = new WordColor(); _words.Add(word, color); - color.color = new DebugColorAttribute.Color((byte)random.Next(), (byte)random.Next(), (byte)random.Next()).UpContrastColor() / 2; + color.color = new MetaColor((byte)random.Next(), (byte)random.Next(), (byte)random.Next()).UpContrastColor() / 2; } color.wordsCount++; colors.Add(color); @@ -122,7 +178,7 @@ namespace DCFApixels.DragonECS } return result; } - public DebugColorAttribute.Color CalcColor() + public MetaColor CalcColor() { float r = 0, g = 0, b = 0; int totalWordsCount = CalcTotalWordsColor(); @@ -134,11 +190,11 @@ namespace DCFApixels.DragonECS g += m * color.color.g; b += m * color.color.b; } - return new DebugColorAttribute.Color((byte)r, (byte)g, (byte)b); + return new MetaColor((byte)r, (byte)g, (byte)b); } } private static Dictionary _names = new Dictionary(); - private static DebugColorAttribute.Color CalcNameColorFor(Type type) + private static MetaColor CalcNameColorFor(Type type) { Type targetType = type.IsGenericType ? type.GetGenericTypeDefinition() : type; if (!_names.TryGetValue(targetType, out NameColor nameColor)) @@ -148,7 +204,7 @@ namespace DCFApixels.DragonECS } return nameColor.CalcColor(); } - public static List SplitString(string s) + private static List SplitString(string s) { string subs; List words = new List(); @@ -169,47 +225,152 @@ namespace DCFApixels.DragonECS return words; } - public static (byte, byte, byte) GetColorRGB() => GetColorRGB(typeof(T)); - public static (byte, byte, byte) GetColorRGB(Type type) + public static MetaColor GetColor(object obj) { - var atr = type.GetCustomAttribute(); - return atr != null ? (atr.r, atr.g, atr.b) + return obj is IEcsMetaProvider intr ? + GetColor(intr.MetaSource) : + GetColor(type: obj.GetType()); + } + public static MetaColor GetColor() => GetColor(typeof(T)); + public static MetaColor GetColor(Type type) + { + var atr = type.GetCustomAttribute(); + return atr != null ? atr.color #if DEBUG //optimization for release build - : CalcNameColorFor(type).ToTuple(); -#else - : ((byte)0, (byte)0, (byte)0); + : CalcNameColorFor(type); +#else + : MetaColor.BlackColor; #endif } - public static bool TryGetColorRGB(out (byte, byte, byte) color) => TryGetColorRGB(typeof(T), out color); - public static bool TryGetColorRGB(Type type, out (byte, byte, byte) color) + public static bool TryGetColor(object obj, out MetaColor color) { - var atr = type.GetCustomAttribute(); + return obj is IEcsMetaProvider intr ? + TryGetColor(intr.MetaSource, out color) : + TryGetColor(type: obj.GetType(), out color); + } + public static bool TryGetColor(out MetaColor color) => TryGetColor(typeof(T), out color); + public static bool TryGetColor(Type type, out MetaColor color) + { + var atr = type.GetCustomAttribute(); if (atr != null) { - color = (atr.r, atr.g, atr.b); + color = atr.color; return true; } - color = ((byte)0, (byte)0, (byte)0); + color = MetaColor.BlackColor; + return false; + } + #endregion + + #region GetTags + public static IReadOnlyCollection GetTags(object obj) + { + return obj is IEcsMetaProvider intr ? + GetTags(intr.MetaSource) : + GetTags(type: obj.GetType()); + } + public static IReadOnlyCollection GetTags() => GetTags(typeof(T)); + public static IReadOnlyCollection GetTags(Type type) + { + var atr = type.GetCustomAttribute(); + return atr != null ? atr.Tags : Array.Empty(); + } + + public static bool TryGetTags(object obj, out IReadOnlyCollection tags) + { + return obj is IEcsMetaProvider intr ? + TryGetTags(intr.MetaSource, out tags) : + TryGetTags(type: obj.GetType(), out tags); + } + public static bool TryGetTags(out IReadOnlyCollection tags) => TryGetTags(typeof(T), out tags); + public static bool TryGetTags(Type type, out IReadOnlyCollection tags) + { + var atr = type.GetCustomAttribute(); + if (atr != null) + { + tags = atr.Tags; + return true; + } + tags = Array.Empty(); return false; } #endregion #region IsHidden + public static bool IsHidden(object obj) + { + return obj is IEcsMetaProvider intr ? + IsHidden(intr.MetaSource) : + IsHidden(type: obj.GetType()); + } public static bool IsHidden() => IsHidden(typeof(T)); - public static bool IsHidden(Type type) => type.TryGetCustomAttribute(out DebugHideAttribute _); + public static bool IsHidden(Type type) => type.TryGetCustomAttribute(out MetaTagsAttribute atr) && atr.Tags.Contains(MetaTags.HIDDEN); + #endregion + + #region MetaSource + public static bool IsMetaSourceProvided(object obj) + { + return obj is IEcsMetaProvider; + } + public static object GetMetaSource(object obj) + { + return obj is IEcsMetaProvider intr ? intr.MetaSource : obj; + } + #endregion + + #region GenerateTypeDebugData + public static TypeMetaData GenerateTypeDebugData(object obj) + { + return obj is IEcsMetaProvider intr ? + GenerateTypeDebugData(intr.MetaSource) : + GenerateTypeDebugData(type: obj.GetType()); + } + public static TypeMetaData GenerateTypeDebugData() => GenerateTypeDebugData(typeof(T)); + public static TypeMetaData GenerateTypeDebugData(Type type) + { + return new TypeMetaData( + type, + GetName(type), + GetGroup(type), + GetColor(type), + GetDescription(type), + GetTags(type).ToArray()); + } #endregion #region ReflectionExtensions - internal static bool TryGetCustomAttribute(this Type self, out T attribute) where T : Attribute + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryGetCustomAttribute(this Type self, out T attribute) where T : Attribute { attribute = self.GetCustomAttribute(); return attribute != null; } - internal static bool TryGetCustomAttribute(this MemberInfo self, out T attribute) where T : Attribute + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryGetCustomAttribute(this MemberInfo self, out T attribute) where T : Attribute { attribute = self.GetCustomAttribute(); return attribute != null; } #endregion } + + [Serializable] + public sealed class TypeMetaData + { + public readonly Type type; + public readonly string name; + public readonly MetaGroup group; + public readonly MetaColor color; + public readonly string description; + public readonly string[] tags; + public TypeMetaData(Type type, string name, MetaGroup group, MetaColor color, string description, string[] tags) + { + this.type = type; + this.name = name; + this.group = group; + this.color = color; + this.description = description; + this.tags = tags; + } + } } diff --git a/src/Debug/Interfaces.meta b/src/Debug/Interfaces.meta new file mode 100644 index 0000000..50388c5 --- /dev/null +++ b/src/Debug/Interfaces.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 50bc53c3762bf6b4ea127004a89a894e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Debug/Interfaces/IEcsMetaProvider.cs b/src/Debug/Interfaces/IEcsMetaProvider.cs new file mode 100644 index 0000000..a7bc11a --- /dev/null +++ b/src/Debug/Interfaces/IEcsMetaProvider.cs @@ -0,0 +1,7 @@ +namespace DCFApixels.DragonECS +{ + public interface IEcsMetaProvider + { + object MetaSource { get; } + } +} diff --git a/src/Debug/Interfaces/IEcsMetaProvider.cs.meta b/src/Debug/Interfaces/IEcsMetaProvider.cs.meta new file mode 100644 index 0000000..b99941e --- /dev/null +++ b/src/Debug/Interfaces/IEcsMetaProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5379b4d037441ed4cb4171648c1453d4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Debug/Attributes.meta b/src/Debug/MetaAttributes.meta similarity index 100% rename from src/Debug/Attributes.meta rename to src/Debug/MetaAttributes.meta diff --git a/src/Debug/MetaAttributes/MetaColorAttribute.cs b/src/Debug/MetaAttributes/MetaColorAttribute.cs new file mode 100644 index 0000000..3538942 --- /dev/null +++ b/src/Debug/MetaAttributes/MetaColorAttribute.cs @@ -0,0 +1,109 @@ +using System; +using System.Runtime.InteropServices; + +namespace DCFApixels.DragonECS +{ + [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = false)] + public sealed class MetaColorAttribute : Attribute + { + public readonly MetaColor color; + public byte R => color.r; + public byte G => color.g; + public byte B => color.b; + public float FloatR => R / (float)byte.MaxValue; + public float FloatG => G / (float)byte.MaxValue; + public float FloatB => B / (float)byte.MaxValue; + public MetaColorAttribute(byte r, byte g, byte b) => color = new MetaColor(r, g, b, 255); + public MetaColorAttribute(int colorCode) => color = new MetaColor(colorCode, true); + } + [StructLayout(LayoutKind.Explicit, Pack = 1, Size = 4)] + public readonly struct MetaColor + { + public static readonly MetaColor BlackColor = new MetaColor(Black); + /// color code Red. RGB is (255, 0, 0) + public const int Red = (255 << 24) | (000 << 16) | (000 << 8) | 255; + /// color code Green. RGB is (0, 255, 0) + public const int Green = (000 << 24) | (255 << 16) | (000 << 8) | 255; + /// color code Blue. RGB is (0, 0, 255) + public const int Blue = (000 << 24) | (000 << 16) | (255 << 8) | 255; + + /// color code Yellow. RGB is (255, 255, 0) + public const int Yellow = (255 << 24) | (255 << 16) | (000 << 8) | 255; + /// color code Cyan. RGB is (0, 255, 255) + public const int Cyan = (000 << 24) | (255 << 16) | (255 << 8) | 255; + /// color code Magenta. RGB is (255, 0, 255) + public const int Magenta = (255 << 24) | (000 << 16) | (255 << 8) | 255; + + /// color code Orange. RGB is (255, 165, 0) + public const int Orange = (255 << 24) | (165 << 16) | (000 << 8) | 255; + /// color code OrangeRed. RGB is (255, 69, 0) + public const int OrangeRed = (255 << 24) | (69 << 16) | (000 << 8) | 255; + /// color code Lime. RGB is (125, 255, 0) + public const int Lime = (125 << 24) | (255 << 16) | (000 << 8) | 255; + /// color code Aquamarine. RGB is (127, 255, 212) + public const int Aquamarine = (127 << 24) | (255 << 16) | (212 << 8) | 255; + /// color code Goldenrod. RGB is (218, 165, 32) + public const int Goldenrod = (218 << 24) | (165 << 16) | (32 << 8) | 255; + /// color code DeepPink. RGB is (255, 105, 180) + public const int DeepPink = (255 << 24) | (105 << 16) | (180 << 8) | 255; + /// color code Crimson. RGB is (220, 20, 60) + public const int Crimson = (220 << 24) | (20 << 16) | (60 << 8) | 255; + /// color code BlueViolet. RGB is (138, 43, 226) + public const int BlueViolet = (138 << 24) | (43 << 16) | (226 << 8) | 255; + /// color code AmericanRose. RGB is (255, 3, 62) + public const int AmericanRose = (255 << 24) | (3 << 16) | (62 << 8) | 255; + + /// color code Grey/Gray. RGB is (127, 127, 127) + public const int Gray = (127 << 24) | (127 << 16) | (127 << 8) | 255; + /// color code Grey/Gray. RGB is (127, 127, 127) + public const int Grey = Gray; + /// color code Silver. RGB is (192, 192, 192) + public const int Silver = (192 << 24) | (192 << 16) | (192 << 8) | 255; + /// color code White. RGB is (255, 255, 255) + public const int White = -1; + /// color code Black. RGB is (0, 0, 0) + public const int Black = 0; + + [FieldOffset(0)] public readonly int colorCode; + [FieldOffset(3)] public readonly byte r; + [FieldOffset(2)] public readonly byte g; + [FieldOffset(1)] public readonly byte b; + [FieldOffset(0)] public readonly byte a; + public float FloatR => r / (float)byte.MaxValue; + public float FloatG => g / (float)byte.MaxValue; + public float FloatB => b / (float)byte.MaxValue; + public float FloatA => a / (float)byte.MaxValue; + public MetaColor(byte r, byte g, byte b) : this() + { + this.r = r; + this.g = g; + this.b = b; + a = 255; + } + public MetaColor(byte r, byte g, byte b, byte a) : this() + { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + public MetaColor(int colorCode) : this() => this.colorCode = colorCode; + public MetaColor(int colorCode, bool withoutAlpha) : this() => this.colorCode = withoutAlpha ? colorCode | 255 : colorCode; + public (byte, byte, byte) ToTupleRGB() => (r, g, b); + public (byte, byte, byte, byte) ToTupleRGBA() => (r, g, b, a); + + public MetaColor UpContrastColor() + { + byte minChannel = Math.Min(Math.Min(r, g), b); + byte maxChannel = Math.Max(Math.Max(r, g), b); + if (maxChannel == minChannel) + return default; + float factor = 255f / (maxChannel - minChannel); + return new MetaColor((byte)((r - minChannel) * factor), (byte)((g - minChannel) * factor), (byte)((b - minChannel) * factor)); + } + public static MetaColor operator /(MetaColor a, float b) + { + return new MetaColor((byte)(a.r / b), (byte)(a.g / b), (byte)(a.b / b)); + } + } +} \ No newline at end of file diff --git a/src/Debug/Attributes/DebugColorAttribute.cs.meta b/src/Debug/MetaAttributes/MetaColorAttribute.cs.meta similarity index 100% rename from src/Debug/Attributes/DebugColorAttribute.cs.meta rename to src/Debug/MetaAttributes/MetaColorAttribute.cs.meta diff --git a/src/Debug/MetaAttributes/MetaDescriptionAttribute.cs b/src/Debug/MetaAttributes/MetaDescriptionAttribute.cs new file mode 100644 index 0000000..38b4f16 --- /dev/null +++ b/src/Debug/MetaAttributes/MetaDescriptionAttribute.cs @@ -0,0 +1,11 @@ +using System; + +namespace DCFApixels.DragonECS +{ + [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = false)] + public sealed class MetaDescriptionAttribute : Attribute + { + public readonly string description; + public MetaDescriptionAttribute(string description) => this.description = description; + } +} diff --git a/src/Debug/Attributes/DebugDescriptionAttribute.cs.meta b/src/Debug/MetaAttributes/MetaDescriptionAttribute.cs.meta similarity index 100% rename from src/Debug/Attributes/DebugDescriptionAttribute.cs.meta rename to src/Debug/MetaAttributes/MetaDescriptionAttribute.cs.meta diff --git a/src/Debug/MetaAttributes/MetaGroupAttribute.cs b/src/Debug/MetaAttributes/MetaGroupAttribute.cs new file mode 100644 index 0000000..455877e --- /dev/null +++ b/src/Debug/MetaAttributes/MetaGroupAttribute.cs @@ -0,0 +1,33 @@ +using System; +using System.Text.RegularExpressions; + +namespace DCFApixels.DragonECS +{ + [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = false)] + public sealed class MetaGroupAttribute : Attribute + { + public static readonly MetaGroupAttribute Empty = new MetaGroupAttribute(""); + public readonly string name; + public readonly string rootCategory; + public MetaGroupAttribute(string name) + { + name = Regex.Replace(name, @"^[/|\\]+|[/|\\]+$", ""); + rootCategory = Regex.Match(name, @"^(.*?)[/\\]").Groups[1].Value; + this.name = name; + } + public string[] SplitCategories() + { + return Regex.Split(name, @"[/|\\]"); + } + public MetaGroup GetData() => new MetaGroup(this); + } + public readonly struct MetaGroup + { + public static readonly MetaGroup Empty = new MetaGroup(MetaGroupAttribute.Empty); + private readonly MetaGroupAttribute _source; + public string Name => _source.name; + public string RootCategory => _source.rootCategory; + public MetaGroup(MetaGroupAttribute source) => _source = source; + public string[] SplitCategories() => _source.SplitCategories(); + } +} diff --git a/src/Debug/MetaAttributes/MetaGroupAttribute.cs.meta b/src/Debug/MetaAttributes/MetaGroupAttribute.cs.meta new file mode 100644 index 0000000..594d6fb --- /dev/null +++ b/src/Debug/MetaAttributes/MetaGroupAttribute.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c447392c75f8b4a42a2e5c3eb49e5b82 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Debug/MetaAttributes/MetaNameAttribute.cs b/src/Debug/MetaAttributes/MetaNameAttribute.cs new file mode 100644 index 0000000..c4c8acd --- /dev/null +++ b/src/Debug/MetaAttributes/MetaNameAttribute.cs @@ -0,0 +1,11 @@ +using System; + +namespace DCFApixels.DragonECS +{ + [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = false)] + public sealed class MetaNameAttribute : Attribute + { + public readonly string name; + public MetaNameAttribute(string name) => this.name = name; + } +} diff --git a/src/Debug/Attributes/DebugNameAttribute.cs.meta b/src/Debug/MetaAttributes/MetaNameAttribute.cs.meta similarity index 100% rename from src/Debug/Attributes/DebugNameAttribute.cs.meta rename to src/Debug/MetaAttributes/MetaNameAttribute.cs.meta diff --git a/src/Debug/MetaAttributes/MetaTagsAttribute.cs b/src/Debug/MetaAttributes/MetaTagsAttribute.cs new file mode 100644 index 0000000..1dbf92a --- /dev/null +++ b/src/Debug/MetaAttributes/MetaTagsAttribute.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace DCFApixels.DragonECS +{ + [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class | AttributeTargets.Interface, Inherited = false, AllowMultiple = false)] + public sealed class MetaTagsAttribute : Attribute + { + private readonly string[] _tags; + public IReadOnlyCollection Tags => _tags; + + [Obsolete("With empty parameters, this attribute makes no sense.", true)] + public MetaTagsAttribute() { } + public MetaTagsAttribute(params string[] tags) + { + _tags = tags.Where(o => !string.IsNullOrEmpty(o)).ToArray(); + } + } + public readonly ref struct MetaTags + { + public const string HIDDEN = EcsConsts.META_HIDDEN_TAG; + } +} diff --git a/src/Debug/Attributes/DebugHideAttribute.cs.meta b/src/Debug/MetaAttributes/MetaTagsAttribute.cs.meta similarity index 100% rename from src/Debug/Attributes/DebugHideAttribute.cs.meta rename to src/Debug/MetaAttributes/MetaTagsAttribute.cs.meta diff --git a/src/EcsAspect.cs b/src/EcsAspect.cs index 3c47c0b..abadc2a 100644 --- a/src/EcsAspect.cs +++ b/src/EcsAspect.cs @@ -1,6 +1,6 @@ -using System; +using DCFApixels.DragonECS.Internal; +using System; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; using System.Linq; using System.Reflection; @@ -11,9 +11,7 @@ namespace DCFApixels.DragonECS { public abstract class EcsAspect { - [EditorBrowsable(EditorBrowsableState.Always)] internal EcsWorld source; - [EditorBrowsable(EditorBrowsableState.Always)] internal EcsMask mask; private bool _isInit; @@ -85,7 +83,7 @@ namespace DCFApixels.DragonECS { int id = _world.GetComponentID(type); #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (_inc.Contains(id) || _exc.Contains(id)) throw new EcsFrameworkException($"{type.Name} already in constraints list."); + if (_inc.Contains(id) || _exc.Contains(id)) Throw.ConstraintIsAlreadyContainedInMask(type); #endif _inc.Add(id); } @@ -93,7 +91,7 @@ namespace DCFApixels.DragonECS { int id = _world.GetComponentID(type); #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (_inc.Contains(id) || _exc.Contains(id)) throw new EcsFrameworkException($"{type.Name} already in constraints list."); + if (_inc.Contains(id) || _exc.Contains(id)) Throw.ConstraintIsAlreadyContainedInMask(type); #endif _exc.Add(id); } @@ -125,10 +123,10 @@ namespace DCFApixels.DragonECS foreach (var item in _combined) { EcsMask submask = item.aspect.mask; - maskInc.ExceptWith(submask._exc);//удаляю конфликтующие ограничения - maskExc.ExceptWith(submask._inc);//удаляю конфликтующие ограничения - maskInc.UnionWith(submask._inc); - maskExc.UnionWith(submask._exc); + maskInc.ExceptWith(submask.exc);//удаляю конфликтующие ограничения + maskExc.ExceptWith(submask.inc);//удаляю конфликтующие ограничения + maskInc.UnionWith(submask.inc); + maskExc.UnionWith(submask.exc); } maskInc.ExceptWith(_exc);//удаляю конфликтующие ограничения maskExc.ExceptWith(_inc);//удаляю конфликтующие ограничения @@ -141,12 +139,29 @@ namespace DCFApixels.DragonECS maskExc = _exc; } + Dictionary r = new Dictionary(); + foreach (var id in maskInc) + { + var bit = EcsMaskBit.FromID(id); + if (!r.TryAdd(bit.chankIndex, bit.mask)) + r[bit.chankIndex] = r[bit.chankIndex] | bit.mask; + } + EcsMaskBit[] incMasks = r.Select(o => new EcsMaskBit(o.Key, o.Value)).ToArray(); + r.Clear(); + foreach (var id in maskExc) + { + var bit = EcsMaskBit.FromID(id); + if (!r.TryAdd(bit.chankIndex, bit.mask)) + r[bit.chankIndex] = r[bit.chankIndex] | bit.mask; + } + EcsMaskBit[] excMasks = r.Select(o => new EcsMaskBit(o.Key, o.Value)).ToArray(); + var inc = maskInc.ToArray(); Array.Sort(inc); var exc = maskExc.ToArray(); Array.Sort(exc); - mask = new EcsMask(_world.WorldTypeID, inc, exc); + mask = new EcsMask(_world.id, inc, exc, incMasks, excMasks); _world = null; _inc = null; _exc = null; @@ -173,16 +188,16 @@ namespace DCFApixels.DragonECS { return new EcsAspectIterator(this, source.Entities); } - public EcsAspectIterator GetIteratorFor(EcsReadonlyGroup sourceGroup) + public EcsAspectIterator GetIteratorFor(EcsSpan span) { - return new EcsAspectIterator(this, sourceGroup); + return new EcsAspectIterator(this, span); } #endregion - private struct Combined + private readonly struct Combined { - public EcsAspect aspect; - public int order; + public readonly EcsAspect aspect; + public readonly int order; public Combined(EcsAspect aspect, int order) { this.aspect = aspect; @@ -201,27 +216,56 @@ namespace DCFApixels.DragonECS #endregion #region Mask + public readonly struct EcsMaskBit + { + private const int BITS = 32; + private const int DIV_SHIFT = 5; + private const int MOD_MASK = BITS - 1; + + public readonly int chankIndex; + public readonly int mask; + public EcsMaskBit(int chankIndex, int mask) + { + this.chankIndex = chankIndex; + this.mask = mask; + } + public static EcsMaskBit FromID(int id) + { + return new EcsMaskBit(id >> DIV_SHIFT, 1 << (id & MOD_MASK)); //аналогично new EcsMaskBit(id / BITS, 1 << (id % BITS)) но быстрее + } + public override string ToString() + { + return $"bit({chankIndex}, {mask})"; + } + } + [DebuggerTypeProxy(typeof(DebuggerProxy))] public sealed class EcsMask { - internal readonly int _worldTypeID; + internal readonly int worldID; + internal readonly EcsMaskBit[] incChunckMasks; + internal readonly EcsMaskBit[] excChunckMasks; + internal readonly int[] inc; + internal readonly int[] exc; + public int WorldID => worldID; /// Including constraints - internal readonly int[] _inc; + public ReadOnlySpan Inc => inc; /// Excluding constraints - internal readonly int[] _exc; - internal EcsMask(int worldTypeID, int[] inc, int[] exc) + public ReadOnlySpan Exc => exc; + internal EcsMask(int worldID, int[] inc, int[] exc, EcsMaskBit[] incChunckMasks, EcsMaskBit[] excChunckMasks) { #if DEBUG - if (worldTypeID == 0) throw new ArgumentException(); CheckConstraints(inc, exc); #endif - _worldTypeID = worldTypeID; - _inc = inc; - _exc = exc; + this.inc = inc; + this.exc = exc; + this.worldID = worldID; + this.incChunckMasks = incChunckMasks; + this.excChunckMasks = excChunckMasks; } #region Object - public override string ToString() => CreateLogString(_worldTypeID, _inc, _exc); + public override string ToString() => CreateLogString(worldID, inc, exc); #endregion #region Debug utils @@ -249,10 +293,10 @@ namespace DCFApixels.DragonECS return false; } #endif - private static string CreateLogString(int worldTypeID, int[] inc, int[] exc) + private static string CreateLogString(int worldID, int[] inc, int[] exc) { #if (DEBUG && !DISABLE_DEBUG) - string converter(int o) => EcsDebugUtility.GetGenericTypeName(WorldMetaStorage.GetComponentType(worldTypeID, o), 1); + string converter(int o) => EcsDebugUtility.GetGenericTypeName(EcsWorld.GetWorld(worldID).AllPools[o].ComponentType, 1); return $"Inc({string.Join(", ", inc.Select(converter))}) Exc({string.Join(", ", exc.Select(converter))})"; #else return $"Inc({string.Join(", ", inc)}) Exc({string.Join(", ", exc)})"; // Release optimization @@ -260,31 +304,28 @@ namespace DCFApixels.DragonECS } internal class DebuggerProxy { - public readonly Type worldType; - public readonly int worldTypeID; + public readonly EcsWorld world; + public readonly int worldID; + public readonly EcsMaskBit[] includedChunkMasks; + public readonly EcsMaskBit[] excludedChunkMasks; public readonly int[] included; public readonly int[] excluded; public readonly Type[] includedTypes; public readonly Type[] excludedTypes; + public DebuggerProxy(EcsMask mask) { - worldType = WorldMetaStorage.GetWorldType(mask._worldTypeID); - worldTypeID = mask._worldTypeID; - included = mask._inc; - excluded = mask._exc; - Type converter(int o) => WorldMetaStorage.GetComponentType(worldTypeID, o); + world = EcsWorld.GetWorld(mask.worldID); + worldID = mask.worldID; + includedChunkMasks = mask.incChunckMasks; + excludedChunkMasks = mask.excChunckMasks; + included = mask.inc; + excluded = mask.exc; + Type converter(int o) => world.GetComponentType(o); includedTypes = included.Select(converter).ToArray(); excludedTypes = excluded.Select(converter).ToArray(); } - public override string ToString() => CreateLogString(worldTypeID, included, excluded); - } - #endregion - - #region ThrowHelper - internal static class ThrowHelper - { - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowArgumentDifferentWorldsException() => throw new ArgumentException("The groups belong to different worlds."); + public override string ToString() => CreateLogString(worldID, included, excluded); } #endregion } @@ -293,14 +334,16 @@ namespace DCFApixels.DragonECS #region Iterator public ref struct EcsAspectIterator { + public readonly int worldID; public readonly EcsMask mask; - private EcsReadonlyGroup _sourceGroup; + private EcsSpan _span; private Enumerator _enumerator; - public EcsAspectIterator(EcsAspect aspect, EcsReadonlyGroup sourceGroup) + public EcsAspectIterator(EcsAspect aspect, EcsSpan span) { - mask = aspect.mask; - _sourceGroup = sourceGroup; + worldID = aspect.World.id; + mask = aspect.mask; + _span = span; _enumerator = default; } @@ -320,6 +363,30 @@ namespace DCFApixels.DragonECS while (enumerator.MoveNext()) group.AddInternal(enumerator.Current); } + public int CopyTo(ref int[] array) + { + var enumerator = GetEnumerator(); + int count = 0; + while (enumerator.MoveNext()) + { + if(array.Length <= count) + Array.Resize(ref array, array.Length << 1); + array[count++] = enumerator.Current; + } + return count; + } + public EcsSpan CopyToSpan(ref int[] array) + { + var enumerator = GetEnumerator(); + int count = 0; + while (enumerator.MoveNext()) + { + if (array.Length <= count) + Array.Resize(ref array, array.Length << 1); + array[count++] = enumerator.Current; + } + return new EcsSpan(worldID, array, count); + } #region object public override string ToString() @@ -336,38 +403,48 @@ namespace DCFApixels.DragonECS #region Enumerator [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Enumerator GetEnumerator() => new Enumerator(_sourceGroup, mask); + public Enumerator GetEnumerator() => new Enumerator(_span, mask); public ref struct Enumerator { - private EcsGroup.Enumerator _sourceGroup; - private readonly int[] _inc; - private readonly int[] _exc; - private readonly IEcsPoolImplementation[] _pools; + private ReadOnlySpan.Enumerator _span; + private readonly EcsMaskBit[] _inc; + private readonly EcsMaskBit[] _exc; + private readonly int[][] _entitiesComponentMasks; - public Enumerator(EcsReadonlyGroup sourceGroup, EcsMask mask) + public Enumerator(EcsSpan span, EcsMask mask) { - _sourceGroup = sourceGroup.GetEnumerator(); - _inc = mask._inc; - _exc = mask._exc; - _pools = sourceGroup.World._pools; + _span = span.GetEnumerator(); + _inc = mask.incChunckMasks; + _exc = mask.excChunckMasks; + _entitiesComponentMasks = span.World._entitiesComponentMasks; } public int Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _sourceGroup.Current; + get => _span.Current; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool MoveNext() { - while (_sourceGroup.MoveNext()) + while (_span.MoveNext()) { - int e = _sourceGroup.Current; + int e = _span.Current; + EcsMaskBit bit; for (int i = 0, iMax = _inc.Length; i < iMax; i++) - if (!_pools[_inc[i]].Has(e)) goto next; + { + bit = _inc[i]; + //if ((_entitiesComponentMasks[e][bit.chankIndex] & bit.mask) == 0) goto skip; + if ((_entitiesComponentMasks[e][bit.chankIndex] & bit.mask) != bit.mask) goto skip; + } for (int i = 0, iMax = _exc.Length; i < iMax; i++) - if (_pools[_exc[i]].Has(e)) goto next; + { + bit = _exc[i]; + //if ((_entitiesComponentMasks[e][bit.chankIndex] & bit.mask) != 0) goto skip; + if ((_entitiesComponentMasks[e][bit.chankIndex] & bit.mask) == bit.mask) goto skip; + } return true; - next: continue; + skip: continue; } return false; } diff --git a/src/EcsGroup.cs b/src/EcsGroup.cs deleted file mode 100644 index 7ec7c8c..0000000 --- a/src/EcsGroup.cs +++ /dev/null @@ -1,566 +0,0 @@ -using System; -using System.Linq; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS -using static DCFApixels.DragonECS.EcsGroup.ThrowHelper; -#endif - -namespace DCFApixels.DragonECS -{ - [StructLayout(LayoutKind.Sequential, Pack = 0, Size = 8)] - [DebuggerTypeProxy(typeof(DebuggerProxy))] - public readonly ref struct EcsReadonlyGroup - { - private readonly EcsGroup _source; - - #region Constructors - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public EcsReadonlyGroup(EcsGroup source) => _source = source; - #endregion - - #region Properties - public bool IsNull => _source == null; - public EcsWorld World - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _source.World; - } - public int Count - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _source.Count; - } - public int CapacityDense - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _source.CapacityDense; - } - public int CapacitySparce - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _source.CapacitySparce; - } - public bool IsReleazed - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _source.IsReleased; - } - public int this[int index] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _source[index]; - } - #endregion - - #region Methods - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Has(int entityID) => _source.Has(entityID); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int IndexOf(int entityID) => _source.IndexOf(entityID); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public EcsGroup.Enumerator GetEnumerator() => _source.GetEnumerator(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public EcsGroup Clone() => _source.Clone(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int[] Bake() => _source.Bake(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Bake(ref int[] entities) => _source.Bake(ref entities); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Bake(List entities) => _source.Bake(entities); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ReadOnlySpan ToSpan() => _source.ToSpan(); - public ReadOnlySpan ToSpan(int start, int length) => _source.ToSpan(start, length); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int First() => _source.First(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Last() => _source.Last(); - #endregion - - #region Object - public override string ToString() => _source != null ? _source.ToString() : "NULL"; - public override int GetHashCode() => _source.GetHashCode(); - public override bool Equals(object obj) => obj is EcsGroup group && group == this; - public bool Equals(EcsReadonlyGroup other) => _source == other._source; - #endregion - - #region Internal - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal EcsGroup GetGroupInternal() => _source; - - #endregion - - #region operators - public static bool operator ==(EcsReadonlyGroup a, EcsReadonlyGroup b) => a.Equals(b); - public static bool operator ==(EcsReadonlyGroup a, EcsGroup b) => a.Equals(b); - public static bool operator !=(EcsReadonlyGroup a, EcsReadonlyGroup b) => !a.Equals(b); - public static bool operator !=(EcsReadonlyGroup a, EcsGroup b) => !a.Equals(b); - #endregion - - #region DebuggerProxy - internal class DebuggerProxy : EcsGroup.DebuggerProxy - { - public DebuggerProxy(EcsReadonlyGroup group) : base(group._source) { } - } - #endregion - } - - [DebuggerTypeProxy(typeof(DebuggerProxy))] - public unsafe class EcsGroup : IDisposable, IEquatable, IEnumerable - { - private EcsWorld _source; - private int[] _dense; - private int[] _sparse; - private int _count; - internal bool _isReleased = true; - - #region Properties - public EcsWorld World => _source; - public int Count - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _count; - } - public int CapacityDense - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _dense.Length; - } - public int CapacitySparce - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _sparse.Length; - } - public EcsReadonlyGroup Readonly - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => new EcsReadonlyGroup(this); - } - public bool IsReleased - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _isReleased; - } - public int this[int index] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { -#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (index < 0 || index >= Count) ThrowArgumentOutOfRange(); -#endif - return _dense[++index]; - } - } - #endregion - - #region Constrcutors/Dispose - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static EcsGroup New(EcsWorld world) - { - return world.GetFreeGroup(); - } - internal EcsGroup(EcsWorld world, int denseCapacity = 64) - { - _source = world; - _source.RegisterGroup(this); - _dense = new int[denseCapacity]; - _sparse = new int[world.Capacity]; - - _count = 0; - } - public void Dispose() => _source.ReleaseGroup(this); - #endregion - - #region Has/IndexOf - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Has(int entityID) - { - return _sparse[entityID] > 0; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int IndexOf(int entityID) - { - return _sparse[entityID]; - } - #endregion - - #region Add/Remove - public void UncheckedAdd(int entityID) => AddInternal(entityID); - public void Add(int entityID) - { - if (Has(entityID)) return; - AddInternal(entityID); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void AddInternal(int entityID) - { -#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (Has(entityID)) ThrowAlreadyContains(entityID); -#endif - if (++_count >= _dense.Length) - Array.Resize(ref _dense, _dense.Length << 1); - _dense[_count] = entityID; - _sparse[entityID] = _count; - } - - public void UncheckedRemove(int entityID) => RemoveInternal(entityID); - public void Remove(int entityID) - { - if (!Has(entityID)) return; - RemoveInternal(entityID); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void RemoveInternal(int entityID) - { -#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (!Has(entityID)) ThrowDoesNotContain(entityID); -#endif - _dense[_sparse[entityID]] = _dense[_count]; - _sparse[_dense[_count--]] = _sparse[entityID]; - _sparse[entityID] = 0; - } - - public void RemoveUnusedEntityIDs() - { - foreach (var e in this) - { - if (!_source.IsUsed(e)) - RemoveInternal(e); - } - } - #endregion - - #region Clear - public void Clear() - { - _count = 0; - //массив _dense нет смысла очищать - for (int i = 0; i < _sparse.Length; i++) - _sparse[i] = 0; - } - #endregion - - #region CopyFrom/Clone/Bake/ToSpan - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void CopyFrom(EcsReadonlyGroup group) => CopyFrom(group.GetGroupInternal()); - public void CopyFrom(EcsGroup group) - { -#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (group.World != _source) throw new ArgumentException("groupFilter.WorldIndex != WorldIndex"); -#endif - if (_count > 0) - Clear(); - foreach (var item in group) - AddInternal(item); - } - public EcsGroup Clone() - { - EcsGroup result = _source.GetFreeGroup(); - result.CopyFrom(this); - return result; - } - public int[] Bake() - { - int[] result = new int[_count]; - Array.Copy(_dense, 1, result, 0, _count); - return result; - } - public int Bake(ref int[] entities) - { - if (entities.Length < _count) - entities = new int[_count]; - Array.Copy(_dense, 1, entities, 0, _count); - return _count; - } - public void Bake(List entities) - { - entities.Clear(); - foreach (var e in this) - entities.Add(e); - } - public ReadOnlySpan ToSpan() => new ReadOnlySpan(_dense, 0, _count); - public ReadOnlySpan ToSpan(int start, int length) - { -#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (start + length > _count) ThrowArgumentOutOfRangeException(); -#endif - return new ReadOnlySpan(_dense, start, length); - } - #endregion - - #region Set operations - /// as Union sets - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void UnionWith(EcsReadonlyGroup group) => UnionWith(group.GetGroupInternal()); - /// as Union sets - public void UnionWith(EcsGroup group) - { -#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (_source != group.World) ThrowArgumentDifferentWorldsException(); -#endif - foreach (var item in group) - if (!Has(item)) - AddInternal(item); - } - - /// as Except sets - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void ExceptWith(EcsReadonlyGroup group) => ExceptWith(group.GetGroupInternal()); - /// as Except sets - public void ExceptWith(EcsGroup group) - { -#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (_source != group.World) ThrowArgumentDifferentWorldsException(); -#endif - foreach (var item in this) - if (group.Has(item)) - RemoveInternal(item); - } - - /// as Intersect sets - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void IntersectWith(EcsReadonlyGroup group) => IntersectWith(group.GetGroupInternal()); - /// as Intersect sets - public void IntersectWith(EcsGroup group) - { -#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (World != group.World) ThrowArgumentDifferentWorldsException(); -#endif - foreach (var item in this) - if (!group.Has(item)) - RemoveInternal(item); - } - - /// as Symmetric Except sets - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void SymmetricExceptWith(EcsReadonlyGroup group) => SymmetricExceptWith(group.GetGroupInternal()); - /// as Symmetric Except sets - public void SymmetricExceptWith(EcsGroup group) - { -#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (_source != group.World) ThrowArgumentDifferentWorldsException(); -#endif - foreach (var item in group) - if (Has(item)) - RemoveInternal(item); - else - AddInternal(item); - } - public void Inverse() - { - foreach (var item in _source.Entities) - if (Has(item)) - RemoveInternal(item); - else - AddInternal(item); - } - #endregion - - #region Static Set operations - /// as Intersect sets - /// new group from pool - public static EcsGroup Union(EcsGroup a, EcsGroup b) - { -#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (a._source != b._source) ThrowArgumentDifferentWorldsException(); -#endif - EcsGroup result = a._source.GetFreeGroup(); - foreach (var item in a) - result.AddInternal(item); - foreach (var item in b) - result.Add(item); - return result; - } - /// as Except sets - /// new group from pool - public static EcsGroup Except(EcsGroup a, EcsGroup b) - { -#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (a._source != b._source) ThrowArgumentDifferentWorldsException(); -#endif - EcsGroup result = a._source.GetFreeGroup(); - foreach (var item in a) - if (!b.Has(item)) - result.AddInternal(item); - return result; - } - /// as Intersect sets - /// new group from pool - public static EcsGroup Intersect(EcsGroup a, EcsGroup b) - { -#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (a._source != b._source) ThrowArgumentDifferentWorldsException(); -#endif - EcsGroup result = a._source.GetFreeGroup(); - foreach (var item in a) - if (b.Has(item)) - result.AddInternal(item); - return result; - } - - /// as Symmetric Except sets - /// new group from pool - public static EcsGroup SymmetricExcept(EcsGroup a, EcsGroup b) - { -#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (a._source != b._source) ThrowArgumentDifferentWorldsException(); -#endif - EcsGroup result = a._source.GetFreeGroup(); - foreach (var item in a) - if (!b.Has(item)) - result.AddInternal(item); - foreach (var item in b) - if (!a.Has(item)) - result.AddInternal(item); - return result; - } - - public static EcsGroup Inverse(EcsGroup a) - { - EcsGroup result = a._source.GetFreeGroup(); - foreach (var item in a._source.Entities) - if (!a.Has(item)) - result.AddInternal(item); - return result; - } - #endregion - - #region Enumerator - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Enumerator GetEnumerator() - { - return new Enumerator(this); - } - IEnumerator IEnumerable.GetEnumerator() - { - for (int i = 0; i < _count; i++) - yield return _dense[i]; - } - IEnumerator IEnumerable.GetEnumerator() - { - for (int i = 0; i < _count; i++) - yield return _dense[i]; - } - public ref struct Enumerator - { - private readonly int[] _dense; - private readonly int _count; - private int _index; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Enumerator(EcsGroup group) - { - _dense = group._dense; - _count = group._count; - _index = 0; - } - public int Current - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => _dense[_index]; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool MoveNext() => ++_index <= _count && _count < _dense.Length; // <= потму что отсчет начинается с индекса 1 //_count < _dense.Length дает среде понять что проверки на выход за границы не нужны - } - #endregion - - #region Object - public override string ToString() => string.Join(", ", _dense.Cast(), 0, _count); - public override bool Equals(object obj) => obj is EcsGroup group && Equals(group); - public bool Equals(EcsReadonlyGroup other) => Equals(other.GetGroupInternal()); - public bool Equals(EcsGroup other) - { - if (other is null || other.Count != Count) - return false; - foreach (var e in other) - if (!Has(e)) - return false; - return true; - } - public override int GetHashCode() - { - int hash = 0; - foreach (var item in this) - hash ^= 1 << (item % 32); //реализация от балды, так как не нужен, но фишка в том что хеш не учитывает порядок сущьностей, что явлется правильным поведением. - return hash; - } - #endregion - - #region OtherMethods - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int First() => this[0]; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int Last() => this[_count - 1]; - - #endregion - - #region operators - private static bool StaticEquals(EcsGroup a, EcsReadonlyGroup b) => StaticEquals(a, b.GetGroupInternal()); - private static bool StaticEquals(EcsGroup a, EcsGroup b) - { - if (a is null) return false; - return a.Equals(b); - } - public static bool operator ==(EcsGroup a, EcsGroup b) => StaticEquals(a, b); - public static bool operator ==(EcsGroup a, EcsReadonlyGroup b) => StaticEquals(a, b); - public static bool operator !=(EcsGroup a, EcsGroup b) => !StaticEquals(a, b); - public static bool operator !=(EcsGroup a, EcsReadonlyGroup b) => !StaticEquals(a, b); - #endregion - - #region OnWorldResize - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void OnWorldResize(int newSize) - { - Array.Resize(ref _sparse, newSize); - } - #endregion - - #region ThrowHalper -#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - internal static class ThrowHelper - { - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowAlreadyContains(int entityID) => throw new EcsFrameworkException($"This group already contains entity {entityID}."); - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowArgumentOutOfRange() => throw new ArgumentOutOfRangeException($"index is less than 0 or is equal to or greater than Count."); - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowDoesNotContain(int entityID) => throw new EcsFrameworkException($"This group does not contain entity {entityID}."); - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowArgumentOutOfRangeException() => throw new ArgumentOutOfRangeException(); - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowArgumentDifferentWorldsException() => throw new ArgumentException("The groups belong to different worlds."); - } -#endif - #endregion - - #region DebuggerProxy - internal class DebuggerProxy - { - private EcsGroup _group; - public EcsWorld World => _group.World; - public bool IsReleased => _group.IsReleased; - public entlong[] Entities - { - get - { - entlong[] result = new entlong[_group.Count]; - int i = 0; - foreach (var e in _group) - result[i++] = _group.World.GetEntityLong(e); - return result; - } - } - public int Count => _group.Count; - public int CapacityDense => _group.CapacityDense; - public int CapacitySparce => _group.CapacitySparce; - - public override string ToString() => _group.ToString(); - public DebuggerProxy(EcsGroup group) => _group = group; - } - #endregion - } -} \ No newline at end of file diff --git a/src/EcsPipeline.cs b/src/EcsPipeline.cs index 5f915df..b77cccc 100644 --- a/src/EcsPipeline.cs +++ b/src/EcsPipeline.cs @@ -12,14 +12,14 @@ namespace DCFApixels.DragonECS public sealed class EcsPipeline { private IEcsProcess[] _allSystems; - private Dictionary _runners; + private Dictionary _runners = new Dictionary(); private IEcsRunProcess _runRunnerCache; private ReadOnlyCollection _allSystemsSealed; private ReadOnlyDictionary _allRunnersSealed; - private bool _isInit; - private bool _isDestoryed; + private bool _isInit = false; + private bool _isDestoryed = false; #region Properties public ReadOnlyCollection AllSystems => _allSystemsSealed; @@ -32,13 +32,8 @@ namespace DCFApixels.DragonECS private EcsPipeline(IEcsProcess[] systems) { _allSystems = systems; - _runners = new Dictionary(); - _allSystemsSealed = new ReadOnlyCollection(_allSystems); _allRunnersSealed = new ReadOnlyDictionary(_runners); - - _isInit = false; - _isDestoryed = false; } #endregion @@ -64,7 +59,7 @@ namespace DCFApixels.DragonECS { if (_isInit == true) { - EcsDebug.Print("[Warning]", $"This {nameof(EcsPipeline)} has already been initialized"); + EcsDebug.PrintWarning($"This {nameof(EcsPipeline)} has already been initialized"); return; } @@ -72,60 +67,41 @@ namespace DCFApixels.DragonECS ecsPipelineInjectRunner.Inject(this); EcsRunner.Destroy(ecsPipelineInjectRunner); var preInitRunner = GetRunner(); - preInitRunner.PreInit(this); + preInitRunner.PreInit(); EcsRunner.Destroy(preInitRunner); var initRunner = GetRunner(); - initRunner.Init(this); + initRunner.Init(); EcsRunner.Destroy(initRunner); _runRunnerCache = GetRunner(); _isInit = true; + GC.Collect(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Run() { #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - CheckBeforeInitForMethod(nameof(Run)); - CheckAfterDestroyForMethod(nameof(Run)); + if (!_isInit) Throw.Pipeline_MethodCalledBeforeInitialisation(nameof(Run)); + if (_isDestoryed) Throw.Pipeline_MethodCalledAfterDestruction(nameof(Run)); #endif - _runRunnerCache.Run(this); + _runRunnerCache.Run(); } public void Destroy() { #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - CheckBeforeInitForMethod(nameof(Run)); + if (!_isInit) Throw.Pipeline_MethodCalledBeforeInitialisation(nameof(Destroy)); #endif - if (_isDestoryed == true) + if (_isDestoryed) { - EcsDebug.Print("[Warning]", $"This {nameof(EcsPipeline)} has already been destroyed"); + EcsDebug.PrintWarning($"This {nameof(EcsPipeline)} has already been destroyed"); return; } _isDestoryed = true; - GetRunner().Destroy(this); + GetRunner().Destroy(); } #endregion - #region StateChecks -#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - private void CheckBeforeInitForMethod(string methodName) - { - if (!_isInit) - throw new MethodAccessException($"It is forbidden to call {methodName}, before initialization {nameof(EcsPipeline)}"); - } - private void CheckAfterInitForMethod(string methodName) - { - if (_isInit) - throw new MethodAccessException($"It is forbidden to call {methodName}, after initialization {nameof(EcsPipeline)}"); - } - private void CheckAfterDestroyForMethod(string methodName) - { - if (_isDestoryed) - throw new MethodAccessException($"It is forbidden to call {methodName}, after destroying {nameof(EcsPipeline)}"); - } -#endif - #endregion - #region Builder public static Builder New() => new Builder(); public class Builder @@ -184,7 +160,7 @@ namespace DCFApixels.DragonECS } public EcsPipeline Build() { - Add(new DeleteEmptyEntitesSystem(), EcsConsts.POST_END_LAYER); + Add(new EndFrameSystem(), EcsConsts.POST_END_LAYER); List result = new List(32); List basicBlockList = _systems[_basicLayer]; foreach (var item in _systems) diff --git a/src/EcsRunner.cs b/src/EcsRunner.cs index a86185a..e229c4d 100644 --- a/src/EcsRunner.cs +++ b/src/EcsRunner.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Reflection; -using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; using static DCFApixels.DragonECS.EcsDebugUtility; namespace DCFApixels.DragonECS @@ -22,6 +22,31 @@ namespace DCFApixels.DragonECS } public EcsRunnerFilterAttribute(object filter) : this(null, filter) { } } +#if UNITY_2020_3_OR_NEWER + [UnityEngine.Scripting.RequireDerived, UnityEngine.Scripting.Preserve] +#endif + [AttributeUsage(AttributeTargets.Interface, Inherited = false, AllowMultiple = false)] + public sealed class BindWithEcsRunnerAttribute : Attribute + { + private static readonly Type _baseType = typeof(EcsRunner<>); + public readonly Type runnerType; + public BindWithEcsRunnerAttribute(Type runnerType) + { + if (runnerType == null) + throw new ArgumentNullException(); + if (!CheckSubclass(runnerType)) + throw new ArgumentException(); + this.runnerType = runnerType; + } + private bool CheckSubclass(Type type) + { + if (type.IsGenericType && type.GetGenericTypeDefinition() == _baseType) + return true; + if (type.BaseType != null) + return CheckSubclass(type.BaseType); + return false; + } + } public interface IEcsProcess { } @@ -39,86 +64,6 @@ namespace DCFApixels.DragonECS void Destroy(); } - internal static class EcsRunnerActivator - { - private static Dictionary _runnerHandlerTypes; //interface base type/Runner handler type pairs; - static EcsRunnerActivator() - { - List delayedExceptions = new List(); - Type runnerBaseType = typeof(EcsRunner<>); - List runnerHandlerTypes = new List(); - - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) - { - runnerHandlerTypes.AddRange(assembly.GetTypes() - .Where(type => type.BaseType != null && type.BaseType.IsGenericType && runnerBaseType == type.BaseType.GetGenericTypeDefinition())); - } - -#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - for (int i = 0; i < runnerHandlerTypes.Count; i++) - { - var e = CheckRunnerValide(runnerHandlerTypes[i]); - if (e != null) - { - runnerHandlerTypes.RemoveAt(i--); - delayedExceptions.Add(e); - } - } -#endif - _runnerHandlerTypes = new Dictionary(); - foreach (var item in runnerHandlerTypes) - { - Type interfaceType = item.BaseType.GenericTypeArguments[0]; - _runnerHandlerTypes.Add(interfaceType.IsGenericType ? interfaceType.GetGenericTypeDefinition() : interfaceType, item); - } - if (delayedExceptions.Count > 0) - { - foreach (var item in delayedExceptions) EcsDebug.Print(EcsConsts.DEBUG_ERROR_TAG, item.Message); - throw delayedExceptions[0]; - } - } - - private static Exception CheckRunnerValide(Type type) //TODO доработать проверку валидности реалиазации ранера - { - Type baseType = type.BaseType; - Type baseTypeArgument = baseType.GenericTypeArguments[0]; - - if (type.ReflectedType != null) - { - return new EcsRunnerImplementationException($"{GetGenericTypeFullName(type, 1)}.ReflectedType must be Null, but equal to {GetGenericTypeFullName(type.ReflectedType, 1)}."); - } - if (!baseTypeArgument.IsInterface) - { - return new EcsRunnerImplementationException($"Argument T of class EcsRunner, can only be an inetrface. The {GetGenericTypeFullName(baseTypeArgument, 1)} type is not an interface."); - } - - var interfaces = type.GetInterfaces(); - - if (!interfaces.Any(o => o == baseTypeArgument)) - { - return new EcsRunnerImplementationException($"Runner {GetGenericTypeFullName(type, 1)} does not implement interface {GetGenericTypeFullName(baseTypeArgument, 1)}."); - } - - return null; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void InitFor() where TInterface : IEcsProcess - { - Type interfaceType = typeof(TInterface); - - if (!_runnerHandlerTypes.TryGetValue(interfaceType.IsGenericType ? interfaceType.GetGenericTypeDefinition() : interfaceType, out Type runnerType)) - { - throw new EcsRunnerImplementationException($"There is no implementation of a runner for the {GetGenericTypeFullName(1)} interface."); - } - if (interfaceType.IsGenericType) - { - Type[] genericTypes = interfaceType.GetGenericArguments(); - runnerType = runnerType.MakeGenericType(genericTypes); - } - EcsRunner.Register(runnerType); - } - } #if UNITY_2020_3_OR_NEWER [UnityEngine.Scripting.RequireDerived, UnityEngine.Scripting.Preserve] #endif @@ -185,9 +130,58 @@ namespace DCFApixels.DragonECS #endregion #region Instantiate + private static void CheckRunnerValide(Type type) //TODO доработать проверку валидности реалиазации ранера + { + Type targetInterface = typeof(TInterface); + if (type.IsAbstract) + { + throw new Exception(); + } + + Type GetRunnerBaseType(Type type) + { + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(EcsRunner<>)) + return type; + if (type.BaseType != null) + return GetRunnerBaseType(type.BaseType); + return null; + } + Type baseType = GetRunnerBaseType(type); + Type baseTypeArgument = baseType.GenericTypeArguments[0]; + + if (baseTypeArgument != targetInterface) + { + throw new Exception(); + } + + if (!type.GetInterfaces().Any(o => o == targetInterface)) + { + throw new EcsRunnerImplementationException($"Runner {GetGenericTypeFullName(type, 1)} does not implement interface {GetGenericTypeFullName(baseTypeArgument, 1)}."); + } + } + private static TInterface Instantiate(EcsPipeline source, TInterface[] targets, bool isHasFilter, object filter) { - if (_subclass == null) EcsRunnerActivator.InitFor(); + if (_subclass == null) + { + Type interfaceType = typeof(TInterface); + if (interfaceType.TryGetCustomAttribute(out BindWithEcsRunnerAttribute atr)) + { + Type runnerType = atr.runnerType; + if (interfaceType.IsGenericType) + { + Type[] genericTypes = interfaceType.GetGenericArguments(); + runnerType = runnerType.MakeGenericType(genericTypes); + } + CheckRunnerValide(runnerType); + _subclass = runnerType; + } + else + { + throw new EcsFrameworkException("Процесс не связан с раннером, используйте атрибуут BindWithEcsRunner(Type runnerType)"); + } + } + var instance = (EcsRunner)Activator.CreateInstance(_subclass); return (TInterface)(IEcsProcess)instance.Set(source, targets, isHasFilter, filter); } @@ -245,7 +239,7 @@ namespace DCFApixels.DragonECS _filter = null; OnDestroy(); } - protected virtual void OnSetup() { } + protected virtual void OnSetup() { } //rename to OnInitialize protected virtual void OnDestroy() { } } } @@ -263,4 +257,85 @@ namespace DCFApixels.DragonECS } } #endregion + + public static class EcsProcessUtility + { + private struct ProcessInterface + { + public Type interfaceType; + public string processName; + public ProcessInterface(Type interfaceType, string processName) + { + this.interfaceType = interfaceType; + this.processName = processName; + } + } + private static Dictionary _processes = new Dictionary(); + private static HashSet _systems = new HashSet(); + + static EcsProcessUtility() + { + Type processBasicInterface = typeof(IEcsProcess); + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + var types = assembly.GetTypes(); + foreach (var type in types) + { + if (type.GetInterface(nameof(IEcsProcess)) != null || type == processBasicInterface) + { + if (type.IsInterface) + { + string name = type.Name; + if (name[0] == 'I' && name.Length > 1 && char.IsUpper(name[1])) + name = name.Substring(1); + name = Regex.Replace(name, @"\bEcs|Process\b", ""); + if (Regex.IsMatch(name, "`\\w{1,}$")) + { + var s = name.Split("`"); + name = s[0] + $"<{s[1]}>"; + } + _processes.Add(type, new ProcessInterface(type, name)); + } + else + { + _systems.Add(type); + } + } + } + } + } + + #region Systems + public static bool IsSystem(Type type) => _systems.Contains(type); + public static bool IsEcsSystem(this Type type) => _systems.Contains(type); + #endregion + + #region Process + public static bool IsProcessInterface(Type type) + { + if (type.IsGenericType) type = type.GetGenericTypeDefinition(); + return _processes.ContainsKey(type); + } + public static bool IsEcsProcessInterface(this Type type) => IsProcessInterface(type); + + public static string GetProcessInterfaceName(Type type) + { + if (type.IsGenericType) type = type.GetGenericTypeDefinition(); + return _processes[type].processName; + } + public static bool TryGetProcessInterfaceName(Type type, out string name) + { + if (type.IsGenericType) type = type.GetGenericTypeDefinition(); + bool result = _processes.TryGetValue(type, out ProcessInterface data); + name = data.processName; + return result; + } + + public static IEnumerable GetEcsProcessInterfaces(this Type self) + { + return self.GetInterfaces().Where(o => o.IsEcsProcessInterface()); + } + #endregion + + } } diff --git a/src/EcsWorld.cache.cs b/src/EcsWorld.cache.cs new file mode 100644 index 0000000..8729c4b --- /dev/null +++ b/src/EcsWorld.cache.cs @@ -0,0 +1,50 @@ +namespace DCFApixels.DragonECS +{ + public abstract partial class EcsWorld + { + internal readonly struct PoolCache : IEcsWorldComponent> + where T : IEcsPoolImplementation, new() + { + public readonly T instance; + public PoolCache(T instance) => this.instance = instance; + void IEcsWorldComponent>.Init(ref PoolCache component, EcsWorld world) + { + component = new PoolCache(world.CreatePool()); + } + void IEcsWorldComponent>.OnDestroy(ref PoolCache component, EcsWorld world) + { + component = default; + } + } + internal readonly struct AspectCache : IEcsWorldComponent> + where T : EcsAspect + { + public readonly T instance; + public AspectCache(T instance) => this.instance = instance; + void IEcsWorldComponent>.Init(ref AspectCache component, EcsWorld world) + { + component = new AspectCache(EcsAspect.Builder.Build(world)); + } + void IEcsWorldComponent>.OnDestroy(ref AspectCache component, EcsWorld world) + { + component = default; + } + } + internal readonly struct ExcecutorCache : IEcsWorldComponent> + where T : EcsQueryExecutor, new() + { + public readonly T instance; + public ExcecutorCache(T instance) => this.instance = instance; + void IEcsWorldComponent>.Init(ref ExcecutorCache component, EcsWorld world) + { + T instance = new T(); + instance.Initialize(world); + component = new ExcecutorCache(instance); + } + void IEcsWorldComponent>.OnDestroy(ref ExcecutorCache component, EcsWorld world) + { + component = default; + } + } + } +} diff --git a/src/EcsWorld.cache.cs.meta b/src/EcsWorld.cache.cs.meta new file mode 100644 index 0000000..63d2a9d --- /dev/null +++ b/src/EcsWorld.cache.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5cae52accc835594f95c8d972e40c57e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/EcsWorld.cs b/src/EcsWorld.cs index 4d3b758..a071784 100644 --- a/src/EcsWorld.cs +++ b/src/EcsWorld.cs @@ -3,107 +3,14 @@ using DCFApixels.DragonECS.Utils; using System; using System.Collections.Generic; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace DCFApixels.DragonECS { - [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 4)] - public struct EcsWorldCmp where T : struct - { - private int _worldID; - public EcsWorldCmp(int worldID) => _worldID = worldID; - public EcsWorld World => EcsWorld.GetWorld(_worldID); - public ref T Value => ref EcsWorld.GetData(_worldID); - } - public abstract partial class EcsWorld - { - private const short GEN_BITS = 0x7fff; - private const short DEATH_GEN_BIT = short.MinValue; - private const int DEL_ENT_BUFFER_SIZE_OFFSET = 2; - - private static EcsWorld[] Worlds = new EcsWorld[4]; - private static IntDispenser _worldIdDispenser = new IntDispenser(0); - - private static List _dataReleaseres = new List(); - - static EcsWorld() - { - Worlds[0] = new EcsNullWorld(); - } - private static void ReleaseData(int worldID) - { - for (int i = 0, iMax = _dataReleaseres.Count; i < iMax; i++) - _dataReleaseres[i].Release(worldID); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static EcsWorld GetWorld(int worldID) => Worlds[worldID]; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref T GetData(int worldID) => ref WorldComponentPool.GetForWorld(worldID); - - private abstract class DataReleaser - { - public abstract void Release(int worldID); - } - private static class WorldComponentPool - { - private static T[] _items = new T[4]; - private static int[] _mapping = new int[4]; - private static int _count; - private static int[] _recycledItems = new int[4]; - private static int _recycledItemsCount; - private static IEcsWorldComponent _interface = EcsWorldComponentHandler.instance; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref T Get(int itemIndex) => ref _items[itemIndex]; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static ref T GetForWorld(int worldID) => ref _items[GetItemIndex(worldID)]; - public static int GetItemIndex(int worldID) - { - if (_mapping.Length < Worlds.Length) - Array.Resize(ref _mapping, Worlds.Length); - - ref int itemIndex = ref _mapping[worldID]; - if (itemIndex <= 0) - { - if (_recycledItemsCount > 0) - { - _count++; - itemIndex = _recycledItems[--_recycledItemsCount]; - } - else - { - itemIndex = ++_count; - } - _interface.Init(ref _items[itemIndex], Worlds[worldID]); - _dataReleaseres.Add(new Releaser()); - } - return itemIndex; - } - private static void Release(int worldID) - { - ref int itemIndex = ref _mapping[worldID]; - if (itemIndex != 0) - { - _interface.OnDestroy(ref _items[itemIndex], Worlds[worldID]); - _recycledItems[_recycledItemsCount++] = itemIndex; - } - } - private sealed class Releaser : DataReleaser - { - public sealed override void Release(int worldID) - { - WorldComponentPool.Release(worldID); - } - } - } - } public abstract partial class EcsWorld { public readonly short id; - private Type _worldType; - private int _worldTypeID; + private bool _isDestroyed; private IntDispenser _entityDispenser; private int _entitiesCount; @@ -114,12 +21,9 @@ namespace DCFApixels.DragonECS private int[] _delEntBuffer; private int _delEntBufferCount; - - internal IEcsPoolImplementation[] _pools; - private EcsNullPool _nullPool = EcsNullPool.instance; - - private EcsAspect[] _aspects; - private EcsQueryExecutor[] _executors; + private int _delEntBufferMinCount; + private int _freeSpace; + private bool _isEnableReleaseDelEntBuffer = true; private List> _groups = new List>(); private Stack _groupsPool = new Stack(64); @@ -127,10 +31,18 @@ namespace DCFApixels.DragonECS private List _listeners = new List(); private List _entityListeners = new List(); + internal int[][] _entitiesComponentMasks; + + private readonly PoolsMediator _poolsMediator; + #region Properties - public int WorldTypeID => _worldTypeID; + public bool IsDestroyed => _isDestroyed; public int Count => _entitiesCount; public int Capacity => _entitesCapacity; //_denseEntities.Length; + + public int DelEntBufferCount => _delEntBufferCount; + public bool IsEnableReleaseDelEntBuffer => _isEnableReleaseDelEntBuffer; + public EcsReadonlyGroup Entities => _allEntites.Readonly; public ReadOnlySpan AllPools => _pools;// new ReadOnlySpan(pools, 0, _poolsCount); #endregion @@ -139,19 +51,18 @@ namespace DCFApixels.DragonECS public EcsWorld() : this(true) { } internal EcsWorld(bool isIndexable) { + _poolsMediator = new PoolsMediator(this); + _entitesCapacity = 512; if (isIndexable) { - id = (short)_worldIdDispenser.GetFree(); + id = (short)_worldIdDispenser.UseFree(); if (id >= Worlds.Length) Array.Resize(ref Worlds, Worlds.Length << 1); Worlds[id] = this; } - _worldType = this.GetType(); - _worldTypeID = WorldMetaStorage.GetWorldID(_worldType); - _entityDispenser = new IntDispenser(0); _pools = new IEcsPoolImplementation[512]; ArrayUtility.Fill(_pools, _nullPool); @@ -161,12 +72,15 @@ namespace DCFApixels.DragonECS ArrayUtility.Fill(_gens, DEATH_GEN_BIT); _delEntBufferCount = 0; - _delEntBuffer = new int[_entitesCapacity >> DEL_ENT_BUFFER_SIZE_OFFSET]; + //_delEntBuffer = new int[_entitesCapacity >> DEL_ENT_BUFFER_SIZE_OFFSET]; + _delEntBuffer = new int[_entitesCapacity]; + _entitiesComponentMasks = new int[_entitesCapacity][]; + for (int i = 0; i < _entitesCapacity; i++) + _entitiesComponentMasks[i] = new int[_pools.Length / 32 + 1]; + + _delEntBufferMinCount = Math.Max(_delEntBuffer.Length >> DEL_ENT_BUFFER_SIZE_OFFSET, DEL_ENT_BUFFER_MIN_SIZE); _allEntites = GetFreeGroup(); - - _aspects = new EcsAspect[128]; - _executors = new EcsQueryExecutor[128]; } public void Destroy() { @@ -174,131 +88,149 @@ namespace DCFApixels.DragonECS _gens = null; _pools = null; _nullPool = null; - _aspects = null; - _executors = null; Worlds[id] = null; ReleaseData(id); _worldIdDispenser.Release(id); + _isDestroyed = true; + _poolIds = null; + _componentIds = null; } #endregion - #region ComponentInfo - public int GetComponentID() => WorldMetaStorage.GetComponentID(_worldTypeID); - public int GetComponentID(Type type) => WorldMetaStorage.GetComponentID(type, _worldTypeID); - public Type GetComponentType(int componentID) => WorldMetaStorage.GetComponentType(_worldTypeID, componentID); - public bool IsComponentTypeDeclared() => IsComponentTypeDeclared(typeof(T)); - public bool IsComponentTypeDeclared(Type type) => WorldMetaStorage.IsComponentTypeDeclared(_worldTypeID, type); - #endregion - #region Getters #if UNITY_2020_3_OR_NEWER [UnityEngine.Scripting.Preserve] #endif - public TPool GetPool() where TPool : IEcsPoolImplementation, new() - { - int index = WorldMetaStorage.GetPoolID(_worldTypeID); - if (index >= _pools.Length) - { - int oldCapacity = _pools.Length; - Array.Resize(ref _pools, _pools.Length << 1); - ArrayUtility.Fill(_pools, _nullPool, oldCapacity, oldCapacity - _pools.Length); - } - if (_pools[index] == _nullPool) - { - var pool = new TPool(); - _pools[index] = pool; - pool.OnInit(this, index); - } - return (TPool)_pools[index]; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public TAspect GetAspect() where TAspect : EcsAspect { - int index = WorldMetaStorage.GetAspectID(_worldTypeID); - if (index >= _aspects.Length) - Array.Resize(ref _aspects, _aspects.Length << 1); - if (_aspects[index] == null) - _aspects[index] = EcsAspect.Builder.Build(this); - return (TAspect)_aspects[index]; + return Get>().instance; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public TExecutor GetExecutor() where TExecutor : EcsQueryExecutor, new() { - int index = WorldMetaStorage.GetExecutorID(_worldTypeID); - if (index >= _executors.Length) - Array.Resize(ref _executors, _executors.Length << 1); - var result = _executors[index]; - if (result == null) - { - result = new TExecutor(); - _executors[index] = result; - result.Initialize(this); - } - return (TExecutor)result; + return Get>().instance; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T Get() where T : struct + { + return ref WorldComponentPool.GetForWorld(id); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref T GetUnchecked() where T : struct + { + return ref WorldComponentPool.GetForWorldUnchecked(id); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T Get(int worldID) where T : struct + { + return ref WorldComponentPool.GetForWorld(worldID); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetUnchecked(int worldID) where T : struct + { + return ref WorldComponentPool.GetForWorldUnchecked(worldID); } - public ref T Get() where T : struct => ref WorldComponentPool.GetForWorld(id); #endregion #region Where Query - public EcsReadonlyGroup WhereFor(EcsReadonlyGroup sourceGroup, out TAspect aspect) where TAspect : EcsAspect + public EcsReadonlyGroup WhereToGroupFor(EcsSpan span, out TAspect aspect) where TAspect : EcsAspect { + if(_isEnableReleaseDelEntBuffer) + { + ReleaseDelEntityBufferAll(); + } var executor = GetExecutor>(); aspect = executor.Aspect; - return executor.ExecuteFor(sourceGroup); + return executor.ExecuteFor(span); } - public EcsReadonlyGroup WhereFor(EcsReadonlyGroup sourceGroup) where TAspect : EcsAspect + public EcsReadonlyGroup WhereToGroupFor(EcsSpan span) where TAspect : EcsAspect { - return GetExecutor>().ExecuteFor(sourceGroup); + if (_isEnableReleaseDelEntBuffer) + { + ReleaseDelEntityBufferAll(); + } + return GetExecutor>().ExecuteFor(span); } - public EcsReadonlyGroup Where(out TAspect aspect) where TAspect : EcsAspect + public EcsReadonlyGroup WhereToGroup(out TAspect aspect) where TAspect : EcsAspect { + if (_isEnableReleaseDelEntBuffer) + { + ReleaseDelEntityBufferAll(); + } var executor = GetExecutor>(); aspect = executor.Aspect; return executor.Execute(); } - public EcsReadonlyGroup Where() where TAspect : EcsAspect + public EcsReadonlyGroup WhereToGroup() where TAspect : EcsAspect { + if (_isEnableReleaseDelEntBuffer) + { + ReleaseDelEntityBufferAll(); + } return GetExecutor>().Execute(); } + + public EcsSpan WhereFor(EcsSpan span, out TAspect aspect) where TAspect : EcsAspect + { + if (_isEnableReleaseDelEntBuffer) + { + ReleaseDelEntityBufferAll(); + } + var executor = GetExecutor>(); + aspect = executor.Aspect; + return executor.ExecuteFor(span); + } + public EcsSpan WhereFor(EcsSpan span) where TAspect : EcsAspect + { + if (_isEnableReleaseDelEntBuffer) + { + ReleaseDelEntityBufferAll(); + } + return GetExecutor>().ExecuteFor(span); + } + public EcsSpan Where(out TAspect aspect) where TAspect : EcsAspect + { + if (_isEnableReleaseDelEntBuffer) + { + ReleaseDelEntityBufferAll(); + } + var executor = GetExecutor>(); + aspect = executor.Aspect; + return executor.Execute(); + } + public EcsSpan Where() where TAspect : EcsAspect + { + if (_isEnableReleaseDelEntBuffer) + { + ReleaseDelEntityBufferAll(); + } + return GetExecutor>().Execute(); + } #endregion #region Entity - public int NewEmptyEntity() + public int NewEntity() { + //if (_isEnableReleaseDelEntBuffer && _freeSpace <= 1 && _delEntBufferCount > _delEntBufferMinCount) + // ReleaseDelEntityBufferAll(); + int entityID = _entityDispenser.GetFree(); + _freeSpace--; _entitiesCount++; if (_gens.Length <= entityID) - { - Array.Resize(ref _gens, _gens.Length << 1); - Array.Resize(ref _componentCounts, _gens.Length); - ArrayUtility.Fill(_gens, DEATH_GEN_BIT, _entitesCapacity); - _entitesCapacity = _gens.Length; + Upsize(); - for (int i = 0; i < _groups.Count; i++) - { - if (_groups[i].TryGetTarget(out EcsGroup group)) - { - group.OnWorldResize(_gens.Length); - } - else - { - int last = _groups.Count - 1; - _groups[i--] = _groups[last]; - _groups.RemoveAt(last); - } - } - foreach (var item in _pools) - item.OnWorldResize(_gens.Length); - - _listeners.InvokeOnWorldResize(_gens.Length); - } _gens[entityID] &= GEN_BITS; _allEntites.Add(entityID); _entityListeners.InvokeOnNewEntity(entityID); return entityID; } - public entlong NewEmptyEntityLong() + public entlong NewEntityLong() { - int e = NewEmptyEntity(); + int e = NewEntity(); return GetEntityLong(e); } public void DelEntity(int entityID) @@ -308,12 +240,15 @@ namespace DCFApixels.DragonECS _gens[entityID] |= DEATH_GEN_BIT; _entitiesCount--; _entityListeners.InvokeOnDelEntity(entityID); - - if (_delEntBufferCount >= _delEntBuffer.Length) - ReleaseDelEntityBuffer(); + //if (_delEntBufferCount >= _delEntBuffer.Length) + // ReleaseDelEntityBufferAll(); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public entlong GetEntityLong(int entityID) => new entlong(entityID, _gens[entityID], id); //TODO придумать получше имя метода + public unsafe entlong GetEntityLong(int entityID) + { + long x = (long)id << 48 | (long)_gens[entityID] << 32 | (long)entityID; + return *(entlong*)&x; + } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool IsAlive(int entityID, short gen) => _gens[entityID] == gen; [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -326,36 +261,30 @@ namespace DCFApixels.DragonECS public bool IsMatchesMask(EcsMask mask, int entityID) { #if (DEBUG && !DISABLE_DEBUG) || !DISABLE_DRAGONECS_ASSERT_CHEKS - if (mask._worldTypeID != _worldTypeID) + if (mask.worldID != id) throw new EcsFrameworkException("The types of the target world of the mask and this world are different."); #endif - for (int i = 0, iMax = mask._inc.Length; i < iMax; i++) + for (int i = 0, iMax = mask.incChunckMasks.Length; i < iMax; i++) { - if (!_pools[mask._inc[i]].Has(entityID)) + if (!_pools[mask.inc[i]].Has(entityID)) return false; } - for (int i = 0, iMax = mask._exc.Length; i < iMax; i++) + for (int i = 0, iMax = mask.excChunckMasks.Length; i < iMax; i++) { - if (_pools[mask._exc[i]].Has(entityID)) + if (_pools[mask.exc[i]].Has(entityID)) return false; } return true; } - public void ReleaseDelEntityBuffer() - { - ReadOnlySpan buffser = new ReadOnlySpan(_delEntBuffer, 0, _delEntBufferCount); - foreach (var pool in _pools) - pool.OnReleaseDelEntityBuffer(buffser); - _listeners.InvokeOnReleaseDelEntityBuffer(buffser); - for (int i = 0; i < _delEntBufferCount; i++) - _entityDispenser.Release(_delEntBuffer[i]); - _delEntBufferCount = 0; - } + public void DeleteEmptyEntites() { foreach (var e in _allEntites) { - if (_componentCounts[e] <= 0) DelEntity(e); + if (_componentCounts[e] <= 0) + { + DelEntity(e); + } } } @@ -364,15 +293,30 @@ namespace DCFApixels.DragonECS { foreach (var pool in _pools) { - if (pool.Has(fromEntityID)) pool.Copy(fromEntityID, toEntityID); + if (pool.Has(fromEntityID)) + pool.Copy(fromEntityID, toEntityID); + } + } + public void CopyEntity(int fromEntityID, EcsWorld toWorld, int toEntityID) + { + foreach (var pool in _pools) + { + if (pool.Has(fromEntityID)) + pool.Copy(fromEntityID, toWorld, toEntityID); } } public int CloneEntity(int fromEntityID) { - int newEntity = NewEmptyEntity(); + int newEntity = NewEntity(); CopyEntity(fromEntityID, newEntity); return newEntity; } + public int CloneEntity(int fromEntityID, EcsWorld toWorld) + { + int newEntity = NewEntity(); + CopyEntity(fromEntityID, toWorld, newEntity); + return newEntity; + } public void CloneEntity(int fromEntityID, int toEntityID) { CopyEntity(fromEntityID, toEntityID); @@ -382,24 +326,128 @@ namespace DCFApixels.DragonECS pool.Del(toEntityID); } } + //public void CloneEntity(int fromEntityID, EcsWorld toWorld, int toEntityID) #endregion - #region Components Increment + #region Pools mediation [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void IncrementEntityComponentCount(int entityID) => _componentCounts[entityID]++; + private void RegisterEntityComponent(int entityID, int componentTypeID, EcsMaskBit maskBit) + { + _componentCounts[entityID]++; + _entitiesComponentMasks[entityID][maskBit.chankIndex] |= maskBit.mask; + } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void DecrementEntityComponentCount(int entityID) + private void UnregisterEntityComponent(int entityID, int componentTypeID, EcsMaskBit maskBit) { var count = --_componentCounts[entityID]; - if (count == 0 && _allEntites.Has(entityID)) DelEntity(entityID); -#if (DEBUG && !DISABLE_DEBUG) || !DISABLE_DRAGONECS_ASSERT_CHEKS - if (count < 0) throw new EcsFrameworkException("нарушен баланс инкремента/декремента компонентов"); + _entitiesComponentMasks[entityID][maskBit.chankIndex] &= ~maskBit.mask; + + if (count == 0 && _allEntites.Has(entityID)) + DelEntity(entityID); +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (count < 0) Throw.World_InvalidIncrementComponentsBalance(); #endif } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool HasEntityComponent(int entityID, EcsMaskBit maskBit) + { + return (_entitiesComponentMasks[entityID][maskBit.chankIndex] & maskBit.mask) != maskBit.mask; + } #endregion #endregion + #region DelEntBuffer + public AutoReleaseDelEntBufferLonkUnloker DisableAutoReleaseDelEntBuffer() + { + _isEnableReleaseDelEntBuffer = false; + return new AutoReleaseDelEntBufferLonkUnloker(this); + } + public void EnableAutoReleaseDelEntBuffer() + { + _isEnableReleaseDelEntBuffer = true; + } + public readonly struct AutoReleaseDelEntBufferLonkUnloker : IDisposable + { + private readonly EcsWorld _source; + public AutoReleaseDelEntBufferLonkUnloker(EcsWorld source) + { + _source = source; + } + public void Dispose() + { + _source.EnableAutoReleaseDelEntBuffer(); + } + } + public void ReleaseDelEntityBufferAll() + { + ReleaseDelEntityBuffer(-1); + } + public void ReleaseDelEntityBuffer(int count) + { + if (_delEntBufferCount <= 0) + { + return; + } + + if (count < 0) + { + count = _delEntBufferCount; + } + else if (count > _delEntBufferCount) + { + count = _delEntBufferCount; + } + _delEntBufferCount -= count; + ReadOnlySpan buffser = new ReadOnlySpan(_delEntBuffer, _delEntBufferCount, count); + for (int i = 0; i < _poolsCount; i++) + { + _pools[i].OnReleaseDelEntityBuffer(buffser); + } + _listeners.InvokeOnReleaseDelEntityBuffer(buffser); + for (int i = 0; i < buffser.Length; i++) + { + _entityDispenser.Release(buffser[i]); + } + _freeSpace += count;// _entitesCapacity - _entitiesCount; + } + #endregion + + #region Upsize + [MethodImpl(MethodImplOptions.NoInlining)] + private void Upsize() + { + Array.Resize(ref _gens, _gens.Length << 1); + Array.Resize(ref _componentCounts, _gens.Length); + Array.Resize(ref _delEntBuffer, _gens.Length); + Array.Resize(ref _entitiesComponentMasks, _gens.Length); + for (int i = _entitesCapacity; i < _gens.Length; i++) + _entitiesComponentMasks[i] = new int[_pools.Length / 32 + 1]; + + _delEntBufferMinCount = Math.Max(_delEntBuffer.Length >> DEL_ENT_BUFFER_SIZE_OFFSET, DEL_ENT_BUFFER_MIN_SIZE); + ArrayUtility.Fill(_gens, DEATH_GEN_BIT, _entitesCapacity); + _entitesCapacity = _gens.Length; + + for (int i = 0; i < _groups.Count; i++) + { + if (_groups[i].TryGetTarget(out EcsGroup group)) + { + group.OnWorldResize(_gens.Length); + } + else + { + int last = _groups.Count - 1; + _groups[i--] = _groups[last]; + _groups.RemoveAt(last); + } + } + foreach (var item in _pools) + item.OnWorldResize(_gens.Length); + + _listeners.InvokeOnWorldResize(_gens.Length); + } + #endregion + #region Groups Pool internal void RegisterGroup(EcsGroup group) { @@ -414,8 +462,7 @@ namespace DCFApixels.DragonECS internal void ReleaseGroup(EcsGroup group) { #if (DEBUG && !DISABLE_DEBUG) || !DISABLE_DRAGONECS_ASSERT_CHEKS - if (group.World != this) - throw new ArgumentException("groupFilter.WorldIndex != this"); + if (group.World != this) Throw.World_GroupDoesNotBelongWorld(); #endif group._isReleased = true; group.Clear(); @@ -447,18 +494,69 @@ namespace DCFApixels.DragonECS { list.Clear(); var itemsCount = GetComponentsCount(entityID); - - for (var i = 0; i < _pools.Length; i++) + for (var i = 0; i < _poolsCount; i++) { if (_pools[i].Has(entityID)) + { + itemsCount--; list.Add(_pools[i].GetRaw(entityID)); + if (itemsCount <= 0) + break; + } + } + } + public void GetComponentTypes(int entityID, HashSet typeSet) + { + typeSet.Clear(); + var itemsCount = GetComponentsCount(entityID); + for (var i = 0; i < _poolsCount; i++) + { + if (_pools[i].Has(entityID)) + { + itemsCount--; + typeSet.Add(_pools[i].ComponentType); + if (itemsCount <= 0) + break; + } + } + } + #endregion + + #region PoolsMediator + public readonly struct PoolsMediator + { + private readonly EcsWorld _world; + internal PoolsMediator(EcsWorld world) + { + if (world == null) + { + throw new ArgumentNullException(); + } + if (world._poolsMediator._world != null) + { + throw new MethodAccessException(); + } + _world = world; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void RegisterComponent(int entityID, int componentTypeID, EcsMaskBit maskBit) + { + _world.RegisterEntityComponent(entityID, componentTypeID, maskBit); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void UnregisterComponent(int entityID, int componentTypeID, EcsMaskBit maskBit) + { + _world.UnregisterEntityComponent(entityID, componentTypeID, maskBit); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool HasComponent(int entityID, EcsMaskBit maskBit) + { + return _world.HasEntityComponent(entityID, maskBit); } } #endregion } - internal sealed class EcsNullWorld : EcsWorld { } - #region Callbacks Interface public interface IEcsWorldEventListener { diff --git a/src/EcsWorld.pools.cs b/src/EcsWorld.pools.cs new file mode 100644 index 0000000..dd2a53c --- /dev/null +++ b/src/EcsWorld.pools.cs @@ -0,0 +1,111 @@ +using DCFApixels.DragonECS.Internal; +using DCFApixels.DragonECS.Utils; +using System; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace DCFApixels.DragonECS +{ + public abstract partial class EcsWorld + { + private SparseArray _poolIds = new SparseArray(); + private SparseArray _componentIds = new SparseArray(); + private int _poolsCount; + internal IEcsPoolImplementation[] _pools; + private EcsNullPool _nullPool = EcsNullPool.instance; + + #region ComponentInfo + public int GetComponentID() => DeclareComponentType(EcsTypeCode.Get()); + public int GetComponentID(Type type) => DeclareComponentType(EcsTypeCode.Get(type)); + public bool IsComponentTypeDeclared() => _componentIds.Contains(EcsTypeCode.Get()); + public bool IsComponentTypeDeclared(Type type) => _componentIds.Contains(EcsTypeCode.Get(type)); + public Type GetComponentType(int componentID) => _pools[componentID].ComponentType; + #endregion + + #region Getters + +#if UNITY_2020_3_OR_NEWER + [UnityEngine.Scripting.Preserve] +#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TPool GetPool() where TPool : IEcsPoolImplementation, new() + { + return Get>().instance; + } +#if UNITY_2020_3_OR_NEWER + [UnityEngine.Scripting.Preserve] +#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TPool GetPoolUnchecked() where TPool : IEcsPoolImplementation, new() + { + return GetUnchecked>().instance; + } +#if UNITY_2020_3_OR_NEWER + [UnityEngine.Scripting.Preserve] +#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPool GetPool(int worldID) where TPool : IEcsPoolImplementation, new() + { + return Get>(worldID).instance; + } +#if UNITY_2020_3_OR_NEWER + [UnityEngine.Scripting.Preserve] +#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPool UncheckedGetPool(int worldID) where TPool : IEcsPoolImplementation, new() + { + return GetUnchecked>(worldID).instance; + } + #endregion + + #region Declare/Create + private int DeclareComponentType(int typeCode) + { + if (!_componentIds.TryGetValue(typeCode, out int componentId)) + { + componentId = _poolsCount++; + _componentIds.Add(typeCode, componentId); + } + return componentId; + } + private TPool CreatePool() where TPool : IEcsPoolImplementation, new() + { + int poolTypeCode = EcsTypeCode.Get(); + if (_poolIds.Contains(poolTypeCode)) + throw new EcsFrameworkException("The pool has already been created."); + + Type componentType = typeof(TPool).GetInterfaces().First(o => o.IsGenericType && o.GetGenericTypeDefinition() == typeof(IEcsPoolImplementation<>)).GetGenericArguments()[0]; + int componentTypeCode = EcsTypeCode.Get(componentType); + + if (_componentIds.TryGetValue(componentTypeCode, out int componentTypeID)) + { + _poolIds[poolTypeCode] = componentTypeID; + } + else + { + componentTypeID = _poolsCount++; + _poolIds[poolTypeCode] = componentTypeID; + _componentIds[componentTypeCode] = componentTypeID; + } + + if (_poolsCount >= _pools.Length) + { + int oldCapacity = _pools.Length; + Array.Resize(ref _pools, _pools.Length << 1); + ArrayUtility.Fill(_pools, _nullPool, oldCapacity, oldCapacity - _pools.Length); + + for (int i = 0; i < _entitesCapacity; i++) + Array.Resize(ref _entitiesComponentMasks[i], _pools.Length / 32 + 1); + } + + if (_pools[componentTypeID] == _nullPool) + { + var pool = new TPool(); + _pools[componentTypeID] = pool; + pool.OnInit(this, _poolsMediator, componentTypeID); + } + return (TPool)_pools[componentTypeID]; + } + #endregion + } +} diff --git a/src/EcsWorld.pools.cs.meta b/src/EcsWorld.pools.cs.meta new file mode 100644 index 0000000..59c9a1c --- /dev/null +++ b/src/EcsWorld.pools.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2832746be4142a847b513bab5c276ba7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/EcsWorld.static.cs b/src/EcsWorld.static.cs new file mode 100644 index 0000000..10c18e9 --- /dev/null +++ b/src/EcsWorld.static.cs @@ -0,0 +1,130 @@ +using DCFApixels.DragonECS.Utils; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace DCFApixels.DragonECS +{ + [StructLayout(LayoutKind.Sequential, Pack = 4, Size = 4)] + public struct EcsWorldCmp where T : struct + { + private int _worldID; + public EcsWorldCmp(int worldID) => _worldID = worldID; + public EcsWorld World => EcsWorld.GetWorld(_worldID); + public ref T Value => ref EcsWorld.GetData(_worldID); + } + public abstract partial class EcsWorld + { + private const short GEN_BITS = 0x7fff; + private const short DEATH_GEN_BIT = short.MinValue; + private const int DEL_ENT_BUFFER_SIZE_OFFSET = 5; + private const int DEL_ENT_BUFFER_MIN_SIZE = 64; + + private static EcsWorld[] Worlds = new EcsWorld[4]; + private static IdDispenser _worldIdDispenser = new IdDispenser(0); + + private static List _dataReleaseres = new List(); + + static EcsWorld() + { + Worlds[0] = new EcsNullWorld(); + } + private static void ReleaseData(int worldID) + { + for (int i = 0, iMax = _dataReleaseres.Count; i < iMax; i++) + _dataReleaseres[i].Release(worldID); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EcsWorld GetWorld(int worldID) => Worlds[worldID]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetData(int worldID) => ref WorldComponentPool.GetForWorld(worldID); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T UncheckedGetData(int worldID) => ref WorldComponentPool.GetForWorldUnchecked(worldID); + + private abstract class DataReleaser + { + public abstract void Release(int worldID); + } + private static class WorldComponentPool + { + private static T[] _items = new T[4]; + private static short[] _mapping = new short[4]; + private static short _count; + private static short[] _recycledItems = new short[4]; + private static short _recycledItemsCount; + private static IEcsWorldComponent _interface = EcsWorldComponentHandler.instance; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T Get(int itemIndex) + { + return ref _items[itemIndex]; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetForWorld(int worldID) + { + int index = GetItemIndex(worldID); + return ref _items[index]; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref T GetForWorldUnchecked(int worldID) + { +#if (DEBUG && !DISABLE_DEBUG) + if (_mapping[worldID] <= 0) + throw new Exception(); +#endif + return ref _items[_mapping[worldID]]; + } + public static int GetItemIndex(int worldID) + { + if (_mapping.Length < Worlds.Length) + { + Array.Resize(ref _mapping, Worlds.Length); + + } + + ref short itemIndex = ref _mapping[worldID]; + if (itemIndex <= 0) + { + if (_recycledItemsCount > 0) + { + _count++; + itemIndex = _recycledItems[--_recycledItemsCount]; + } + else + { + itemIndex = ++_count; + } + if(_items.Length <= itemIndex) + { + Array.Resize(ref _items, _items.Length << 1); + } + _interface.Init(ref _items[itemIndex], Worlds[worldID]); + _dataReleaseres.Add(new Releaser()); + } + return itemIndex; + } + private static void Release(int worldID) + { + ref short itemIndex = ref _mapping[worldID]; + if (itemIndex != 0) + { + _interface.OnDestroy(ref _items[itemIndex], Worlds[worldID]); + _recycledItems[_recycledItemsCount++] = itemIndex; + } + } + private sealed class Releaser : DataReleaser + { + public sealed override void Release(int worldID) + { + WorldComponentPool.Release(worldID); + } + } + } + } + internal sealed class EcsNullWorld : EcsWorld + { + internal EcsNullWorld() : base(false) { } + } +} diff --git a/src/EcsWorld.static.cs.meta b/src/EcsWorld.static.cs.meta new file mode 100644 index 0000000..d99ff4b --- /dev/null +++ b/src/EcsWorld.static.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 30c8fd4d7c5aeae4486e16024b4f50cc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Executors/EcsQueryExecutor.cs b/src/Executors/EcsQueryExecutor.cs index c33cb3f..fdbf56a 100644 --- a/src/Executors/EcsQueryExecutor.cs +++ b/src/Executors/EcsQueryExecutor.cs @@ -1,16 +1,32 @@ -namespace DCFApixels.DragonECS +using System.Runtime.CompilerServices; + +namespace DCFApixels.DragonECS { public abstract class EcsQueryExecutor { - private EcsWorld _world; - public EcsWorld World => _world; + private EcsWorld _source; + public int WorldID + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _source.id; + } + public EcsWorld World + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _source; + } + public abstract long Version { get; } internal void Initialize(EcsWorld world) { - _world = world; + _source = world; OnInitialize(); } - internal void Destroy() => OnDestroy(); + internal void Destroy() + { + OnDestroy(); + _source = null; + } protected abstract void OnInitialize(); protected abstract void OnDestroy(); } -} +} \ No newline at end of file diff --git a/src/Executors/EcsWhereExecutor.cs b/src/Executors/EcsWhereExecutor.cs index 89a45ac..df3df19 100644 --- a/src/Executors/EcsWhereExecutor.cs +++ b/src/Executors/EcsWhereExecutor.cs @@ -1,19 +1,29 @@ -namespace DCFApixels.DragonECS +using System.Runtime.CompilerServices; + +namespace DCFApixels.DragonECS { public sealed class EcsWhereExecutor : EcsQueryExecutor where TAspect : EcsAspect { private TAspect _aspect; private EcsGroup _filteredGroup; - private long _executeVersion; + private long _version; #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - private EcsProfilerMarker _executeWhere = new EcsProfilerMarker("Where"); + private readonly EcsProfilerMarker _executeMarker = new EcsProfilerMarker("Where"); #endif #region Properties - public TAspect Aspect => _aspect; - internal long ExecuteVersion => _executeVersion; + public TAspect Aspect + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _aspect; + } + public sealed override long Version + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _version; + } #endregion #region OnInitialize/OnDestroy @@ -29,19 +39,80 @@ #endregion #region Methods + [MethodImpl(MethodImplOptions.AggressiveInlining)] public EcsReadonlyGroup Execute() => ExecuteFor(_aspect.World.Entities); - public EcsReadonlyGroup ExecuteFor(EcsReadonlyGroup sourceGroup) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EcsReadonlyGroup ExecuteFor(EcsSpan span) { #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - _executeWhere.Begin(); - if (sourceGroup.IsNull) throw new System.ArgumentNullException();//TODO составить текст исключения. + _executeMarker.Begin(); + if (span.IsNull) throw new System.ArgumentNullException();//TODO составить текст исключения. + if (span.WorldID != WorldID) throw new System.ArgumentException();//TODO составить текст исключения. #endif - _aspect.GetIteratorFor(sourceGroup).CopyTo(_filteredGroup); + unchecked { _version++; } + _aspect.GetIteratorFor(span).CopyTo(_filteredGroup); #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - _executeWhere.End(); + _executeMarker.End(); #endif return _filteredGroup.Readonly; } #endregion } + + + + + public sealed class EcsWhereSpanExecutor : EcsQueryExecutor where TAspect : EcsAspect + { + private TAspect _aspect; + private int[] _filteredEntities; + + private long _version; + +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + private readonly EcsProfilerMarker _executeMarker = new EcsProfilerMarker("Where"); +#endif + + #region Properties + public TAspect Aspect + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _aspect; + } + public sealed override long Version + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _version; + } + #endregion + + #region OnInitialize/OnDestroy + protected sealed override void OnInitialize() + { + _aspect = World.GetAspect(); + _filteredEntities = new int[32]; + } + protected sealed override void OnDestroy() { } + #endregion + + #region Methods + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EcsSpan Execute() => ExecuteFor(_aspect.World.Entities); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public EcsSpan ExecuteFor(EcsSpan span) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + _executeMarker.Begin(); + if (span.IsNull) throw new System.ArgumentNullException();//TODO составить текст исключения. + if (span.WorldID != WorldID) throw new System.ArgumentException();//TODO составить текст исключения. +#endif + unchecked { _version++; } + EcsSpan result = _aspect.GetIteratorFor(span).CopyToSpan(ref _filteredEntities); +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + _executeMarker.End(); +#endif + return result; + } + #endregion + } } diff --git a/src/Pools/EcsHybridPool.cs b/src/Pools/EcsHybridPool.cs new file mode 100644 index 0000000..e48ecda --- /dev/null +++ b/src/Pools/EcsHybridPool.cs @@ -0,0 +1,382 @@ +using DCFApixels.DragonECS.Internal; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace DCFApixels.DragonECS +{ + namespace Internal + { + public interface IEcsHybridPoolInternal : IEcsPool + { + void AddRefInternal(int entityID, object component, bool isAppend); + void DelInternal(int entityID, bool isAppend); + } + } + /// Pool for IEcsHybridComponent components + public sealed class EcsHybridPool : IEcsPoolImplementation, IEcsHybridPool, IEcsHybridPoolInternal, IEnumerable //IEnumerable - IntelliSense hack + where T : IEcsHybridComponent + { + private EcsWorld _source; + private int _componentTypeID; + private EcsMaskBit _maskBit; + + private int[] _mapping;// index = entityID / value = itemIndex;/ value = 0 = no entityID + private T[] _items; //dense + private int[] _entities; + private int _itemsCount; + + private int[] _recycledItems; + private int _recycledItemsCount; + + private List _listeners = new List(); + + private EcsWorld.PoolsMediator _mediator; + + #region Properites + public int Count => _itemsCount; + public int Capacity => _items.Length; + public int ComponentID => _componentTypeID; + public Type ComponentType => typeof(T); + public EcsWorld World => _source; + #endregion + + #region Methods + void IEcsHybridPoolInternal.AddRefInternal(int entityID, object component, bool isMain) + { + AddInternal(entityID, (T)component, isMain); + } + private void AddInternal(int entityID, T component, bool isMain) + { + ref int itemIndex = ref _mapping[entityID]; +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (itemIndex > 0) EcsPoolThrowHalper.ThrowAlreadyHasComponent(entityID); +#endif + if (_recycledItemsCount > 0) + { + itemIndex = _recycledItems[--_recycledItemsCount]; + _itemsCount++; + } + else + { + itemIndex = ++_itemsCount; + if (itemIndex >= _items.Length) + { + Array.Resize(ref _items, _items.Length << 1); + Array.Resize(ref _entities, _items.Length); + } + } + _mediator.RegisterComponent(entityID, _componentTypeID, _maskBit); + _listeners.InvokeOnAdd(entityID); + if (isMain) + component.OnAddToPool(_source.GetEntityLong(entityID)); + _items[itemIndex] = component; + _entities[itemIndex] = entityID; + } + public void Add(int entityID, T component) + { + HybridMapping mapping = _source.GetHybridMapping(component.GetType()); + mapping.GetTargetTypePool().AddRefInternal(entityID, component, false); + foreach (var pool in mapping.GetPools()) + pool.AddRefInternal(entityID, component, true); + } + public void Set(int entityID, T component) + { + if (Has(entityID)) + Del(entityID); + Add(entityID, component); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public T Get(int entityID) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (!Has(entityID)) EcsPoolThrowHalper.ThrowNotHaveComponent(entityID); +#endif + _listeners.InvokeOnGet(entityID); + return _items[_mapping[entityID]]; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref readonly T Read(int entityID) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (!Has(entityID)) EcsPoolThrowHalper.ThrowNotHaveComponent(entityID); +#endif + return ref _items[_mapping[entityID]]; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Has(int entityID) + { + return _mapping[entityID] > 0; + } + void IEcsHybridPoolInternal.DelInternal(int entityID, bool isMain) + { + DelInternal(entityID, isMain); + } + private void DelInternal(int entityID, bool isMain) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (!Has(entityID)) EcsPoolThrowHalper.ThrowNotHaveComponent(entityID); +#endif + ref int itemIndex = ref _mapping[entityID]; + T component = _items[itemIndex]; + if (isMain) + component.OnDelFromPool(_source.GetEntityLong(entityID)); + if (_recycledItemsCount >= _recycledItems.Length) + Array.Resize(ref _recycledItems, _recycledItems.Length << 1); + _recycledItems[_recycledItemsCount++] = itemIndex; + _mapping[entityID] = 0; + _entities[itemIndex] = 0; + _itemsCount--; + _mediator.UnregisterComponent(entityID, _componentTypeID, _maskBit); + _listeners.InvokeOnDel(entityID); + } + public void Del(int entityID) + { + var component = Get(entityID); + HybridMapping mapping = _source.GetHybridMapping(component.GetType()); + mapping.GetTargetTypePool().DelInternal(entityID, false); + foreach (var pool in mapping.GetPools()) + pool.DelInternal(entityID, true); + } + public void TryDel(int entityID) + { + if (Has(entityID)) Del(entityID); + } + public void Copy(int fromEntityID, int toEntityID) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (!Has(fromEntityID)) EcsPoolThrowHalper.ThrowNotHaveComponent(fromEntityID); +#endif + Set(toEntityID, Get(fromEntityID)); + } + public void Copy(int fromEntityID, EcsWorld toWorld, int toEntityID) + { +#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS + if (!Has(fromEntityID)) EcsPoolThrowHalper.ThrowNotHaveComponent(fromEntityID); +#endif + toWorld.GetPool().Set(toEntityID, Get(fromEntityID)); + } + + public void ClearNotAliveComponents() + { + for (int i = _itemsCount - 1; i >= 0; i--) + { + if (!_items[i].IsAlive) + Del(_entities[i]); + } + } + #endregion + + #region Callbacks + void IEcsPoolImplementation.OnInit(EcsWorld world, EcsWorld.PoolsMediator mediator, int componentTypeID) + { + _source = world; + _mediator = mediator; + _componentTypeID = componentTypeID; + _maskBit = EcsMaskBit.FromID(componentTypeID); + + const int capacity = 512; + + _mapping = new int[world.Capacity]; + _recycledItems = new int[128]; + _recycledItemsCount = 0; + _items = new T[capacity]; + _entities = new int[capacity]; + _itemsCount = 0; + } + void IEcsPoolImplementation.OnWorldResize(int newSize) + { + Array.Resize(ref _mapping, newSize); + } + void IEcsPoolImplementation.OnWorldDestroy() { } + void IEcsPoolImplementation.OnReleaseDelEntityBuffer(ReadOnlySpan buffer) + { + foreach (var entityID in buffer) + TryDel(entityID); + } + #endregion + + #region Other + void IEcsPool.AddRaw(int entityID, object dataRaw) => Add(entityID, (T)dataRaw); + object IEcsPool.GetRaw(int entityID) => Read(entityID); + void IEcsPool.SetRaw(int entityID, object dataRaw) => Set(entityID, (T)dataRaw); + #endregion + + #region Listeners + public void AddListener(IEcsPoolEventListener listener) + { + if (listener == null) { throw new ArgumentNullException("listener is null"); } + _listeners.Add(listener); + } + public void RemoveListener(IEcsPoolEventListener listener) + { + if (listener == null) { throw new ArgumentNullException("listener is null"); } + _listeners.Remove(listener); + } + #endregion + + #region IEnumerator - IntelliSense hack + IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException(); + IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException(); + #endregion + } + /// Hybrid component + public interface IEcsHybridComponent + { + bool IsAlive { get; } + void OnAddToPool(entlong entity); + void OnDelFromPool(entlong entity); + } + public static class EcsHybridPoolExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsNullOrNotAlive(this IEcsHybridComponent self) => self == null || self.IsAlive; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EcsHybridPool GetPool(this EcsWorld self) where T : IEcsHybridComponent + { + return self.GetPool>(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EcsHybridPool GetPoolUnchecked(this EcsWorld self) where T : IEcsHybridComponent + { + return self.GetPoolUnchecked>(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EcsHybridPool Include(this EcsAspectBuilderBase self) where T : IEcsHybridComponent + { + return self.Include>(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EcsHybridPool Exclude(this EcsAspectBuilderBase self) where T : IEcsHybridComponent + { + return self.Exclude>(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EcsHybridPool Optional(this EcsAspectBuilderBase self) where T : IEcsHybridComponent + { + return self.Optional>(); + } + + //------------------------------------------------- + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EcsHybridPool GetHybridPool(this EcsWorld self) where T : IEcsHybridComponent + { + return self.GetPool>(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EcsHybridPool GetHybridPoolUnchecked(this EcsWorld self) where T : IEcsHybridComponent + { + return self.GetPoolUnchecked>(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EcsHybridPool IncludeHybrid(this EcsAspectBuilderBase self) where T : IEcsHybridComponent + { + return self.Include>(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EcsHybridPool ExcludeHybrid(this EcsAspectBuilderBase self) where T : IEcsHybridComponent + { + return self.Exclude>(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EcsHybridPool OptionalHybrid(this EcsAspectBuilderBase self) where T : IEcsHybridComponent + { + return self.Optional>(); + } + } + + public abstract partial class EcsWorld + { + private Dictionary _hybridMapping = new Dictionary(); + internal HybridMapping GetHybridMapping(Type type) + { + if (!_hybridMapping.TryGetValue(type, out HybridMapping mapping)) + { + mapping = new HybridMapping(this, type); + _hybridMapping.Add(type, mapping); + } + return mapping; + } + } + + internal class HybridMapping + { + private EcsWorld _source; + private object[] _sourceForReflection; + private Type _type; + + private IEcsHybridPoolInternal _targetTypePool; + private List _relatedPools; + + private static Type hybridPoolType = typeof(EcsHybridPool<>); + private static MethodInfo getHybridPoolMethod = typeof(EcsHybridPoolExtensions).GetMethod($"{nameof(EcsHybridPoolExtensions.GetPool)}", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + + private static HashSet _hybridComponents = new HashSet(); + static HybridMapping() + { + Type hybridComponentType = typeof(IEcsHybridComponent); + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) + { + var types = assembly.GetTypes(); + foreach (var type in types) + { + if (type.GetInterface(nameof(IEcsHybridComponent)) != null && type != hybridComponentType) + { + _hybridComponents.Add(type); + } + } + } + } + public static bool IsEcsHybridComponentType(Type type) + { + return _hybridComponents.Contains(type); + } + + public HybridMapping(EcsWorld source, Type type) + { + if (!type.IsClass) + throw new ArgumentException(); + + _source = source; + _type = type; + _relatedPools = new List(); + _sourceForReflection = new object[] { source }; + _targetTypePool = CreateHybridPool(type); + foreach (var item in type.GetInterfaces()) + { + if (IsEcsHybridComponentType(item)) + { + _relatedPools.Add(CreateHybridPool(item)); + } + } + Type baseType = type.BaseType; + while (baseType != typeof(object) && IsEcsHybridComponentType(baseType)) + { + _relatedPools.Add(CreateHybridPool(baseType)); + baseType = baseType.BaseType; + } + } + private IEcsHybridPoolInternal CreateHybridPool(Type componentType) + { + //var x = (IEcsHybridPoolInternal)getHybridPoolMethod.MakeGenericMethod(componentType).Invoke(null, _sourceForReflection); + //Debug.Log("_" + x.ComponentID + "_" +x.ComponentType.Name); + //return x; + return (IEcsHybridPoolInternal)getHybridPoolMethod.MakeGenericMethod(componentType).Invoke(null, _sourceForReflection); + } + + public IEcsHybridPoolInternal GetTargetTypePool() + { + return _targetTypePool; + } + public List GetPools() + { + return _relatedPools; + } + } +} diff --git a/src/Pools/EcsHybridPool.cs.meta b/src/Pools/EcsHybridPool.cs.meta new file mode 100644 index 0000000..ec1c8be --- /dev/null +++ b/src/Pools/EcsHybridPool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 511487e83f936f94780e572063b68a87 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Pools/EcsPool.cs b/src/Pools/EcsPool.cs index cc1cb6d..315422b 100644 --- a/src/Pools/EcsPool.cs +++ b/src/Pools/EcsPool.cs @@ -2,16 +2,16 @@ using System; using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; -using static DCFApixels.DragonECS.EcsPoolThrowHalper; namespace DCFApixels.DragonECS { /// Pool for IEcsComponent components - public sealed class EcsPool : IEcsPoolImplementation, IEcsStructsPool, IEnumerable //IEnumerable - IntelliSense hack + public sealed class EcsPool : IEcsPoolImplementation, IEcsStructPool, IEnumerable //IEnumerable - IntelliSense hack where T : struct, IEcsComponent { private EcsWorld _source; - private int _id; + private int _componentTypeID; + private EcsMaskBit _maskBit; private int[] _mapping;// index = entityID / value = itemIndex;/ value = 0 = no entityID private T[] _items; //dense @@ -19,46 +19,27 @@ namespace DCFApixels.DragonECS private int[] _recycledItems; private int _recycledItemsCount; - private IEcsComponentReset _componentResetHandler; - private IEcsComponentCopy _componentCopyHandler; + private IEcsComponentReset _componentResetHandler = EcsComponentResetHandler.instance; + private IEcsComponentCopy _componentCopyHandler = EcsComponentCopyHandler.instance; - private List _listeners; + private List _listeners = new List(); + + private EcsWorld.PoolsMediator _mediator; #region Properites public int Count => _itemsCount; public int Capacity => _items.Length; - public int ComponentID => _id; + public int ComponentID => _componentTypeID; public Type ComponentType => typeof(T); public EcsWorld World => _source; #endregion - #region Init - void IEcsPoolImplementation.OnInit(EcsWorld world, int componentID) - { - _source = world; - _id = componentID; - - const int capacity = 512; - - _mapping = new int[world.Capacity]; - _recycledItems = new int[128]; - _recycledItemsCount = 0; - _items = new T[capacity]; - _itemsCount = 0; - - _listeners = new List(); - - _componentResetHandler = EcsComponentResetHandler.instance; - _componentCopyHandler = EcsComponentCopyHandler.instance; - } - #endregion - #region Methods public ref T Add(int entityID) { ref int itemIndex = ref _mapping[entityID]; #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (itemIndex > 0) ThrowAlreadyHasComponent(entityID); + if (itemIndex > 0) EcsPoolThrowHalper.ThrowAlreadyHasComponent(entityID); #endif if (_recycledItemsCount > 0) { @@ -71,7 +52,7 @@ namespace DCFApixels.DragonECS if (itemIndex >= _items.Length) Array.Resize(ref _items, _items.Length << 1); } - this.IncrementEntityComponentCount(entityID); + _mediator.RegisterComponent(entityID, _componentTypeID, _maskBit); _listeners.InvokeOnAddAndGet(entityID); return ref _items[itemIndex]; } @@ -79,7 +60,7 @@ namespace DCFApixels.DragonECS public ref T Get(int entityID) { #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (!Has(entityID)) ThrowNotHaveComponent(entityID); + if (!Has(entityID)) EcsPoolThrowHalper.ThrowNotHaveComponent(entityID); #endif _listeners.InvokeOnGet(entityID); return ref _items[_mapping[entityID]]; @@ -88,7 +69,7 @@ namespace DCFApixels.DragonECS public ref readonly T Read(int entityID) { #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (!Has(entityID)) ThrowNotHaveComponent(entityID); + if (!Has(entityID)) EcsPoolThrowHalper.ThrowNotHaveComponent(entityID); #endif return ref _items[_mapping[entityID]]; } @@ -108,7 +89,7 @@ namespace DCFApixels.DragonECS if (itemIndex >= _items.Length) Array.Resize(ref _items, _items.Length << 1); } - this.IncrementEntityComponentCount(entityID); + _mediator.RegisterComponent(entityID, _componentTypeID, _maskBit); _listeners.InvokeOnAdd(entityID); } _listeners.InvokeOnGet(entityID); @@ -122,7 +103,7 @@ namespace DCFApixels.DragonECS public void Del(int entityID) { #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (!Has(entityID)) ThrowNotHaveComponent(entityID); + if (!Has(entityID)) EcsPoolThrowHalper.ThrowNotHaveComponent(entityID); #endif ref int itemIndex = ref _mapping[entityID]; _componentResetHandler.Reset(ref _items[itemIndex]); @@ -131,7 +112,7 @@ namespace DCFApixels.DragonECS _recycledItems[_recycledItemsCount++] = itemIndex; _mapping[entityID] = 0; _itemsCount--; - this.DecrementEntityComponentCount(entityID); + _mediator.UnregisterComponent(entityID, _componentTypeID, _maskBit); _listeners.InvokeOnDel(entityID); } public void TryDel(int entityID) @@ -141,20 +122,35 @@ namespace DCFApixels.DragonECS public void Copy(int fromEntityID, int toEntityID) { #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (!Has(fromEntityID)) ThrowNotHaveComponent(fromEntityID); + if (!Has(fromEntityID)) EcsPoolThrowHalper.ThrowNotHaveComponent(fromEntityID); #endif _componentCopyHandler.Copy(ref Get(fromEntityID), ref TryAddOrGet(toEntityID)); } public void Copy(int fromEntityID, EcsWorld toWorld, int toEntityID) { #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (!Has(fromEntityID)) ThrowNotHaveComponent(fromEntityID); + if (!Has(fromEntityID)) EcsPoolThrowHalper.ThrowNotHaveComponent(fromEntityID); #endif _componentCopyHandler.Copy(ref Get(fromEntityID), ref toWorld.GetPool().TryAddOrGet(toEntityID)); } #endregion #region Callbacks + void IEcsPoolImplementation.OnInit(EcsWorld world, EcsWorld.PoolsMediator mediator, int componentTypeID) + { + _source = world; + _mediator = mediator; + _componentTypeID = componentTypeID; + _maskBit = EcsMaskBit.FromID(componentTypeID); + + const int capacity = 512; + + _mapping = new int[world.Capacity]; + _recycledItems = new int[128]; + _recycledItemsCount = 0; + _items = new T[capacity]; + _itemsCount = 0; + } void IEcsPoolImplementation.OnWorldResize(int newSize) { Array.Resize(ref _mapping, newSize); @@ -171,8 +167,8 @@ namespace DCFApixels.DragonECS void IEcsPool.AddRaw(int entityID, object dataRaw) => Add(entityID) = (T)dataRaw; object IEcsPool.GetRaw(int entityID) => Read(entityID); void IEcsPool.SetRaw(int entityID, object dataRaw) => Get(entityID) = (T)dataRaw; - ref readonly T IEcsStructsPool.Read(int entityID) => ref Read(entityID); - ref T IEcsStructsPool.Get(int entityID) => ref Get(entityID); + ref readonly T IEcsStructPool.Read(int entityID) => ref Read(entityID); + ref T IEcsStructPool.Get(int entityID) => ref Get(entityID); #endregion #region Listeners @@ -197,19 +193,28 @@ namespace DCFApixels.DragonECS public interface IEcsComponent { } public static class EcsPoolExt { + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static EcsPool GetPool(this EcsWorld self) where TComponent : struct, IEcsComponent { return self.GetPool>(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EcsPool GetPoolUnchecked(this EcsWorld self) where TComponent : struct, IEcsComponent + { + return self.GetPoolUnchecked>(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static EcsPool Include(this EcsAspectBuilderBase self) where TComponent : struct, IEcsComponent { return self.Include>(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static EcsPool Exclude(this EcsAspectBuilderBase self) where TComponent : struct, IEcsComponent { return self.Exclude>(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static EcsPool Optional(this EcsAspectBuilderBase self) where TComponent : struct, IEcsComponent { return self.Optional>(); diff --git a/src/Pools/EcsPoolBase.cs b/src/Pools/EcsPoolBase.cs index bb4d4cd..4ccffad 100644 --- a/src/Pools/EcsPoolBase.cs +++ b/src/Pools/EcsPoolBase.cs @@ -30,16 +30,26 @@ namespace DCFApixels.DragonECS void RemoveListener(IEcsPoolEventListener listener); #endregion } - public interface IEcsStructsPool + public interface IEcsStructPool : IEcsPool { ref T Add(int entityID); ref readonly T Read(int entityID); ref T Get(int entityID); } + public interface IEcsClassPool : IEcsPool + { + T Add(int entityID); + T Get(int entityID); + } + public interface IEcsHybridPool : IEcsPool + { + void Add(int entityID, T component); + T Get(int entityID); + } /// Only used to implement a custom pool. In other contexts use IEcsPool or IEcsPool. public interface IEcsPoolImplementation : IEcsPool { - void OnInit(EcsWorld world, int componentID); + void OnInit(EcsWorld world, EcsWorld.PoolsMediator mediator, int componentTypeID); void OnWorldResize(int newSize); void OnReleaseDelEntityBuffer(ReadOnlySpan buffer); void OnWorldDestroy(); @@ -52,26 +62,15 @@ namespace DCFApixels.DragonECS { public static void ThrowAlreadyHasComponent(int entityID) { - throw new EcsFrameworkException($"Entity({entityID}) already has component {typeof(T).Name}."); + throw new EcsFrameworkException($"Entity({entityID}) already has component {EcsDebugUtility.GetGenericTypeName()}."); } public static void ThrowNotHaveComponent(int entityID) { - throw new EcsFrameworkException($"Entity({entityID}) has no component {typeof(T).Name}."); + throw new EcsFrameworkException($"Entity({entityID}) has no component {EcsDebugUtility.GetGenericTypeName()}."); } } public static class IEcsPoolImplementationExtensions { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void IncrementEntityComponentCount(this IEcsPoolImplementation self, int entityID) - { - self.World.IncrementEntityComponentCount(entityID); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void DecrementEntityComponentCount(this IEcsPoolImplementation self, int entityID) - { - self.World.DecrementEntityComponentCount(entityID); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsNullOrDummy(this IEcsPool self) { @@ -106,7 +105,7 @@ namespace DCFApixels.DragonECS #endregion #region Callbacks - void IEcsPoolImplementation.OnInit(EcsWorld world, int componentID) { } + void IEcsPoolImplementation.OnInit(EcsWorld world, EcsWorld.PoolsMediator mediator, int componentTypeID) { } void IEcsPoolImplementation.OnWorldDestroy() { } void IEcsPoolImplementation.OnWorldResize(int newSize) { } void IEcsPoolImplementation.OnReleaseDelEntityBuffer(ReadOnlySpan buffer) { } diff --git a/src/Pools/EcsTagPool.cs b/src/Pools/EcsTagPool.cs index 01c958f..86529de 100644 --- a/src/Pools/EcsTagPool.cs +++ b/src/Pools/EcsTagPool.cs @@ -1,54 +1,56 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Reflection; using System.Runtime.CompilerServices; -using static DCFApixels.DragonECS.EcsPoolThrowHalper; namespace DCFApixels.DragonECS { - public sealed class EcsTagPool : IEcsPoolImplementation, IEcsStructsPool, IEnumerable //IEnumerable - IntelliSense hack + public sealed class EcsTagPool : IEcsPoolImplementation, IEcsStructPool, IEnumerable //IEnumerable - IntelliSense hack where T : struct, IEcsTagComponent { private EcsWorld _source; - private int _id; + private int _componentTypeID; + private EcsMaskBit _maskBit; private bool[] _mapping;// index = entityID / value = itemIndex;/ value = 0 = no entityID private int _count; - private List _listeners; + private List _listeners = new List(); private T _fakeComponent; + private EcsWorld.PoolsMediator _mediator; + + #region Constructors + private static bool _isInvalidType; + static EcsTagPool() + { + _isInvalidType = typeof(T).GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic).Length > 0; + } + public EcsTagPool() + { + if (_isInvalidType) + throw new EcsFrameworkException($"{typeof(T).Name} type must not contain any data."); + } + #endregion #region Properites public int Count => _count; int IEcsPool.Capacity => -1; - public int ComponentID => _id; + public int ComponentID => _componentTypeID; public Type ComponentType => typeof(T); public EcsWorld World => _source; #endregion - #region Init - void IEcsPoolImplementation.OnInit(EcsWorld world, int componentID) - { - _source = world; - _id = componentID; - - _mapping = new bool[world.Capacity]; - _count = 0; - - _listeners = new List(); - } - #endregion - #region Method public void Add(int entityID) { #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (Has(entityID)) ThrowAlreadyHasComponent(entityID); + if (Has(entityID)) EcsPoolThrowHalper.ThrowAlreadyHasComponent(entityID); #endif _count++; _mapping[entityID] = true; - this.IncrementEntityComponentCount(entityID); + _mediator.RegisterComponent(entityID, _componentTypeID, _maskBit); _listeners.InvokeOnAdd(entityID); } public void TryAdd(int entityID) @@ -57,7 +59,7 @@ namespace DCFApixels.DragonECS { _count++; _mapping[entityID] = true; - this.IncrementEntityComponentCount(entityID); + _mediator.RegisterComponent(entityID, _componentTypeID, _maskBit); _listeners.InvokeOnAdd(entityID); } } @@ -69,11 +71,11 @@ namespace DCFApixels.DragonECS public void Del(int entityID) { #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (!Has(entityID)) ThrowNotHaveComponent(entityID); + if (!Has(entityID)) EcsPoolThrowHalper.ThrowNotHaveComponent(entityID); #endif _mapping[entityID] = false; _count--; - this.DecrementEntityComponentCount(entityID); + _mediator.UnregisterComponent(entityID, _componentTypeID, _maskBit); _listeners.InvokeOnDel(entityID); } public void TryDel(int entityID) @@ -83,14 +85,14 @@ namespace DCFApixels.DragonECS public void Copy(int fromEntityID, int toEntityID) { #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (!Has(fromEntityID)) ThrowNotHaveComponent(fromEntityID); + if (!Has(fromEntityID)) EcsPoolThrowHalper.ThrowNotHaveComponent(fromEntityID); #endif TryAdd(toEntityID); } public void Copy(int fromEntityID, EcsWorld toWorld, int toEntityID) { #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (!Has(fromEntityID)) ThrowNotHaveComponent(fromEntityID); + if (!Has(fromEntityID)) EcsPoolThrowHalper.ThrowNotHaveComponent(fromEntityID); #endif toWorld.GetPool().TryAdd(toEntityID); } @@ -117,6 +119,16 @@ namespace DCFApixels.DragonECS #endregion #region Callbacks + void IEcsPoolImplementation.OnInit(EcsWorld world, EcsWorld.PoolsMediator mediator, int componentTypeID) + { + _source = world; + _mediator = mediator; + _componentTypeID = componentTypeID; + _maskBit = EcsMaskBit.FromID(componentTypeID); + + _mapping = new bool[world.Capacity]; + _count = 0; + } void IEcsPoolImplementation.OnWorldResize(int newSize) { Array.Resize(ref _mapping, newSize); @@ -131,22 +143,22 @@ namespace DCFApixels.DragonECS #endregion #region Other - ref T IEcsStructsPool.Add(int entityID) + ref T IEcsStructPool.Add(int entityID) { Add(entityID); return ref _fakeComponent; } - ref readonly T IEcsStructsPool.Read(int entityID) + ref readonly T IEcsStructPool.Read(int entityID) { #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (!Has(entityID)) ThrowNotHaveComponent(entityID); + if (!Has(entityID)) EcsPoolThrowHalper.ThrowNotHaveComponent(entityID); #endif return ref _fakeComponent; } - ref T IEcsStructsPool.Get(int entityID) + ref T IEcsStructPool.Get(int entityID) { #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (!Has(entityID)) ThrowNotHaveComponent(entityID); + if (!Has(entityID)) EcsPoolThrowHalper.ThrowNotHaveComponent(entityID); #endif return ref _fakeComponent; } @@ -154,14 +166,14 @@ namespace DCFApixels.DragonECS object IEcsPool.GetRaw(int entityID) { #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (!Has(entityID)) ThrowNotHaveComponent(entityID); + if (!Has(entityID)) EcsPoolThrowHalper.ThrowNotHaveComponent(entityID); #endif return _fakeComponent; } void IEcsPool.SetRaw(int entityID, object dataRaw) { #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (!Has(entityID)) ThrowNotHaveComponent(entityID); + if (!Has(entityID)) EcsPoolThrowHalper.ThrowNotHaveComponent(entityID); #endif } #endregion @@ -189,22 +201,60 @@ namespace DCFApixels.DragonECS public interface IEcsTagComponent { } public static class EcsTagPoolExt { + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static EcsTagPool GetPool(this EcsWorld self) where TTagComponent : struct, IEcsTagComponent { return self.GetPool>(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EcsTagPool GetPoolUnchecked(this EcsWorld self) where TTagComponent : struct, IEcsTagComponent + { + return self.GetPoolUnchecked>(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static EcsTagPool Include(this EcsAspectBuilderBase self) where TTagComponent : struct, IEcsTagComponent { return self.Include>(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static EcsTagPool Exclude(this EcsAspectBuilderBase self) where TTagComponent : struct, IEcsTagComponent { return self.Exclude>(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static EcsTagPool Optional(this EcsAspectBuilderBase self) where TTagComponent : struct, IEcsTagComponent { return self.Optional>(); } + + //--------------------------------------------------- + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EcsTagPool GetTagPool(this EcsWorld self) where TTagComponent : struct, IEcsTagComponent + { + return self.GetPool>(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EcsTagPool GetTagPoolUnchecked(this EcsWorld self) where TTagComponent : struct, IEcsTagComponent + { + return self.GetPoolUnchecked>(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EcsTagPool IncludeTag(this EcsAspectBuilderBase self) where TTagComponent : struct, IEcsTagComponent + { + return self.Include>(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EcsTagPool ExcludeTag(this EcsAspectBuilderBase self) where TTagComponent : struct, IEcsTagComponent + { + return self.Exclude>(); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EcsTagPool OptionalTag(this EcsAspectBuilderBase self) where TTagComponent : struct, IEcsTagComponent + { + return self.Optional>(); + } } } diff --git a/src/Utils/ArrayUtility.cs b/src/Utils/ArrayUtility.cs index 9eab0b6..7ad4540 100644 --- a/src/Utils/ArrayUtility.cs +++ b/src/Utils/ArrayUtility.cs @@ -1,4 +1,8 @@ -namespace DCFApixels.DragonECS.Utils +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System; + +namespace DCFApixels.DragonECS.Utils { internal static class ArrayUtility { @@ -12,4 +16,39 @@ array[i] = value; } } + + internal static unsafe class UnmanagedArrayUtility + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T* New(int capacity) where T : unmanaged + { + return (T*)Marshal.AllocHGlobal(Marshal.SizeOf() * capacity).ToPointer(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T* NewAndInit(int capacity) where T : unmanaged + { + int newSize = Marshal.SizeOf(typeof(T)) * capacity; + byte* newPointer = (byte*)Marshal.AllocHGlobal(newSize).ToPointer(); + + for (int i = 0; i < newSize; i++) + *(newPointer + i) = 0; + + return (T*)newPointer; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Free(void* pointer) + { + Marshal.FreeHGlobal(new IntPtr(pointer)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T* Resize(void* oldPointer, int newCount) where T : unmanaged + { + return (T*)(Marshal.ReAllocHGlobal( + new IntPtr(oldPointer), + new IntPtr(Marshal.SizeOf(typeof(T)) * newCount))).ToPointer(); + } + } } diff --git a/src/Utils/EcsTypeCode.cs b/src/Utils/EcsTypeCode.cs new file mode 100644 index 0000000..142e394 --- /dev/null +++ b/src/Utils/EcsTypeCode.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace DCFApixels.DragonECS.Internal +{ + public static class EcsTypeCode + { + private static readonly Dictionary _codes = new Dictionary(); + private static int _incremetn = 1; + public static int Count => _codes.Count; + public static int Get(Type type) + { + if (!_codes.TryGetValue(type, out int code)) + { + code = _incremetn++; + _codes.Add(type, code); + } + return code; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Get() => EcsTypeCodeCache.code; + public static bool Has(Type type) => _codes.ContainsKey(type); + public static bool Has() => _codes.ContainsKey(typeof(T)); + public static IEnumerable GetDeclared() => _codes.Select(o => new TypeCodeInfo(o.Key, o.Value)); + } + public static class EcsTypeCodeCache + { + public static readonly int code = EcsTypeCode.Get(typeof(T)); + } + public struct TypeCodeInfo + { + public Type type; + public int code; + public TypeCodeInfo(Type type, int code) + { + this.type = type; + this.code = code; + } + + public override string ToString() + { + return this.AutoToString(false) + "\n\r"; + } + } +} \ No newline at end of file diff --git a/src/Utils/EcsTypeCode.cs.meta b/src/Utils/EcsTypeCode.cs.meta new file mode 100644 index 0000000..8f99598 --- /dev/null +++ b/src/Utils/EcsTypeCode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: be4c15a212b2a6941bdbe78f18b038b3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Utils/Exceptions.cs b/src/Utils/Exceptions.cs index a965542..ed4e955 100644 --- a/src/Utils/Exceptions.cs +++ b/src/Utils/Exceptions.cs @@ -1,8 +1,90 @@ using System; +using System.Runtime.CompilerServices; using System.Runtime.Serialization; namespace DCFApixels.DragonECS { + namespace Internal + { + internal static class Throw + { + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void ArgumentNull() + { + throw new ArgumentNullException(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void ConstraintIsAlreadyContainedInMask(Type type) + { + throw new EcsFrameworkException($"The {EcsDebugUtility.GetGenericTypeName(type)} constraint is already contained in the mask."); + } + + //[MethodImpl(MethodImplOptions.NoInlining)] + //public static void ArgumentDifferentWorldsException() + //{ + // throw new ArgumentException("The groups belong to different worlds."); + //} + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void ArgumentOutOfRange() + { + throw new ArgumentOutOfRangeException($"index is less than 0 or is equal to or greater than Count."); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void Group_AlreadyContains(int entityID) + { + throw new EcsFrameworkException($"This group already contains entity {entityID}."); + } + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void Group_DoesNotContain(int entityID) + { + throw new EcsFrameworkException($"This group does not contain entity {entityID}."); + } + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void Group_ArgumentDifferentWorldsException() + { + throw new ArgumentException("The groups belong to different worlds."); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void Pipeline_MethodCalledAfterInitialisation(string methodName) + { + throw new MethodAccessException($"It is forbidden to call {methodName}, after initialization {nameof(EcsPipeline)}."); + } + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void Pipeline_MethodCalledBeforeInitialisation(string methodName) + { + throw new MethodAccessException($"It is forbidden to call {methodName}, before initialization {nameof(EcsPipeline)}."); + } + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void Pipeline_MethodCalledAfterDestruction(string methodName) + { + throw new MethodAccessException($"It is forbidden to call {methodName}, after destroying {nameof(EcsPipeline)}."); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void World_InvalidIncrementComponentsBalance() + { + throw new MethodAccessException("Invalid increment components balance."); + } + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void World_GroupDoesNotBelongWorld() + { + throw new MethodAccessException("The Group does not belong in this world."); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + internal static void Ent_ThrowIsNotAlive(entlong entity) + { + if (entity.IsNull) + throw new EcsFrameworkException($"The {entity} is null."); + else + throw new EcsFrameworkException($"The {entity} is not alive."); + } + } + } + [Serializable] public class EcsFrameworkException : Exception { diff --git a/src/Utils/GenericEnumerable.cs b/src/Utils/GenericEnumerable.cs new file mode 100644 index 0000000..a03a71d --- /dev/null +++ b/src/Utils/GenericEnumerable.cs @@ -0,0 +1,21 @@ +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; + +namespace DCFApixels.DragonECS.Internal +{ + public readonly struct GenericEnumerable : IEnumerable where TEnumerator : IEnumerator + { + public readonly TEnumerator _enumerator; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public GenericEnumerable(TEnumerator enumerator) => _enumerator = enumerator; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TEnumerator GetEnumerator() => _enumerator; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + IEnumerator IEnumerable.GetEnumerator() => _enumerator; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + IEnumerator IEnumerable.GetEnumerator() => _enumerator; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator GenericEnumerable(TEnumerator enumerator) => new GenericEnumerable(enumerator); + } +} \ No newline at end of file diff --git a/src/Utils/GenericEnumerable.cs.meta b/src/Utils/GenericEnumerable.cs.meta new file mode 100644 index 0000000..358d843 --- /dev/null +++ b/src/Utils/GenericEnumerable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 38e5b41bce0941b488ae64c200dcf965 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Utils/IdDispenser.cs b/src/Utils/IdDispenser.cs new file mode 100644 index 0000000..433edbb --- /dev/null +++ b/src/Utils/IdDispenser.cs @@ -0,0 +1,415 @@ +// Sparse Set based ID dispenser, with the ability to reserve IDs. +// Warning! Release version omits error exceptions, incorrect use may lead to unstable state. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace DCFApixels.DragonECS.Utils +{ + [Serializable] + [DebuggerTypeProxy(typeof(DebuggerProxy))] + public class IdDispenser : IEnumerable, IReadOnlyCollection + { + private const int MIN_SIZE = 4; + + private int[] _dense = Array.Empty(); + private int[] _sparse = Array.Empty(); + private IDState[] _sparseState = Array.Empty(); + + private int _usedCount; //[ |uuuu| ] + private int _reservedCount; //[rrr| | ] + private int _size; //[rrr|uuuu|ffffff] + + private int _nullID; + + #region Properties + /// Used Count + public int Count => _usedCount; + public int ReservedCount => _reservedCount; + public int Size => _size; + public int NullID => _nullID; + #endregion + + public IdDispenser(int capacity, int nullID = 0) + { + if (capacity % MIN_SIZE > 0) + capacity += MIN_SIZE; + Resize(capacity); + SetNullID(nullID); + + Reserved = new ReservedSpan(this); + Used = new UsedSpan(this); + } + + #region Use/Reserve/Release + /// Marks as used and returns next free id. + public int UseFree() + { + int count = _usedCount + _reservedCount; + CheckOrResize(count + 1); + int id = _dense[count]; + Add(id); + _sparseState[id] = IDState.Used; + return id; + } + public void UseFreeRange(ref int[] array, int range) + { + if (array.Length < range) + Array.Resize(ref array, range); + for (int i = 0; i < range; i++) + array[i] = UseFree(); + } + public void UseFreeRange(List list, int range) + { + for (int i = 0; i < range; i++) + list.Add(UseFree()); + } + /// Marks as used a free or reserved id, after this id cannot be retrieved via UseFree. + public void Use(int id) + { + CheckOrResize(id); +#if DEBUG + if (IsUsed(id) || IsReserved(id)) + { + if (IsUsed(id)) ThrowHalper.ThrowIsAlreadyInUse(id); + else ThrowHalper.ThrowIsHasBeenReserved(id); + } +#endif + if (IsFree(id)) + Add(id); + _sparseState[id] = IDState.Used; + } + public void UseRange(IEnumerable ids) + { + foreach (var item in ids) + Use(item); + } + /// Marks as reserved and returns next free id, after this id cannot be retrieved via UseFree. + public int ReserveFree() + { + int count = _usedCount + _reservedCount; + CheckOrResize(count + 1); + int id = _dense[count]; + _sparseState[id] = IDState.Reserved; + AddReserved(id); + return id; + } + /// Marks as reserved a free id, after this id cannot be retrieved via UseFree. + public void Reserve(int id) + { + CheckOrResize(id); +#if DEBUG + if (!IsFree(id)) ThrowHalper.ThrowIsNotAvailable(id); +#endif + _sparseState[id] = IDState.Reserved; + AddReserved(id); + } + public void ReserveRange(IEnumerable ids) + { + foreach (var item in ids) + Reserve(item); + } + public void Release(int id) + { + CheckOrResize(id); +#if DEBUG + if (IsFree(id) || IsNullID(id)) + { + if (IsFree(id)) ThrowHalper.ThrowIsNotUsed(id); + else ThrowHalper.ThrowIsNullID(id); + } +#endif + if (_sparseState[id] == IDState.Used) + Remove(id); + else + RemoveReserved(id); + _sparseState[id] = IDState.Free; + } + public void ReleaseRange(IEnumerable ids) + { + foreach (var item in ids) + Release(item); + } + public void ReleaseAll() + { + _usedCount = 0; + _reservedCount = 0; + for (int i = 0; i < _size;) + { + _sparse[i] = i; + _sparseState[i] = IDState.Free; + _dense[i] = i++; + _sparse[i] = i; + _sparseState[i] = IDState.Free; + _dense[i] = i++; + _sparse[i] = i; + _sparseState[i] = IDState.Free; + _dense[i] = i++; + _sparse[i] = i; + _sparseState[i] = IDState.Free; + _dense[i] = i++; + } + SetNullID(_nullID); + } + #endregion + + #region Checks + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsFree(int id) => _sparseState[id] == IDState.Free; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsReserved(int id) => _sparseState[id] == IDState.Reserved; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsUsed(int id) => _sparseState[id] == IDState.Used; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsNullID(int id) => id == _nullID; + + #endregion + + #region Sort + /// O(n) Sort. n = Size. Allows the UseFree method to return denser ids. + public void Sort() + { + int usedInc = _reservedCount; + int reservedInc = 0; + int freeInc = _reservedCount + _usedCount; + for (int i = 0; i < _size; i++) + { + switch (_sparseState[i]) + { + case IDState.Free: + _sparse[i] = freeInc; + _dense[freeInc++] = i; + break; + case IDState.Reserved: + _sparse[i] = reservedInc; + _dense[reservedInc++] = i; + break; + case IDState.Used: + _sparse[i] = usedInc; + _dense[usedInc++] = i; + break; + } + } + } + #endregion + + #region Other + private void SetNullID(int nullID) + { + _nullID = nullID; + if (nullID >= 0) + { + AddReserved(nullID); + _sparseState[nullID] = IDState.Reserved; + } + } + private bool IsValid() + { + for (int i = 0; i < _usedCount; i++) + { + if (_sparse[_dense[i]] != i || _dense[_sparse[i]] != i) + return false; + } + return true; + } + private void CheckOrResize(int id) + { + if (id > _size) + { + int leftBit = 0; + while (id != 0) + { + id >>= 1; + id &= int.MaxValue; + leftBit++; + } + if (leftBit >= 32) + Resize(int.MaxValue); + else + Resize(1 << leftBit); + } + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Add(int value) + { + Swap(value, _reservedCount + _usedCount++); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Remove(int value) + { + Swap(value, _reservedCount + --_usedCount); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AddReserved(int value) + { + Swap(value, _reservedCount + _usedCount); + Swap(value, _reservedCount++); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void RemoveReserved(int value) + { + Swap(value, --_reservedCount); + Swap(value, _reservedCount + _usedCount); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void Swap(int sparseIndex, int denseIndex) + { + int _dense_denseIndex_ = _dense[denseIndex]; + int _sparse_sparseIndex_ = _sparse[sparseIndex]; + _dense[denseIndex] = _dense[_sparse_sparseIndex_]; + _dense[_sparse_sparseIndex_] = _dense_denseIndex_; + _sparse[_dense_denseIndex_] = _sparse_sparseIndex_; + _sparse[sparseIndex] = denseIndex; + } + private void Resize(int newSize) + { + if (newSize < MIN_SIZE) + newSize = MIN_SIZE; + Array.Resize(ref _dense, newSize); + Array.Resize(ref _sparse, newSize); + Array.Resize(ref _sparseState, newSize); + for (int i = _size; i < newSize;) + { + _sparse[i] = i; + _dense[i] = i++; + _sparse[i] = i; + _dense[i] = i++; + _sparse[i] = i; + _dense[i] = i++; + _sparse[i] = i; + _dense[i] = i++; + } + _size = newSize; + Resized(newSize); + } + #endregion + + public delegate void ResizedHandler(int newSize); + public event ResizedHandler Resized = delegate { }; + + internal enum IDState : byte + { + Free, + Reserved, + Used, + } + + #region Enumerable + public UsedSpan Used; + public ReservedSpan Reserved; + public Enumerator GetEnumerator() => new Enumerator(_dense, _reservedCount, _usedCount); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public struct Enumerator : IEnumerator + { + private readonly int[] _dense; + private readonly int _count; + private int _index; + public int Current => _dense[_index]; + object IEnumerator.Current => Current; + public Enumerator(int[] dense, int startIndex, int count) + { + _dense = dense; + _count = startIndex + count; + _index = startIndex - 1; + } + public bool MoveNext() => ++_index < _count; + public void Dispose() { } + public void Reset() => _index = -1; + } + public readonly struct UsedSpan : IEnumerable + { + private readonly IdDispenser _instance; + public int Count => _instance._usedCount; + internal UsedSpan(IdDispenser instance) => _instance = instance; + public Enumerator GetEnumerator() => new Enumerator(_instance._dense, _instance._reservedCount, _instance._usedCount); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + public readonly struct ReservedSpan : IEnumerable + { + private readonly IdDispenser _instance; + public int Count => _instance._reservedCount; + internal ReservedSpan(IdDispenser instance) => _instance = instance; + public Enumerator GetEnumerator() => new Enumerator(_instance._dense, 0, _instance._reservedCount); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + #endregion + + #region Utils + private static class ThrowHalper + { + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowIsAlreadyInUse(int id) => throw new ArgumentException($"Id {id} is already in use."); + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowIsHasBeenReserved(int id) => throw new ArgumentException($"Id {id} has been reserved."); + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowIsNotUsed(int id) => throw new ArgumentException($"Id {id} is not used."); + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowIsNotAvailable(int id) => throw new ArgumentException($"Id {id} is not available."); + + [MethodImpl(MethodImplOptions.NoInlining)] + public static void ThrowIsNullID(int id) => throw new ArgumentException($"Id {id} cannot be released because it is used as a null id."); + } + + internal class DebuggerProxy + { + private IdDispenser _dispenser; + public DebuggerProxy(IdDispenser dispenser) => _dispenser = dispenser; +#if DEBUG + public IEnumerable Used => _dispenser.Used; + public IEnumerable Reserved => _dispenser.Reserved; + public Pair[] Pairs + { + get + { + Pair[] result = new Pair[_dispenser.Size]; + for (int i = 0; i < result.Length; i++) + result[i] = new Pair(_dispenser._dense[i], _dispenser._sparse[i]); + return result; + } + } + public ID[] All + { + get + { + ID[] result = new ID[_dispenser.Size]; + for (int i = 0; i < result.Length; i++) + { + int id = _dispenser._dense[i]; + result[i] = new ID(id, _dispenser._sparseState[id].ToString()); + } + return result; + } + } + public bool IsValid => _dispenser.IsValid(); + public int Count => _dispenser.ReservedCount; + public int Size => _dispenser.Size; + public int NullID => _dispenser._nullID; + internal readonly struct ID + { + public readonly int id; + public readonly string state; + public ID(int id, string state) { this.id = id; this.state = state; } + public override string ToString() => $"{id} - {state}"; + } + internal readonly struct Pair + { + public readonly int dense; + public readonly int sparse; + public Pair(int dense, int sparse) { this.dense = dense; this.sparse = sparse; } + public override string ToString() => $"{dense} - {sparse}"; + } +#endif + } + #endregion + } +} \ No newline at end of file diff --git a/src/Utils/IdDispenser.cs.meta b/src/Utils/IdDispenser.cs.meta new file mode 100644 index 0000000..eda80df --- /dev/null +++ b/src/Utils/IdDispenser.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 54c009cfa7ae0fd49938525468703c8b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Utils/ReadOnlySpanDummy.cs b/src/Utils/ReadOnlySpanDummy.cs index 74ddd8b..508b375 100644 --- a/src/Utils/ReadOnlySpanDummy.cs +++ b/src/Utils/ReadOnlySpanDummy.cs @@ -38,13 +38,13 @@ namespace DCFApixels.DragonECS #endregion #region Constructors + [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan(T[] array) { _array = array ?? Array.Empty(); _start = 0; _length = array.Length; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan(T[] array, int start, int length) { diff --git a/src/Utils/SparseArray.cs b/src/Utils/SparseArray.cs new file mode 100644 index 0000000..23fed2d --- /dev/null +++ b/src/Utils/SparseArray.cs @@ -0,0 +1,228 @@ +//SparseArray. Analogous to Dictionary, but faster. +//Benchmark result of indexer.get speed test with 300 elements: +//[Dictinary: 5.786us] [SparseArray: 2.047us]. +using System; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace DCFApixels.DragonECS.Utils +{ + public class SparseArray + { + public const int MIN_CAPACITY_BITS_OFFSET = 4; + public const int MIN_CAPACITY = 1 << MIN_CAPACITY_BITS_OFFSET; + private const int EMPTY = -1; + + private int[] _buckets = Array.Empty(); + private Entry[] _entries = Array.Empty(); + + private int _count; + + private int _freeList; + private int _freeCount; + + private int _modBitMask; + + #region Properties + public TValue this[int keyX, int keyY] + { + get => _entries[FindEntry((keyX << 16) | keyY)].value; + set => Insert(keyX + (keyY << 16), value); + } + public TValue this[int key] + { + get => _entries[FindEntry(key)].value; + set => Insert(key, value); + } + public int Count => _count; + #endregion + + #region Constructors + public SparseArray(int minCapacity = MIN_CAPACITY) + { + minCapacity = NormalizeCapacity(minCapacity); + _buckets = new int[minCapacity]; + for (int i = 0; i < minCapacity; i++) + _buckets[i] = EMPTY; + _entries = new Entry[minCapacity]; + _modBitMask = (minCapacity - 1) & 0x7FFFFFFF; + } + #endregion + + #region Add/Contains/Remove + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(int keyX, int keyY, TValue value) => Add((keyX << 16) | keyY, value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(int key, TValue value) + { +#if DEBUG + if (Contains(key)) + throw new ArgumentException("Contains(hashKey) is true"); +#endif + Insert(key, value); + } + + public bool Contains(int keyX, int keyY) => FindEntry((keyX << 16) | keyY) >= 0; + public bool Contains(int key) => FindEntry(key) >= 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Remove(int keyX, int keyY) => Remove((keyX << 16) | keyY); + public bool Remove(int key) + { + int bucket = key & _modBitMask; + int last = -1; + for (int i = _buckets[bucket]; i >= 0; last = i, i = _entries[i].next) + { + if (_entries[i].hashKey == key) + { + if (last < 0) + { + _buckets[bucket] = _entries[i].next; + } + else + { + _entries[last].next = _entries[i].next; + } + _entries[i].next = _freeList; + _entries[i].hashKey = -1; + _entries[i].value = default; + _freeList = i; + _freeCount++; + return true; + } + } + return false; + } + #endregion + + #region Find/Insert + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int FindEntry(int key) + { + for (int i = _buckets[key & _modBitMask]; i >= 0; i = _entries[i].next) + if (_entries[i].hashKey == key) return i; + return -1; + } + private void Insert(int key, TValue value) + { + int targetBucket = key & _modBitMask; + + for (int i = _buckets[targetBucket]; i >= 0; i = _entries[i].next) + { + if (_entries[i].hashKey == key) + { + _entries[i].value = value; + return; + } + } + + int index; + if (_freeCount > 0) + { + index = _freeList; + _freeList = _entries[index].next; + _freeCount--; + } + else + { + if (_count == _entries.Length) + { + Resize(); + targetBucket = key & _modBitMask; + } + index = _count++; + } + + _entries[index].next = _buckets[targetBucket]; + _entries[index].hashKey = key; + _entries[index].value = value; + _buckets[targetBucket] = index; + } + #endregion + + #region TryGetValue + public bool TryGetValue(int keyX, int keyY, out TValue value) + { + int index = FindEntry((keyX << 16) | keyY); + if (index < 0) + { + value = default; + return false; + } + value = _entries[index].value; + return true; + } + public bool TryGetValue(int key, out TValue value) + { + int index = FindEntry(key); + if (index < 0) + { + value = default; + return false; + } + value = _entries[index].value; + return true; + } + #endregion + + #region Clear + public void Clear() + { + if (_count > 0) + { + for (int i = 0; i < _buckets.Length; i++) + { + _buckets[i] = -1; + } + Array.Clear(_entries, 0, _count); + _count = 0; + } + } + #endregion + + #region Resize + private void Resize() + { + int newSize = _buckets.Length << 1; + _modBitMask = (newSize - 1) & 0x7FFFFFFF; + + Contract.Assert(newSize >= _entries.Length); + int[] newBuckets = new int[newSize]; + for (int i = 0; i < newBuckets.Length; i++) + newBuckets[i] = EMPTY; + + Entry[] newEntries = new Entry[newSize]; + Array.Copy(_entries, 0, newEntries, 0, _count); + for (int i = 0; i < _count; i++) + { + if (newEntries[i].hashKey >= 0) + { + int bucket = newEntries[i].hashKey % newSize; + newEntries[i].next = newBuckets[bucket]; + newBuckets[bucket] = i; + } + } + _buckets = newBuckets; + _entries = newEntries; + } + + private int NormalizeCapacity(int capacity) + { + int result = MIN_CAPACITY; + while (result < capacity) result <<= 1; + return result; + } + #endregion + + #region Utils + [StructLayout(LayoutKind.Sequential, Pack = 4)] + private struct Entry + { + public int next; // Index of next entry, -1 if last + public int hashKey; + public TValue value; + } + #endregion + } +} \ No newline at end of file diff --git a/src/Utils/SparseArray.cs.meta b/src/Utils/SparseArray.cs.meta new file mode 100644 index 0000000..11d53db --- /dev/null +++ b/src/Utils/SparseArray.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 25afb1113718f904c85dcbedd993d85e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Utils/SparseArray64.cs b/src/Utils/SparseArray64.cs new file mode 100644 index 0000000..946f800 --- /dev/null +++ b/src/Utils/SparseArray64.cs @@ -0,0 +1,236 @@ +//SparseArray64. Analogous to Dictionary, but faster. +//Benchmark result of indexer.get speed test with 300 elements: +//[Dictinary: 6.705us] [SparseArray64: 2.512us]. +using System; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace DCFApixels.DragonECS.Utils +{ + internal class SparseArray64 + { + public const int MIN_CAPACITY_BITS_OFFSET = 4; + public const int MIN_CAPACITY = 1 << MIN_CAPACITY_BITS_OFFSET; + private const int EMPTY = -1; + + private int[] _buckets = Array.Empty(); + private Entry[] _entries = Array.Empty(); + + private int _count; + + private int _freeList; + private int _freeCount; + + private int _modBitMask; + + #region Properties + public ref TValue this[long keyX, long keyY] + { + get => ref _entries[FindEntry(keyX + (keyY << 32))].value; + //set => Insert(keyX + (keyY << 32), value); + } + public ref TValue this[long key] + { + get => ref _entries[FindEntry(key)].value; + //set => Insert(key, value); + } + + public int Count => _count; + #endregion + + #region Constructors + public SparseArray64(int minCapacity = MIN_CAPACITY) + { + minCapacity = NormalizeCapacity(minCapacity); + _buckets = new int[minCapacity]; + for (int i = 0; i < minCapacity; i++) + _buckets[i] = EMPTY; + _entries = new Entry[minCapacity]; + _modBitMask = (minCapacity - 1) & 0x7FFFFFFF; + } + #endregion + + #region Add + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(long keyX, long keyY, TValue value) => Add(keyX + (keyY << 32), value); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(long key, TValue value) + { +#if DEBUG + if (Contains(key)) + throw new ArgumentException("Contains(hashKey) is true"); +#endif + Insert(key, value); + } + #endregion + + #region Find/Insert/Remove + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int FindEntry(long key) + { + for (int i = _buckets[unchecked((int)key & _modBitMask)]; i >= 0; i = _entries[i].next) + if (_entries[i].hashKey == key) return i; + return -1; + } + private void Insert(long key, TValue value) + { + int targetBucket = unchecked((int)key & _modBitMask); + + for (int i = _buckets[targetBucket]; i >= 0; i = _entries[i].next) + { + if (_entries[i].hashKey == key) + { + _entries[i].value = value; + return; + } + } + + int index; + if (_freeCount > 0) + { + index = _freeList; + _freeList = _entries[index].next; + _freeCount--; + } + else + { + if (_count == _entries.Length) + { + Resize(); + targetBucket = unchecked((int)key & _modBitMask); + } + index = _count++; + } + + _entries[index].next = _buckets[targetBucket]; + _entries[index].hashKey = key; + _entries[index].value = value; + _buckets[targetBucket] = index; + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Remove(long keyX, long keyY) => Remove(keyX + (keyY << 32)); + public bool Remove(long key) + { + int bucket = unchecked((int)key & _modBitMask); + int last = -1; + for (int i = _buckets[bucket]; i >= 0; last = i, i = _entries[i].next) + { + if (_entries[i].hashKey == key) + { + if (last < 0) + { + _buckets[bucket] = _entries[i].next; + } + else + { + _entries[last].next = _entries[i].next; + } + _entries[i].next = _freeList; + _entries[i].hashKey = -1; + _entries[i].value = default; + _freeList = i; + _freeCount++; + return true; + } + } + return false; + } + #endregion + + #region TryGetValue + public bool TryGetValue(long key, out TValue value) + { + int index = FindEntry(key); + if (index < 0) + { + value = default; + return false; + } + value = _entries[index].value; + return true; + } + public bool TryGetValue(long keyX, long keyY, out TValue value) + { + int index = FindEntry(keyX + (keyY << 32)); + if (index < 0) + { + value = default; + return false; + } + value = _entries[index].value; + return true; + } + #endregion + + #region Contains + public bool Contains(long keyX, long keyY) + { + return FindEntry(keyX + (keyY << 32)) >= 0; + } + public bool Contains(long key) + { + return FindEntry(key) >= 0; + } + #endregion + + #region Clear + public void Clear() + { + if (_count > 0) + { + for (int i = 0; i < _buckets.Length; i++) + { + _buckets[i] = -1; + } + Array.Clear(_entries, 0, _count); + _count = 0; + } + } + #endregion + + #region Resize + private void Resize() + { + int newSize = _buckets.Length << 1; + _modBitMask = (newSize - 1) & 0x7FFFFFFF; + + Contract.Assert(newSize >= _entries.Length); + int[] newBuckets = new int[newSize]; + for (int i = 0; i < newBuckets.Length; i++) + newBuckets[i] = EMPTY; + + Entry[] newEntries = new Entry[newSize]; + Array.Copy(_entries, 0, newEntries, 0, _count); + for (int i = 0; i < _count; i++) + { + if (newEntries[i].hashKey >= 0) + { + int bucket = unchecked((int)newEntries[i].hashKey & _modBitMask); + newEntries[i].next = newBuckets[bucket]; + newBuckets[bucket] = i; + } + } + _buckets = newBuckets; + _entries = newEntries; + } + + private int NormalizeCapacity(int capacity) + { + int result = MIN_CAPACITY; + while (result < capacity) result <<= 1; + return result; + } + #endregion + + #region Utils + [StructLayout(LayoutKind.Sequential, Pack = 4)] + private struct Entry + { + public int next; // Index of next entry, -1 if last + public long hashKey; + public TValue value; + } + #endregion + } +} \ No newline at end of file diff --git a/src/Utils/SparseArray64.cs.meta b/src/Utils/SparseArray64.cs.meta new file mode 100644 index 0000000..623cbd0 --- /dev/null +++ b/src/Utils/SparseArray64.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cab791e97df38274294035e89dc9de8a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/Utils/WorldMetaStorage.cs b/src/Utils/WorldMetaStorage.cs deleted file mode 100644 index da3b563..0000000 --- a/src/Utils/WorldMetaStorage.cs +++ /dev/null @@ -1,299 +0,0 @@ -using DCFApixels.DragonECS.Utils; -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; - -namespace DCFApixels.DragonECS -{ - //TODO этот класс требует переработки, изначально такая конструкция имела хорошую производительность, но сейчас он слишком раздулся - internal static class WorldMetaStorage - { - private static int _tokenCount = 0; - private static List _resizers = new List(); - private static WorldTypeMeta[] _metas = new WorldTypeMeta[0]; - private static Dictionary _worldIds = new Dictionary(); - private static class WorldIndex - { - public static int id = GetWorldID(typeof(TWorldArchetype)); - } - private static int GetToken(Type worldType) - { - WorldTypeMeta meta = new WorldTypeMeta(worldType); - meta.id = _tokenCount; - Array.Resize(ref _metas, ++_tokenCount); - _metas[_tokenCount - 1] = meta; - - foreach (var item in _resizers) - item.Resize(_tokenCount); - return _tokenCount - 1; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetWorldID(Type worldType) - { - if (!_worldIds.TryGetValue(worldType, out int id)) - { - id = GetToken(worldType); - _worldIds.Add(worldType, id); - } - return id; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Type GetWorldType(int worldTypeID) => _metas[worldTypeID].worldType; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetWorldID() => WorldIndex.id; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetComponentID(int worldID) => Component.Get(worldID); - public static int GetComponentID(Type type, int worldID) => _metas[worldID].GetComponentID(type); - public static bool IsComponentTypeDeclared(int worldID, Type type) => _metas[worldID].IsDeclaredComponentType(type); - public static Type GetComponentType(int worldID, int componentID) => _metas[worldID].GetComponentType(componentID); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetPoolID(int worldID) => Pool.Get(worldID); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetAspectID(int worldID) => Aspect.Get(worldID); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetExecutorID(int worldID) => Executor.Get(worldID); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int GetWorldComponentID(int worldID) => WorldComponent.Get(worldID); - public static int GetWorldComponentID(Type type, int worldID) => _metas[worldID].GetWorldComponentID(type); - - - private abstract class ResizerBase - { - public abstract Type Type { get; } - public abstract int[] IDS { get; } - public abstract void Resize(int size); - } - - #region Containers - public static class PoolComponentIdArrays - { - private static Dictionary _componentTypeArrayPairs = new Dictionary(); - - public static int[] GetIdsArray(Type type) - { - int targetSize = _tokenCount; - if (!_componentTypeArrayPairs.TryGetValue(type, out int[] result)) - { - result = new int[targetSize]; - for (int i = 0; i < result.Length; i++) - result[i] = -1; - _componentTypeArrayPairs.Add(type, result); - } - else - { - if (result.Length < targetSize) - { - int oldSize = result.Length; - Array.Resize(ref result, targetSize); - ArrayUtility.Fill(result, -1, oldSize, targetSize); - _componentTypeArrayPairs[type] = result; - } - } - - return result; - } - - public static int GetComponentID(Type type, int token) - { - GetIdsArray(type); - ref int id = ref _componentTypeArrayPairs[type][token]; - if (id < 0) - id = _metas[token].DeclareComponentType(type); - return id; - } - } - private static class Pool - { - public static int[] ids; - private static Type componentType = typeof(T).GetGenericArguments()[0]; - static Pool() - { - ids = PoolComponentIdArrays.GetIdsArray(componentType); - _resizers.Add(new Resizer()); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Get(int token) - { - ref int id = ref ids[token]; - if (id < 0) - { - id = PoolComponentIdArrays.GetComponentID(componentType, token); - } - return id; - } - private sealed class Resizer : ResizerBase - { - public override Type Type => typeof(T); - public override int[] IDS => ids; - public override void Resize(int size) - { - ids = PoolComponentIdArrays.GetIdsArray(componentType); - } - } - } - private static class Component - { - public static int[] ids; - static Component() - { - ids = PoolComponentIdArrays.GetIdsArray(typeof(T)); - _resizers.Add(new Resizer()); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Get(int token) - { - ref int id = ref ids[token]; - if (id < 0) - { - id = PoolComponentIdArrays.GetComponentID(typeof(T), token); - } - return id; - } - private sealed class Resizer : ResizerBase - { - public override Type Type => typeof(T); - public override int[] IDS => ids; - public override void Resize(int size) - { - ids = PoolComponentIdArrays.GetIdsArray(typeof(T)); - } - } - } - private static class Aspect - { - public static int[] ids; - static Aspect() - { - ids = new int[_tokenCount]; - for (int i = 0; i < ids.Length; i++) - ids[i] = -1; - _resizers.Add(new Resizer()); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Get(int token) - { - ref int id = ref ids[token]; - if (id < 0) - id = _metas[token].aspectsCount++; - return id; - } - private sealed class Resizer : ResizerBase - { - public override Type Type => typeof(T); - public override int[] IDS => ids; - public override void Resize(int size) - { - int oldSize = ids.Length; - Array.Resize(ref ids, size); - ArrayUtility.Fill(ids, -1, oldSize, size); - } - } - } - private static class Executor - { - public static int[] ids; - static Executor() - { - ids = new int[_tokenCount]; - for (int i = 0; i < ids.Length; i++) - ids[i] = -1; - _resizers.Add(new Resizer()); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Get(int token) - { - ref int id = ref ids[token]; - if (id < 0) - id = _metas[token].executorsCount++; - return id; - } - private sealed class Resizer : ResizerBase - { - public override Type Type => typeof(T); - public override int[] IDS => ids; - public override void Resize(int size) - { - int oldSize = ids.Length; - Array.Resize(ref ids, size); - ArrayUtility.Fill(ids, -1, oldSize, size); - } - } - } - private static class WorldComponent - { - public static int[] ids; - static WorldComponent() - { - ids = new int[_tokenCount]; - for (int i = 0; i < ids.Length; i++) - ids[i] = -1; - _resizers.Add(new Resizer()); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int Get(int token) - { - ref int id = ref ids[token]; - if (id < 0) - id = _metas[token].GetWorldComponentID(typeof(T)); - return id; - } - private sealed class Resizer : ResizerBase - { - public override Type Type => typeof(T); - public override int[] IDS => ids; - public override void Resize(int size) - { - int oldSize = ids.Length; - Array.Resize(ref ids, size); - ArrayUtility.Fill(ids, -1, oldSize, size); - } - } - } - #endregion - private class WorldTypeMeta - { - public readonly Type worldType; - public int id; - public int componentCount; - public int aspectsCount; - public int executorsCount; - public int worldComponentCount; - private Type[] _types = new Type[10]; - private Dictionary _declaredComponentTypes = new Dictionary(); - private Dictionary _declaredWorldComponentTypes = new Dictionary(); - - public WorldTypeMeta(Type worldType) - { - this.worldType = worldType; - } - - public int DeclareComponentType(Type type) - { - int id = componentCount++; - if (_types.Length <= id) - Array.Resize(ref _types, id + 10); - _types[id] = type; - _declaredComponentTypes.Add(type, id); - return id; - } - public bool IsDeclaredComponentType(Type type) => _declaredComponentTypes.ContainsKey(type); - public Type GetComponentType(int componentID) => _types[componentID]; - public int GetComponentID(Type type) => PoolComponentIdArrays.GetComponentID(type, id); - - - public int DeclareWorldComponentType(Type type) - { - int id = worldComponentCount++; - _declaredWorldComponentTypes.Add(type, id); - return id; - } - public bool IsDeclaredWorldComponentType(Type type) => _declaredWorldComponentTypes.ContainsKey(type); - public int GetWorldComponentID(Type type) - { - if (!_declaredWorldComponentTypes.TryGetValue(type, out int id)) - id = DeclareWorldComponentType(type); - return id; - } - } - } -} diff --git a/src/entlong.cs b/src/entlong.cs index 904db21..8ccdfd8 100644 --- a/src/entlong.cs +++ b/src/entlong.cs @@ -1,10 +1,10 @@ #pragma warning disable IDE1006 +using DCFApixels.DragonECS.Internal; using System; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using static DCFApixels.DragonECS.entlong.ThrowHalper; namespace DCFApixels.DragonECS { @@ -12,16 +12,17 @@ namespace DCFApixels.DragonECS /// Strong identifier/Permanent entity identifier [StructLayout(LayoutKind.Explicit, Pack = 2, Size = 8)] [DebuggerTypeProxy(typeof(DebuggerProxy))] + [Serializable] public readonly struct entlong : IEquatable, IEquatable { public static readonly entlong NULL = default; [FieldOffset(0)] internal readonly long full; //Union - [FieldOffset(0)] + [FieldOffset(0), NonSerialized] internal readonly int id; - [FieldOffset(4)] + [FieldOffset(4), NonSerialized] internal readonly short gen; - [FieldOffset(6)] + [FieldOffset(6), NonSerialized] internal readonly short world; #region Properties @@ -33,7 +34,7 @@ namespace DCFApixels.DragonECS public bool IsNull { [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => this == NULL; + get => full == 0L; } public int ID { @@ -41,7 +42,7 @@ namespace DCFApixels.DragonECS get { #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (!IsAlive) ThrowIsNotAlive(this); + if (!IsAlive) Throw.Ent_ThrowIsNotAlive(this); #endif return id; } @@ -52,7 +53,7 @@ namespace DCFApixels.DragonECS get { #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (!IsAlive) ThrowIsNotAlive(this); + if (!IsAlive) Throw.Ent_ThrowIsNotAlive(this); #endif return gen; } @@ -63,7 +64,7 @@ namespace DCFApixels.DragonECS get { #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (!IsAlive) ThrowIsNotAlive(this); + if (!IsAlive) Throw.Ent_ThrowIsNotAlive(this); #endif return EcsWorld.GetWorld(world); } @@ -74,7 +75,7 @@ namespace DCFApixels.DragonECS get { #if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS - if (!IsAlive) ThrowIsNotAlive(this); + if (!IsAlive) Throw.Ent_ThrowIsNotAlive(this); #endif return world; } @@ -94,6 +95,12 @@ namespace DCFApixels.DragonECS { this.full = full; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static unsafe entlong NewUnsafe(long id, long gen, long world) + { + long x = id << 48 | gen << 32 | id; + return *(entlong*)&x; + } #endregion #region TryGetters @@ -107,57 +114,63 @@ namespace DCFApixels.DragonECS world = EcsWorld.GetWorld(this.world); return IsAlive; } + public bool TryGetWorldID(out int worldID) + { + worldID = world; + return IsAlive; + } + public void Unpack(out EcsWorld world, out int id) + { + world = EcsWorld.GetWorld(this.world); + id = this.id; + } + public void Unpack(out int worldID, out int id) + { + worldID = world; + id = this.id; + } public bool TryUnpack(out EcsWorld world, out int id) { world = EcsWorld.GetWorld(this.world); id = this.id; return IsAlive; } + public bool TryUnpack(out int worldID, out int id) + { + worldID = world; + id = this.id; + return IsAlive; + } #endregion - #region Equals + #region operators [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(entlong other) => full == other.full; + public static bool operator ==(entlong a, entlong b) => a.full == b.full; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool Equals(long other) => full == other; + public static bool operator !=(entlong a, entlong b) => a.full != b.full; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator long(entlong a) => a.full; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator entlong(long a) => new entlong(a); + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator int(entlong a) => a.ID; #endregion - #region Object + #region Other [MethodImpl(MethodImplOptions.AggressiveInlining)] public override int GetHashCode() => unchecked((int)full) ^ (int)(full >> 32); [MethodImpl(MethodImplOptions.AggressiveInlining)] public override string ToString() => $"entity(id:{id} g:{gen} w:{world} {(IsNull ? "null" : IsAlive ? "alive" : "not alive")})"; [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(object obj) => obj is entlong other && full == other.full; - #endregion - - #region operators [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator ==(in entlong a, in entlong b) => a.full == b.full; + public bool Equals(entlong other) => full == other.full; [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool operator !=(in entlong a, in entlong b) => a.full != b.full; + public bool Equals(long other) => full == other; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator long(in entlong a) => a.full; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static explicit operator entlong(in long a) => new entlong(a); - #endregion - - #region ThrowHalper - internal static class ThrowHalper - { - [MethodImpl(MethodImplOptions.NoInlining)] - public static void ThrowIsNotAlive(entlong entity) - { - if (entity.IsNull) - throw new EcsFrameworkException($"The {entity} is null."); - else - throw new EcsFrameworkException($"The {entity} is not alive."); - } - } - #endregion - - #region DebuggerProxy internal class DebuggerProxy { private List _componentsList;