Merge branch 'readme'

This commit is contained in:
Mikhail 2024-10-19 16:05:03 +08:00
commit 1526addaac
3 changed files with 116 additions and 34 deletions

View File

@ -41,7 +41,7 @@
</br> </br>
DragonECS - это [ECS](https://en.wikipedia.org/wiki/Entity_component_system) фреймворк нацеленный на максимальную удобность, модульность, расширяемость и производительность динамического изменения сущностей. Разработан на чистом C#, без зависимостей и генерации кода. Вдохновлен [LeoEcs](https://github.com/Leopotam/ecslite). DragonECS - это [ECS](https://en.wikipedia.org/wiki/Entity_component_system) фреймворк нацеленный на максимальную удобность, модульность, расширяемость и производительность динамического изменения сущностей. Разработан на чистом C#, без зависимостей и генерации кода. Вдохновлен [LeoEcs Lite](https://github.com/Leopotam/ecslite).
> [!WARNING] > [!WARNING]
> Проект предрелизной версии, поэтому API может меняться. В ветке main актуальная и рабочая версия.</br> > Проект предрелизной версии, поэтому API может меняться. В ветке main актуальная и рабочая версия.</br>
@ -58,7 +58,7 @@ DragonECS - это [ECS](https://en.wikipedia.org/wiki/Entity_component_system)
- [Построение](#построение) - [Построение](#построение)
- [Внедрение зависимостей](#внедрение-зависимостей) - [Внедрение зависимостей](#внедрение-зависимостей)
- [Модули](#модули) - [Модули](#модули)
- [Слои](#слои) - [Сортировка](#сортировка)
- [Процессы](#процессы) - [Процессы](#процессы)
- [Мир](#мир) - [Мир](#мир)
- [Пул](#пул) - [Пул](#пул)
@ -149,18 +149,17 @@ if (entity.TryGetID(out int entityID)) { }
> **NOTICE:** Сущности не могут существовать без компонентов, пустые сущности будут автоматически удаляться сразу после удаления последнего компонента либо в конце тика. > **NOTICE:** Сущности не могут существовать без компонентов, пустые сущности будут автоматически удаляться сразу после удаления последнего компонента либо в конце тика.
## Component ## Component
**Компоненты** - это данные для сущностей. Обязаны реализовывать интерфейс `IEcsComponent` или другой указывающий вид компонента. **Компоненты** - это данные которые крепятся к сущностям.
```c# ```c#
// Компоненты IEcsComponent хранятся в обычном хранилище.
struct Health : IEcsComponent struct Health : IEcsComponent
{ {
public float health; public float health;
public int armor; public int armor;
} }
// Компоненты с IEcsTagComponent хранятся в оптимизированном для тегов хранилище.
struct PlayerTag : IEcsTagComponent {} struct PlayerTag : IEcsTagComponent {}
``` ```
Встроенные виды компонентов:
* `IEcsComponent` - Компоненты с данными. Универсальный тип компонентов.
* `IEcsTagComponent` - Компоненты-теги. Без данных.
## System ## System
**Системы** - это основная логика, тут задается поведение сущностей. Существуют в виде пользовательских классов, реализующих как минимум один из интерфейсов процессов. Основные процессы: **Системы** - это основная логика, тут задается поведение сущностей. Существуют в виде пользовательских классов, реализующих как минимум один из интерфейсов процессов. Основные процессы:
@ -271,8 +270,10 @@ EcsPipeline pipeline = EcsPipeline.New()
.BuildAndInit(); .BuildAndInit();
``` ```
### Слои ### Сортировка
Очередь систем можно разбить на слои. Слой определяет место в очереди для вставки систем. Например, если необходимо чтобы какая-то система была вставлена в конце очереди, вне зависимости от места добавления, эту систему можно добавить в слой EcsConsts.END_LAYER. Дла управления расположением систем в пайплайне, вне зависимости от порядка добавления, есть 2 способа: Слои и Порядок сортировки.
#### Слои
Слой определяет место в пайплайне для вставки систем. Например, если необходимо чтобы система была вставлена в конце пайплайна, эту систему можно добавить в слой `EcsConsts.END_LAYER`.
``` c# ``` c#
const string SOME_LAYER = nameof(SOME_LAYER); const string SOME_LAYER = nameof(SOME_LAYER);
EcsPipeline pipeline = EcsPipeline.New() EcsPipeline pipeline = EcsPipeline.New()
@ -287,9 +288,20 @@ EcsPipeline pipeline = EcsPipeline.New()
Встроенные слои расположены в следующем порядке: Встроенные слои расположены в следующем порядке:
* `EcsConst.PRE_BEGIN_LAYER` * `EcsConst.PRE_BEGIN_LAYER`
* `EcsConst.BEGIN_LAYER` * `EcsConst.BEGIN_LAYER`
* `EcsConst.BASIC_LAYER` (Если при добавлении системы не указать слой, то она будет добавлена сюда) * `EcsConst.BASIC_LAYER` (По умолчанию системы добавляются сюда)
* `EcsConst.END_LAYER` * `EcsConst.END_LAYER`
* `EcsConst.POST_END_LAYER` * `EcsConst.POST_END_LAYER`
#### Порядок сортировки
Для сортировки систем в рамках слоя используется int значение порядка сортировки. По умолчанию системы добавляются с sortOrder = 0.
``` c#
EcsPipeline pipeline = EcsPipeline.New()
// ...
// Система SomeSystem будет вставлена в слой EcsConsts.BEGIN_LAYER
// и расположена после систем с sortOrder меньше 10.
.Add(New SomeSystem(), EcsConsts.BEGIN_LAYER, 10)
// ...
.BuildAndInit();
```
## Процессы ## Процессы
Процессы - это очереди систем реализующие общий интерфейс, например `IEcsRun`. Для запуска процессов используются Runner-ы. Встроенные процессы запускаются автоматически. Есть возможность реализации пользовательских процессов. Процессы - это очереди систем реализующие общий интерфейс, например `IEcsRun`. Для запуска процессов используются Runner-ы. Встроенные процессы запускаются автоматически. Есть возможность реализации пользовательских процессов.
@ -345,17 +357,20 @@ _pipeline.GetRunnerInstance<DoSomethingProcessRunner>.Do()
</details> </details>
## Мир ## Мир
Является контейнером для сущностей и компонентов. Контейнер для сущностей и компонентов.
``` c# ``` c#
// Создание экземпляра мира. // Создание экземпляра мира.
_world = new EcsDefaultWorld(); _world = new EcsDefaultWorld();
// Создание и удаление сущности по примеру из раздела Сущности. // Создание и удаление сущности по примеру из раздела Сущности.
var e = _world.NewEntity(); var e = _world.NewEntity();
_world.DelEntity(e); _world.DelEntity(e);
```
> **NOTICE:** Необходимо вызывать EcsWorld.Destroy() у экземпляра мира если он больше не используется, иначе он будет висеть в памяти.
### Конфигурация мира // Уничтожение мира и освобождение ресурсов. Обязательно вызывать, иначе он будет висеть в памяти.
_world.Destroy();
```
> Миры изолированы друг от друга и могут обрабатываться в отдельных потоках. Но мультипоточная обработка одного мира поддерживается только при отсутсвии добавления/удаляения компонентов у сущностей.
Для инициализации мира сразу необходимого размера и сокращения времени прогрева, в конструктор можно передать экземпляр `EcsWorldConfig`. Для инициализации мира сразу необходимого размера и сокращения времени прогрева, в конструктор можно передать экземпляр `EcsWorldConfig`.
``` c# ``` c#
@ -363,14 +378,16 @@ EcsWorldConfig config = new EcsWorldConfig(
// Предварительно инициализирует вместимость мира для 2000 сущностей. // Предварительно инициализирует вместимость мира для 2000 сущностей.
entitiesCapacity: 2000, entitiesCapacity: 2000,
// Предварительно инициализирует вместимость пулов для 2000 компонентов. // Предварительно инициализирует вместимость пулов для 2000 компонентов.
poolComponentsCapacity: 2000); poolComponentsCapacity: 2000
// ... Есть и другие параметры
);
_world = new EcsDefaultWorld(config); _world = new EcsDefaultWorld(config);
``` ```
## Пул ## Пул
Является хранилищем для компонентов, предоставляет методы для добавления/чтения/редактирования/удаления компонентов на сущности. Есть несколько видов пулов, для разных целей: Хранилище для компонентов, пул предоставляет методы для добавления/чтения/редактирования/удаления компонентов на сущности. Есть несколько видов пулов, для разных видов компонентов:
* `EcsPool` - универсальный пул, хранит struct-компоненты реализующие интерфейс `IEcsComponent`; * `EcsPool` - универсальный пул, хранит struct-компоненты реализующие интерфейс `IEcsComponent`;
* `EcsTagPool` - специальный пул для пустых компонентов-тегов, хранит struct-компоненты с `IEcsTagComponent` как bool значения, что в сравнении с реализацией `EcsPool` имеет лучше оптимизацию памяти и скорости; * `EcsTagPool` - специальный пул, оптимизированный под компоненты-теги, хранит struct-компоненты с `IEcsTagComponent`;
Пулы имеют 5 основных метода и их разновидности: Пулы имеют 5 основных метода и их разновидности:
``` c# ``` c#
@ -392,12 +409,50 @@ if (poses.Has(entityID)) { /* ... */ }
// Удалит компонент у сущности, бросит исключение если у сущности нет этого компонента. // Удалит компонент у сущности, бросит исключение если у сущности нет этого компонента.
poses.Del(entityID); poses.Del(entityID);
``` ```
> [!WARNING]
> В `Release` сброке отключаются проверки на исключения.
> Есть "безопасные" методы, которые сначала выполнят проверку наличия/отсутствия компонента, названия таких методов начинаются с `Try`. > Есть "безопасные" методы, которые сначала выполнят проверку наличия/отсутствия компонента, названия таких методов начинаются с `Try`.
> Имеется возможность реализации пользовательского пула. Эта функция будет описана в ближайшее время. > Имеется возможность реализации пользовательского пула. Эта функция будет описана в ближайшее время.
## Маска
Применяется для фильтрации сущностей по наличию или отсутствию компонентов.
``` c#
// Создание маски которая проверяет что у сущностей есть компоненты
// SomeCmp1 и SomeCmp2, но нет компонента SomeCmp3.
EcsMask mask = EcsMask.New(_world)
// Inc - Условие наличия компонента.
.Inc<SomeCmp1>()
.Inc<SomeCmp2>()
// Exc - Условие отсутствия компонента.
.Exc<SomeCmp3>()
.Build();
```
<details>
<summary>Статическая маска</summary>
`EcsMask` привязаны к конкретным экземплярам мира которые необходимо передавать в `EcsMask.New(world)`, но есть `EcsStaticMask` которую можно создать без привязки к миру.
``` c#
class SomeSystem : IEcsRun
{
// EcsStaticMask можно создавать в статических полях.
static EcsStaticMask _staticMask = EcsStaticMask.Inc<SomeCmp1>().Inc<SomeCmp2>().Exc<SomeCmp3>().Build();
// ...
}
```
``` c#
// Конвертация в обычную маску.
EcsMask mask = _staticMask.ToMask(_world);
```
</details>
## Аспект ## Аспект
Это пользовательские классы наследуемые от `EcsAspect` и используемые для взаимодействия с сущностями. Аспекты одновременно являются кешем пулов и маской компонентов для фильтрации сущностей. Можно рассматривать аспекты как описание того с какими сущностями работает система. Пользовательские классы наследуемые от `EcsAspect` и используемые для взаимодействия с сущностями. Аспекты одновременно являются кешем пулов и содержат маску. Можно рассматривать аспекты как описание того с какими сущностями работает система.
Упрощенный синтаксис: Упрощенный синтаксис:
``` c# ``` c#
@ -418,7 +473,9 @@ class Aspect : EcsAspect
} }
``` ```
Явный синтаксис (результат идентичен примеру выше): <details>
<summary>Явный синтаксис (результат идентичен примеру выше):</summary>
``` c# ``` c#
using DCFApixels.DragonECS; using DCFApixels.DragonECS;
// ... // ...
@ -435,6 +492,8 @@ class Aspect : EcsAspect
} }
``` ```
</details>
<details> <details>
<summary>Комбинирование аспектов</summary> <summary>Комбинирование аспектов</summary>
@ -470,8 +529,13 @@ class Aspect : EcsAspect
</details> </details>
## Запросы ## Запросы
Что бы получить необходимый набор сущностей используется метод-запрос `EcsWorld.Where<TAspect>(out TAspect aspect)`. В качестве `TAspect` указывается аспект, сущности будут отфильтрованны по маске указанного аспекта. Запрос `Where` применим как к `EcsWorld` так и коллекциям фреймворка (в этом плане Where чем-то похож на аналогичный из Linq). Фильтруют сущности и выдают коллекции сущностей удовлетворяющие определенным условиям. Встроенный запрос `Where` фильтрует на соответствие условиям маски компонентов и имеет несколько перегрузок:
Пример: + `EcsWorld.Where(EcsMask mask)` - Обычная фильтрация по маске;
+ `EcsWorld.Where<TAspect>(out TAspect aspect)` - Сочетает в себе фильтрацию по маске из аспекта и получение аспекта;
Запрос `Where` применим как к `EcsWorld` так и коллекциям фреймворка (в этом плане Where чем-то похож на аналогичный из Linq). Так же имеются перегрузки для сортировки сущностей по `Comparison<int>`.
Пример системы:
``` c# ``` c#
public class SomeDamageSystem : IEcsRun, IEcsInject<EcsDefaultWorld> public class SomeDamageSystem : IEcsRun, IEcsInject<EcsDefaultWorld>
{ {
@ -480,6 +544,8 @@ public class SomeDamageSystem : IEcsRun, IEcsInject<EcsDefaultWorld>
public EcsPool<Health> healths = Inc; public EcsPool<Health> healths = Inc;
public EcsPool<DamageSignal> damageSignals = Inc; public EcsPool<DamageSignal> damageSignals = Inc;
public EcsTagPool<IsInvulnerable> isInvulnerables = Exc; public EcsTagPool<IsInvulnerable> isInvulnerables = Exc;
// Наличие или отсутвие этого компонента не проверяется.
public EcsTagPool<IsDiedSignal> isDiedSignals = Opt;
} }
EcsDefaultWorld _world; EcsDefaultWorld _world;
public void Inject(EcsDefaultWorld world) => _world = world; public void Inject(EcsDefaultWorld world) => _world = world;
@ -489,7 +555,15 @@ public class SomeDamageSystem : IEcsRun, IEcsInject<EcsDefaultWorld>
foreach (var e in _world.Where(out Aspect a)) foreach (var e in _world.Where(out Aspect a))
{ {
// Сюда попадают сущности с компонентами Health, DamageSignal и без IsInvulnerable. // Сюда попадают сущности с компонентами Health, DamageSignal и без IsInvulnerable.
a.healths.Get(e).points -= a.damageSignals.Get(e).points; ref var health = ref a.healths.Get(e);
if(health.points > 0)
{
health.points -= a.damageSignals.Get(e).points;
if(health.points <= 0)
{ // Создаем сигнал другим системам о том что сущность умерла.
a.isDiedSignals.TryAdd(e);
}
}
} }
} }
} }
@ -690,6 +764,9 @@ using DCFApixels.DragonECS;
// Добавляет описание типу. // Добавляет описание типу.
[MetaDescription("The quick brown fox jumps over the lazy dog")] [MetaDescription("The quick brown fox jumps over the lazy dog")]
// Добавляет строковый уникальный идентификатор.
[MetaID("8D56F0949201D0C84465B7A6C586DCD6")] // Строки должны быть уникальными, и не допускают символы ,<> .
// Добавляет строковые теги. // Добавляет строковые теги.
[MetaTags("Tag1", "Tag2", ...)] // [MetaTags(MetaTags.HIDDEN))] чтобы скрыть в редакторе [MetaTags("Tag1", "Tag2", ...)] // [MetaTags(MetaTags.HIDDEN))] чтобы скрыть в редакторе
public struct Component : IEcsComponent { /* ... */ } public struct Component : IEcsComponent { /* ... */ }
@ -700,17 +777,16 @@ TypeMeta typeMeta = someComponent.GetMeta();
// или // или
TypeMeta typeMeta = pool.ComponentType.ToMeta(); TypeMeta typeMeta = pool.ComponentType.ToMeta();
var name = typeMeta.Name; var name = typeMeta.Name; // [MetaName]
var color = typeMeta.Color; var group = typeMeta.Group; // [MetaGroup]
var description = typeMeta.Description; var color = typeMeta.Color; // [MetaColor]
var group = typeMeta.Group; var description = typeMeta.Description; // [MetaDescription]
var tags = typeMeta.Tags; var metaID = typeMeta.MetaID; // [MetaID]
var tags = typeMeta.Tags; // [MetaTags]
``` ```
## EcsDebug ## EcsDebug
Имеет набор методов для отладки и логирования. Реализован как статический класс вызывающий методы Debug-сервисов. Debug-сервисы - это посредники между системами отладки среды и EcsDebug. Это позволяет не изменяя отладочный код проекта, переносить проект на другие движки, достаточно только реализовать соответствующий Debug-сервис. Вспомогательный тип с набором методов для отладки и логирования. Реализован как статический класс вызывающий методы Debug-сервисов. Debug-сервисы - это посредники между EcsDebug и инструментами отладки среды. Такая реализация позволяет не изменяя отладочный код, менять его поведение или переносить проект в другие среды, достаточно только реализовать соответствующий Debug-сервис.
По умолчанию используется `DefaultDebugService` который выводит логи в консоль. Для реализации пользовательского создайте класс наследуемый от `DebugService` и реализуйте абстрактные члены класса.
``` c# ``` c#
// Вывод лога. // Вывод лога.
@ -726,12 +802,17 @@ EcsDebug.Break();
EcsDebug.Set<OtherDebugService>(); EcsDebug.Set<OtherDebugService>();
``` ```
> По умолчанию используется `DefaultDebugService` который выводит логи в консоль. Для реализации пользовательского создайте класс наследуемый от `DebugService` и реализуйте абстрактные члены класса.
> `EcsDebug` потокобезопасен, за счет того что каждый поток использует свой изолированный экземпляр сервиса. Экземпляры для потоков создаются в абстрактном методе `DebugService.CreateThreadInstance`.
## Профилирование ## Профилирование
За реализацию профайлера так же отвечает Debug-сервис. Для выделения участка кода используется `EcsProfilerMarker`;
``` c# ``` c#
// Создание маркера с именем SomeMarker. // Создание маркера с именем SomeMarker.
private static readonly EcsProfilerMarker marker = new EcsProfilerMarker("SomeMarker"); private static readonly EcsProfilerMarker marker = new EcsProfilerMarker("SomeMarker");
... // ...
marker.Begin(); marker.Begin();
// Код для которого замеряется скорость. // Код для которого замеряется скорость.
@ -744,6 +825,7 @@ using (marker.Auto())
// Код для которого замеряется скорость. // Код для которого замеряется скорость.
} }
``` ```
> `DefaultDebugService` использует реализацию на основе `Stopwatch` и выводом в консоль.
</br> </br>

View File

@ -41,7 +41,7 @@
</br> </br>
DragonECS 是一个[实体组件系统](https://www.imooc.com/article/331544)框架。专注于提升便利性、模块性、可扩展性和动态实体修改性能。 用纯C#开发的,没有依赖和代码生成。灵感来自于[LeoEcs](https://github.com/Leopotam/ecslite)。 DragonECS 是一个[实体组件系统](https://www.imooc.com/article/331544)框架。专注于提升便利性、模块性、可扩展性和动态实体修改性能。 用纯C#开发的,没有依赖和代码生成。灵感来自于[LeoEcs Lite](https://github.com/Leopotam/ecslite)。
> [!WARNING] > [!WARNING]
> 该框架是预发布版本,因此 API 可能会有变化。在 `main` 分支中是当前的工作版本。</br> > 该框架是预发布版本,因此 API 可能会有变化。在 `main` 分支中是当前的工作版本。</br>

View File

@ -43,7 +43,7 @@
</br> </br>
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). 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 Lite](https://github.com/Leopotam/ecslite).
> [!WARNING] > [!WARNING]
> The project is a work in progress, API may change. > The project is a work in progress, API may change.