Version License Discord QQ

# DragonECS - C# Entity Component System Framework
Readme Languages:

Русский

English

中文
The [ECS](https://en.wikipedia.org/wiki/Entity_component_system) Framework aims to maximize usability, modularity, extensibility and performance for dynamic entity changes, without code generation or external dependencies. Inspired by [LeoEcs Lite](https://github.com/Leopotam/ecslite). > [!WARNING] > The project is a work in progress; the API may change. > > The most current version of the README is in [Russian version](https://github.com/DCFApixels/DragonECS/blob/main/README-RU.md). > > If there are unclear points, you can ask questions here [Feedback](#Feedback) ## Table of Contents - [Installation](#installation) - [Extensions](#extensions) - [Basic Concepts](#basic-concepts) - [Entity](#entity) - [Component](#component) - [System](#system) - [Framework Concepts](#framework-concepts) - [Pipeline](#pipeline) - [Building](#building) - [Dependency Injection](#dependency-injection) - [Modules](#modules) - [Sorting](#sorting) - [Processes](#Processes) - [World](#World) - [Pool](#Pool) - [Mask](#mask) - [Aspect](#aspect) - [Queries](#queries) - [Collections](#collections) - [ECS Root](#ecs-root) - [Debug](#debug) - [Meta Attributes](#meta-attributes) - [EcsDebug](#ecsdebug) - [Profiling](#profiling) - [Define Symbols](#define-symbols) - [Framework Extension Tools](#framework-extension-tools) - [World Components](#world-components) - [Configs](#configs) - [Projects powered by DragonECS](#projects-powered-by-dragonecs) - [Extensions](#extensions) - [FAQ](#faq) - [Feedback](#feedback) - [License](#license)
# Installation Versioning semantics - [Open](https://gist.github.com/DCFApixels/af79284955bf40e9476cdcac79d7b098#file-dcfapixels_versioning-md) ## Environment Requirements: * Minimum C# version: 7.3. Supported: * NativeAOT; * Game engines with C#: Unity, Godot, MonoGame, etc. Tested with: * **Unity:** Minimum version 2021.2.0. ## Unity Installation > It is also recommended to install the Unity engine integration extension [Unity integration](https://github.com/DCFApixels/DragonECS-Unity) * ### Unity package Installation as a Unity package is supported by adding the Git URL [in PackageManager](https://docs.unity3d.com/2023.2/Documentation/Manual/upm-ui-giturl.html): ``` https://github.com/DCFApixels/DragonECS.git ``` Or add the package entry to `Packages/manifest.json`: ``` "com.dcfa_pixels.dragonecs": "https://github.com/DCFApixels/DragonECS.git", ``` * ### As source code The framework sources can be copied directly into the project. # Extensions * Integrations: * [Unity](https://github.com/DCFApixels/DragonECS-Unity) * [Godot](https://gitlab.com/InauniusOwn/Libraries/DraGodot) * Packages: * [Dependency autoinjections](https://github.com/DCFApixels/DragonECS-AutoInjections) * [Classic C# multithreading](https://github.com/DCFApixels/DragonECS-ClassicThreads) * [Recursivity](https://github.com/DCFApixels/DragonECS-Recursivity) * [Hybrid](https://github.com/DCFApixels/DragonECS-Hybrid) * [Graphs](https://github.com/DCFApixels/DragonECS-Graphs) * Utilities: * [Simple syntax](https://gist.github.com/DCFApixels/d7bfbfb8cb70d141deff00be24f28ff0) * [EcsRefPool](https://gist.github.com/DCFApixels/73e392ccabdd98b3d4a517017d8a3f22) * [Timers](https://gist.github.com/DCFApixels/71a416275660c465ece76242290400df) * [One-Frame Components](https://gist.github.com/DCFApixels/46d512dbcf96c115b94c3af502461f60) * [Code Templates for IDE](https://gist.github.com/ctzcs/0ba948b0e53aa41fe1c87796a401660b) and [for Unity](https://gist.github.com/ctzcs/d4c7730cf6cd984fe6f9e0e3f108a0f1) > *Your extension? Extensions for DragonECS are welcome, you can share it [here](#feedback). # Basic Concepts ## Entity Container for components. Two identifier types are used to reference entities: * `int` - short-lived identifier, valid within a single tick. Not recommended for long-term storage; * `entlong` - long-term identifier that includes a generation tag, making it unique across entity lifetimes. Suitable for long-term storage. ```c# // Creating a new entity in the world. int entityID = _world.NewEntity(); // Deleting an entity. _world.DelEntity(entityID); // Copying components from one entity to another. _world.CopyEntity(entityID, otherEntityID); // Cloning an entity. int newEntityID = _world.CloneEntity(entityID); ```
Working with entlong ```c# // Convert int to entlong. entlong entity = _world.GetEntityLong(entityID); // or entlong entity = (_world, entityID); // Check that the entity is still alive. if (entity.IsAlive) { } // Converting entlong to int. Throws an exception if the entity no longer exists. int entityID = entity.ID; // or var (entityID, world) = entity; // Converting entlong to int. Returns true and the int identifier if the entity is still alive. if (entity.TryGetID(out int entityID)) { } ```
> Entities cannot exist without components. Removing the last component automatically deletes the entity along with it. ## Component Data for entities. ```c# // IEcsComponent components are stored in regular storage. struct Health : IEcsComponent { public float health; public int armor; } // Components with IEcsTagComponent are stored in tag-optimized storage. struct PlayerTag : IEcsTagComponent {} ``` ## System Represents the core logic defining entity behaviors. Systems are implemented as user-defined classes that implement one or more process interfaces. Key processes include: ```c# class SomeSystem : IEcsPreInit, IEcsInit, IEcsRun, IEcsDestroy { // Called once during EcsPipeline.Init() and before IEcsInit.Init(). public void PreInit () { } // Called once during EcsPipeline.Init() and after IEcsPreInit.PreInit(). public void Init () { } // Called each time during EcsPipeline.Run(). public void Run () { } // Called once during EcsPipeline.Destroy(). public void Destroy () { } } ``` > For implementing additional processes, refer to the [Processes](#Processes) section.
# Framework Concepts ## Pipeline Container and engine of systems. Responsible for setting up the system call queue, providing mechanisms for communication between systems, and dependency injection. Implemented as the `EcsPipeline` class. ### Building Builder is responsible for building the pipeline. Systems are added to the Builder, and the pipeline is built at the end. Example: ```c# EcsPipeline pipeline = EcsPipeline.New() // Creates a pipeline builder. // Adds System1 to the systems queue. .Add(new System1()) // Adds System2 to the queue after System1. .Add(new System2()) // Adds System3 to the queue after System2, as a unique instance. .AddUnique(new System3()) // Completes the pipeline building and returns its instance. .Build(); pipeline.Init(); // Initializes the pipeline. ``` ```c# class SomeSystem : IEcsRun, IEcsPipelineMember { // Gets the pipeline instance to which the system belongs. public EcsPipeline Pipeline { get ; set; } public void Run () { } } ``` > For simultaneous building and initialization, use `Builder.BuildAndInit()`. ### Dependency Injection The framework implements dependency injection for systems. Injection process occurs during pipeline initialization and injects data passed to the Builder. > Built-in Dependency injection is optional. ```c# class SomeDataA { /* ... */ } class SomeDataB : SomeDataA { /* ... */ } // ... SomeDataB _someDataB = new SomeDataB(); EcsPipeline pipeline = EcsPipeline.New() // ... // Injects _someDataB into systems implementing IEcsInject. .Inject(_someDataB) // Adds systems implementing IEcsInject to the injection tree, // now these systems will also receive _someDataB. .Injector.AddNode() // ... .Add(new SomeSystem()) // ... .BuildAndInit(); // ... // Injection uses the interface IEcsInject and its method Inject(T obj). class SomeSystem : IEcsInject, IEcsRun { SomeDataA _someDataA; // obj will be an instance of type SomeDataB. public void Inject(SomeDataA obj) => _someDataA = obj; public void Run () { _someDataA.DoSomething(); } } ``` ### Modules Groups of systems that implement a common feature can be grouped into modules and added to the Pipeline. ```c# using DCFApixels.DragonECS; class Module1 : IEcsModule { public void Import(EcsPipeline.Builder b) { b.Add(new System1()); b.Add(new System2()); b.AddModule(new Module2()); // ... } } ``` ``` c# EcsPipeline pipeline = EcsPipeline.New() // ... .AddModule(new Module1()) // ... .BuildAndInit(); ``` ### Sorting To manage system order in the pipeline regardless of addition order, use Layers and Sorting Order. #### Layers Queues of systems can be segmented into layers. A layer defines a position in the queue for inserting systems. For example, to insert a system at the end of the queue regardless of where it is added, add it to the `EcsConsts.END_LAYER` layer. ``` c# const string SOME_LAYER = nameof(SOME_LAYER); EcsPipeline pipeline = EcsPipeline.New() // ... // Inserts a new layer before the end layer EcsConsts.END_LAYER .Layers.Insert(EcsConsts.END_LAYER, SOME_LAYER) // System SomeSystem will be added to the SOME_LAYER layer .Add(new SomeSystem(), SOME_LAYER) // ... .BuildAndInit(); ``` The built-in layers are arranged in the following order: * `EcsConsts.PRE_BEGIN_LAYER` * `EcsConsts.BEGIN_LAYER` * `EcsConsts.BASIC_LAYER` (Systems are added here if no layer is specified during addition) * `EcsConsts.END_LAYER` * `EcsConsts.POST_END_LAYER` #### Sorting Order The sort order integer value is used to sort systems within a layer. By default, systems are added with `sortOrder = 0`. ```c# EcsPipeline pipeline = EcsPipeline.New() // ... // System SomeSystem will be inserted into the layer EcsConsts.BEGIN_LAYER // and placed after systems with sortOrder less than 10. .Add(new SomeSystem(), EcsConsts.BEGIN_LAYER, 10) // ... .BuildAndInit(); ``` ## Processes Processes are queues of systems that implement a common interface, such as `IEcsRun`. Runners are used to start processes. Built-in processes are started automatically; custom processes can be implemented.
Built-in processes * `IEcsPreInit`, `IEcsInit`, `IEcsRun`, `IEcsDestroy` - lifecycle processes of `EcsPipeline`. * `IEcsInject` - [Dependency Injection](#Dependency-Injection) processes. * `IOnInitInjectionComplete` - Similar to the [Dependency Injection](#Dependency-Injection) process, but signals completion of initialization injection.
Custom Processes To add a new process, create an interface inheriting from `IEcsProcess` and create a runner for it. A runner is a class that implements the process interface and inherits from `EcsRunner`. Example: ``` c# // Process interface. interface IDoSomethingProcess : IEcsProcess { void Do(); } // Runner implementation. An example of implementation can also be seen in built-in processes. sealed class DoSomethingProcessRunner : EcsRunner, IDoSomethingProcess { public void Do() { foreach (var item in Process) item.Do(); } } ``` ``` c# // Adding the runner when creating the pipeline _pipeline = EcsPipeline.New() //... .AddRunner() //... .BuildAndInit(); // Running the runner if it was added _pipeline.GetRunner().Do(); // or if the runner was not added (calling GetRunnerInstance will also add the runner to the pipeline). _pipeline.GetRunnerInstance().Do(); ```
Advanced Implementation of a Runner ``` c# internal sealed class DoSomethingProcessRunner : EcsRunner, IDoSomethingProcess { // RunHelper simplifies the implementation similar to the built-in processes implementation. // It automatically triggers the profiler marker and also includes a try-catch block. private RunHelper _helper; protected override void OnSetup() { // The second argument specifies the name of the marker; if not specified, the name will be chosen automatically. _helper = new RunHelper(this, nameof(Do)); } public void Do() { _helper.Run(p => p.Do()); } } ```
> Runner requirements: > * Inheritance from `EcsRunner` must be direct. > * A runner may only contain one interface (excluding `IEcsProcess`). > * The inheriting class of `EcsRunner` must also implement the `T` interface.
## World A container for entities and components. ``` c# // Creating an instance of the world. _world = new EcsDefaultWorld(); // Creating and deleting an entity as shown in the Entities section. var e = _world.NewEntity(); _world.DelEntity(e); ``` > **NOTICE:** Call `EcsWorld.Destroy()` on the world instance when it is no longer needed to release resources. Otherwise it will remain in memory. ### World Configuration To initialize the world with a required size upfront and reduce warm-up time, pass an `EcsWorldConfig` instance to the constructor. ``` c# EcsWorldConfig config = new EcsWorldConfig( // Pre-initializes the world capacity for 2000 entities. entitiesCapacity: 2000, // Pre-initializes the pools capacity for 2000 components. poolComponentsCapacity: 2000); _world = new EcsDefaultWorld(config); ``` ## Pool Stash of components, providing methods for adding, reading, editing, and removing components on entities. Several pool types are available: * `EcsPool` - universal pool for struct components implementing `IEcsComponent`; * `EcsTagPool` - pool optimized for tag components implementing `IEcsTagComponent`; Pools provide 5 common methods and their variations: ``` c# // One way to get a pool from the world. EcsPool poses = _world.GetPool(); // Adds component to entity, throws an exception if the entity already has the component. ref var addedPose = ref poses.Add(entityID); // Returns existing component, throws an exception if the entity does not have this component. ref var gottenPose = ref poses.Get(entityID); // Returns a read-only component, throwing an exception if the entity does not have this component. ref readonly var readonlyPose = ref poses.Read(entityID); // Returns true if the entity has the component, otherwise false. if (poses.Has(entityID)) { /* ... */ } // Removes component from entity, throws an exception if the entity does not have this component. poses.Del(entityID); ``` > [!WARNING] > Exceptions are disabled in the `Release` build. > There are "Safe" methods that first check presence or absence of a component are prefixed with `Try`.
Custom Pools A pool can be any type that implements the IEcsPoolImplementation interface and has a parameterless constructor. Key points when implementing a pool: * For an example of a pool implementation, reference can be made to the implementation of the built-in EcsPool. * The IEcsPoolImplementation interface and its members are not intended for public use; the interface members should be implemented explicitly. * The type T substituted in the IEcsPoolImplementation interface and the type returned by the ComponentType and ComponentTypeID properties must match. * All pool changes must be registered with the EcsWorld.PoolsMediator instance passed in the OnInit method. * EcsWorld.PoolsMediator is intended for use only inside the pool. * The DISABLE_POOLS_EVENTS define disables the implemented AddListener and RemoveListener methods. * The static class EcsPoolThrowHelper defines throwing of the most common exception types. * The OnReleaseDelEntityBuffer method handles cleanup of deleted entities. * It is recommended to define an interface that marks components intended for the new pool. Based on this interface, extension methods such as GetPool() can be implemented to simplify access to pools. * Pools must implement locking. Pool locking works only in Debug mode and should throw exceptions when attempting to add or remove a component.
## Mask Used to filter entities by presence or absence of components. Usually masks are not used standalone, they are part of `EcsAspect` and used by queries to filter entities. ``` c# // Creating a mask that checks if entities have components // SomeCmp1 and SomeCmp2, but do not have component SomeCmp3. EcsMask mask = EcsMask.New(_world) // Inc - Condition for the presence of a component. .Inc() .Inc() // Exc - Condition for the absence of a component. .Exc() .Build(); ```
Static Mask `EcsMask` is tied to specific world instances, which need to be passed to `EcsMask.New(world)`. `EcsStaticMask` can be created without a world reference. ``` c# class SomeSystem : IEcsRun { // EcsStaticMask can be created in static fields. static readonly EcsStaticMask _staticMask = EcsStaticMask.Inc().Inc().Exc().Build(); // ... } ``` ``` c# // Converting to a regular mask. EcsMask mask = _staticMask.ToMask(_world); ```
## Aspect User-defined classes that inherit from `EcsAspect` and describe sets of components a system works with. An aspect serves two purposes: - Mask — initializes and holds an `EcsMask` used by queries for filtering entities. - Pool cache — provides fast access to component pools. In short, an aspect is a convenient way to declare "which entities I work with and how to access their components." Simplified syntax: ``` c# using DCFApixels.DragonECS; // ... class Aspect : EcsAspect { // Caches the Pose pool and adds it to the inclusive constraint. public EcsPool poses = Inc; // Caches the Velocity pool and adds it to the inclusive constraint. public EcsPool velocities = Inc; // Caches the FreezedTag pool and adds it to the exclusive constraint. public EcsTagPool freezedTags = Exc; // During queries, it checks for the presence of components // in the inclusive constraint and absence in the exclusive constraint. // Opt only caches the pool without affecting the mask. } ``` Purpose of the static markers: * `Inc` — component must be present (inclusive) and caches the pool. * `Exc` — component must NOT be present (exclusive) and caches the pool. * `Opt` — component may be present; does not affect filtering (only caches the pool). * `Any` — at least one of the components marked with `Any` must be present; caches the pool.
Initialization via a method (the result is identical to the example above): Explicit syntax (equivalent result): ``` c# using DCFApixels.DragonECS; // ... class Aspect : EcsAspect { public EcsPool poses; public EcsPool velocities; protected override void Init(Builder b) { poses = b.Inc(); velocities = b.Inc(); b.Exc(); } } ```
Combining aspects Aspects can be combined by adding other aspects; constraints are merged accordingly. ``` c# using DCFApixels.DragonECS; // ... class Aspect : EcsAspect { public OtherAspect1 otherAspect1; public OtherAspect2 otherAspect2; public EcsPool poses; protected override void Init(Builder b) { // Combines with OtherAspect1. otherAspect1 = b.Combine(1); // Although Combine was called earlier for OtherAspect1, it will first combine with OtherAspect2 because the default order is 0. otherAspect2 = b.Combine(); // If b.Exc() was specified in OtherAspect1 or OtherAspect2, it will be replaced with b.Inc() here. poses = b.Inc(); } } ``` If conflicting constraints exist between combined aspects, the newer constraints replace earlier ones. Constraints from the root aspect always replace constraints from added aspects. Example of constraint combination: | | cmp1 | cmp2 | cmp3 | cmp4 | cmp5 | Conflict resolution | | :--- | :--- | :--- | :--- | :--- | :--- |:--- | | OtherAspect2 | :heavy_check_mark: | :x: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_check_mark: | | | OtherAspect1 | :heavy_minus_sign: | :heavy_check_mark: | :heavy_minus_sign: | :x: | :heavy_minus_sign: | For `cmp2`, :heavy_check_mark: will be chosen. | | Aspect | :x: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_check_mark: | For `cmp1`, :x: will be chosen. | | Final Constraints | :x: | :heavy_check_mark: | :heavy_minus_sign: | :x: | :heavy_check_mark: | |
## Queries Filter entities and return collections of entities that match conditions. The built-in `Where` query filters by component mask and has several overloads: + `EcsWorld.Where(EcsMask mask)` - standard filtering by mask; + `EcsWorld.Where(out TAspect aspect)` - combines aspect mask filtering and returns the aspect; `Where` can be applied to both `EcsWorld` and framework collections (similar to LINQ). Overloads for sorting using `Comparison` are available. Example system: ``` c# public class SomeDamageSystem : IEcsRun, IEcsInject { class Aspect : EcsAspect { public EcsPool healths = Inc; public EcsPool damageSignals = Inc; public EcsTagPool isInvulnerables = Exc; // The presence or absence of this component is not checked. public EcsTagPool isDiedSignals = Opt; } EcsDefaultWorld _world; public void Inject(EcsDefaultWorld world) => _world = world; public void Run() { foreach (var e in _world.Where(out Aspect a)) { // Entities with Health, DamageSignal, and without IsInvulnerable will be here. ref var health = ref a.healths.Get(e); if(health.points > 0) { health.points -= a.damageSignals.Get(e).points; if(health.points <= 0) { // Create a signal to other systems that the entity has died. a.isDiedSignals.TryAdd(e); } } } } } ``` > You can use an [Extension](#extensions) to simplify query syntax and interactions with components - [Simplified Syntax](https://github.com/DCFApixels/DragonECS-AutoInjections). ## Collections ### EcsSpan A `ref struct` collection of entities, available only for reading. Consists of a reference to an array, a length, and a world identifier. Similar to `ReadOnlySpan`. ``` c# // Where query returns entities as EcsSpan. EcsSpan es = _world.Where(out Aspect a); // Iteration is possible using foreach and for loops. foreach (var e in es) { // ... } for (int i = 0; i < es.Count; i++) { int e = es[i]; // ... } ``` > Although `EcsSpan` is an array reference, duplicate entities are not allowed. ### EcsGroup Sparse Set based auxiliary collection for storing a set of entities with O(1) add/delete/check operations, etc. ``` c# // Getting a new group. EcsWorld contains a pool of groups, // a new one will be created or a free one reused. EcsGroup group = EcsGroup.New(_world); // Release the group. group.Dispose(); ``` ``` c# // Add entityID to the group. group.Add(entityID); // Check if entityID exists in the group. group.Has(entityID); // Remove entityID from the group. group.Remove(entityID); ``` ``` c# // WhereToGroup query returns entities as a read-only group EcsReadonlyGroup. EcsReadonlyGroup group = _world.WhereToGroup(out Aspect a); // Iteration is possible using foreach and for loops. foreach (var e in group) { // ... } for (int i = 0; i < group.Count; i++) { int e = group[i]; // ... } ``` Groups implement the `ISet` interface. Editing methods have two variants: one that writes the result to `groupA`, and another that returns a new group. ``` c# // Union of groupA and groupB. groupA.UnionWith(groupB); EcsGroup newGroup = EcsGroup.Union(groupA, groupB); ``` ``` c# // Intersection of groupA and groupB. groupA.IntersectWith(groupB); EcsGroup newGroup = EcsGroup.Intersect(groupA, groupB); ``` ``` c# // Difference of groupA and groupB. groupA.ExceptWith(groupB); EcsGroup newGroup = EcsGroup.Except(groupA, groupB); ``` ``` c# // Symmetric difference of groupA and groupB. groupA.SymmetricExceptWith(groupB); EcsGroup newGroup = EcsGroup.SymmetricExcept(groupA, groupB); ``` ``` c# // Difference of all entities in world and groupA. groupA.Inverse(); EcsGroup newGroup = EcsGroup.Inverse(groupA); ``` ## ECS Root A custom class that serves as the entry point for ECS. Its main purpose is to initialize systems, run them on each engine update, and release resources when no longer needed. ### Example for Unity ``` c# using DCFApixels.DragonECS; using UnityEngine; public class EcsRoot : MonoBehaviour { private EcsPipeline _pipeline; private EcsDefaultWorld _world; private void Start() { // Creating world for entities and components. _world = new EcsDefaultWorld(); // Creating pipeline for systems. _pipeline = EcsPipeline.New() // Adding systems. // .Add(new SomeSystem1()) // .Add(new SomeSystem2()) // .Add(new SomeSystem3()) // Injecting world into systems. .Inject(_world) // Other injections. // .Inject(SomeData) // Finalizing the pipeline construction. .Build(); // Initialize the Pipeline and run IEcsPreInit.PreInit() // and IEcsInit.Init() on all added systems. _pipeline.Init(); } private void Update() { // Invoking IEcsRun.Run() on all added systems. _pipeline.Run(); } private void OnDestroy() { // Invoking IEcsDestroy.Destroy() on all added systems. _pipeline.Destroy(); _pipeline = null; // Requires deleting worlds that will no longer be used. _world.Destroy(); _world = null; } } ``` ### Generic example ``` c# using DCFApixels.DragonECS; public class EcsRoot { private EcsPipeline _pipeline; private EcsDefaultWorld _world; // Engine initialization. public void Init() { // Creating world for entities and components. _world = new EcsDefaultWorld(); // Creating pipeline for systems. _pipeline = EcsPipeline.New() // Adding systems. // .Add(new SomeSystem1()) // .Add(new SomeSystem2()) // .Add(new SomeSystem3()) // Injecting world into systems. .Inject(_world) // Other injections. // .Inject(SomeData) // Finalizing the pipeline construction. .Build(); // Initialize the Pipeline and run IEcsPreInit.PreInit() // and IEcsInit.Init() on all added systems. _pipeline.Init(); } // Engine update loop. public void Update() { // Invoking IEcsRun.Run() on all added systems. _pipeline.Run(); } // Engine cleanup. public void Destroy() { // Invoking IEcsDestroy.Destroy() on all added systems. _pipeline.Destroy(); _pipeline = null; // Requires deleting worlds that will no longer be used. _world.Destroy(); _world = null; } } ```
# Debug The framework provides tools for debugging and logging, independent of the environment. Many types have DebuggerProxy implementations for more informative display in IDEs. ## Meta Attributes By default, meta-attributes have no use, but are used in integrations with engines to specify display in debugging tools and editors. And can also be used to generate automatic documentation. ``` c# using DCFApixels.DragonECS; // Specifies custom name for the type, defaults to the type name. [MetaName("SomeComponent")] // Used for grouping types. [MetaGroup("Abilities", "Passive", ...)] // or [MetaGroup("Abilities/Passive/...")] // Sets the type color in RGB format, where each channel ranges from 0 to 255; defaults to white. [MetaColor(MetaColor.Red)] // or [MetaColor(255, 0, 0)] // Adds description to the type. [MetaDescription("The quick brown fox jumps over the lazy dog")] // Adds a string unique identifier. [MetaID("8D56F0949201D0C84465B7A6C586DCD6")] // Strings must be unique and cannot contain characters ,<> . // Adds string tags to the type. [MetaTags("Tag1", "Tag2", ...)] // [MetaTags(MetaTags.HIDDEN))] to hide in the editor public struct Component : IEcsComponent { /* ... */ } ``` Getting meta-information: ``` c# TypeMeta typeMeta = someComponent.GetMeta(); // or TypeMeta typeMeta = pool.ComponentType.ToMeta(); var name = typeMeta.Name; // [MetaName] var group = typeMeta.Group; // [MetaGroup] var color = typeMeta.Color; // [MetaColor] var description = typeMeta.Description; // [MetaDescription] var metaID = typeMeta.MetaID; // [MetaID] var tags = typeMeta.Tags; // [MetaTags] ``` > To simplify generate unique MetaID values, use `MetaID.GenerateNewUniqueID()` or the [Browser Generator](https://dcfapixels.github.io/DragonECS-MetaID_Generator_Online/). ## EcsDebug Provides methods for debugging and logging. Implemented as a static class that forwards calls to a Debug service. Debug services act as intermediaries between environment-specific debugging systems and EcsDebug, enabling portability. By default, `DefaultDebugService` outputs logs to the console. To implement a custom service, inherit from `DebugService` and implement its abstract members. ``` c# // Output log. EcsDebug.Print("Message"); // Output log with tag. EcsDebug.Print("Tag", "Message"); // Break execution. EcsDebug.Break(); // Set another Debug Service. EcsDebug.Set(); ``` ## Profiling ``` c# // Creating a marker named SomeMarker. private static readonly EcsProfilerMarker _marker = new EcsProfilerMarker("SomeMarker"); ``` ``` c# _marker.Begin(); // Code whose execution time is being measured. _marker.End(); // or using (_marker.Auto()) { // Code whose execution time is being measured. } ```
# Define Symbols + `DRAGONECS_DISABLE_POOLS_EVENTS` - Disables reactive behavior in pools. + `DRAGONECS_ENABLE_DEBUG_SERVICE` - Enables EcsDebug functionality in release builds. + `DRAGONECS_STABILITY_MODE` - By default, many exception checks are skipped in release builds for performance. This define replaces checks with error-resilient code to increase stability at the cost of some performance. + `DRAGONECS_DISABLE_CATH_EXCEPTIONS` - Turns off the default exception handling behavior. By default, the framework will catch exceptions with the exception information output via EcsDebug and continue working. + `REFLECTION_DISABLED` - Restricts the framework's use of Reflection. + `DISABLE_DEBUG` - For environments where manual DEBUG disabling is not supported (e.g., Unity).
# Framework Extension Tools Additional tools improve framework extensibility. ## Configs Constructors of `EcsWorld` and `EcsPipeline` accept config containers implementing `IConfigContainer` or `IConfigContainerWriter`. These containers pass data and dependencies. The built-in container implementation is `ConfigContainer`. Сustom implementations are supported. Example of using configs for EcsWorld: ``` c# var configs = new ConfigContainer() .Set(new EcsWorldConfig(entitiesCapacity: 2000, poolsCapacity: 2000)) .Set(new SomeDataA(/* ... */)) .Set(new SomeDataB(/* ... */)); EcsDefaultWorld _world = new EcsDefaultWorld(configs); // ... var _someDataA = _world.Configs.Get(); var _someDataB = _world.Configs.Get(); ``` Example of using configs for EcsPipeline: ``` c# _pipeline = EcsPipeline.New() // similarly _pipeline = EcsPipeline.New(new ConfigContainer()) .Configs.Set(new SomeDataA(/* ... */)) .Configs.Set(new SomeDataB(/* ... */)) // ... .BuildAndInit(); // ... var _someDataA = _pipeline.Configs.Get(); var _someDataB = _pipeline.Configs.Get(); ``` ## World Components World components attach additional data to worlds. World components are `struct` types. Access via `Get` is optimized and performs similarly to class field access. Get component: ``` c# ref WorldComponent component = ref _world.Get(); ``` Component implementation: ``` c# public struct WorldComponent { // Data. } ``` Or with lifecycle callbacks: ``` c# public struct WorldComponent : IEcsWorldComponent { // Data. void IEcsWorldComponent.Init(ref WorldComponent component, EcsWorld world) { // Actions during component initialization. Called before the first return from EcsWorld.Get(). } void IEcsWorldComponent.OnDestroy(ref WorldComponent component, EcsWorld world) { // Actions when EcsWorld.Destroy is called. // OnDestroy requires manual reset of the component if necessary. component = default; } } ```
Example of use The `IEcsWorldComponent` interface events can be used to initialize component fields and release resources automatically. ``` c# public struct WorldComponent : IEcsWorldComponent { private SomeClass _object; // Object to be disposed. private SomeReusedClass _reusedObject; // Object to be reused. public SomeClass Object => _object; public SomeReusedClass ReusedObject => _reusedObject; void IEcsWorldComponent.Init(ref WorldComponent component, EcsWorld world) { if (component._reusedObject == null) component._reusedObject = new SomeReusedClass(); component._object = new SomeClass(); // After getting the component via EcsWorld.Get, _reusedObject and _object will be ready. } void IEcsWorldComponent.OnDestroy(ref WorldComponent component, EcsWorld world) { // Dispose and clear the reference so GC can collect it. component._object.Dispose(); component._object = null; // Optionally reset the reused object. // component._reusedObject.Reset(); // If full reset is needed, uncomment the line below. // component = default; } } ```
> Components and configs can be used to create extensions in conjunction with extension methods.
# Projects powered by DragonECS ## With sources:
3D Platformer (Example) Tiny Aliens (Ludum Dare 56) screenshot
Arkanoid screenshot _____________ screenshot
## Released games:
Crystal Siege Order matters
Башенки Смерти screenshot _____________ screenshot

# FAQ ## How to enable/disable systems? Directly — not supported. The need to enable or disable systems usually appears when the overall game state changes; this may also mean switching a group of systems. Conceptually this is a change of processes. Two solutions are recommended: - For global process changes, create a new `EcsPipeline` and run the appropriate pipeline in the engine update loop. - Split `IEcsRun` into multiple processes and run the desired process in the engine update loop. Create a new process interface, implement a runner for it, and obtain the runner via `EcsPipeline.GetRunner()`. ## Recommendations list: [DragonECS-Vault](https://github.com/DCFApixels/DragonECS-Vault)
# Feedback + Discord (RU-EN) [https://discord.gg/kqmJjExuCf](https://discord.gg/kqmJjExuCf) + QQ (中文) [949562781](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=IbDcH43vhfArb30luGMP1TMXB3GCHzxm&authKey=s%2FJfqvv46PswFq68irnGhkLrMR6y9tf%2FUn2mogYizSOGiS%2BmB%2B8Ar9I%2Fnr%2Bs4oS%2B&noverify=0&group_code=949562781)
# License The MIT License: [Open](LICENSE.md)