Merge branch 'new_dev'

This commit is contained in:
Mikhail 2023-12-31 23:57:32 +08:00
commit c3f38232c5
66 changed files with 4545 additions and 1723 deletions

View File

@ -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)
- [Обратная связь](#обратная-связь)
</br>
@ -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<SomeData>, IEcsRunProcess
{
// Для внедрения используется интерфейс IInject<T> и его метод 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()
<details>
<summary>Пользовательские процессы</summary>
Для добавления нового процесса создайте интерфейс наследованный от `IEcsProcess` и создайте раннер для него. Раннер это класс реализующий интерфейс запускаемого процесса и наследуемый от EcsRunner<TInterface>. Пример:
Для добавления нового процесса создайте интерфейс наследованный от `IEcsProcess` и создайте раннер для него. Раннер это класс реализующий интерфейс запускаемого процесса и наследуемый от EcsRunner<TInterface>. А после к интерфейсу добавте атрибут `BindWithEcsRunner` для связи. Пример:
```c#
[BindWithEcsRunner(typeof(DoSomethingProcessRunner))]
interface IDoSomethingProcess : IEcsProcess
{
void Do();
@ -317,10 +360,11 @@ public struct WorldComponent : IEcsWorldComponent<WorldComponent>
> Компоненты можно применять для создания расширений в связке с методами расширений.
## Пул
Является контейнером для компонентов, предоставляет методы для добавления/чтения/редактирования/удаления компонентов на сущности. Есть несколько видов пулов, для разных целей
* `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, либо с возвращением новой группы:
<details>
<summary> Визуализация методов</summary>
![Визуализация методов группы](https://github.com/DCFApixels/DragonECS/assets/99481254/f2c85a9f-949c-4908-9a02-acc3c883a22b)
</details>
Так как группы это множества, они содержат методы аналогичные `ISet<T>`. Редактирующие методы имеет 2 варианта, с записью результата в groupA, либо с возвращением новой группы:
``` c#
// Объединение groupA и groupB
@ -563,40 +600,163 @@ public class EcsRoot
}
}
```
## Гибридность
Для смешивания архитектурных подходов классического OOP и ECS используется специальный пул `EcsHybridPool<T>`. Принцип работы этого пула несколько отличается от других и он упрощает поддержу наследования и полиморфизма.
<details>
<summary>Как это работает?</summary>
При добавлении элемента в пул, пул сканирует его иерархию наследования и реализуемые интерфейсы в поиске типов у которых есть интерфес `IEcsHybridComponent` и автоматически добавляет компонент в соответсвующие этим типам пулы. Таким же образом происходит удаление. Сканирвоание просиходит не для типа T а для типа экземпляра, поэтому в примере ниже строчка в `_world.GetPool<ITransform>().Add(entity, _rigidbody);` добавляет не только в пул `EcsHybridPool<ITransform>` но и в остальные.
</details>
Пример использования:
``` 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<Transform> transforms;
public Aspect(Builder b)
{
transforms = b.Include<Transform>();
}
}
// ...
EcsWorld _world;
Rigidbody _rigidbody;
// ...
// Создадим пустую сущность.
int entity = _world.NewEmptyEntity();
// Получаем пул EcsHybridPool<ITransform> и добавляем в него для сущности компонент _rigidbody.
// Если вместо ITransform подставить Transform или Rigidbody, то результат будет одинаковый
_world.GetPool<ITransform>().Add(entity, _rigidbody);
// ...
//Все эти строчки вернут экземпляр _rigidbody.
ITransform iTransform = _world.GetPool<ITransform>().Get(entity);
Transform transform = _world.GetPool<Transform>().Get(entity);
Rigidbody rigidbody = _world.GetPool<Rigidbody>().Get(entity);
//Исключение - отсутсвует компонент. Camera не является наследником или наследуемым классом для _rigidbody.
Camera camera = _world.GetPool<Camera>().Get(entity);
//Вернет True. Поэтому фишка гибридных пулов будет работать и в запросах сущностей
bool isMatches = _world.GetAspect<TransformAspect>().IsMatches(entity);
//Все эти строчки вернут True.
bool isITransform = _world.GetPool<ITransform>().Has(entity);
bool isTransform = _world.GetPool<Transform>().Has(entity);
bool isRigidbody = _world.GetPool<Rigidbody>().Has(entity);
//Эта строчка вернет False.
bool isCamera = _world.GetPool<Camera>().Has(entity);
// ...
// Удалим у сущности компонент.
_world.GetPool<ITransform>().Del(entity);
// ...
//Все эти строчки вернут False.
bool isITransform = _world.GetPool<ITransform>().Has(entity);
bool isTransform = _world.GetPool<Transform>().Has(entity);
bool isRigidbody = _world.GetPool<Rigidbody>().Has(entity);
bool isCamera = _world.GetPool<Camera>().Has(entity);
// ...
```
</br>
# 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<OtherDebugService>();
```
## Профилирование
``` csharp
// Создание маркера с именем SomeMarker.
private static readonly EcsProfilerMarker marker = new EcsProfilerMarker("SomeMarker");
...
marker.Begin();
// Код для которого замеряется скорость.
marker.End();
// или
using (marker.Auto())
{
// Код для которого замеряется скорость.
}
```
</br>
# Расширения
* [Автоматическое внедрение зависимостей](https://github.com/DCFApixels/DragonECS-AutoInjections)
* [Поддержка классической C# многопоточности](https://github.com/DCFApixels/DragonECS-ClassicThreads)
* Отношения (Work in progress)
* Интеграция с движком Unity (Work in progress)
<!--* Твое расширение? Если разрабатываешь свои расширения для DragonECS, дай знать и они будут добавлены сюда-->
</br>
# 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)
</br></br>
</br></br></br>

View File

@ -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

View File

@ -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"

View File

@ -121,20 +121,20 @@
var combined = self.GetAspect<CombinedAspect<A0, A1>>();
a0 = combined.a0;
a1 = combined.a1;
return self.WhereFor<CombinedAspect<A0, A1>>(sourceGroup);
return self.WhereToGroupFor<CombinedAspect<A0, A1>>(sourceGroup);
}
public static EcsReadonlyGroup Where<A0, A1>(this EcsWorld self)
where A0 : EcsAspect
where A1 : EcsAspect
{
return self.Where<CombinedAspect<A0, A1>>();
return self.WhereToGroup<CombinedAspect<A0, A1>>();
}
public static EcsReadonlyGroup WhereFor<A0, A1>(this EcsWorld self, EcsReadonlyGroup sourceGroup)
where A0 : EcsAspect
where A1 : EcsAspect
{
return self.WhereFor<CombinedAspect<A0, A1>>(sourceGroup);
return self.WhereToGroupFor<CombinedAspect<A0, A1>>(sourceGroup);
}
#endregion
@ -155,7 +155,7 @@
a0 = combined.a0;
a1 = combined.a1;
a2 = combined.a2;
return self.WhereFor<CombinedAspect<A0, A1, A2>>(sourceGroup);
return self.WhereToGroupFor<CombinedAspect<A0, A1, A2>>(sourceGroup);
}
public static EcsReadonlyGroup Where<A0, A1, A2>(this EcsWorld self)
@ -163,14 +163,14 @@
where A1 : EcsAspect
where A2 : EcsAspect
{
return self.Where<CombinedAspect<A0, A1, A2>>();
return self.WhereToGroup<CombinedAspect<A0, A1, A2>>();
}
public static EcsReadonlyGroup WhereFor<A0, A1, A2>(this EcsWorld self, EcsReadonlyGroup sourceGroup)
where A0 : EcsAspect
where A1 : EcsAspect
where A2 : EcsAspect
{
return self.WhereFor<CombinedAspect<A0, A1, A2>>(sourceGroup);
return self.WhereToGroupFor<CombinedAspect<A0, A1, A2>>(sourceGroup);
}
#endregion
@ -194,7 +194,7 @@
a1 = combined.a1;
a2 = combined.a2;
a3 = combined.a3;
return self.WhereFor<CombinedAspect<A0, A1, A2, A3>>(sourceGroup);
return self.WhereToGroupFor<CombinedAspect<A0, A1, A2, A3>>(sourceGroup);
}
public static EcsReadonlyGroup Where<A0, A1, A2, A3>(this EcsWorld self)
@ -203,7 +203,7 @@
where A2 : EcsAspect
where A3 : EcsAspect
{
return self.Where<CombinedAspect<A0, A1, A2, A3>>();
return self.WhereToGroup<CombinedAspect<A0, A1, A2, A3>>();
}
public static EcsReadonlyGroup WhereFor<A0, A1, A2, A3>(this EcsWorld self, EcsReadonlyGroup sourceGroup)
where A0 : EcsAspect
@ -211,7 +211,7 @@
where A2 : EcsAspect
where A3 : EcsAspect
{
return self.WhereFor<CombinedAspect<A0, A1, A2, A3>>(sourceGroup);
return self.WhereToGroupFor<CombinedAspect<A0, A1, A2, A3>>(sourceGroup);
}
#endregion
@ -238,7 +238,7 @@
a2 = combined.a2;
a3 = combined.a3;
a4 = combined.a4;
return self.WhereFor<CombinedAspect<A0, A1, A2, A3, A4>>(sourceGroup);
return self.WhereToGroupFor<CombinedAspect<A0, A1, A2, A3, A4>>(sourceGroup);
}
@ -249,7 +249,7 @@
where A3 : EcsAspect
where A4 : EcsAspect
{
return self.Where<CombinedAspect<A0, A1, A2, A3, A4>>();
return self.WhereToGroup<CombinedAspect<A0, A1, A2, A3, A4>>();
}
public static EcsReadonlyGroup WhereFor<A0, A1, A2, A3, A4>(this EcsWorld self, EcsReadonlyGroup sourceGroup)
where A0 : EcsAspect
@ -258,7 +258,7 @@
where A3 : EcsAspect
where A4 : EcsAspect
{
return self.WhereFor<CombinedAspect<A0, A1, A2, A3, A4>>(sourceGroup);
return self.WhereToGroupFor<CombinedAspect<A0, A1, A2, A3, A4>>(sourceGroup);
}
#endregion
@ -288,7 +288,7 @@
a3 = combined.a3;
a4 = combined.a4;
a5 = combined.a5;
return self.WhereFor<CombinedAspect<A0, A1, A2, A3, A4, A5>>(sourceGroup);
return self.WhereToGroupFor<CombinedAspect<A0, A1, A2, A3, A4, A5>>(sourceGroup);
}
@ -300,7 +300,7 @@
where A4 : EcsAspect
where A5 : EcsAspect
{
return self.Where<CombinedAspect<A0, A1, A2, A3, A4, A5>>();
return self.WhereToGroup<CombinedAspect<A0, A1, A2, A3, A4, A5>>();
}
public static EcsReadonlyGroup WhereFor<A0, A1, A2, A3, A4, A5>(this EcsWorld self, EcsReadonlyGroup sourceGroup)
where A0 : EcsAspect
@ -310,7 +310,7 @@
where A4 : EcsAspect
where A5 : EcsAspect
{
return self.WhereFor<CombinedAspect<A0, A1, A2, A3, A4, A5>>(sourceGroup);
return self.WhereToGroupFor<CombinedAspect<A0, A1, A2, A3, A4, A5>>(sourceGroup);
}
#endregion
}

View File

@ -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>, 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>, 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>, 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>, 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);
}

View File

@ -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<T> : 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>, 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<T> : EcsRunner<IEcsInject<T>>, IEcsInject<T>
{
private EcsBaseTypeInjectRunner _baseTypeInjectRunner;
void IEcsInject<T>.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<T> : EcsBaseTypeInjectRunner
internal sealed class EcsBaseTypeInjectRunner<T> : EcsBaseTypeInjectRunner
{
private IEcsInject<T> _runner;
public EcsBaseTypeInjectRunner(EcsPipeline pipeline) => _runner = pipeline.GetRunner<IEcsInject<T>>();
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<IEcsPreInject>();
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>, 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<T> : InjectSystemBase, IEcsPreInitProcess, IEcsInject<PreInitInjectController>, IEcsPreInitInjectProcess
public abstract class InjectSystemBase { }
[MetaTags(MetaTags.HIDDEN)]
[MetaColor(MetaColor.Gray)]
public class InjectSystem<T> : InjectSystemBase, IEcsInject<EcsPipeline>, IEcsPreInitProcess, IEcsInject<PreInitInjectController>, IEcsPreInitInjectProcess
{
private T _injectedData;
private EcsPipeline _pipeline;
void IEcsInject<EcsPipeline>.Inject(EcsPipeline obj) => _pipeline = obj;
private PreInitInjectController _injectController;
void IEcsInject<PreInitInjectController>.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<IEcsInject<PreInitInjectController>>();
pipeline.GetRunner<IEcsPreInitInjectProcess>().OnPreInitInjectionBefore();
_injectController = new PreInitInjectController(_pipeline);
var injectMapRunner = _pipeline.GetRunner<IEcsInject<PreInitInjectController>>();
_pipeline.GetRunner<IEcsPreInitInjectProcess>().OnPreInitInjectionBefore();
injectMapRunner.Inject(_injectController);
}
var injectRunnerGeneric = pipeline.GetRunner<IEcsInject<T>>();
var injectRunnerGeneric = _pipeline.GetRunner<IEcsInject<T>>();
injectRunnerGeneric.Inject(_injectedData);
if (_injectController.OnInject())
{
_injectController.Destroy();
var injectCallbacksRunner = pipeline.GetRunner<IEcsPreInitInjectProcess>();
var injectCallbacksRunner = _pipeline.GetRunner<IEcsPreInitInjectProcess>();
injectCallbacksRunner.OnPreInitInjectionAfter();
EcsRunner.Destroy(injectCallbacksRunner);
}
@ -149,8 +161,11 @@ namespace DCFApixels.DragonECS
{
public static EcsPipeline.Builder Inject<T>(this EcsPipeline.Builder self, T data)
{
if (data == null) throw new ArgumentNullException();
return self.Add(new InjectSystem<T>(data));
if (data == null) Throw.ArgumentNull();
self.Add(new InjectSystem<T>(data));
if (data is IEcsModule module)
self.AddModule(module);
return self;
}
public static EcsPipeline.Builder Inject<A, B>(this EcsPipeline.Builder self, A a, B b)
{

View File

@ -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<EcsWorld>
[MetaTags(MetaTags.HIDDEN)]
[MetaColor(MetaColor.Grey)]
public class EndFrameSystem : IEcsRunProcess, IEcsInject<EcsWorld>
{
private List<EcsWorld> _worlds = new List<EcsWorld>();
private readonly List<EcsWorld> _worlds = new List<EcsWorld>();
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<TComponent> : IEcsRunProcess, IEcsInject<EcsWorld>
where TComponent : struct, IEcsComponent
{
public EcsPipeline pipeline { get; set; }
private sealed class Aspect : EcsAspect
{
public EcsPool<TComponent> pool;
public Aspect(Builder b) => pool = b.Include<TComponent>();
}
List<EcsWorld> _worlds = new List<EcsWorld>();
private readonly List<EcsWorld> _worlds = new List<EcsWorld>();
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<TComponent>())
{
foreach (var e in world.Where(out Aspect a))
foreach (var e in world.WhereToGroup(out Aspect a))
a.pool.Del(e);
}
}

8
src/Collections.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2e026d1a6d4fd884ea7324b6097703c5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

812
src/Collections/EcsGroup.cs Normal file
View File

@ -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<int> 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<int>
{
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<int> 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
/// <summary>as Union sets</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void UnionWith(EcsReadonlyGroup group) => UnionWith(group.GetGroupInternal());
/// <summary>as Union sets</summary>
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
/// <summary>as Except sets</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ExceptWith(EcsReadonlyGroup group) => ExceptWith(group.GetGroupInternal());
/// <summary>as Except sets</summary>
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
/// <summary>as Intersect sets</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void IntersectWith(EcsReadonlyGroup group) => IntersectWith(group.GetGroupInternal());
/// <summary>as Intersect sets</summary>
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
/// <summary>as Symmetric Except sets</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SymmetricExceptWith(EcsReadonlyGroup group) => SymmetricExceptWith(group.GetGroupInternal());
/// <summary>as Symmetric Except sets</summary>
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
/// <summary>as Intersect sets</summary>
/// <returns>new group from pool</returns>
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;
}
/// <summary>as Except sets</summary>
/// <returns>new group from pool</returns>
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;
}
/// <summary>as Intersect sets</summary>
/// <returns>new group from pool</returns>
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;
}
/// <summary>as Symmetric Except sets</summary>
/// <returns>new group from pool</returns>
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<int> IEnumerable<int>.GetEnumerator() => GetEnumerator();
public LongsIterator GetLongs() => new LongsIterator(this);
public struct Enumerator : IEnumerator<int>
{
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<entlong>
{
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<entlong> IEnumerable<entlong>.GetEnumerator()
{
for (int i = 0; i < _group._count; i++)
yield return _group.World.GetEntityLong(_group._dense[i]);
}
public struct Enumerator : IEnumerator<entlong>
{
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
{
/// <summary>Alias for UnionWith</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Add(this EcsGroup self, EcsGroup group)
{
self.UnionWith(group);
}
/// <summary>Alias for UnionWith</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Add(this EcsGroup self, EcsReadonlyGroup group)
{
self.UnionWith(group);
}
/// <summary>Alias for ExceptWith</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Remove(this EcsGroup self, EcsGroup group)
{
self.ExceptWith(group);
}
/// <summary>Alias for ExceptWith</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Remove(this EcsGroup self, EcsReadonlyGroup group)
{
self.ExceptWith(group);
}
/// <summary>Alias for SymmetricExceptWith</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Xor(this EcsGroup self, EcsGroup group)
{
self.SymmetricExceptWith(group);
}
/// <summary>Alias for SymmetricExceptWith</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Xor(this EcsGroup self, EcsReadonlyGroup group)
{
self.SymmetricExceptWith(group);
}
/// <summary>Alias for IntersectWith</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void And(this EcsGroup self, EcsGroup group)
{
self.IntersectWith(group);
}
/// <summary>Alias for IntersectWith</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void And(this EcsGroup self, EcsReadonlyGroup group)
{
self.IntersectWith(group);
}
}
#endif
}

125
src/Collections/EcsSpan.cs Normal file
View File

@ -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<int> _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<int> 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<int>(array, 0, length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal EcsSpan(int worldID, int[] array, int start, int length)
{
_worldID = worldID;
_values = new ReadOnlySpan<int>(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<int> 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<int>.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
}
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 5e06cf4352ab9414293b145eb27daba7
guid: 55c6215b2c0f45849b191532a01e1dfe
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -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";
}
}

View File

@ -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
{
/// <summary> Red. RGB is (255, 0, 0)</summary>
Red = (255 << 24) + (000 << 16) + (000 << 8),
/// <summary> Green. RGB is (0, 255, 0)</summary>
Green = (000 << 24) + (255 << 16) + (000 << 8),
/// <summary> Blue. RGB is (0, 0, 255)</summary>
Blue = (000 << 24) + (000 << 16) + (255 << 8),
/// <summary> Yellow. RGB is (255, 255, 0)</summary>
Yellow = (255 << 24) + (255 << 16) + (000 << 8),
/// <summary> Cyan. RGB is (0, 255, 255)</summary>
Cyan = (000 << 24) + (255 << 16) + (255 << 8),
/// <summary> Magenta. RGB is (255, 0, 255)</summary>
Magenta = (255 << 24) + (000 << 16) + (255 << 8),
/// <summary> Yellow. RGB is (255, 165, 0)</summary>
Orange = (255 << 24) + (165 << 16) + (000 << 8),
/// <summary> Yellow. RGB is (255, 69, 0)</summary>
OrangeRed = (255 << 24) + (69 << 16) + (000 << 8),
/// <summary> Lime. RGB is (125, 255, 0)</summary>
Lime = (125 << 24) + (255 << 16) + (000 << 8),
/// <summary> Lime. RGB is (127, 255, 212)</summary>
Aquamarine = (127 << 24) + (255 << 16) + (212 << 8),
/// <summary> Lime. RGB is (218, 165, 32)</summary>
Goldenrod = (218 << 24) + (165 << 16) + (32 << 8),
/// <summary> Yellow. RGB is (255, 105, 180)</summary>
DeepPink = (255 << 24) + (105 << 16) + (180 << 8),
/// <summary> Yellow. RGB is (220, 20, 60)</summary>
Crimson = (220 << 24) + (20 << 16) + (60 << 8),
/// <summary> Yellow. RGB is (138, 43, 226)</summary>
BlueViolet = (138 << 24) + (43 << 16) + (226 << 8),
/// <summary> Yellow. RGB is (255, 3, 62)</summary>
AmericanRose = (255 << 24) + (3 << 16) + (62 << 8),
/// <summary> Grey/Gray. RGB is (127, 127, 127)</summary>
Gray = (127 << 24) + (127 << 16) + (127 << 8),
/// <summary> Grey/Gray. RGB is (127, 127, 127)</summary>
Grey = Gray,
/// <summary> Grey/Gray. RGB is (192, 192, 192)</summary>
Silver = (192 << 24) + (192 << 16) + (192 << 8),
/// <summary> White. RGB is (255, 255, 255)</summary>
White = -1,
/// <summary> Black. RGB is (0, 0, 0)</summary>
Black = 0,
}
}

View File

@ -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;
}
}

View File

@ -1,7 +0,0 @@
using System;
namespace DCFApixels.DragonECS
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)]
public sealed class DebugHideAttribute : Attribute { }
}

View File

@ -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;
}
}

View File

@ -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<T>() where T : DebugService, new() => DebugService.Set<T>();
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)
{

View File

@ -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<T>(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<T>() => GetName(typeof(T));
public static string GetName(Type type) => type.TryGetCustomAttribute(out DebugNameAttribute atr) ? atr.name : GetGenericTypeName(type);
public static bool TryGetCustomName<T>(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<T>(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<T>(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<T>() => 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<T>(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<T>() => 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<T>(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<string, WordColor> _words = new Dictionary<string, WordColor>();
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<Type, NameColor> _names = new Dictionary<Type, NameColor>();
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<string> SplitString(string s)
private static List<string> SplitString(string s)
{
string subs;
List<string> words = new List<string>();
@ -169,47 +225,152 @@ namespace DCFApixels.DragonECS
return words;
}
public static (byte, byte, byte) GetColorRGB<T>() => GetColorRGB(typeof(T));
public static (byte, byte, byte) GetColorRGB(Type type)
public static MetaColor GetColor(object obj)
{
var atr = type.GetCustomAttribute<DebugColorAttribute>();
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<T>() => GetColor(typeof(T));
public static MetaColor GetColor(Type type)
{
var atr = type.GetCustomAttribute<MetaColorAttribute>();
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<T>(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<DebugColorAttribute>();
return obj is IEcsMetaProvider intr ?
TryGetColor(intr.MetaSource, out color) :
TryGetColor(type: obj.GetType(), out color);
}
public static bool TryGetColor<T>(out MetaColor color) => TryGetColor(typeof(T), out color);
public static bool TryGetColor(Type type, out MetaColor color)
{
var atr = type.GetCustomAttribute<MetaColorAttribute>();
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<string> GetTags(object obj)
{
return obj is IEcsMetaProvider intr ?
GetTags(intr.MetaSource) :
GetTags(type: obj.GetType());
}
public static IReadOnlyCollection<string> GetTags<T>() => GetTags(typeof(T));
public static IReadOnlyCollection<string> GetTags(Type type)
{
var atr = type.GetCustomAttribute<MetaTagsAttribute>();
return atr != null ? atr.Tags : Array.Empty<string>();
}
public static bool TryGetTags(object obj, out IReadOnlyCollection<string> tags)
{
return obj is IEcsMetaProvider intr ?
TryGetTags(intr.MetaSource, out tags) :
TryGetTags(type: obj.GetType(), out tags);
}
public static bool TryGetTags<T>(out IReadOnlyCollection<string> tags) => TryGetTags(typeof(T), out tags);
public static bool TryGetTags(Type type, out IReadOnlyCollection<string> tags)
{
var atr = type.GetCustomAttribute<MetaTagsAttribute>();
if (atr != null)
{
tags = atr.Tags;
return true;
}
tags = Array.Empty<string>();
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<T>() => 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<T>() => 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<T>(this Type self, out T attribute) where T : Attribute
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGetCustomAttribute<T>(this Type self, out T attribute) where T : Attribute
{
attribute = self.GetCustomAttribute<T>();
return attribute != null;
}
internal static bool TryGetCustomAttribute<T>(this MemberInfo self, out T attribute) where T : Attribute
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool TryGetCustomAttribute<T>(this MemberInfo self, out T attribute) where T : Attribute
{
attribute = self.GetCustomAttribute<T>();
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;
}
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 50bc53c3762bf6b4ea127004a89a894e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,7 @@
namespace DCFApixels.DragonECS
{
public interface IEcsMetaProvider
{
object MetaSource { get; }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5379b4d037441ed4cb4171648c1453d4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
/// <summary> color code Red. RGB is (255, 0, 0)</summary>
public const int Red = (255 << 24) | (000 << 16) | (000 << 8) | 255;
/// <summary> color code Green. RGB is (0, 255, 0)</summary>
public const int Green = (000 << 24) | (255 << 16) | (000 << 8) | 255;
/// <summary> color code Blue. RGB is (0, 0, 255)</summary>
public const int Blue = (000 << 24) | (000 << 16) | (255 << 8) | 255;
/// <summary> color code Yellow. RGB is (255, 255, 0)</summary>
public const int Yellow = (255 << 24) | (255 << 16) | (000 << 8) | 255;
/// <summary> color code Cyan. RGB is (0, 255, 255)</summary>
public const int Cyan = (000 << 24) | (255 << 16) | (255 << 8) | 255;
/// <summary> color code Magenta. RGB is (255, 0, 255)</summary>
public const int Magenta = (255 << 24) | (000 << 16) | (255 << 8) | 255;
/// <summary> color code Orange. RGB is (255, 165, 0)</summary>
public const int Orange = (255 << 24) | (165 << 16) | (000 << 8) | 255;
/// <summary> color code OrangeRed. RGB is (255, 69, 0)</summary>
public const int OrangeRed = (255 << 24) | (69 << 16) | (000 << 8) | 255;
/// <summary> color code Lime. RGB is (125, 255, 0)</summary>
public const int Lime = (125 << 24) | (255 << 16) | (000 << 8) | 255;
/// <summary> color code Aquamarine. RGB is (127, 255, 212)</summary>
public const int Aquamarine = (127 << 24) | (255 << 16) | (212 << 8) | 255;
/// <summary> color code Goldenrod. RGB is (218, 165, 32)</summary>
public const int Goldenrod = (218 << 24) | (165 << 16) | (32 << 8) | 255;
/// <summary> color code DeepPink. RGB is (255, 105, 180)</summary>
public const int DeepPink = (255 << 24) | (105 << 16) | (180 << 8) | 255;
/// <summary> color code Crimson. RGB is (220, 20, 60)</summary>
public const int Crimson = (220 << 24) | (20 << 16) | (60 << 8) | 255;
/// <summary> color code BlueViolet. RGB is (138, 43, 226)</summary>
public const int BlueViolet = (138 << 24) | (43 << 16) | (226 << 8) | 255;
/// <summary> color code AmericanRose. RGB is (255, 3, 62)</summary>
public const int AmericanRose = (255 << 24) | (3 << 16) | (62 << 8) | 255;
/// <summary> color code Grey/Gray. RGB is (127, 127, 127)</summary>
public const int Gray = (127 << 24) | (127 << 16) | (127 << 8) | 255;
/// <summary> color code Grey/Gray. RGB is (127, 127, 127)</summary>
public const int Grey = Gray;
/// <summary> color code Silver. RGB is (192, 192, 192)</summary>
public const int Silver = (192 << 24) | (192 << 16) | (192 << 8) | 255;
/// <summary> color code White. RGB is (255, 255, 255)</summary>
public const int White = -1;
/// <summary> color code Black. RGB is (0, 0, 0)</summary>
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));
}
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c447392c75f8b4a42a2e5c3eb49e5b82
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}

View File

@ -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<string> 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;
}
}

View File

@ -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<int, int> r = new Dictionary<int, int>();
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;
/// <summary>Including constraints</summary>
internal readonly int[] _inc;
public ReadOnlySpan<int> Inc => inc;
/// <summary>Excluding constraints</summary>
internal readonly int[] _exc;
internal EcsMask(int worldTypeID, int[] inc, int[] exc)
public ReadOnlySpan<int> 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<int>.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;
}

View File

@ -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<int> entities) => _source.Bake(entities);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan<int> ToSpan() => _source.ToSpan();
public ReadOnlySpan<int> 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<EcsGroup>, IEnumerable<int>
{
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<int> entities)
{
entities.Clear();
foreach (var e in this)
entities.Add(e);
}
public ReadOnlySpan<int> ToSpan() => new ReadOnlySpan<int>(_dense, 0, _count);
public ReadOnlySpan<int> ToSpan(int start, int length)
{
#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS
if (start + length > _count) ThrowArgumentOutOfRangeException();
#endif
return new ReadOnlySpan<int>(_dense, start, length);
}
#endregion
#region Set operations
/// <summary>as Union sets</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void UnionWith(EcsReadonlyGroup group) => UnionWith(group.GetGroupInternal());
/// <summary>as Union sets</summary>
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);
}
/// <summary>as Except sets</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void ExceptWith(EcsReadonlyGroup group) => ExceptWith(group.GetGroupInternal());
/// <summary>as Except sets</summary>
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);
}
/// <summary>as Intersect sets</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void IntersectWith(EcsReadonlyGroup group) => IntersectWith(group.GetGroupInternal());
/// <summary>as Intersect sets</summary>
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);
}
/// <summary>as Symmetric Except sets</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SymmetricExceptWith(EcsReadonlyGroup group) => SymmetricExceptWith(group.GetGroupInternal());
/// <summary>as Symmetric Except sets</summary>
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
/// <summary>as Intersect sets</summary>
/// <returns>new group from pool</returns>
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;
}
/// <summary>as Except sets</summary>
/// <returns>new group from pool</returns>
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;
}
/// <summary>as Intersect sets</summary>
/// <returns>new group from pool</returns>
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;
}
/// <summary>as Symmetric Except sets</summary>
/// <returns>new group from pool</returns>
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<int> IEnumerable<int>.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<string>(), 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
}
}

View File

@ -12,14 +12,14 @@ namespace DCFApixels.DragonECS
public sealed class EcsPipeline
{
private IEcsProcess[] _allSystems;
private Dictionary<Type, IEcsRunner> _runners;
private Dictionary<Type, IEcsRunner> _runners = new Dictionary<Type, IEcsRunner>();
private IEcsRunProcess _runRunnerCache;
private ReadOnlyCollection<IEcsProcess> _allSystemsSealed;
private ReadOnlyDictionary<Type, IEcsRunner> _allRunnersSealed;
private bool _isInit;
private bool _isDestoryed;
private bool _isInit = false;
private bool _isDestoryed = false;
#region Properties
public ReadOnlyCollection<IEcsProcess> AllSystems => _allSystemsSealed;
@ -32,13 +32,8 @@ namespace DCFApixels.DragonECS
private EcsPipeline(IEcsProcess[] systems)
{
_allSystems = systems;
_runners = new Dictionary<Type, IEcsRunner>();
_allSystemsSealed = new ReadOnlyCollection<IEcsProcess>(_allSystems);
_allRunnersSealed = new ReadOnlyDictionary<Type, IEcsRunner>(_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<IEcsPreInitProcess>();
preInitRunner.PreInit(this);
preInitRunner.PreInit();
EcsRunner.Destroy(preInitRunner);
var initRunner = GetRunner<IEcsInitProcess>();
initRunner.Init(this);
initRunner.Init();
EcsRunner.Destroy(initRunner);
_runRunnerCache = GetRunner<IEcsRunProcess>();
_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<IEcsDestroyProcess>().Destroy(this);
GetRunner<IEcsDestroyProcess>().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<IEcsProcess> result = new List<IEcsProcess>(32);
List<IEcsProcess> basicBlockList = _systems[_basicLayer];
foreach (var item in _systems)

View File

@ -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<Type, Type> _runnerHandlerTypes; //interface base type/Runner handler type pairs;
static EcsRunnerActivator()
{
List<Exception> delayedExceptions = new List<Exception>();
Type runnerBaseType = typeof(EcsRunner<>);
List<Type> runnerHandlerTypes = new List<Type>();
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<Type, Type>();
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<T>, 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<TInterface>() 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<TInterface>(1)} interface.");
}
if (interfaceType.IsGenericType)
{
Type[] genericTypes = interfaceType.GetGenericArguments();
runnerType = runnerType.MakeGenericType(genericTypes);
}
EcsRunner<TInterface>.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<TInterface>();
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<TInterface>)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<Type, ProcessInterface> _processes = new Dictionary<Type, ProcessInterface>();
private static HashSet<Type> _systems = new HashSet<Type>();
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<Type> GetEcsProcessInterfaces(this Type self)
{
return self.GetInterfaces().Where(o => o.IsEcsProcessInterface());
}
#endregion
}
}

50
src/EcsWorld.cache.cs Normal file
View File

@ -0,0 +1,50 @@
namespace DCFApixels.DragonECS
{
public abstract partial class EcsWorld
{
internal readonly struct PoolCache<T> : IEcsWorldComponent<PoolCache<T>>
where T : IEcsPoolImplementation, new()
{
public readonly T instance;
public PoolCache(T instance) => this.instance = instance;
void IEcsWorldComponent<PoolCache<T>>.Init(ref PoolCache<T> component, EcsWorld world)
{
component = new PoolCache<T>(world.CreatePool<T>());
}
void IEcsWorldComponent<PoolCache<T>>.OnDestroy(ref PoolCache<T> component, EcsWorld world)
{
component = default;
}
}
internal readonly struct AspectCache<T> : IEcsWorldComponent<AspectCache<T>>
where T : EcsAspect
{
public readonly T instance;
public AspectCache(T instance) => this.instance = instance;
void IEcsWorldComponent<AspectCache<T>>.Init(ref AspectCache<T> component, EcsWorld world)
{
component = new AspectCache<T>(EcsAspect.Builder.Build<T>(world));
}
void IEcsWorldComponent<AspectCache<T>>.OnDestroy(ref AspectCache<T> component, EcsWorld world)
{
component = default;
}
}
internal readonly struct ExcecutorCache<T> : IEcsWorldComponent<ExcecutorCache<T>>
where T : EcsQueryExecutor, new()
{
public readonly T instance;
public ExcecutorCache(T instance) => this.instance = instance;
void IEcsWorldComponent<ExcecutorCache<T>>.Init(ref ExcecutorCache<T> component, EcsWorld world)
{
T instance = new T();
instance.Initialize(world);
component = new ExcecutorCache<T>(instance);
}
void IEcsWorldComponent<ExcecutorCache<T>>.OnDestroy(ref ExcecutorCache<T> component, EcsWorld world)
{
component = default;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5cae52accc835594f95c8d972e40c57e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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<T> 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<T>(_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<DataReleaser> _dataReleaseres = new List<DataReleaser>();
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<T>(int worldID) => ref WorldComponentPool<T>.GetForWorld(worldID);
private abstract class DataReleaser
{
public abstract void Release(int worldID);
}
private static class WorldComponentPool<T>
{
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<T> _interface = EcsWorldComponentHandler<T>.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<T>.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<WeakReference<EcsGroup>> _groups = new List<WeakReference<EcsGroup>>();
private Stack<EcsGroup> _groupsPool = new Stack<EcsGroup>(64);
@ -127,10 +31,18 @@ namespace DCFApixels.DragonECS
private List<IEcsWorldEventListener> _listeners = new List<IEcsWorldEventListener>();
private List<IEcsEntityEventListener> _entityListeners = new List<IEcsEntityEventListener>();
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<IEcsPoolImplementation> AllPools => _pools;// new ReadOnlySpan<IEcsPoolImplementation>(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<T>() => WorldMetaStorage.GetComponentID<T>(_worldTypeID);
public int GetComponentID(Type type) => WorldMetaStorage.GetComponentID(type, _worldTypeID);
public Type GetComponentType(int componentID) => WorldMetaStorage.GetComponentType(_worldTypeID, componentID);
public bool IsComponentTypeDeclared<T>() => 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<TPool>() where TPool : IEcsPoolImplementation, new()
{
int index = WorldMetaStorage.GetPoolID<TPool>(_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<TAspect>() where TAspect : EcsAspect
{
int index = WorldMetaStorage.GetAspectID<TAspect>(_worldTypeID);
if (index >= _aspects.Length)
Array.Resize(ref _aspects, _aspects.Length << 1);
if (_aspects[index] == null)
_aspects[index] = EcsAspect.Builder.Build<TAspect>(this);
return (TAspect)_aspects[index];
return Get<AspectCache<TAspect>>().instance;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TExecutor GetExecutor<TExecutor>() where TExecutor : EcsQueryExecutor, new()
{
int index = WorldMetaStorage.GetExecutorID<TExecutor>(_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<ExcecutorCache<TExecutor>>().instance;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T Get<T>() where T : struct
{
return ref WorldComponentPool<T>.GetForWorld(id);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ref T GetUnchecked<T>() where T : struct
{
return ref WorldComponentPool<T>.GetForWorldUnchecked(id);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T Get<T>(int worldID) where T : struct
{
return ref WorldComponentPool<T>.GetForWorld(worldID);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T GetUnchecked<T>(int worldID) where T : struct
{
return ref WorldComponentPool<T>.GetForWorldUnchecked(worldID);
}
public ref T Get<T>() where T : struct => ref WorldComponentPool<T>.GetForWorld(id);
#endregion
#region Where Query
public EcsReadonlyGroup WhereFor<TAspect>(EcsReadonlyGroup sourceGroup, out TAspect aspect) where TAspect : EcsAspect
public EcsReadonlyGroup WhereToGroupFor<TAspect>(EcsSpan span, out TAspect aspect) where TAspect : EcsAspect
{
if(_isEnableReleaseDelEntBuffer)
{
ReleaseDelEntityBufferAll();
}
var executor = GetExecutor<EcsWhereExecutor<TAspect>>();
aspect = executor.Aspect;
return executor.ExecuteFor(sourceGroup);
return executor.ExecuteFor(span);
}
public EcsReadonlyGroup WhereFor<TAspect>(EcsReadonlyGroup sourceGroup) where TAspect : EcsAspect
public EcsReadonlyGroup WhereToGroupFor<TAspect>(EcsSpan span) where TAspect : EcsAspect
{
return GetExecutor<EcsWhereExecutor<TAspect>>().ExecuteFor(sourceGroup);
if (_isEnableReleaseDelEntBuffer)
{
ReleaseDelEntityBufferAll();
}
return GetExecutor<EcsWhereExecutor<TAspect>>().ExecuteFor(span);
}
public EcsReadonlyGroup Where<TAspect>(out TAspect aspect) where TAspect : EcsAspect
public EcsReadonlyGroup WhereToGroup<TAspect>(out TAspect aspect) where TAspect : EcsAspect
{
if (_isEnableReleaseDelEntBuffer)
{
ReleaseDelEntityBufferAll();
}
var executor = GetExecutor<EcsWhereExecutor<TAspect>>();
aspect = executor.Aspect;
return executor.Execute();
}
public EcsReadonlyGroup Where<TAspect>() where TAspect : EcsAspect
public EcsReadonlyGroup WhereToGroup<TAspect>() where TAspect : EcsAspect
{
if (_isEnableReleaseDelEntBuffer)
{
ReleaseDelEntityBufferAll();
}
return GetExecutor<EcsWhereExecutor<TAspect>>().Execute();
}
public EcsSpan WhereFor<TAspect>(EcsSpan span, out TAspect aspect) where TAspect : EcsAspect
{
if (_isEnableReleaseDelEntBuffer)
{
ReleaseDelEntityBufferAll();
}
var executor = GetExecutor<EcsWhereSpanExecutor<TAspect>>();
aspect = executor.Aspect;
return executor.ExecuteFor(span);
}
public EcsSpan WhereFor<TAspect>(EcsSpan span) where TAspect : EcsAspect
{
if (_isEnableReleaseDelEntBuffer)
{
ReleaseDelEntityBufferAll();
}
return GetExecutor<EcsWhereSpanExecutor<TAspect>>().ExecuteFor(span);
}
public EcsSpan Where<TAspect>(out TAspect aspect) where TAspect : EcsAspect
{
if (_isEnableReleaseDelEntBuffer)
{
ReleaseDelEntityBufferAll();
}
var executor = GetExecutor<EcsWhereSpanExecutor<TAspect>>();
aspect = executor.Aspect;
return executor.Execute();
}
public EcsSpan Where<TAspect>() where TAspect : EcsAspect
{
if (_isEnableReleaseDelEntBuffer)
{
ReleaseDelEntityBufferAll();
}
return GetExecutor<EcsWhereSpanExecutor<TAspect>>().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<int> buffser = new ReadOnlySpan<int>(_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<int> buffser = new ReadOnlySpan<int>(_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<Type> 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
{

111
src/EcsWorld.pools.cs Normal file
View File

@ -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<int> _poolIds = new SparseArray<int>();
private SparseArray<int> _componentIds = new SparseArray<int>();
private int _poolsCount;
internal IEcsPoolImplementation[] _pools;
private EcsNullPool _nullPool = EcsNullPool.instance;
#region ComponentInfo
public int GetComponentID<T>() => DeclareComponentType(EcsTypeCode.Get<T>());
public int GetComponentID(Type type) => DeclareComponentType(EcsTypeCode.Get(type));
public bool IsComponentTypeDeclared<T>() => _componentIds.Contains(EcsTypeCode.Get<T>());
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<TPool>() where TPool : IEcsPoolImplementation, new()
{
return Get<PoolCache<TPool>>().instance;
}
#if UNITY_2020_3_OR_NEWER
[UnityEngine.Scripting.Preserve]
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TPool GetPoolUnchecked<TPool>() where TPool : IEcsPoolImplementation, new()
{
return GetUnchecked<PoolCache<TPool>>().instance;
}
#if UNITY_2020_3_OR_NEWER
[UnityEngine.Scripting.Preserve]
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPool GetPool<TPool>(int worldID) where TPool : IEcsPoolImplementation, new()
{
return Get<PoolCache<TPool>>(worldID).instance;
}
#if UNITY_2020_3_OR_NEWER
[UnityEngine.Scripting.Preserve]
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPool UncheckedGetPool<TPool>(int worldID) where TPool : IEcsPoolImplementation, new()
{
return GetUnchecked<PoolCache<TPool>>(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<TPool>() where TPool : IEcsPoolImplementation, new()
{
int poolTypeCode = EcsTypeCode.Get<TPool>();
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
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2832746be4142a847b513bab5c276ba7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

130
src/EcsWorld.static.cs Normal file
View File

@ -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<T> 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<T>(_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<DataReleaser> _dataReleaseres = new List<DataReleaser>();
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<T>(int worldID) => ref WorldComponentPool<T>.GetForWorld(worldID);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref T UncheckedGetData<T>(int worldID) => ref WorldComponentPool<T>.GetForWorldUnchecked(worldID);
private abstract class DataReleaser
{
public abstract void Release(int worldID);
}
private static class WorldComponentPool<T>
{
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<T> _interface = EcsWorldComponentHandler<T>.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<T>.Release(worldID);
}
}
}
}
internal sealed class EcsNullWorld : EcsWorld
{
internal EcsNullWorld() : base(false) { }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 30c8fd4d7c5aeae4486e16024b4f50cc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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();
}
}
}

View File

@ -1,19 +1,29 @@
namespace DCFApixels.DragonECS
using System.Runtime.CompilerServices;
namespace DCFApixels.DragonECS
{
public sealed class EcsWhereExecutor<TAspect> : 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<TAspect> : 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<TAspect>();
_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
}
}

382
src/Pools/EcsHybridPool.cs Normal file
View File

@ -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);
}
}
/// <summary>Pool for IEcsHybridComponent components</summary>
public sealed class EcsHybridPool<T> : IEcsPoolImplementation<T>, IEcsHybridPool<T>, IEcsHybridPoolInternal, IEnumerable<T> //IEnumerable<T> - 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<IEcsPoolEventListener> _listeners = new List<IEcsPoolEventListener>();
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<T>(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<T>(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<T>(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<T>(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<T>(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<T>(fromEntityID);
#endif
toWorld.GetPool<T>().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<int> 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<T> IEnumerable<T>.GetEnumerator() => throw new NotImplementedException();
IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException();
#endregion
}
/// <summary>Hybrid component</summary>
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<T> GetPool<T>(this EcsWorld self) where T : IEcsHybridComponent
{
return self.GetPool<EcsHybridPool<T>>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsHybridPool<T> GetPoolUnchecked<T>(this EcsWorld self) where T : IEcsHybridComponent
{
return self.GetPoolUnchecked<EcsHybridPool<T>>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsHybridPool<T> Include<T>(this EcsAspectBuilderBase self) where T : IEcsHybridComponent
{
return self.Include<EcsHybridPool<T>>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsHybridPool<T> Exclude<T>(this EcsAspectBuilderBase self) where T : IEcsHybridComponent
{
return self.Exclude<EcsHybridPool<T>>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsHybridPool<T> Optional<T>(this EcsAspectBuilderBase self) where T : IEcsHybridComponent
{
return self.Optional<EcsHybridPool<T>>();
}
//-------------------------------------------------
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsHybridPool<T> GetHybridPool<T>(this EcsWorld self) where T : IEcsHybridComponent
{
return self.GetPool<EcsHybridPool<T>>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsHybridPool<T> GetHybridPoolUnchecked<T>(this EcsWorld self) where T : IEcsHybridComponent
{
return self.GetPoolUnchecked<EcsHybridPool<T>>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsHybridPool<T> IncludeHybrid<T>(this EcsAspectBuilderBase self) where T : IEcsHybridComponent
{
return self.Include<EcsHybridPool<T>>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsHybridPool<T> ExcludeHybrid<T>(this EcsAspectBuilderBase self) where T : IEcsHybridComponent
{
return self.Exclude<EcsHybridPool<T>>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsHybridPool<T> OptionalHybrid<T>(this EcsAspectBuilderBase self) where T : IEcsHybridComponent
{
return self.Optional<EcsHybridPool<T>>();
}
}
public abstract partial class EcsWorld
{
private Dictionary<Type, HybridMapping> _hybridMapping = new Dictionary<Type, HybridMapping>();
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<IEcsHybridPoolInternal> _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<Type> _hybridComponents = new HashSet<Type>();
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<IEcsHybridPoolInternal>();
_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<IEcsHybridPoolInternal> GetPools()
{
return _relatedPools;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 511487e83f936f94780e572063b68a87
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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
{
/// <summary>Pool for IEcsComponent components</summary>
public sealed class EcsPool<T> : IEcsPoolImplementation<T>, IEcsStructsPool<T>, IEnumerable<T> //IEnumerable<T> - IntelliSense hack
public sealed class EcsPool<T> : IEcsPoolImplementation<T>, IEcsStructPool<T>, IEnumerable<T> //IEnumerable<T> - 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<T> _componentResetHandler;
private IEcsComponentCopy<T> _componentCopyHandler;
private IEcsComponentReset<T> _componentResetHandler = EcsComponentResetHandler<T>.instance;
private IEcsComponentCopy<T> _componentCopyHandler = EcsComponentCopyHandler<T>.instance;
private List<IEcsPoolEventListener> _listeners;
private List<IEcsPoolEventListener> _listeners = new List<IEcsPoolEventListener>();
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<IEcsPoolEventListener>();
_componentResetHandler = EcsComponentResetHandler<T>.instance;
_componentCopyHandler = EcsComponentCopyHandler<T>.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<T>(entityID);
if (itemIndex > 0) EcsPoolThrowHalper.ThrowAlreadyHasComponent<T>(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<T>(entityID);
if (!Has(entityID)) EcsPoolThrowHalper.ThrowNotHaveComponent<T>(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<T>(entityID);
if (!Has(entityID)) EcsPoolThrowHalper.ThrowNotHaveComponent<T>(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<T>(entityID);
if (!Has(entityID)) EcsPoolThrowHalper.ThrowNotHaveComponent<T>(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<T>(fromEntityID);
if (!Has(fromEntityID)) EcsPoolThrowHalper.ThrowNotHaveComponent<T>(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<T>(fromEntityID);
if (!Has(fromEntityID)) EcsPoolThrowHalper.ThrowNotHaveComponent<T>(fromEntityID);
#endif
_componentCopyHandler.Copy(ref Get(fromEntityID), ref toWorld.GetPool<T>().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<T>.Read(int entityID) => ref Read(entityID);
ref T IEcsStructsPool<T>.Get(int entityID) => ref Get(entityID);
ref readonly T IEcsStructPool<T>.Read(int entityID) => ref Read(entityID);
ref T IEcsStructPool<T>.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<TComponent> GetPool<TComponent>(this EcsWorld self) where TComponent : struct, IEcsComponent
{
return self.GetPool<EcsPool<TComponent>>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsPool<TComponent> GetPoolUnchecked<TComponent>(this EcsWorld self) where TComponent : struct, IEcsComponent
{
return self.GetPoolUnchecked<EcsPool<TComponent>>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsPool<TComponent> Include<TComponent>(this EcsAspectBuilderBase self) where TComponent : struct, IEcsComponent
{
return self.Include<EcsPool<TComponent>>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsPool<TComponent> Exclude<TComponent>(this EcsAspectBuilderBase self) where TComponent : struct, IEcsComponent
{
return self.Exclude<EcsPool<TComponent>>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsPool<TComponent> Optional<TComponent>(this EcsAspectBuilderBase self) where TComponent : struct, IEcsComponent
{
return self.Optional<EcsPool<TComponent>>();

View File

@ -30,16 +30,26 @@ namespace DCFApixels.DragonECS
void RemoveListener(IEcsPoolEventListener listener);
#endregion
}
public interface IEcsStructsPool<T>
public interface IEcsStructPool<T> : IEcsPool
{
ref T Add(int entityID);
ref readonly T Read(int entityID);
ref T Get(int entityID);
}
public interface IEcsClassPool<T> : IEcsPool
{
T Add(int entityID);
T Get(int entityID);
}
public interface IEcsHybridPool<T> : IEcsPool
{
void Add(int entityID, T component);
T Get(int entityID);
}
/// <summary>Only used to implement a custom pool. In other contexts use IEcsPool or IEcsPool<T>.</summary>
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<int> buffer);
void OnWorldDestroy();
@ -52,26 +62,15 @@ namespace DCFApixels.DragonECS
{
public static void ThrowAlreadyHasComponent<T>(int entityID)
{
throw new EcsFrameworkException($"Entity({entityID}) already has component {typeof(T).Name}.");
throw new EcsFrameworkException($"Entity({entityID}) already has component {EcsDebugUtility.GetGenericTypeName<T>()}.");
}
public static void ThrowNotHaveComponent<T>(int entityID)
{
throw new EcsFrameworkException($"Entity({entityID}) has no component {typeof(T).Name}.");
throw new EcsFrameworkException($"Entity({entityID}) has no component {EcsDebugUtility.GetGenericTypeName<T>()}.");
}
}
public static class IEcsPoolImplementationExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void IncrementEntityComponentCount<T>(this IEcsPoolImplementation<T> self, int entityID)
{
self.World.IncrementEntityComponentCount(entityID);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void DecrementEntityComponentCount<T>(this IEcsPoolImplementation<T> 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<int> buffer) { }

View File

@ -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<T> : IEcsPoolImplementation<T>, IEcsStructsPool<T>, IEnumerable<T> //IEnumerable<T> - IntelliSense hack
public sealed class EcsTagPool<T> : IEcsPoolImplementation<T>, IEcsStructPool<T>, IEnumerable<T> //IEnumerable<T> - 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<IEcsPoolEventListener> _listeners;
private List<IEcsPoolEventListener> _listeners = new List<IEcsPoolEventListener>();
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<IEcsPoolEventListener>();
}
#endregion
#region Method
public void Add(int entityID)
{
#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS
if (Has(entityID)) ThrowAlreadyHasComponent<T>(entityID);
if (Has(entityID)) EcsPoolThrowHalper.ThrowAlreadyHasComponent<T>(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<T>(entityID);
if (!Has(entityID)) EcsPoolThrowHalper.ThrowNotHaveComponent<T>(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<T>(fromEntityID);
if (!Has(fromEntityID)) EcsPoolThrowHalper.ThrowNotHaveComponent<T>(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<T>(fromEntityID);
if (!Has(fromEntityID)) EcsPoolThrowHalper.ThrowNotHaveComponent<T>(fromEntityID);
#endif
toWorld.GetPool<T>().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<T>.Add(int entityID)
ref T IEcsStructPool<T>.Add(int entityID)
{
Add(entityID);
return ref _fakeComponent;
}
ref readonly T IEcsStructsPool<T>.Read(int entityID)
ref readonly T IEcsStructPool<T>.Read(int entityID)
{
#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS
if (!Has(entityID)) ThrowNotHaveComponent<T>(entityID);
if (!Has(entityID)) EcsPoolThrowHalper.ThrowNotHaveComponent<T>(entityID);
#endif
return ref _fakeComponent;
}
ref T IEcsStructsPool<T>.Get(int entityID)
ref T IEcsStructPool<T>.Get(int entityID)
{
#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS
if (!Has(entityID)) ThrowNotHaveComponent<T>(entityID);
if (!Has(entityID)) EcsPoolThrowHalper.ThrowNotHaveComponent<T>(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<T>(entityID);
if (!Has(entityID)) EcsPoolThrowHalper.ThrowNotHaveComponent<T>(entityID);
#endif
return _fakeComponent;
}
void IEcsPool.SetRaw(int entityID, object dataRaw)
{
#if (DEBUG && !DISABLE_DEBUG) || ENABLE_DRAGONECS_ASSERT_CHEKS
if (!Has(entityID)) ThrowNotHaveComponent<T>(entityID);
if (!Has(entityID)) EcsPoolThrowHalper.ThrowNotHaveComponent<T>(entityID);
#endif
}
#endregion
@ -189,22 +201,60 @@ namespace DCFApixels.DragonECS
public interface IEcsTagComponent { }
public static class EcsTagPoolExt
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsTagPool<TTagComponent> GetPool<TTagComponent>(this EcsWorld self) where TTagComponent : struct, IEcsTagComponent
{
return self.GetPool<EcsTagPool<TTagComponent>>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsTagPool<TTagComponent> GetPoolUnchecked<TTagComponent>(this EcsWorld self) where TTagComponent : struct, IEcsTagComponent
{
return self.GetPoolUnchecked<EcsTagPool<TTagComponent>>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsTagPool<TTagComponent> Include<TTagComponent>(this EcsAspectBuilderBase self) where TTagComponent : struct, IEcsTagComponent
{
return self.Include<EcsTagPool<TTagComponent>>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsTagPool<TTagComponent> Exclude<TTagComponent>(this EcsAspectBuilderBase self) where TTagComponent : struct, IEcsTagComponent
{
return self.Exclude<EcsTagPool<TTagComponent>>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsTagPool<TTagComponent> Optional<TTagComponent>(this EcsAspectBuilderBase self) where TTagComponent : struct, IEcsTagComponent
{
return self.Optional<EcsTagPool<TTagComponent>>();
}
//---------------------------------------------------
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsTagPool<TTagComponent> GetTagPool<TTagComponent>(this EcsWorld self) where TTagComponent : struct, IEcsTagComponent
{
return self.GetPool<EcsTagPool<TTagComponent>>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsTagPool<TTagComponent> GetTagPoolUnchecked<TTagComponent>(this EcsWorld self) where TTagComponent : struct, IEcsTagComponent
{
return self.GetPoolUnchecked<EcsTagPool<TTagComponent>>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsTagPool<TTagComponent> IncludeTag<TTagComponent>(this EcsAspectBuilderBase self) where TTagComponent : struct, IEcsTagComponent
{
return self.Include<EcsTagPool<TTagComponent>>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsTagPool<TTagComponent> ExcludeTag<TTagComponent>(this EcsAspectBuilderBase self) where TTagComponent : struct, IEcsTagComponent
{
return self.Exclude<EcsTagPool<TTagComponent>>();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static EcsTagPool<TTagComponent> OptionalTag<TTagComponent>(this EcsAspectBuilderBase self) where TTagComponent : struct, IEcsTagComponent
{
return self.Optional<EcsTagPool<TTagComponent>>();
}
}
}

View File

@ -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<T>(int capacity) where T : unmanaged
{
return (T*)Marshal.AllocHGlobal(Marshal.SizeOf<T>() * capacity).ToPointer();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T* NewAndInit<T>(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<T>(void* oldPointer, int newCount) where T : unmanaged
{
return (T*)(Marshal.ReAllocHGlobal(
new IntPtr(oldPointer),
new IntPtr(Marshal.SizeOf(typeof(T)) * newCount))).ToPointer();
}
}
}

47
src/Utils/EcsTypeCode.cs Normal file
View File

@ -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<Type, int> _codes = new Dictionary<Type, int>();
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<T>() => EcsTypeCodeCache<T>.code;
public static bool Has(Type type) => _codes.ContainsKey(type);
public static bool Has<T>() => _codes.ContainsKey(typeof(T));
public static IEnumerable<TypeCodeInfo> GetDeclared() => _codes.Select(o => new TypeCodeInfo(o.Key, o.Value));
}
public static class EcsTypeCodeCache<T>
{
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";
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: be4c15a212b2a6941bdbe78f18b038b3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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
{

View File

@ -0,0 +1,21 @@
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace DCFApixels.DragonECS.Internal
{
public readonly struct GenericEnumerable<T, TEnumerator> : IEnumerable<T> where TEnumerator : IEnumerator<T>
{
public readonly TEnumerator _enumerator;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public GenericEnumerable(TEnumerator enumerator) => _enumerator = enumerator;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TEnumerator GetEnumerator() => _enumerator;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
IEnumerator<T> IEnumerable<T>.GetEnumerator() => _enumerator;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
IEnumerator IEnumerable.GetEnumerator() => _enumerator;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator GenericEnumerable<T, TEnumerator>(TEnumerator enumerator) => new GenericEnumerable<T, TEnumerator>(enumerator);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 38e5b41bce0941b488ae64c200dcf965
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

415
src/Utils/IdDispenser.cs Normal file
View File

@ -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<int>, IReadOnlyCollection<int>
{
private const int MIN_SIZE = 4;
private int[] _dense = Array.Empty<int>();
private int[] _sparse = Array.Empty<int>();
private IDState[] _sparseState = Array.Empty<IDState>();
private int _usedCount; //[ |uuuu| ]
private int _reservedCount; //[rrr| | ]
private int _size; //[rrr|uuuu|ffffff]
private int _nullID;
#region Properties
/// <summary> Used Count </summary>
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
/// <summary>Marks as used and returns next free id.</summary>
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<int> list, int range)
{
for (int i = 0; i < range; i++)
list.Add(UseFree());
}
/// <summary>Marks as used a free or reserved id, after this id cannot be retrieved via UseFree.</summary>
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<int> ids)
{
foreach (var item in ids)
Use(item);
}
/// <summary>Marks as reserved and returns next free id, after this id cannot be retrieved via UseFree.</summary>
public int ReserveFree()
{
int count = _usedCount + _reservedCount;
CheckOrResize(count + 1);
int id = _dense[count];
_sparseState[id] = IDState.Reserved;
AddReserved(id);
return id;
}
/// <summary>Marks as reserved a free id, after this id cannot be retrieved via UseFree.</summary>
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<int> 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<int> 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
/// <summary>O(n) Sort. n = Size. Allows the UseFree method to return denser ids.</summary>
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<int> IEnumerable<int>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public struct Enumerator : IEnumerator<int>
{
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<int>
{
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<int> IEnumerable<int>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
public readonly struct ReservedSpan : IEnumerable<int>
{
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<int> IEnumerable<int>.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<int> Used => _dispenser.Used;
public IEnumerable<int> 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
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 54c009cfa7ae0fd49938525468703c8b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -38,13 +38,13 @@ namespace DCFApixels.DragonECS
#endregion
#region Constructors
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan(T[] array)
{
_array = array ?? Array.Empty<T>();
_start = 0;
_length = array.Length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ReadOnlySpan(T[] array, int start, int length)
{

228
src/Utils/SparseArray.cs Normal file
View File

@ -0,0 +1,228 @@
//SparseArray. Analogous to Dictionary<int, T>, 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<TValue>
{
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<int>();
private Entry[] _entries = Array.Empty<Entry>();
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
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 25afb1113718f904c85dcbedd993d85e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

236
src/Utils/SparseArray64.cs Normal file
View File

@ -0,0 +1,236 @@
//SparseArray64. Analogous to Dictionary<long, T>, 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<TValue>
{
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<int>();
private Entry[] _entries = Array.Empty<Entry>();
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
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cab791e97df38274294035e89dc9de8a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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<ResizerBase> _resizers = new List<ResizerBase>();
private static WorldTypeMeta[] _metas = new WorldTypeMeta[0];
private static Dictionary<Type, int> _worldIds = new Dictionary<Type, int>();
private static class WorldIndex<TWorldArchetype>
{
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<TWorldArchetype>() => WorldIndex<TWorldArchetype>.id;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetComponentID<T>(int worldID) => Component<T>.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<T>(int worldID) => Pool<T>.Get(worldID);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetAspectID<T>(int worldID) => Aspect<T>.Get(worldID);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetExecutorID<T>(int worldID) => Executor<T>.Get(worldID);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int GetWorldComponentID<T>(int worldID) => WorldComponent<T>.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<Type, int[]> _componentTypeArrayPairs = new Dictionary<Type, int[]>();
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<T>
{
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<T>
{
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<T>
{
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<T>
{
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<T>
{
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<Type, int> _declaredComponentTypes = new Dictionary<Type, int>();
private Dictionary<Type, int> _declaredWorldComponentTypes = new Dictionary<Type, int>();
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;
}
}
}
}

View File

@ -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
/// <summary>Strong identifier/Permanent entity identifier</summary>
[StructLayout(LayoutKind.Explicit, Pack = 2, Size = 8)]
[DebuggerTypeProxy(typeof(DebuggerProxy))]
[Serializable]
public readonly struct entlong : IEquatable<long>, IEquatable<entlong>
{
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<object> _componentsList;