0
\$\begingroup\$

I'm putting together a ECS for my game with another two main components: an event bus for communication and a Lua interpreter to load scripts.

Now, the parent element is a framework/game object with references to all this three components. My question is, what is the best communication pattern: drill the interpreter and any other orchestration objects down to every system or centralize all those calls in a subscriptor for the eventbus, and make everything from there.

Example:

// Plan A

   public class CollisionEntitySystem extends EntityProcessingSystem {

   LuaEngine mEngine;

   EventBus mBus;

   MoreThings mThings;

   public CollisionEntitySystem(LuaEngine engine, EventBus bus, MoreThings things){
       mEngine = engine;
       mBus = bus;
       mThings = things;
   }

    @Override
    protected void process(Entity e) {
        //Something wonderful happened here
        mEngine.load("onCollision.lua", parameters);
    }
};

// Plan B

   public class CollisionEntitySystem extends EntityProcessingSystem {

   Bus mEventBus;

   public CollisionEntitySystem(Bus bus){
       mEventBus= bus;
   }

    @Override
    protected void process(Entity e) {
        //Something wonderful happened here
        mEventBus.post(EventScriptCall.create("OnCollision", paremeters))
    }
};

Given that the event bus post is synch the results are the same, so it all boils down to architectural differences: centralized logic and hundreds of event objects or disperse logic with less objects.

\$\endgroup\$
  • 1
    \$\begingroup\$ There's no generally "best option", there's only the best option for you, and you're the only one that can know for sure what the option is. If you're having trouble deciding between two options, make a list of pros and cons for each and pick the one with the highest ratio of pros to cons. I think this question is primarily opinion based. If you have a more specific question, you should ask that instead. \$\endgroup\$ – MichaelHouse Sep 21 '14 at 14:21
1
\$\begingroup\$

There is no right answer to your question and a lot of it comes from personal taste and out of need.

Most engines often need the ability to deliver both asynchronous and synchronous events because certain situations are easier handled in one fashion than others. Secondly, in the case where you are using systems to update your entities in your example, you will need a means to dispatch events between systems specifying single entity for delivery, collection of entities, or all depending on the scenario.

Next, I generally prefer to keep scripting centralized in a script system. If other systems want to trigger some ScriptEvent, I prefer they raise an event and I have the script system capable of listening for that case and dispatching it to the appropriate script handler. This has the benefit that it allows other parts of the system to intercept and handle the same event and keeps the implementation of the script system abstracted so if I change internals or perhaps the language of choice, the engine is unaffected.

In the case of your collision example, when the system detects a collision has occured between two entities, simply send a CollisionEvent with source entity and the colliding entity as parameters. The scripting system sees this event and dispatches it to your script.

In my specific solution, my script system looks for a specific method definition called OnEvent and if found, it automatically hooks the script into the event system. It is worth noting that a single logic component is linked to an entity enabling scripting behavior. The script can require and include any composition of behavior needed.

This allows the script system to delegate default event dispatch into the scripts logic. Additionally, if the script wants to be notified of other events perhaps from other systems, I just call RegisterEvent(eventName) inside the script during it's loading and the script system links that script instance to that event name. When that event fires, the scripting system is responsible for taking the event parameters, packaging them into a lua method call and then calling OnEvent with the event name and the arguments.

In the corner cases perhaps where system A needs to call into the script system and invoke a particular behavior and you'd rather not involve the event system for it, you could always expose a method on the scripting system like RaiseEvent(EventObject) and allow the scripting system to again decompose the event into the scripting language's needs and dispatch the handler.

This is my own preference, but there is nothing that prevents scripting from being a fundamental part of the entire engine where each system can call into it. I've seen some component frameworks where any component can be ammended with a script to alter it's base behavior.

Either approach relies on a specific amount of abstraction to keep details from leaking into other parts of the engine so that refactoring and extending can be done easily without signfiicant time and costs.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.