Event Handling within an ECS

Started by
12 comments, last by mike3 8 years, 3 months ago

First off, this site has been a great resource and it's helped me many times!

I've developed an Entity Component System, where Entities hold a collection of Components, and Systems hold a reference to the Entities that they operate on. Systems additionally can take in certain events to add or remove Entities.

The problem I've gotten hung up on is how to handle events that pertain specifically to Entities rather than Systems. For example, a collision event where if the entity collided with is of type A, the velocity is transferred. But if the Entity is type B, then it is destroyed instead.
I am not sure how to handle this.

I could write a System that does nothing in it's Update() function, but instead just listens for specific Events. The System then performs some logic on the affected Entities.
I do not like this approach, as I feel a System should be responsible for dictating how an Entity would act at every Update(), not just a select few whenever a specific Event rolls through.

Another idea I had was to have a Component store a function pointer. Then, when an Event rolls in, all Entities with the Component that corresponds to the Event calls the function it is storing. This way, Entities can act in a variety of ways even though they are all responding to the same event.

Again, I do not like this method because I am trying to keep all logic out of Components, and make them strictly data-oriented.

The final idea I had was to just have each Entity contain a collection of function pointers, mapped by Event IDs. I am leaning towards this idea, but I am not sure how well I'll like it down the road (I want to completely eliminate the idea of an "Entity" and reduce it to just an ID, with the same ID between systems being the same "Entity".)

I am not sure how to develop this out, and resources I've found on the subject have just barely touched on the subject, preferring to focus more on the ECS side. What have you guys done, if you have implemented such a thing?

Advertisement

Systems don't have to be based around the "virtual void Update()" anti-pattern.

The collision system can have a "resolve collisions" function, which produces several lists of collision events -- one for each system who's components care about collision events. e.g. the collision system might produce struct CollisionOutput{ vector<CollisionEvent<ComponentA>> aEvents; vector<CollisionEvent<ComponentB>> bEvents; };

The ASystem and BSystem can then have functions that consume those lists of events that were produced by the collision system.

A common theme in ECS is batching component processing together, which contradicts with callbacks and immediately-processed events. Another common theme is focusing on the data flows of the program, which contradicts opaque "update" functions.

http://bitsquid.blogspot.com/2011/01/managing-coupling.html -- read all four parts of that series. It covers what Hodgman is talking about with more technical details, as well as some related topics. Read the whole blog, really. That one and the Molecular Musings blog.

I kinda disagree with both Hodgman and the blogs on this topic, but if you're going to go head-first into "by the book" DoD I'd definitely read through them.

Sean Middleditch – Game Systems Engineer – Join my team!

A common theme in ECS is batching component processing together, which contradicts with callbacks and immediately-processed events. Another common theme is focusing on the data flows of the program, which contradicts opaque "update" functions.

If I am understanding you correctly, I am already doing that. For example, when it is time to simulate physics, I go through and all by xVel and then yVel. Then I go through all Entities registered who have a RigidBody component, and check for collisions between them, generating the needed information, etc.
I queue up events, so they are processed at the start of each frame, I don't immediately try to resolve them.

I'm confused at how to handle events for different types. Take Arkanoid/Breakout, for example.
A ball hits a brick, a CollisionEvent is generated and Queued. Then in the next frame, while processing the events, the CollisionEvent comes up. I'm not sure how to handle that.

Should I make some collection of Systems that do nothing but listen for CollisionEvents? Various Entities are subscribed to them, so each Entity handles a CollisionEvent differently? IE, DeleteBrickOnCollisionSystem deletes the brick object upon receiving the CollisionEvent, while BounceBallOnCollisionSystem just reverses the balls y-velocity when it receives a CollisionEvent? And then, what if I wanted to go a step further and have some bricks bounce off other bricks? I'd need to make another system that would only listen for these CollisionEvents, check if it hit a ball or a brick, then do something based on that.

http://bitsquid.blogspot.com/2011/01/managing-coupling.html -- read all four parts of that series. It covers what Hodgman is talking about with more technical details, as well as some related topics. Read the whole blog, really. That one and the Molecular Musings blog.

I kinda disagree with both Hodgman and the blogs on this topic, but if you're going to go head-first into "by the book" DoD I'd definitely read through them.

