An Entity Component System library based on RelECS, with major inspiration from ecslite and flecs.
Note: don't forget to build in RELEASE if you don't need additional checks and helpful exceptions.
A world stores all the data related to the ECS, including entites, their components, etc.
Note: multiple worlds are not supported as for now.
//creating a world
ECSWorld world = new ECSWorld();
//destroying the world
world.Destroy();Each entity is a unique ID, which is associated with a set of components.
Note: IDs of deleted entities will be reused, so if you save entities for later use, it's recommended to ensure they are alive before doing so.
//creating an entity
Entity entity = world.AddEntity();
//checking if the entity is alive
bool alive = entity.IsAlive();
//removing an entity
entity.Remove();Components can be added to entites. They are represented as structs.
Note: if all components are deleted from an entity, the entity itself will be deleted.
struct Velocity { public float x, y; }
//if a component doesn't contain any data, it's considered to be a tag, which makes it faster to add/remove
struct IsDead { }
...
var entity = world.AddEntity();
//adding a component to an entity
entity.Add(new Velocity { x = 5, y = 5 });
//getting a component by reference
var velocity = entity.Get<Velocity>();
velocity.Value.x++;
//does this entity have Velocity?
bool hasVelocity = entity.Has<Velocity>();
//deleting a component from an entity
entity.Remove<Velocity>();
//now this entity is dead :(
entity.Add<Dead>();AutoReset(ref T, ResetState) method (from IAutoReset interface) is called when a component is added or removed.
struct AnimationComponent : IAutoReset<AnimationComponent>
{
    public List<Animation> animations;
    public void AutoReset(ref AnimationComponent c, AutoResetState state)
    {
       //make sure list isn't allocated multiple times
       animations ??= new();
       animations.Clear();
    }
}Relationships are special components which are composed of two things (could be (Entity, Entity), (Entity, Component) or (Component, Component).
struct Likes { }
var ann = world.AddEntity();
var bob = world.AddEntity();
//Ann likes bob
ann.Add<Likes>(bob);
//does bob like Ann? (Apparently, no)
bool likesAnn = bob.Has<Likes>(ann);
//Ann no longer likes bob
ann.Remove<Likes>(bob);Relationships may or may not contain a component with data (it could be any part of a relationships, but only one).
struct Position { public float x, y; }
struct Begin { }
struct End { }
...
var animation = world.AddEntity();
//as (Begin, Position) and (End, Position) have different IDs, one can store multiple instances of the same data type
animation.Add<Begin, Position>();
animation.Add<End, Position>(new Position { x = 20, y = 40 });
//Position is the second part of the relationship, so Get1 is required 
var beginPosition = animation.Get2<Begin, Position>();(ChildOf, Entity) is a built-in relationship that helps create hierarchies of entities.
var solarSystem = world.AddEntity();
//earth is a child of the solar system
var earth = world.AddEntity().ChildOf(solarSystem);
//moon is a child of the earth
var moon = world.AddEntity().ChildOf(earth);
//gets all the children of an entity
foreach (var child in solarSystem.GetChildren())
{
    ...
}Filters, well, filter entities by their components (or absence of them) and help accessing their values. To be able to read/write to a component, specify it as genetic argument of ECSWorld.Filter<..>() methods.
//creating a filter of entities with position
var positionFilter = world.Filter<Position>().Build();
//entries contain an entity and ref fields of specified components, if there are any
foreach (var entry in filter)
{
    var entity = entry.entity;
    ref var position = ref entry.item;
}
//creating a filter of entities with velocity and position, which don't like apples and hate something, which also have dogs or cats (or both)
//make as complex filters as you'd like! :)
var complexFilter = world.Filter<Velocity>().All<Position>().None<Likes, Apples>().All<Hates, Wildcard>().Any<Has, Dogs>().Any<Has, Cats>().Build();Filtering by relationships is cool and all, but sometimes it isn't flexible enough. Wildcards can replace any part of a relationships to include all relationships which match the specified pattern.
//finds all children who have some relation to apples
var allChildrenFilter = world.Filter().All<ChildOf, Wildcard>().All<Wildcard, Apples>().Build();
//finds all entities which relate to player in some way
var relationWithPlayerFilter = world.Filter().All<Wildcard>(world.GetEntity("player")).Build();//use TermN() to specify which relationships correspond to each value
var filter = world.Filter<Position, Owes>()
    .Term1().First<Begin>()
    .Term2().Second<Apples>()
    .Build();
foreach (var entry in filter)
{
    ref var beginPosition = ref entry.item1.GetValue();
    ref var owesApples = ref entry.item2.GetValue();
}components can be marked optional, which will include entites with and without these components.
var filter = world.Flter<Position, Speed>
    //mark Speed as optional
    .Term2().Optional()
    .Build();
foreach (var entry in filter)
{
    Speed speed = default;
    //check if current entity has Speed
    if (!entry.IsNull2())
        speed = entry.item2;
    entry.item1 += speed;
}Systems run all the logic; they are represented as classes which can implement a bunch of interfaces (IUpdateSystem, IPostUpdateSystem, IInitSystem, IPreInitSystem, IDestroySystem).
class MoveSystem : IInitSystem, IUpdateSystem
{
    private Filter<Position, Speed> _movableFilter;
    public void Init(ECSSystems systems)
    {
        //it's recommended to cache filters instead of creating them every frame
        _movableFilter = systems.World.Filter<Position, Speed>
    }
    public void Update()
    {
        foreach (var entry in _movableFilter)
        {
            //adding speed to position
            entry.item1.value += entry.item2.value;
        }
    }
}
...
//systems are stored in a ECSSystems instance
//multiple ECSSystems can be created for a single world
var systems = new ECSSystems(world);
systems
    .Add<MoveSystem>()
     //adding other systems...
    .Init(); //preInit and init are called here
...
//systems are executed in the order they were added
systems.Update(); //PreUpdate and Update are called hereSystems can be grouped; groups are assigned with a name, which can later be used to toggle groups on or off.
var systems = new ECSSystems(world);
systems
    .Add("gameLoop",
        new UpdateUISystem(),
        new UpdateGameSystem(),
        defaultState: false) //this group will be toggled of by default
                             //adding other systems...
     .Init();
...
//now gameLoop group is active
systems.SetGroupState("gameLoop", true);These systems provide a callback when addition and removal of certain components happen.
class OnComponentSystemTest : OnComponentActionSystem
{
    public OnComponentSystemTest()
    {
        //one can also use None() and Any() here
        All<Position>();
    }
    public override void OnComponentAdd(Entity entity)
    {
        Console.WriteLine($"Position component was added to {entity}!");
    }
    public override void OnComponentRemove(Entity entity)
    {
        Console.WriteLine($"Position component was removed from {entity}!");
    }
}An entity can be associated with a unique name.
//now it's a player
var player = world. AddEntity("player");
//oh wait, it's actually player1
player.Name("player1");
//and this is a camera
var camera = world.AddEntity("camera");
//every parent has it's own "namespace", so that entities can share names as long as they have different parents
//(as for the global names, all the entities with them are associated with a single fake parent)
var camera = world.AddEntity().ChildOf(player).Name("camera");Sometimes it's useful to have some kind of a base entity, which other entities can inherit components from — that's what prefabs are. They can also be used to change the value of all inherited components of all prefab instances.
var enemyPrefab = _world.AddPrefab()
    .Add<Position>()
    .Add<Renderable>()
//other components/relationships
    .Add<Collider>();
//enemy inherited all the specified components   
var enemy = _world.AddEntity().InstanceOf(enemyPrefab);
//now all enemies have a position of (10,10) (for some reason)
_world.SetPrefabValue(enemyPrefab, new Position { x = 10, y = 10 });
var yellow = 5;
//If you want to replace some fields and keep others, also pass a delegate. second argument is used to store a temporary value
_world.SetPrefabValue(enemyPrefab,
                      new Renderable { color = yellow },
                      static (ref Renderable c, Renderable temp) =>
                      {
                          c.color = temp.color;
                      });This feature lets one inject classes and filters in systems without having to initialize them manually.
class LevelService
{
   ...
}
class LevelSystem : IInitSystem
{
    private readonly CustomInject<LevelService> _levelService;
    //use Any, None or All objects to add additional arguments to a filter
    private readonly FilterInject<Sprite, Transform> _sprites = new(new None<InvisibleSprite>());
    public void Init(ECSSystems systems)
    {
       //LevelService is ready to use
       var levelService = _levelService.Instance;
       foreach (var entry in _sprites)
       {
           ...
       }
       ...
    }
}
...
systems
    .Add<LevelSystem>()
    //injecting a service
    //Inject() must be called before Init()
    .Inject(new LevelService())
    .Init();Sometimes it's useful to treat components like events, which multiple systems can subscribe to.
struct OnCollisionEvent { Entity collided, collidedWith; }
...
//regular events can be added multiple times
world.AddEvent(new OnCollisionEvent {...});
world.AddEvent(new OnCollisionEvent {...});
...
foreach (var entry in world.GetEvents<OnCollisionEvent>())
{
    var collisionEvent = entity.item;
    if (!collisionEvent.collided.IsAlive() || !collisionEvent.collidedWith.IsAlive())
        //removes specific event
        world.RemoveEntity(entry.entity);
}
...
//removes all events
world.RemoveEvents<OnCollisionEvent>();Singleton events, as the name suggests, are always single instanced.
struct PlayerStateEvent : ISingletonEvent
{
    public PlayerState playerState;
}
...
//adding a singleton
_world.AddSingletonEvent(new PlayerStateEvent { });
//and getting it
ref var playerStateEvent = ref _world.GetSingletonEvent<PlayerStateEvent>();
//removing it
_world.RemoveSingletonEvent<PlayerStateEvent>();DeleteHere is a helper system that deletes a specified component for all entites.
systems
    ...
    // all components (or events) of this type will be deleted
    .DeleteHere<PLayerDiedEvent>()
    .Init();A single instance of an object can be stored in a world to be accessed later.
class SharedData
{
    public readonly Window gameWindow;
    public float deltaTime;
}
...
var systems = new ECSSystems(world, new SharedData { ... });
var sharedData = systems.GetShared<SharedData>();Any entity can be deactivated, in which case it will be excluded from all filters. All other properties stay the same though.
Note: Deactivation/Reactivation also recursively affects children of an entity, children's children an so on.
var myEntity = world.AddEntity().Deactivate();
bool IsActive = myEntity.IsActive();
entity.Activate();