up version to 0.8.20

This commit is contained in:
Mikhail 2024-03-07 08:15:56 +08:00
parent 256b72ad7c
commit 3ab89c7a4b
3 changed files with 214 additions and 227 deletions

View File

@ -14,9 +14,6 @@
Данный [ECS](https://en.wikipedia.org/wiki/Entity_component_system) Фреймворк нацелен на максимальную удобность, модульность, расширяемость и производительность динамического изменения сущностей. Без генерации кода и зависимостей. Вднохновлен [LeoEcs](https://github.com/Leopotam/ecslite).
> [!IMPORTANT]
> 新年快乐!
> [!WARNING]
> Проект в стадии разработки. API может меняться.
> Readme еще не завершен
@ -35,18 +32,18 @@
- [Слои](#слои)
- [Процессы](#процессы)
- [Мир](#мир)
- [Компоненты мира](#компоненты-мира)
- [Конфигурация мира](#конфигурация-мира)
- [Пул](#пул)
- [Аспект](#аспект)
- [Запросы](#запросы)
- [Группа](#группа)
- [Корень ECS](#корень-ecs)
- [Гибридность](#гибридность)
- [Debug](#debug)
- [Атрибуты](#атрибуты)
- [Мета-Атрибуты](#мета-атрибуты)
- [EcsDebug](#ecsdebug)
- [Профилирование](#профилирование)
- [Расширение фреймворка](#расширение-фреймворка)
- [Компоненты мира](#компоненты-мира)
- [Конфиги](#конфиги)
- [Расширения](#расширения)
- [FAQ](#faq)
- [Обратная связь](#обратная-связь)
@ -60,6 +57,7 @@
+ Минимальная версия C# 7.3;
Опционально:
+ Поддержка NativeAOT
+ Минимальная версия Unity 2020.1.0;
## Установка для Unity
@ -78,7 +76,7 @@ https://github.com/DCFApixels/DragonECS.git
**Сущности** - это то к чему крепятся данные. Реализованы в виде идентификаторов, которых есть 2 вида:
* `int` - однократный идентификатор, применяется в пределах одного тика. Не рекомендуется хранить `int` идентификаторы, в место этого используйте `entlong`;
* `entlong` - долговременный идентификатор, содержит в себе полный набор информации для однозначной идентификации;
``` csharp
``` c#
// Создание новой сущности в мире.
int entityID = _world.NewEntity();
@ -95,7 +93,7 @@ int newEntityID = _world.CloneEntity(entityID);
<details>
<summary>Работа с entlong</summary>
``` csharp
``` c#
// Конвертация int в entlong.
entlong entity = _world.GetEntityLong(entityID);
// или
@ -125,14 +123,13 @@ struct Health : IEcsComponent
struct PlayerTag : IEcsTagComponent {}
```
Встроенные виды компонентов:
* `IEcsComponent` - Компоненты с данными.
* `IEcsComponent` - Компоненты с данными. Универсальный тип компонентов.
* `IEcsTagComponent` - Компоненты-теги. Без данных.
* `IEcsHybridComponent` - Гибридные компоненты. Испольщуются для реализации [гибридности](#Гибридность).
## System
**Системы** - это основная логика, тут задается поведение сущностей. Существуют в виде пользовательских классов, реализующих как минимум один из интерфейсов процессов. Основные процессы:
```c#
class SomeSystem : IEcsPreInitProcess, IEcsInitProcess, IEcsRunProcess, IEcsDestroyProcess
class SomeSystem : IEcsPreInit, IEcsInit, IEcsRun, IEcsDestroy
{
// Будет вызван один раз в момент работы EcsPipeline.Init() и до срабатывания IEcsInitProcess.Init()
public void PreInit () { }
@ -170,46 +167,31 @@ pipeline.Init(); // Инициализация пайплайна
```
> Для одновременного построения и инициализации есть метод Builder.BuildAndInit();
### Внедрение зависимостей
Внедрение зависимостей - это процесс который запускается вместе с инициализацией пайплайна и внедряет данные переданные в Builder.
> [!WARNING]
> Внедрение идет параллельно с PreInit, поэтому в PreInit инъекция - не гарантируется.
> [!WARNING]
> Экземпляр EcsPipeline автоматически внедряется еще до PreInit.
Фреймворк реализует внедрение зависимостей для систем. это процесс который запускается вместе с инициализацией пайплайна и внедряет данные переданные в Builder.
> Использование встроенного внедрения зависимостей опционально.
``` c#
SomeData _someData;
class SomeDataA { /*...*/ }
class SomeDataB : SomeDataA { /*...*/ }
//...
SomeDataB _someDataB = new SomeDataB();
EcsPipelone pipeline = EcsPipeline.New()
//...
.Inject(_someData) // Внедрит в системы экземпляр _someData
// Внедрит _someDataB в системы реализующие IEcsInject<SomeDataB>.
.Inject(_someDataB)
// Добавит системы реализующие IEcsInject<SomeDataA> в дерево инъекции
// теперь эти системы так же получат _someDataB.
.Injector.AddNode<SomeDataA>() //
//...
.BuildAndInit();
//...
class SomeSystem : IEcsInject<SomeData>, IEcsRunProcess
// Для внедрения используется интерфейс IEcsInject<T> и его метод Inject(T obj)
class SomeSystem : IEcsInject<SomeDataA>, IEcsRunProcess
{
// Для внедрения используется интерфейс IEcsInject<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
}
SomeDataA _someDataA
//obj будет экземпляром типа SomeDataB
public void Inject(SomeDataA obj) => _someDataA = obj;
}
```
### Модули
@ -226,7 +208,7 @@ class Module : IEcsModule
}
}
```
``` csharp
``` c#
EcsPipelone pipeline = EcsPipeline.New()
//...
.AddModule(new Module())
@ -252,118 +234,78 @@ EcsPipelone pipeline = EcsPipeline.New()
* `EcsConst.POST_END_LAYER`
## Процессы
Процессы - это очереди систем реализующие общий интерфейс, например `IEcsRunProcess`. Для запуска процессов используются Runner-ы. Встроенные процессы вызываются автоматически, для запуска пользовательских процессов используйте раннеры получаемые из `EcsPipeline.GetRunner<TInterface>()`.
> Рекомендуется кешировать полученные через GetRunner раннеры.
Процессы - это очереди систем реализующие общий интерфейс, например `IEcsRun`. Для запуска процессов используются Runner-ы. Втроенные процессы запускаются автоматически. Есть возможность реализации пользовательских процессов.
<details>
<summary>Встроенные процессы</summary>
* `IEcsPreInitProcess`, `IEcsInitProcess`, `IEcsRunProcess`, `IEcsDestroyProcess` - процессы жизненого цикла `EcsPipeline`.
* `IEcsPreInject`, `IEcsInject<T>` - Процессы системы [внедрения зависимостей](#Внедрение-зависимостей).
* `IEcsPreInitInjectProcess` - Так же процесс системы [внедрения зависимостей](#Внедрение-зависимостей), но работает в пределах до выполнения IEcsInitProcess, сигнализирует о начале и окончании предварительных внедрений.
* `IEcsPreInit`, `IEcsInit`, `IEcsRun`, `IEcsDestroy` - процессы жизненого цикла `EcsPipeline`.
* `IEcsInject<T>` - Процессы системы [внедрения зависимостей](#Внедрение-зависимостей).
* `IOnInitInjectionComplete` - Так же процесс системы [внедрения зависимостей](#Внедрение-зависимостей), но сигнализирует о завершении инициализирующей инъекции.
</details>
<details>
<summary>Пользовательские процессы</summary>
Для добавления нового процесса создайте интерфейс наследованный от `IEcsProcess` и создайте раннер для него. Раннер это класс реализующий интерфейс запускаемого процесса и наследуемый от EcsRunner<TInterface>. А после к интерфейсу добавте атрибут `BindWithEcsRunner` для связи. Пример:
```c#
[BindWithEcsRunner(typeof(DoSomethingProcessRunner))]
Для добавления нового процесса создайте интерфейс наследованный от `IEcsProcess` и создайте раннер для него. Раннер это класс реализующий интерфейс запускаемого процесса и наследуемый от `EcsRunner<TInterface>`. Пример:
``` c#
//Интерфейс.
interface IDoSomethingProcess : IEcsProcess
{
void Do();
}
//Реализация раннера. Пример реализации можно так же посмотреть в встроенных процессах
sealed class DoSomethingProcessRunner : EcsRunner<IDoSomethingProcess>, IDoSomethingProcess
{
public void Do()
public void Do()
{
foreach (var item in targets) item.Do();
foreach (var item in Process) item.Do();
}
}
//...
//Добавление раннера при создании пайплайна.
_pipeline = EcsPipeline.New()
//...
.AddRunner<DoSomethingProcessRunner>()
//...
.BuildAndInit();
//Запуск раннера если раннер был добавлен.
_pipeline.GetRunner<IDoSomethingProcess>.Do()
//Или если раннер небыл добавлен(Вызов GetRunnerInstance так же добавит раннер в пайплайн).
_pipeline.GetRunnerInstance<DoSomethingProcessRunner>.Do()
```
> Раннеры имеют ряд требований к реализации:
> * Для одного интерфейса может быть только одна реализация раннера;
> * Наследоваться от `EcsRunner<TInterface>` можно только напрямую;
> * Раннер может содержать только один интерфейс(за исключением `IEcsSystem`);
> * Наследуемый класс `EcsRunner<TInterface>,` в качестве `TInterface` должен принимать реализованный интерфейс;
> * Раннер не может быть размещен внутри другого класса.
> * Наследоваться от `EcsRunner<T>` можно только напрямую;
> * Раннер может содержать только один интерфейс(за исключением `IEcsProcess`);
> * Наследуемый класс `EcsRunner<T>,` далжен так же реализовавыть интерфейс `T`;
> Не рекомендуется в цикле вызывать `GetRunner`, иначе кешируйте полученный раннер.
</details>
## Мир
Является контейнером для сущностей и компонентов.
> **NOTICE:** Необходимо вызывать EcsWorld.Destroy() у экземпляра мира если он больше не нужен.
### Компоненты мира
С помощью компонентов можно прикреплять дополнительные данные к мирам. В качестве компонентов используются `struct` типы.
``` csharp
ref WorldComponent component = ref _world.Get<WorldComponent>();
``` c#
//Создание экземпляра мира
_world = new EcsDefaultWorld();
//Пример из раздела Сущности
var e = _world.NewEntity();
_world.DelEntity(e);
```
Реализация компонента:
``` csharp
public struct WorldComponent
{
// Данные.
}
```
Или:
``` csharp
public struct WorldComponent : IEcsWorldComponent<WorldComponent>
{
// Данные.
void IEcsWorldComponent<WorldComponent>.Init(ref WorldComponent component, EcsWorld world)
{
// Действия при инициализации компонента. Вызывается до первого возвращения из EcsWorld.Get
}
void IEcsWorldComponent<WorldComponent>.OnDestroy(ref WorldComponent component, EcsWorld world)
{
// Действия когда вызывается EcsWorld.Destroy.
// Вызов OnDestroy, обязует пользователя вручную обнулять компонент, если это необходимо.
component = default;
}
}
```
<details>
<summary>Пример использования</summary>
События интерфейса IEcsWorldComponent<T>, могут быть использованы для автоматической инициализации полей компонента, и освобождения ресурсов.
``` csharp
public struct WorldComponent : IEcsWorldComponent<WorldComponent>
{
private SomeClass _object; // Объект который будет утилизироваться.
private SomeReusedClass _resusedObject; // Объект который будет переиспользоваться.
public SomeClass Object => _object;
public SomeReusedClass ResusedObject => _resusedObject;
void IEcsWorldComponent<WorldComponent>.Init(ref WorldComponent component, EcsWorld world)
{
if (component._resusedObject == null)
component._resusedObject = new SomeReusedClass();
component._object = new SomeClass();
// Теперь при получении компонента через EcsWorld.Get, _resusedObject и _object уже будут созданы.
}
void IEcsWorldComponent<WorldComponent>.OnDestroy(ref WorldComponent component, EcsWorld world)
{
// Утилизируем не нужный объект, и освобождаем ссылку на него, чтобы GC мог его собрать.
component._object.Dispose();
component._object = null;
// Как вариант тут можно сделать сброс значений у переиспользуемого объекта.
//component._resusedObject.Reset();
//Так как в этом примере не нужно полное обнуление компонента, то строчка ниже не нужна.
//component = default;
}
}
```
</details>
> **NOTICE:** Необходимо вызывать EcsWorld.Destroy() у экземпляра мира если он больше не используется, иначе он будет висеть в памяти.
### Конфигурация мира
При создании мира, в конструктор можно передать реализацию интерфейса `IEcsWorldConfig`. Реализующие его классы своего рода контейнеры для конфигов. По умолчанию уже реализован стандартный контейнер `EcsWorldConfig` и `Get`/`Set` методы для базовой конфигурации мира.
При создании мира, в конструктор можно передать экземпляр `EcsWorldConfig`.
``` csharp
EcsWorldConfig config = new EcsWorldConfig()
.Set_EntitiesCapacity(512);
``` c#
EcsWorldConfig config = new EcsWorldConfig(
//предварительно инициализирует вместимость мира для 2000 сущностей
entitiesCapacity: 2000,
//предварительно инициализирует вместимость пулов для 2000 компонентов
poolComponentsCapacity: 2000);
_world = new EcsDefaultWorld(config);
```
@ -371,11 +313,10 @@ _world = new EcsDefaultWorld(config);
## Пул
Является контейнером для компонентов, предоставляет методы для добавления/чтения/редактирования/удаления компонентов на сущности. Есть несколько видов пулов, для разных целей:
* `EcsPool` - универсальный пул, хранит struct-компоненты реализующие интерфейс `IEcsComponent`;
* `EcsTagPool` - подходит для хранения пустых компонентов-тегов, в сравнении с `EcsPool` имеет лучше оптимизацию памяти и скорости, хранит struct-компоненты `IEcsTagComponent`;
* `EcsHybridPool` - пул для гибридных компонентов. Испольщуются для реализации [гибридности](#Гибридность), хранит struct-компоненты `IEcsHybridComponent`;
* `EcsTagPool` - подходит для хранения пустых компонентов-тегов, в сравнении с `EcsPool` имеет лучше оптимизацию памяти и скорости, хранит struct-компоненты с `IEcsTagComponent`;
Пулы имеют 5 основных метода и их разновидности:
``` csharp
``` c#
// Один из способов получить пул из мира.
EcsPool<Pose> poses = _world.GetPool<Pose>();
@ -401,7 +342,7 @@ poses.Del(entityID);
## Аспект
Это пользовательские классы наследуемые от `EcsAspect`, которые используются как посредник для взаимодействия с сущностями. Аспекты одновременно являются кешем пулов и ограничением для фильтрации сущностей.
``` csharp
``` c#
using DCFApixels.DragonECS;
...
class Aspect : EcsAspect
@ -424,7 +365,7 @@ class Aspect : EcsAspect
}
```
В аспекты можно добавлять другие аспекты, тем самым комбинируя их. Ограничения так же будут скомбинированы
``` csharp
``` c#
using DCFApixels.DragonECS;
...
class Aspect : EcsAspect
@ -458,7 +399,7 @@ class Aspect : EcsAspect
Используйте метод-запрос `EcsWorld.Where<TAspcet>(out TAspcet aspect)` для получения необходимого системе набора сущностей. Запросы работают в связке с аспектами, аспекты определяют ограничения запросов, результатом запроса становится группа сущностей удовлетворяющая условиям аспекта. По умолчанию запрос делает выборку из всех сущностей в мире, но так же можно сделать выборку из определенной группы сущностей, для этого используйте `EcsWorld.WhereFor<TAspcet>(EcsReadonlyGroup sourceGroup, out TAspcet aspect)`
## Группа
Группы это структуры данных для хранения множества сущностей с быстрыми операциями добавления/удаления/проверки наличия и т.д. Реализованы классом `EcsGroup` и структурой `EcsReadonlyGroup`.
Группы это структуры данных для хранения множества сущностей с O(1) операциями добавления/удаления/проверки наличия и т.д. Реализованы классом `EcsGroup` и структурой `EcsReadonlyGroup`.
``` c#
//Получем новую группу. EcsWorld содержит в себе пул групп,
@ -514,7 +455,7 @@ EcsGroup newGroup = EcsGroup.Inverse(groupA);
## Корень ECS
Это пользовательский класс который явялестя точкой входа для ECS. Основное назначение инициализация, запуск систем на каждый Update движка и очистка по окончанию сипользования.
### Пример для Unity
``` csharp
``` c#
using DCFApixels.DragonECS;
using UnityEngine;
public class EcsRoot : MonoBehaviour
@ -560,7 +501,7 @@ public class EcsRoot : MonoBehaviour
}
```
### Общий пример
``` csharp
``` c#
using DCFApixels.DragonECS;
public class EcsRoot
{
@ -609,94 +550,12 @@ 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
Фреймворк предоставляет дополнительные инструменты для отладки и логирования, не зависящие от среды. Так же многие типы имеют свой DebuggerProxy для более информативного отображения в IDE.
## Атрибуты
## Мета-Атрибуты
В чистом виде мета-атрибуты не имеют применения, но могут быть использованы для генерации автоматической документации и используются в интеграциях с движками для задания отображения в отладочных инструментах и редакторах.
``` c#
using DCFApixels.DragonECS;
@ -708,21 +567,41 @@ using DCFApixels.DragonECS;
[MetaGroup("Abilities/Passive/")]
// Задает цвет типа в системе rgb, где каждый канал принимает значение от 0 до 255, по умолчанию белый.
[MetaColor(MetaColor.Red)] // или [DebugColor(255, 0, 0)]
[MetaColor(MetaColor.Red)] // или [MetaColor(255, 0, 0)]
// Добавляет описание типу.
[MetaDescription("The quick brown fox jumps over the lazy dog")]
// Добавляет строковые теги.
[MetaTags(...)] // [MetaTags(MetaTags.HIDDEN))] чтобы скрыть в редакторе
[MetaTags("Tag1", "Tag2", ...)] // [MetaTags(MetaTags.HIDDEN))] чтобы скрыть в редакторе
public struct Component { }
```
Получение мета-информации. С помощью TypeMeta:
``` c#
TypeMeta typeMeta = someComponent.GetMeta();
// или
TypeMeta typeMeta = pool.ComponentType.ToMeta();
var name = typeMeta.Name;
var color = typeMeta.Color;
var description = typeMeta.Description;
var group = typeMeta.Group;
var tags = typeMeta.Tags;
```
С помощью EcsDebugUtility:
``` c#
EcsDebugUtility.GetMetaName(someComponent);
EcsDebugUtility.GetColor(someComponent);
EcsDebugUtility.GetDescription(someComponent);
EcsDebugUtility.GetGroup(someComponent);
EcsDebugUtility.GetTags(someComponent);
```
## EcsDebug
Имеет набор методов для отладки и логирования. Реализован как статический класс вызывающий методы Debug-сервисов. Debug-сервисы - это посредники между системами отладки среды и EcsDebug. Это позволяет не изменяя отладочный код проекта, переносить проект на другие движки, достаточно только реализовать специальный Debug-сервис.
По умолчанию используется `DefaultDebugService` который выводит логи в консоль. Для реализации пользовательского создайте класс наследуемый от `DebugService` и реализуйте абстрактные члены класса.
``` csharp
``` c#
// Логирование.
EcsDebug.Print("Message");
@ -737,7 +616,7 @@ EcsDebug.Set<OtherDebugService>();
```
## Профилирование
``` csharp
``` c#
// Создание маркера с именем SomeMarker.
private static readonly EcsProfilerMarker marker = new EcsProfilerMarker("SomeMarker");
@ -757,6 +636,96 @@ using (marker.Auto())
</br>
# Расширение фреймворка
Для большей расширемости фреймворкеа есть дополнительные инструменты.
## Конфиги
Конструкторы классов `EcsWorld` и `EcsPipeline` могут принимать контейнеры конфигов реализующие интерфейс `IConfigContainer` или `IConfigContainerWriter`. С помощью этих контейнеров можно передавать данные и зависимости. Встроенная реализация контейнера - `ConfigContainer`</br>
Пример использования конфигов для мира:
``` c#
var configs = new ConfigContainer()
.Set(new EcsWorldConfig(entitiesCapacity: 2000, poolsCapacity: 2000)
.Set(new SomeDataA(/* ... */))
.Set(new SomeDataB(/* ... */)));
EcsDefaultWorld _world = new EcsDefaultWorld(configs);
```
Пример использования конфигов для пайплайна:
``` c#
_pipeline = EcsPipeline.New()// аналогично _pipeline = EcsPipeline.New(new ConfigContainer())
.Configs.Set(new SomeDataA(/* ... */))
.Configs.Set(new SomeDataB(/* ... */))
//...
.BuildAndInit();
//...
var _someDataA = _pipeline.Configs.Get<SomeDataA>();
```
## Компоненты Миры
С помощью компонентов можно прикреплять дополнительные данные к мирам. В качестве компонентов используются `struct` типы.
``` c#
ref WorldComponent component = ref _world.Get<WorldComponent>();
```
Реализация компонента:
``` c#
public struct WorldComponent
{
// Данные.
}
```
Или:
``` c#
public struct WorldComponent : IEcsWorldComponent<WorldComponent>
{
// Данные.
void IEcsWorldComponent<WorldComponent>.Init(ref WorldComponent component, EcsWorld world)
{
// Действия при инициализации компонента. Вызывается до первого возвращения из EcsWorld.Get
}
void IEcsWorldComponent<WorldComponent>.OnDestroy(ref WorldComponent component, EcsWorld world)
{
// Действия когда вызывается EcsWorld.Destroy.
// Вызов OnDestroy, обязует пользователя вручную обнулять компонент, если это необходимо.
component = default;
}
}
```
<details>
<summary>Пример использования</summary>
События интерфейса IEcsWorldComponent<T>, могут быть использованы для автоматической инициализации полей компонента, и освобождения ресурсов.
``` c#
public struct WorldComponent : IEcsWorldComponent<WorldComponent>
{
private SomeClass _object; // Объект который будет утилизироваться.
private SomeReusedClass _resusedObject; // Объект который будет переиспользоваться.
public SomeClass Object => _object;
public SomeReusedClass ResusedObject => _resusedObject;
void IEcsWorldComponent<WorldComponent>.Init(ref WorldComponent component, EcsWorld world)
{
if (component._resusedObject == null)
component._resusedObject = new SomeReusedClass();
component._object = new SomeClass();
// Теперь при получении компонента через EcsWorld.Get, _resusedObject и _object уже будут созданы.
}
void IEcsWorldComponent<WorldComponent>.OnDestroy(ref WorldComponent component, EcsWorld world)
{
// Утилизируем не нужный объект, и освобождаем ссылку на него, чтобы GC мог его собрать.
component._object.Dispose();
component._object = null;
// Как вариант тут можно сделать сброс значений у переиспользуемого объекта.
//component._resusedObject.Reset();
//Так как в этом примере не нужно полное обнуление компонента, то строчка ниже не нужна.
//component = default;
}
}
```
</details>
</br>
# Расширения
* [Автоматическое внедрение зависимостей](https://github.com/DCFApixels/DragonECS-AutoInjections)
* [Поддержка классической C# многопоточности](https://github.com/DCFApixels/DragonECS-ClassicThreads)

View File

@ -14,16 +14,34 @@
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]
> 新年快乐!
> [!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
DragonECS uses this versioning semantics: [Open](https://gist.github.com/DCFApixels/c3b178a308b411f530361d1d56f1f929#file-dcfapixels_versioning_en-md)
</br>
# Installation
Versioning semantics - [Open](https://gist.github.com/DCFApixels/e53281d4628b19fe5278f3e77a7da9e8#file-dcfapixels_versioning_ru-md)
## Environment
Requirements:
+ Minimum C# version 7.3;
Optional:
+ NativeAOT support;
+ Minimum Unity version 2020.1.0;
## Unity Installation
* ### Unity Package
The framework can be installed as a Unity package by adding the Git URL [in the PackageManager](https://docs.unity3d.com/2023.2/Documentation/Manual/upm-ui-giturl.html) or manually adding it to `Packages/manifest.json`:
```
https://github.com/DCFApixels/DragonECS.git
```
* ### Source Code
The framework can also be added to the project as source code.
</br>
# Extensions
* [Dependency autoinjections](https://github.com/DCFApixels/DragonECS-AutoInjections)
* [Classic C# multithreading support](https://github.com/DCFApixels/DragonECS-ClassicThreads)

View File

@ -8,7 +8,7 @@
"displayName": "DragonECS",
"description": "C# Entity Component System Framework",
"unity": "2020.3",
"version": "0.8.8",
"version": "0.8.20",
"repository": {
"type": "git",
"url": "https://github.com/DCFApixels/DragonECS.git"