Git Product home page Git Product logo

ecsrx's Introduction

EcsRx

EcsRx is a reactive take on the common ECS pattern with a well separated design using rx and adhering to IoC and other sensible design patterns.

Build Status Code Quality Status License Nuget Version Join Discord Chat Documentation

Features

  • Simple ECS interfaces to follow
  • Fully reactive architecture
  • Favours composition over inheritance
  • Adheres to inversion of control
  • Lightweight codebase
  • Built in support for events (raise your own and react to them)
  • Built in support for pooling (easy to add your own implementation or wrap 3rd party pooling tools)
  • Built in support for plugins (wrap up your own components/systems/events and share them with others)

The core framework is meant to be used primarily by .net applications/games, there is a unity specific version here which builds on top of this core version, as well as a monogame game version here.

It is also worth mentioning that this framework builds on top of SystemsRx, which can be used without EcsRx for when you want Systems and Events but dont need the Entity Component aspects.

Installation

The library was built to support .net standard 2.0, so you can just reference the assembly, and include a compatible rx implementation.

Quick Start

It is advised to look at the setup docs, this covers the 2 avenues to setup the application using it without the helper libraries, or with the helper libraries which offer you dependency injection and other benefits.

If you are using unity it is recommended you just ignore everything here and use the instructions on the ecsrx.unity repository as that has not been fully mapped over to use this core version yet so is its own eco system until that jump is made.

Simple components

public class HealthComponent : IComponent
{
    public int CurrentHealth { get; set; }
    public int MaxHealth { get; set; }
}

You implement the IComponent interface which marks the class as a component, and you can optionally implement IDisposable if you want to dispose stuff like so:

public class HealthComponent : IComponent, IDisposable
{
    public ReactiveProperty<int> CurrentHealth { get; set; }
    public int MaxHealth { get; set; }
    
    public HealthComponent() 
    { CurrentHealth = new ReactiveProperty<int>(); }
    
    public void Dispose() 
    { CurrentHealth.Dispose; }
}

Any component which is marked with IDisposable will be auto disposed of by entities.

Simple systems

public class CheckForDeathSystem : IReactToEntitySystem
{
    public IGroup TargetGroup => new Group(typeof(HealthComponent)); // Get any entities with health component

    public IObservable<IEntity> ReactToEntity(IEntity entity) // Explain when you want to execute
    {
        var healthComponent = entity.GetComponent<HealthComponent>();
        return healthComponent.CurrentHealth.Where(x => x <= 0).Select(x => entity);
    }
    
    public void Process(IEntity entity) // Logic run whenever the above reaction occurs
    {
        entity.RemoveComponent<HealthComponent>();
        entity.AddComponent<IsDeadComponent>();
    }
}

Systems are conventional, so there are many built in types like IReactToEntitySystem, IReactToGroupSystem, IManualSystem and many others, but you can read about them in the docs/systems, you can add your own conventional systems by extending ISystem and systems are handled for you by the ISystemExecutor.

Check the examples for more use cases, and the unity flavour of ecsrx which has more examples and demo projects, and drop into the discord channel to ask any questions.

Running Examples

If you want to run the examples then just clone it and open examples project in the src folder, then run the examples, I will try to add to as the library matures.

There are also a suite of tests which are being expanded as the project grows, it was written with testability in mind.

Architecture / Infrastructure

EcsRx

This is layered on top of SystemsRx and adds the ECS paradigm to the framework as well as adding a couple of systems specifically for entity handling. This also contains an EcsRx.Infrastructure layer which builds off the SystemsRx.Infrastructure layer to provide additional ECS related infrastructure.

EcsRx.Infrastructure

This builds on top of the SystemsRx.Infrastructure library to provide all the cool DI abstraction and plugin stuffs.

EcsRx.Plugins.*

These are plugins that can be used with EcsRx to add new functionality to the library, from entity persistance/rehydration to schedulable systems, these can optionally be added to your projects.

History

The library was originally developed for unity (way before they had their own ECS framework) and since then has moved to be a regular .net library that can run anywhere (Even in Blazor WASM).

Off the back of this there have been a few other libraries that were created for EcsRx but now live as their own standalone libraries:

  • LazyData - Was created as a serialization layer for EcsRx
  • Persistity - Was created as an ETL style pipeline for EcsRx
  • SystemsRx - Was the supporting system/data layer for EcsRx

Docs

There is a book available which covers the main parts which can be found here:

Documentation

This is basically just the docs folder in a fancy viewer

Community Plugins/Extensions

This can all be found within the docs here

Thanks

Thanks to Jetbrains for providing free liceneses via their Open Source Support program

Jetbrains

ecsrx's People

Contributors

aridtag avatar erinloy avatar fijo avatar floatw0lf avatar grofit avatar jaypavlina avatar liaoyuzh avatar semihbudak avatar thejayman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ecsrx's Issues

Batch Plugin - Struct access corruption in batch struct systems

For some reason when dealing with struct based batched systems the batches seem to corrupt over time, its currently unknown why but @erinloy has made a reproduction here:

https://github.com/erinloy/EcsRxStructTest

It takes a while to happen and has been observed in .net core 2 and 3, it seems to happen anywhere from 30 seconds to 3 minutes in. It is easier to see if you make it trigger on every update (60 times a second), and then after a certain period the structs start to read garbage data rather than the correct data, as shown here:

Ref 7 7348
Ref 8 7348
Str 12 1084
Str 13 140714692425167  <-- These numbers should be around 7000-8000 
Ref 9 7348
Str 14 1015
Str 15 1084
Str 16 140714692432655
Str 17 140714692432655
Str 18 1084

