I’ve been hacking away on a prototype game using Project Darkstar (SGS). I’m using SGS channels for main categories of data (e.g. players, world, chat), but I still need a way to dispatch payloads to the proper message handler implementation. I was never completely happy with my past structures, most involved some kind of registry/wiring/mapping bureaucracy for each command. I had to remember to update the wiring each time I extended the protocol. My next attempt!? Enums that function as a numeric identifier and handler Proxy.
Let’s say we have a message handler interface:
public interface MessageHandler {
void handle(byte[] message);
}
and some very simple MessageHandler implementations:
public class SayHandler implements MessageHandler {
@Override
public void handle(byte[] message) {
System.out.println("Say: " + new String(message));
}
}
public class TellHandler implements MessageHandler {
@Override
public void handle(byte[] message) {
System.out.println("Tell: " + new String(message));
}
}
public class PartyHandler implements MessageHandler {
@Override
public void handle(byte[] message) {
System.out.println("Party: " + new String(message));
}
}
Now let’s define an enum that also implements MessageHandler and dispatches to our implementations:
public enum ChatDispatcher implements MessageHandler {
Say(new SayHandler()),
Party(new PartyHandler()),
Tell(new TellHandler());
private final MessageHandler target;
private ChatDispatcher(MessageHandler target) {
this.target = target;
}
@Override
public void handle(byte[] message) {
target.handle(message);
}
}
Notice that each enum is given a MessageHandler implementation when it is constructed, such as SayHandler, PartyHandler, or TellHandler. It’s also important to understand that each enum entry has an ordinal value associated with it, which is always assigned in the order in which the enums are defined. Check out the javadocs for Enum and the ordinal() method for more information. I plan to send a numeric value across the network that maps to these ordinal values for dispatching.
We’ve defined our dispatcher and handlers, now let’s see what some usage scenarios look like. Here’s a direct call to one of the enums:
ChatDispatcher.Say.handle("Hello World".getBytes());
This code will output:
Say: Hello World
That’s pretty neat, but I only have a handler ID and a payload coming across the net. How do I get my handler within that context? Class.getEnumConstants() and generics are what I want. Here’s a method that will find my enum:
public static <T extends Enum<T> & MessageHandler> T
getMessageHandler(int id, Class<T> dispatcherClass) {
T[] enumValues = dispatcherClass.getEnumConstants();
if (id >= 0 && id < enumValues.length) {
return enumValues[id];
}
return null;
}
That funky looking generic defined as “T” gives us a compiler time check to make sure the dispatcherClass parameter is an enum that implements the MessageHandler interface. Absolutely required? Nope. Stops some potential issues that would otherwise only appear during runtime? Yep.
Now let’s have a look at more usage code, minus NPE checks for brevity’s sake:
MessageHandler handler = getMessageHandler(1, ChatDispatcher.class);
handler.handle("Hello fellow teammates!".getBytes());
MessageHandler handler2 = getMessageHandler(0, ChatDispatcher.class);
handler2.handle("Hey everyone!".getBytes());
MessageHandler handler3 = getMessageHandler(2, ChatDispatcher.class);
handler3.handle("Psst...don't forget your resist gear!".getBytes());
Here’s the output:
Party: Hello fellow teammates!
Say: Hey everyone!
Tell: Psst...don't forget your resist gear!
We can see that our output reflects the fact that each enum is assigned an ordinal, starting at 0, in the order in which they are defined. This example is just one of many cool tricks that can be done with enums, but I need to get back to coding my game