I've read a bit of bitsquids stuff before, it's very helpful! The articles you are suggesting in particular are great, but they aren't exactly what I am looking for. I'll take a peek at Molecular Musings blog, though. I haven't read anything there!

Why do you have to wait until next frame to resolve collisions? Usually you move objects, resolve collisions, then draw them.
Note that in most physics engines, resolving collisions means that you have to move objects for a second time that frame.

For Breakout, there's two Entity templates/prototypes for blocks. One has Sprite+AABB+Bouncy and one has Sprite+AABB+Breakable.

When checking for collisions, you have a loop that reads Ball components and AABB components. Instead of having one global events list, the AABB component could have a property that's used for event routing. The AABB's in the first type of block entity could route it's touch events to the BouncySystem, while the second type could route to the BreakableSystem.
e.g.
struct BallTouchEvents { vector<pair<Ball*,Entity*>> events; }
struct BouncySystem { BallTouchEvents ballEvents; ... }
struct AABB { int x,y,w,h; BallTouchEvents* onTouch; };
void CheckBalls( vector<Ball> balls, vector<AABB> bounds)
{
 foreach balls, foreach bounds
   If ball and bounds touch then
     bound->onTouch.push_back( make_pair(ball, bound->Owner()));
}

A solution I've done before is t simply have a Collision Component added to entities that are colliding. This component has which other entity is being collided with, and has a flag noting whether it's an initial collision, a sustain collision, or a just separated collision. You can then have your collision system operate on entities with collision components, and call the functions required when collisions happen.

Just another way of doing things, not the right way, or the only way, just another possibility.

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

Why do you have to wait until next frame to resolve collisions? Usually you move objects, resolve collisions, then draw them.
Note that in most physics engines, resolving collisions means that you have to move objects for a second time that frame.

For Breakout, there's two Entity templates/prototypes for blocks. One has Sprite+AABB+Bouncy and one has Sprite+AABB+Breakable.

When checking for collisions, you have a loop that reads Ball components and AABB components. Instead of having one global events list, the AABB component could have a property that's used for event routing. The AABB's in the first type of block entity could route it's touch events to the BouncySystem, while the second type could route to the BreakableSystem.
e.g.


struct BallTouchEvents { vector<pair<Ball*,Entity*>> events; }
struct BouncySystem { BallTouchEvents ballEvents; ... }
struct AABB { int x,y,w,h; BallTouchEvents* onTouch; };
void CheckBalls( vector<Ball> balls, vector<AABB> bounds)
{
 foreach balls, foreach bounds
   If ball and bounds touch then
     bound->onTouch.push_back( make_pair(ball, bound->Owner()));
}

A little more miscommunication on my part, but I think I see what you are saying! I am resolving the collisions as far as moving things back so they are no longer colliding, but I am not applying "what happens" immediately. That is what I want the queued event to do. If there is a collision between ball and brick, move the ball so it is not colliding and draw that frame. Next frame, process the collision event, queue the brick for destruction, and "bounce" the ball off of the destroyed brick. Process everything, queue up more events, then draw.

But, I think I understand your example. Each AABB could belong to a different system. When a collision happens, it creates & pushes a CollisionEvent to that system, who would then operate on the collection of CollisionEvents when the time comes to process events?
But then I'm with another problem I mentioned earlier: having a System that does nothing but handle events, and it's Update() function does nothing.
I suppose instead of pushing the events to a particular system, I could queue up a bunch of callbacks and execute them in-order sometime during the next execution.
You mentioned the virtual void Update() antipattern - are you talking about the Open/Closed principle? Should I be passing a function pointer to each System on it's creation, then having the Update() call that function pointer, passing in it's collection of Entities? I don't see how else you could do specific operations on a collection from system to system without overriding the Update() function or passing it a function pointer.

Keep in mind with physics - so far as a generalized pattern goes - you really can't do delayed handling of collisions like this. Physics resolution is non-trivial and requires the physics engine to do a number of iterations over the contacts. If you want to filter out a contact, you must do it during a very specific phase of the physics resolution. With most middleware, you don't get a lot of choice other than to do this all with callbacks. You could setup the callbacks to push the contacts into a queue and then use the general pre-resolution callback to ask all interested Systems to process that stack of collision events, but you certainly can't delay the handling of them until next frame.

Of course, if you're writing your own special-purpose physics for a simple game like Breakout you have a lot more flexibility. Just keep in mind that the pattern you're using is fairly limiting and not applicable to many other types of games.

