sschmid / Entitas

Entitas is a super fast Entity Component System (ECS) Framework specifically made for C# and Unity
MIT License
7.12k stars 1.11k forks source link

Provide an explicit execution order/priority for systems? #454

Closed jonathanpmast closed 7 years ago

jonathanpmast commented 7 years ago

As I understand Entitas system execution today, the order of operation is based on what order the systems are added to the Systems object/collection.

Has there been any thought or appetite toward giving the developer overt control over execution order? I.e. when I add a system to the collection, I give an int priority. Every time a system is added to the Systems object they are sorted internally and executed in order.

Just a thought, as currently there requires a lot of thought as to what order your code is written for adding these systems.

danielbe1 commented 7 years ago

I don't think that really solves the problem though, to solve the problem of dependencies a programmer should be able to define those dependencies and have the system sort the execution order (if at all possible) itself. This would better done at generation time, by a generator, or compile time using annotations.

Something like:

[dependsOn(SystemB)]
SystemA {
  ...
}

However this still doesn't really hit the spot, two better alternatives would be: 1) immutable global data (components) thats is only changeable at the end of a frame through callbacks, or dedicated events. 2) defining systems as flows, where one system takes as input the output of previous systems.

The first option is actually easier to accomplish, even using the current Entitas code, incidentally this also solves the problem of executing systems in parallel.

RichPruitt commented 7 years ago

Yeah, I am having the same problem. I put systems in features grouped by functionality, but this makes priority very tricky sometimes. I have lots of systems and it is very hard to keep them in the right order.

Would be nice if we could just ensure that all systems picked up every change. Probably will cause some unwanted side effects, but with a large number of systems ensuring system ordering is correct is very painful.

The priority on a system might be an easy alternative.

ghost commented 7 years ago

Challenge Accepted. ;)

Sounds like fun.

ghost commented 7 years ago

The only problem I currently see with generating system chains is with the system constructors. If you add an attribute to a system with a priority and in a data provider gather all systems with that attribute you would then pass the data along to a generator which would add the types to chain. But what if they have a constructor which isn't empty? How would you fill them?

ghost commented 7 years ago

Systems with parameter-less constructors could easily be generated though.

danielbe1 commented 7 years ago

@T2RKUS could you give an example for how it would be used?

ghost commented 7 years ago

add an attribute like: [System(10)] onto a system etc. It would gather all systems with System attributes and generate a single chain of systems from the highest priority to the lowest. System with priority 0 would be called before a system with priority 10.

paraboxx commented 7 years ago

Don't instantiate them from a generator. Just leave everything as is and in a final step - before the context calls the Initializers - execute a Sort() or whatever is needed to get them in order. I see more of a problem with nested systems in @RichPruitts usecase. Since each Feature is responsible for executing its child systems, the sorting can only be relative to siblings.

ghost commented 7 years ago

Wouldn't that mean rewriting the Systems class?

paraboxx commented 7 years ago

If you want to integrate it properly, yes. On the other hand, you can probably derive your own system and do it in there.

ghost commented 7 years ago

In that case we can create a new systems class called PrioritySystems which derive from Systems with a Sort method (as you said) and then sort the systems by priority value on a priority interface. Something like that perhaps?

ghost commented 7 years ago

Didn't realize the lists were protected.

OndrejNepozitek commented 7 years ago

I've run into this problem recently, too. I am now experimenting with class attributes. I've divided all systems into phases (which is something like the priority you were talking about - but with names instead of numbers). The usage is something like:

[SystemPhase(Phase.SomePhase)]
public class ASystem : ReactiveSystem<GameEntity>

This helps me to prevent order collisions by defining logical groups with exact order. But this is sometimes not enough. So I have another layer of control with something like this:

[ExecutesAfter(typeof(AnotherSystem), typeof(YetAnotherSystem))]
public class ASystem : ReactiveSystem<GameEntity>

It makes a graph of dependencies and than adds the systems to each logical group with corresponding order (if there are no cycles).

I don't know if it solves all my problems but it looks like it could save me some time and make the systems not that fragile.

ghost commented 7 years ago