After a certain point it just blows up with a Fatal error. Internal CLR error. (0x80131506) which seems to indicate that memory is being corrupted at some point.

Behind the scenes structs are stored as arrays of components (i.e 1 array for each component type) and the batches effectively pins the memory for the arrays and then creates lookups for each component needed in the batch.

public Batch<T1, T2>[] Build(IReadOnlyList<IEntity> entities)
{
	var componentArray1 = ComponentDatabase.GetComponents<T1>(_componentTypeId1);
	var componentArray2 = ComponentDatabase.GetComponents<T2>(_componentTypeId2);
	
	var batches = new Batch<T1, T2>[entities.Count];

	fixed (T1* component1Handle = componentArray1)
	fixed (T2* component2Handle = componentArray2)
	{
		for (var i = 0; i < entities.Count; i++)
		{
			if (entities.Count != batches.Length)
			{ return new Batch<T1, T2>[0]; }
			
			var entity = entities[i];
			var component1Allocation = entity.ComponentAllocations[_componentTypeId1];
			var component2Allocation = entity.ComponentAllocations[_componentTypeId2];
			batches[i] = new Batch<T1, T2>(entity.Id, &component1Handle[component1Allocation], &component2Handle[component2Allocation]);
		}
	}

	return batches;
}

Once this has been done until the related entities/components change in any way it will keep re-using this batch so I am not sure if it is at some point moving the memory around after the lookup has been created so its now reading incorrect memory or if its something else.

I dont have much time to look into it at the moment but if anyone else has experience with unmanaged scenarios would be great to get some advice.

Removing entities during certain system handler setup processes causes missing key exceptions

It was reported that due to the way certain system handlers enumerate and setup subscriptions for observables you could have a system added which starts up and processes the entities associated but they get removed when processing which triggers the OnEntityRemoved event which then wants to clean up the associated entity subscriptions in the handler, the problem here being the ProcessEntity methods are generally not finished by this point so subscriptions are not added to the dictionaries.

To side step this we pre-enumerate the observable groups (to bypass underlying enumerator change exceptions) and then pre-emptively create CompositeDisposable sub containers to be added to the sub lists before the processing happens, this now solves the issues for IReactToEntitySystem, IReactToDataSystem and ISetupSystem.

Collection isolation not enforced in observable groups

When an observable group is created it can optionally be given a collection name to constrain on (incase you have multiple collections of entities).

The collection isolation is enforced at entity addition/removal events, but it is not enforced on component changes as it doesnt know the collection an entity is associated with, this was not too much of an issue before, but now we are bringing in excluded components in groups you need to be a bit more flexible with how you handle entity component interactions.

An idea is when a collection constraint is in place pass in the collection reference to the observable group, and subscribe directly on that to changes rather than globally.

EntityDatabaseManagerExtensions.GetEntity() not behaving as expected

Today I noticed that the EntityDatabaseManagerExtensions.GetEntity(entityDatabase, id) does not work as it is suposed to when using multiple EntityCollections and the entity with the specified id isn't found in the first collection.
An exception is thrown here but the code body in that extension method suggests the writer was assuming it'd collection.GetEntity(id) would return null if there's no entity with the id present in that entity collection.

CustomComponentLookups not working with structs?

Hi,

I'm new to your framework and followed your guide from this page ( https://ecsrx.gitbook.io/project/performance/component-type-lookups ).

I created a Module for the Customlookups and added it to my application like this:

 protected override void LoadModules()
  {
        base.LoadModules();
        this.Container.LoadModule(new CustomComponentLookupsModule());
   }

When adding a component, that is a struct and was registered in the dictionary, to an entity via Blueprint I get an exception:

System.InvalidCastException
HResult=0x80004002
Message=Unable to cast object of type 'EcsRx.Components.ComponentPool1[GF.FriendsInSpace.Core.Components.EntityIdComponent]' to type 'EcsRx.Components.IComponentPool1[EcsRx.Components.IComponent]'.
Source=EcsRx
StackTrace:
at EcsRx.Components.Database.ComponentDatabase.GetPoolFor[T](Int32 componentTypeId)
at EcsRx.Components.Database.ComponentDatabase.Set[T](Int32 componentTypeId, Int32 allocationIndex, T component)
at EcsRx.Entities.Entity.AddComponents(IReadOnlyList`1 components)
at EcsRx.Extensions.IEntityExtensions.AddComponents(IEntity entity, IComponent[] components)

I can work around this when replacing the struct with a class. But it would be great if you could make it work again or if I made a mistake maybe you can offer a solution :-)

Priority is no longer adhered to

Since the change to SystemExecutor to move handling logic to run off observable groups the priority aspect of things has been regressed.

There are 2 scenarios here which would have historically worked but right now will not:

  1. Systems setup ahead of entities would process the entity based on system type then priority order.
  2. Systems added at runtime would process future entities in priority order.

The first scenario should be easy enough to fix, but the latter scenario seems quite tricky to fix and as this is a niche scenario for now I am happy enough to live with it as it wont affect most consumers and can be looked at again once the entity changes have been run, as they may potentially change this entire process a bit.

Broken link

Link for " bare bones required setup" is broken on the main readme page under Quick Start

Add docs - Things to think about for performance

Could do with adding docs that cover how to get more performance out of the lib covering things like:

  • Using component type ids vs types
  • Segregating collections with system affinities
  • Presetting the default sizes/expansions of certain objects
  • Using ReactToGroup over other systems if you are just batch processing
  • Using structs for pure value type data
  • Prefetching components if needed

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.