Jump to content
  • Advertisement

AShinyAcorn

Member
  • Content Count

    5
  • Joined

  • Last visited

Community Reputation

126 Neutral

About AShinyAcorn

  • Rank
    Newbie
  1. AShinyAcorn

    Event Handling within an ECS

    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 >   Ah, okay. I understand now why the virtual void Update() anti-pattern should be avoided. I didn't think much of it at first, but once you brought these up, it makes perfect sense.  Thank you for the extended explanation !
  2. AShinyAcorn

    Event Handling within an ECS

    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!!
  3. AShinyAcorn

    Event Handling within an ECS

    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. 
  4. AShinyAcorn

    Event Handling within an ECS

    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.        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!
  5. 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
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!