5

Using the Entity-Component-System pattern I want to connect some systems with events. So some systems shouldn't run in a loop, they should just run on demand.

Given the example of a Health system a Death system should only run when a component gets below 1 health.

I thought about having two types of systems. The first type is a periodic system. This runs once per frame, for example a Render or Movement System. The other type is an event based system. As mentioned before a connection between Health and Death.

First I created a basic interface used by both system types.

internal interface ISystem
{
    List<Guid> EntityCache { get; } // Only relevant entities get stored in there

    ComponentRequirements ComponentRequirements { get; } // the required components for this system

    void InitComponentRequirements();

    void InitComponentPools(EntityManager entityManager);

    void UpdateCacheEntities(); // update all entities from the cache

    void UpdateCacheEntity(Guid cacheEntityId); // update a single entity from the cache
}

Further I created the interfaces

internal interface IReactiveSystem : ISystem
{
// event based
}

and

internal interface IPeriodicSystem : ISystem
{
// runs in a loop
}

but I'm not sure if they will be necessary. There is no problem using

foreach (ISystem system in entityManager.Systems)
{
    system.UpdateCacheEntities();
}

but I don't want to run a system if not needed.

There are two types of Events, a ChangeEvent and a ExecuteEvent. The first gets triggered when a value from a component has changed. The second one gets triggered when something should be done with a specific entity.

If you Need or want to you can have a look at the EntityManager

https://pastebin.com/NnfBc0N9

the ComponentRequirements

https://pastebin.com/xt3YGVSv

and the usage of the ECS

https://pastebin.com/Yuze72xf

An example System would be something like this

internal class HealthSystem : IReactiveSystem
{
    public HealthSystem(EntityManager entityManager)
    {
        InitComponentRequirements();
        InitComponentPools(entityManager);
    }

    private Dictionary<Guid, HealthComponent> healthComponentPool;

    public List<Guid> EntityCache { get; } = new List<Guid>();

    public ComponentRequirements ComponentRequirements { get; } = new ComponentRequirements();

    public void InitComponentRequirements()
    {
        ComponentRequirements.AddRequiredType<HealthComponent>();
    }

    public void InitComponentPools(EntityManager entityManager)
    {
        healthComponentPool = entityManager.GetComponentPoolByType<HealthComponent>();
    }

    public void UpdateCacheEntities()
    {
        for (int i = 0; i < EntityCache.Count; i++)
        {
            UpdateCacheEntity(EntityCache[i]);
        }
    }

    public void UpdateCacheEntity(Guid cacheEntityId)
    {
        Health healthComponent = healthComponentPool[cacheEntityId];
        healthComponent.Value += 10; // just some tests
        // update UI 
    }
}

How can I create ChangeEvents and ExecuteEvents for the different systems?


EDIT

Is there a way to add event delegates to the components to run a specific system for this entity on change if a change event is listening or on demand if an execute event is listening?

By mentioning ChangeEvent and ExecuteEvent I just mean event delegates.

Currently I could do something like this

internal class HealthSystem : IReactiveSystem
{
    //… other stuff

    IReactiveSystem deathSystem = entityManager.GetSystem<Death>(); // Get a system by its type

    public void UpdateCacheEntity(Guid cacheEntityId)
    {
        // Change Health component
        // Update UI

        if(currentHealth < 1) // call the death system if the entity will be dead
        {
            deathSystem.UpdateCacheEntity(cacheEntityId);
        }
    }
}

But I was hoping to achieve a better architecture by using event delegates to make systems communicate and share data between each other.

  • Are you using IoC container in your project? – LukaszBalazy Jan 27 '19 at 15:32
  • Sorry, what is IoC? But no, I don't :) –  Jan 27 '19 at 15:50
  • 1
    IoC is Inversion of Control it's programming paradigm which is responsible for removing dependencies from your program. Widely described here: https://stackoverflow.com/questions/3058/what-is-inversion-of-control :) – LukaszBalazy Jan 27 '19 at 16:03
  • 3
    This question should have been closed as too broad. Can you narrow this down to a specific issue? "How do I design my application" is not an answerable question. – theMayer Jan 31 '19 at 13:08
  • @theMayer I updated my question. I hope this is better now.. –  Feb 01 '19 at 20:06
  • 1
    @totalBeginner no it doesn't, because I have no idea (a) what you want to do, (b) what you have tried, (c) what isn't working and (d) the precise error message(s) or issue that you're having. Simply saying you want to use event delegates is insufficient. Use them for what, in what context, to achieve what? – theMayer Feb 01 '19 at 20:20

2 Answers2

2

I am not an expert on this design pattern but I read something on it and my advice is: try not to forget the real purpose of this pattern. This time I found the article on Wikipedia really interesting. It is basically saying (at least it is what I understood) that this pattern has been "designed" to avoid creating too many dependencies, losing the decoupling. Here an example I took from the article:

Suppose there is a drawing function. This would be a "System" that iterates through all entities that have both a physical and a visible component, and draws them. The visible component could typically have some information about how an entity should look (e.g. human, monster, sparks flying around, flying arrow), and use the physical component to know where to draw it. Another system could be collision detection. It would iterate through all entities that have a physical component, as it would not care how the entity is drawn. This system would then, for instance, detect arrows that collide with monsters, and generate an event when that happens. It should not need to understand what an arrow is, and what it means when another object is hit by an arrow. Yet another component could be health data, and a system that manages health. Health components would be attached to the human and monster entities, but not to arrow entities. The health management system would subscribe to the event generated from collisions and update health accordingly. This system could also now and then iterate through all entities with the health component, and regenerate health.

I think that you overcomplicated your architecture, losing the advantages that this pattern can give you.

First of all: why do you need the EntityManager? I quote again:

The ECS architecture handles dependencies in a very safe and simple way. Since components are simple data buckets, they have no dependencies.

Instead your components are constructed with the EntityManager dependency injected:

entityManager.AddSystem(new Movement(entityManager));

The outcome is a relatively complex internal structure to store entities and the associated components.

After fixing this, the question is: how can you "communicate" with the ISystems? Again, answer is in the article: Observer Pattern. Essentially each component has a set of attached systems, which are notified every time a certain action occurs.

Marco Luzzara
  • 5,540
  • 3
  • 16
  • 42
1

by what im getting at this, you want to have a repetitive, once every tick type event alongside a once in a year type event (exaggerated but clear), you can do this with a delegate call back function IE:

public delegate void Event(object Sender, EventType Type, object EventData);
public event Event OnDeath;
public event Event OnMove;
public void TakeDamage(int a)
{
    Health-=a;
    if(Health<1)
        OnDeath?.Invoke(this,EventType.PlayerDeath,null);
}
public void ThreadedMovementFunction()
{
    while(true)
    {
        int x,y;
        (x,y) = GetMovementDirection(); 
        if(x!=0||y!=0)
            OnMove?.Invoke(this,EventType.PlayerMove,(x,y));
    }
}

you can implement this into an interface, and then store the object class and only access the needed stuff like events and so on. but tbh i don't quite understand what you're looking for, so if you could elaborate on the exact issue or thing you need to solve, that would be greatly appreciated!