views:

599

answers:

4

I'm working on a little Java game in which all sorts of events can happen. There are at least a couple of dozen basic events that various event handlers may be interested in. There are also several places in the code where these events might be triggered. Rather than forcing the event listeners to know which class they need to register with, I'd like to create some sort of a centralized message-dispatching system, which some classes would submit events into and interested classes could hook into to listen for certain kinds of events.

But I have some questions. Firstly, this seems like an obvious and common problem. Are there favorite implementations of simple, in-VM messaging systems? Seems like there would be.

Secondly, and more importantly, I'm trying to work out a reasonably elegant way for the dispatching class to know as little as possible about types of messages. I'd love to be able to create new kinds of events without modifying the message dispatcher at all. However, I have an opposite concern. I'd really like for the method signatures of the handling methods to be clear. In other words, I would prefer the following:

public class CollisionConsoleHandler implements CollisionListener {
  @Override
  public void spaceshipCollidedWithMeteor( Spaceship spaceship, Meteor meteor ) {
      //...
  }
}

over something more generic and harder to read:

public class CollisionConsoleHandler implements GameMessageListener {
   @Override
   public void handleMessage( GameMessage message ) {
     if( message instanceof SpaceshipCollisionMessage ) {
        Spaceship spaceship = ((SpaeshipCollisionMessage)message).getSpaceship();
        Meteor meteor = ((SpaeshipCollisionMessage)message).getMeteor();
        //...
     }
   }
}

But I don't see any good ways to keep type-specific knowledge out of the dispatcher while at the same time keeping the method signatures clean and readable.

Ideas?

A: 

If you want to avoid instanceof, then your only bet is to use inheritance to route a method call to the right method. You cannot use method overloading, since that is decided at compile time by the declared type of the variable you are passing to a method. You have to use inheritance.

If you cannot use inheritance, then your only other choice (that I'm aware of) involves a lot of instanceof.

Regarding a messaging system, you can use ActiveMQ as an in-the-JVM message transport. You do not have to use it via socket or other means. I can't imagine that ActiveMQ would not be efficient enough for your means.

Eddie
ActiveMQ looks really cool, but it might just be a tiny little bit oversized for what the OP is trying to do.
n3rd
A: 

Java beans should have had this interface: it makes life simpler.

interface PropertyChangeProvider {
  void addPropertyChangeListener(PropertyChangeListener l);
  void addPropertyChangeListener(String property, PropertyChangeListener l);
  void removePropertyChangeListener(PropertyChangeListener l);
  void removePropertyChangeListener(String property, PropertyChangeListener l);
}

Implement it all over the place.

Make a blackboard class (probably a singleton. this is a sketch only)

public class Blackboard implements PropertyChangeListener,PropertyChangeProvider {

static Blackboard getInstance(){
    // implement this
}

void initialise(){
   // start the thread here
}

void republish(){
     // this can save you heartache too.
}


}

Give Blackboard a thread, listen for events and republish using its own thread.

Classes can just publish their events to the blackboard.

Subscribe to the blackboard for events.

If you want you can persist events, allow republishing etc.

For something inside an app it is very good. (works just as well as a data interchange interface as well!)

Tim Williscroft
A: 

This isn't a direct answer to your question, but a good model to work with would be Amazon's SQS system. It's a messaging system designed for their Elastic Compute Cloud, and while it's neither implemented in java nor open source, the structure is freely available, and even better there's a lot of literature and research already out there on the topic. You may very well find a prebuilt one out there, but if you don't, I would suggest implementing something based off of SQS. And hey, if you make something sturdy enough, it would be a very useful open source tool!

dimo414
+3  A: 

If there is a specific listener interface for each event. Each event is able to issue listeners calls itself. Then, the role of the dispatcher is to identify target listeners and to trigger the event notification on them.

For example, a generic event definition can be:

public interface GameEvent<L> {

   public void notify( final L listener);
}

If your CollisionListener is:

public interface CollisionListener {

    public void spaceshipCollidedWithMeteor( Spaceship spaceship, Meteor meteor );

}

Then, the corresponding event can be:

public final class Collision implements GameEvent<CollisionListener> {

   private final Spaceship ship;
   private final Meteor meteor;

   public Collision( final Spaceship aShip, final Meteor aMeteor ) {
      this.ship = aShip;
      this.meteor = aMeteor;
   }

   public void notify( final CollisionListener listener) {
      listener.spaceshipCollidedWithMeteor( ship, meteor );
   }

}

You can imagine a dispatcher which is able to propagate this event on the target listeners like in the following scenario (Events is the dispatcher class):

// A unique dispatcher
final static Events events = new Events();

// Somewhere, an observer is interested by collision events 
CollisionListener observer = ...
events.listen( Collision.class, observer );

// there is some moving parts        
Spaceship aShip = ...
Meteor aMeteor = ...

// Later they collide => a collision event is notified trough the dispatcher
events.notify( new Collision( ship, meteor  ) );

In this scenario, the dispatcher did not require any knowledge on events and listeners. It triggers an individual event notification to each listener, using only the GameEvent interface. Each event/listener pair chooses it's own dialog modalities (they can exchange many messages if they want).

A typical implementation of a such dispatcher should be something like:

public final class Events {

   /** mapping of class events to active listeners **/
   private final HashMap<Class,ArrayList> map = new HashMap<Class,ArrayList >( 10 );

   /** Add a listener to an event class **/
   public <L> void listen( Class<? extends GameEvent<L>> evtClass, L listener) {
      final ArrayList<L> listeners = listenersOf( evtClass );
      synchronized( listeners ) {
         if ( !listeners.contains( listener ) ) {
            listeners.add( listener );
         }
      }
   }

    /** Stop sending an event class to a given listener **/
    public <L> void mute( Class<? extends GameEvent<L>> evtClass, L listener) {
      final ArrayList<L> listeners = listenersOf( evtClass );
      synchronized( listeners ) {
         listeners.remove( listener );
      }
   }

   /** Gets listeners for a given event class **/
   private <L> ArrayList<L> listenersOf(Class<? extends GameEvent<L>> evtClass) {
      synchronized ( map ) {
         @SuppressWarnings("unchecked")
         final ArrayList<L> existing = map.get( evtClass );
         if (existing != null) {
            return existing;
         }

         final ArrayList<L> emptyList = new ArrayList<L>(5);
         map.put(evtClass, emptyList);
         return emptyList;
      }
   }


   /** Notify a new event to registered listeners of this event class **/
   public <L> void notify( final GameEvent<L> evt) {
      @SuppressWarnings("unchecked")
      Class<GameEvent<L>> evtClass = (Class<GameEvent<L>>) evt.getClass();

      for ( L listener : listenersOf(  evtClass ) ) {
         evt.notify(listener);
      }
   }

}

I suppose its fulfills your requirements:

  • very light,
  • fast,
  • no casts (at usage),
  • Every thing is checked at compile time (no possible mistake),
  • No API constraints on listeners (each event choose it's own messages),
  • Evolutive (no dependencies between different events and/or listeners),
  • The dispatcher is a generic black box,
  • Consumers and producers don't need to know each other.
Laurent Simon
Merci! This is perfect! Thanks.
CaptainAwesomePants
It's solutions like these that make me love programming :D. Merci beaucoup!
Pie21