You mentioned the virtual void Update() antipattern - are you talking about the Open/Closed principle? Should I be passing a function pointer to each System on it's creation, then having the Update() call that function pointer, passing in it's collection of Entities? I don't see how else you could do specific operations on a collection from system to system without overriding the Update() function or passing it a function pointer.


Virtual Update is usually more frowned upon when used in contexts of individual game objects or components rather than high-level managers or systems. You'll possibly have hundreds of entities and thousands of components but only dozens of systems in a small game, so looping through all components to call Update is painful while looping through all systems to call Update is fine. The other big problem of the general virtual Update pattern is that the naive implementation limits your ability to control the order of when systems are updated which is usually pretty important; this is solvable in a number of ways, though.

That said, you don't need function pointers for event queue processing. One solution is to let Systems have dependencies on each other, e.g. the BreakableBlockSystem is allowed to know about the CollisionSystem and directly call a CollisionSystem::ReadCollisionEventQueue() or the like. It's totally acceptable to have dependencies within code that is naturally coupled; in that case you also want to very explicit about the dependencies and avoid _implicit_ coupling (which is what leads to difficult-to-understand code and bugs).

You could avoid dependencies between systems and have only dependencies on event types, too. For instance, if you have some general event queue manager that tracks all queues of events, your System need only depend on that event queue and the specific type of event it needs. The way to look at this is that BreakableBlockSystem has no reason to care about how collisions were detected (so it doesn't need to know if collision events came from CollisionSystem or from DebugReplaySystem or whatever) but only cares about the collisions themselves (hence it only depends on EventSystem::Queue<CollisionEvent> or the like). This starts moving a little more toward implicit dependencies but is still more than "explicit enough" for most purposes.

Sean Middleditch – Game Systems Engineer – Join my team!

Keep in mind with physics - so far as a generalized pattern goes - you really can't do delayed handling of collisions like this. Physics resolution is non-trivial and requires the physics engine to do a number of iterations over the contacts. If you want to filter out a contact, you must do it during a very specific phase of the physics resolution. With most middleware, you don't get a lot of choice other than to do this all with callbacks. You could setup the callbacks to push the contacts into a queue and then use the general pre-resolution callback to ask all interested Systems to process that stack of collision events, but you certainly can't delay the handling of them until next frame.

Of course, if you're writing your own special-purpose physics for a simple game like Breakout you have a lot more flexibility. Just keep in mind that the pattern you're using is fairly limiting and not applicable to many other types of games.

You mentioned the virtual void Update() antipattern - are you talking about the Open/Closed principle? Should I be passing a function pointer to each System on it's creation, then having the Update() call that function pointer, passing in it's collection of Entities? I don't see how else you could do specific operations on a collection from system to system without overriding the Update() function or passing it a function pointer.


Virtual Update is usually more frowned upon when used in contexts of individual game objects or components rather than high-level managers or systems. You'll possibly have hundreds of entities and thousands of components but only dozens of systems in a small game, so looping through all components to call Update is painful while looping through all systems to call Update is fine. The other big problem of the general virtual Update pattern is that the naive implementation limits your ability to control the order of when systems are updated which is usually pretty important; this is solvable in a number of ways, though.

That said, you don't need function pointers for event queue processing. One solution is to let Systems have dependencies on each other, e.g. the BreakableBlockSystem is allowed to know about the CollisionSystem and directly call a CollisionSystem::ReadCollisionEventQueue() or the like. It's totally acceptable to have dependencies within code that is naturally coupled; in that case you also want to very explicit about the dependencies and avoid _implicit_ coupling (which is what leads to difficult-to-understand code and bugs).

You could avoid dependencies between systems and have only dependencies on event types, too. For instance, if you have some general event queue manager that tracks all queues of events, your System need only depend on that event queue and the specific type of event it needs. The way to look at this is that BreakableBlockSystem has no reason to care about how collisions were detected (so it doesn't need to know if collision events came from CollisionSystem or from DebugReplaySystem or whatever) but only cares about the collisions themselves (hence it only depends on EventSystem::Queue<CollisionEvent> or the like). This starts moving a little more toward implicit dependencies but is still more than "explicit enough" for most purposes.

I see! That makes a lot more sense. Back to the drawing board for me! Thank you all, these replies have been super helpful!!

You mentioned the virtual void Update() antipattern - are you talking about the Open/Closed principle? Should I be passing a function pointer to each System on it's creation, then having the Update() call that function pointer, passing in it's collection of Entities? I don't see how else you could do specific operations on a collection from system to system without overriding the Update() function or passing it a function pointer.


Virtual Update is usually more frowned upon when used in contexts of individual game objects or components rather than high-level managers or systems. You'll possibly have hundreds of entities and thousands of components but only dozens of systems in a small game, so looping through all components to call Update is painful while looping through all systems to call Update is fine. The other big problem of the general virtual Update pattern is that the naive implementation limits your ability to control the order of when systems are updated which is usually pretty important; this is solvable in a number of ways, though.

I don't think it's a performance issue so much as a design issue. My problem with it is that a "system" is a rather generic thing to begin with, and "update()" is a rather generic operation on that rather generic thing.

By having systems that share a base class, I'm basically saying, "CodeThatDoesStuff.doStuff()", and unnecessarily limiting and constricting my systems' otherwise-diverse interfaces to cram them into a generic one-size-fits-all for no reason.

Each of the systems are similar in a general architectural pattern sense, but are completely different in purpose. They are as similar as two functions are arbitrarily similar. Just because two functions happen to have the same interface does not mean they should be put into a vector and iterated over.

It's easy to see that this is wrong:


myFunctions.push_back(                    DeleteFile(string filepath)  );
myFunctions.push_back(             PrintErrorMessage(string text)      );
myFunctions.push_back(  OpenWebpageInExternalBrowser(string url)       );
myFunctions.push_back(              SetWindowCaption(string title)     );
myFunctions.push_back(                  LoadSettings(string filepath)  );

for(every function in myFunctions)
     function("blah");

.

This is the same thing, but in disguise:


mySystems.push_back( PhysicsSystem  );
mySystems.push_back( RenderSystem   );
mySystems.push_back( MovementSystem );
mySystems.push_back( AudioSystem    );
mySystems.push_back( AISystem       );
mySystems.push_back( OtherSystem    );

for(every system in mySystems)
    system.Update();

.

"System" is a description of the architectural pattern used abstractly for reasoning about the code's flow (not the code's purpose). Because all these are named 'system', people automatically go, "I can make them all derived from the same class!" often without a good reason. Occasionally there is a good reason, just as occasionally there is a good reason to put a bunch of unrelated functions in a vector - but by default, the functions shouldn't go in a vector and the systems shouldn't be forced into the same interface. Merely being able to loop over them to call Update(), is not a valid enough reason, and has the detriment that it forces the programmer to artificially cram the diverse, unrelated, systems into a common, uniform, crippled, interface.

It's a crippled interface, because you have to either trim the interface of some systems if you cater to the lowest common denominator, or bloat the interface of the other systems if you cater to the greatest common denominator; i.e. you have to remove functions you should have (as Hodgman points out) crippling it, or add functions you shouldn't have (empty virtual functions) bloating it, to make all systems artificially identical in their interface.

If I'm using the Flyweight pattern for images, and the Flyweight pattern for text, it doesn't automatically mean they should share a common interface and be crammed in a vector.

Systems shouldn't inherit from a base class just because they happen to share the same design pattern. Likewise, different component types shouldn't inherit from an abstract interface just because they happen to be components. Functions shouldn't be crammed into a vector just because they are functions that take the same arguments.

In some code bases, it may make sense, but other people shouldn't do it unless in it makes sense in their framework. In my opinion, as a default one should assume it doesn't make sense unless they have compelling evidence otherwise.

To clarify: I like ECS systems and I'm not against inheritance (and I'm not against polymorphism and the use of inheritance for consistent interfaces when the code needs it). I'm against the use of inheritance in this circumstance, if the only purpose is for putting a dozen or so unrelated classes in a vector for convenience. For codebases with better reasons that that, I have no qualms with.

Or to put it a different way: Just because some classes have an identical interface, doesn't mean they are the same or should be treated the same - and recognizing coincidental interface similarities from similarities genuinely emerging because of similar purposes helps here. This is also where duck-typing, like in C++'s templates, can (sometimes) comes in handy, because you can temporarily treat the interfaces as if they were same when you need to, without actually forcing the interfaces to be the same.

</unnecessarily large dump of conjectured thoughts nobody asked for smile.png>

This topic is closed to new replies.

Advertisement