why build up a dependency graph when you can simply prioritize them by a number and sort them by it.

if you want to a "phase" you could have several systems with '100' priority and begin another phase at '200' for example. A system of priority '1' would run before '2' etc.

OndrejNepozitek commented 7 years ago

Well, I find it easier to just declare that a system must run after some other systems rather than managing priorities with numbers.

What is the benefit of managing priorities? It seems to me that it is not really different from what I was doing earlier - having a list of systems in my GameController and thinking about what system goes first.

I like the dependency diagram because some systems are completely independent so I do not have to specify any order. But then there are some system that must be enabled after like 3 other systems so I just write it as [ExecutesAfter(..)] and it finds the order itself. I just have to specify the dependencies and don't have to worry about the bigger picture.

PS: I have the "phases" mainly because my game has also a multiplayer and I need to maintain some invariants regarding systems order. It could work without them - just with dependecies.

ghost commented 7 years ago

Well if you find it easier, fair enough. I personally find the priority approach easier because I don't have to deal with a graph of dependencies; simpler code. Might not be easier to read but it forces me to keep the execution order in mind yet still easier than rearranging systems around. I can just change a number and it can be every '100' or so if I want to run things in between.

beck-daniel commented 7 years ago

@T2RKUS Defining dependencies explicitly has the advantage of being easier to use, less error prone, more informative, and save the programmer the actual work of managing dependencies.

Specifying indexes gives you no more than just a bit of more convenient way to do what can already be done.

sschmid commented 7 years ago

Hi, sorry joining late to this conversation :)

Not sure if I'm missing sth, but I can't see an advantage having priorities over a simple list of systems like:

systems.Add(systemA); // prio 1
systems.Add(systemB); // prio 2

There's no functionality added, right? I only see 2 downsides:

  1. Systems can be specified with the same prio. that should not be possible as the order of systems is one of the most important things usually
  2. Systems itself shouldn't be aware of any order or the application they are running in. Imagine sharing a MoveSystem that contains information about the execution order and use it in an other game, doesn't make sense, right? Adding an index to the systems hides the execution order from the outside and might feel like magic, which should always be avoided imo. Systems should be managed and ordered by a supervisor, the Systems class, e.g. in the GameController.
sschmid commented 7 years ago

I wouldn't recommend it, but if you want to use priorities anyway, you could add a custom interface for the priority and implement it in systems and use it to sort and populate the systems class.

jonathanpmast commented 7 years ago

It just goes counter to the concept of features as has been stated. I see both sides to this argument, my primary concern is the order of execution for systems is implicitly determined by the order they are added to the supervisor, rather than configuration.

This means that if I have a lot of systems--which any non-trivial simulation will have dozens or more--then in having to basically add all of the systems to a collection in a serial process one after another.

I suppose as has been said, one way to do this is to just add them all to a dictionary, sort them, and add them based on sort order at initialization time.

sschmid commented 7 years ago

my primary concern is the order of execution for systems is implicitly determined by the order they are added to the supervisor

True, but I'd say it's explicitly determined :)

If a solution like the sorted dict makes sense for you, you can definitely do that.

This means that if I have a lot of systems--which any non-trivial simulation will have dozens or more--

Very true! My previous project had ~700 systems (including parent systems). I heavily use parent / child systems which helps a lot to manage these many systems. I imagine using explicit priorities alone would be very difficult to do, especially since developing a game is a dynamic process where you add and remove systems frequently. Adding a system should result in re-applying and re-thinking priorities of existing systems.

jonathanpmast commented 7 years ago

I heavily use parent / child systems which helps a lot to manage these many systems.

🤔 what's a parent system?

sschmid commented 7 years ago

E.g. Match One - ViewSystems

Just a container with subsystems. I use them to group and manage systems

jonathanpmast commented 7 years ago

I see. So really in this case the Feature is the Parent System and it contains a bunch of Child systems.

This is where my concern comes in around execution priority as if I break Systems down logically like this I have to crawl through all the Parents/Features to determine in what order the execute.

Obviously ways to fix this outside of Entitas, I've just seen the priority method leveraged in other ECS so was curious if/why not in Entitas.

Closing now 🚀