So, I'm nearing completion of my Telnet implementation (fully RFC compliant, using the Q Method), and I'm at the point now where I'd like to emit events, which can then be listened for and handled anywhere else in the game. To give an example, my Client class, which represents a remote connection to the game, is interested in knowing when TELOPT_EOR is enabled or disabled, as the game needs to be able to ask Client for its EOR setting in order to know whether to send IAC EOR after the prompt or not.
Anyhow, currently I have an EventDispatcher compositioned into the Engine class. Objects register callbacks with the EventDispatcher for desired Event types. It's sort of like an Observer pattern, I suppose. The problem I'm running into with this method (and my class organization in general) is that I'm ending up having to write code like this:
It is my opinions that get() chains like that are bad style: the coupling it promotes is very strong. In that single statement, you couple game, server, engine, the engine's event dispatcher and events all together.
My question would be: why does the game need to be able to ask the client for its EOR settings? Don't ask: TELL! The game should simply pass structured data to the client, which is then responsible for displaying it in the best way possible.
@Kaz: You sort of answered your own question. Engine asks Client for its EOR setting because its Engine's job to construct the prompt and then send it to Client. It's the same reason Engine asks Character for its HP. If Client had to construct its own prompt, then Client would be asking Character and whatever else for data, which couples Client to the other classes.
That really wasn't the issue I'm trying to address, however. I'm trying to figure out how to get an Event from some arbitrary class (e.g. my Telnet layer) to the games's EventDispatcher without climbing 5 levels of hierarchy using get() chains. Or whether tis design is common/accepted and I'm worred about nothing.
What Tyche suggested is pretty standard. Have a raiseEvent/dispatchEvent/bla method, and let it be responsible for dealing with that chain of finding the dispatcher. Don't have that whole line of code every time you need to raise an event, that's bad for all kinds of reasons.
Engine asks Client for its EOR setting because its Engine's job to construct the prompt and then send it to Client.
That seems wrong.
Engine->construct_prompt(): do some stuff client->send_prompt(stuff)
client->send_prompt(prompt stuff): send(prompt stuff) if (me has EOR): send EOR
Sending the EOR or not is a detail of the client, not specific to the game engine, the character, or other things.
@David: I disagree. To give a somewhat convoluted example of why - try sending the IAC GA as well. Either Engine has to query Client to see if has suppressed the GAs before calling Client::sendPrompt() or Client::sendPrompt() has to query Engine to see if the game is even sending GAs at all (config setting is turned off or what have you). I think the former makes more sense. Prompts should be constructed by the game and handed off to the client as any other output would be.
Also, thank you for the suggestions WRT my question. I'm definitely going to create a dispatchEvent()-type method like Tyche suggested. I guess I'm still curious to know if there's a "better" design, though. Keeping a reference to EventDispatcher in each class seems like overkill, and using the Singleton pattern irks me, since it's basically global state. I'm not really sure how else to get rid of the long chains.
Client::sendPrompt() has to query Engine to see if the game is even sending GAs at all (config setting is turned off or what have you).
Or, the engine passes it in as an optional parameter to the method.
Prompts are actually different than regular output, because of the line ending stuff. But I think the real convolution here is the event dispatching, not the handling of the GA… and it looks like you're more or less happy with that solution, so I guess we're done with the bulk of the issues. :smile:
I guess I would ask yourself why you need to get rid of the long chain. It depends, ultimately, on where the responsibility lies.
I would consider adding an event method to each intermediate layer. It's quite easy to picture the client raising an event on the server, which is listened to by the engine, which in turn brings up its global event dispatcher to send off the event. In this case, the client code – even hidden in a helper function – needs only know about the server handling events.
So, client -> hey server: event! server -> hey engine: event! engine -> hey, everybody on the event dispatch: event!
This obviously introduces the overhead of the indirection, but, well, it depends on what problem you're trying to solve. This extra indirection makes it more generic, reduces coupling, etc. It comes at the cost of performance (depending on just how generic you really make it) and extra steps to follow when figuring out the "day in the life" of an event. (Rather than seeing a single line in a helper function, you have to follow each event layer.)
To summarize, the alternative I've seen here is to have each layer be its own event receiver and event generator, with the top-level event dispatcher listening to events generated below it before dispatching them out to appropriate receivers.
@David: I'd like to get rid of the chains because it exposes the whole object graph to every class, leading to tighter coupling, and breaking the Law of Demeter. Bubbling events like you're suggesting is something I never thought about. I guess that kind of mimics exceptions, come to think of it. Well, if every layer caught and re-threw the exceptions, anyhow.
WRT the GA thing, you're right about prompts being special in that respect. Engine still needs to construct the prompt, though, since the data that goes into it is spread across several classes (current game time, player's HP, mount's stamina, etc.). I suppose I could have it construct everything except the EOR, and call a Client::prompt() method to append the EOR and then Client::send() it. I can see the logic in doing it either way.
Reactor in my diagram is probably a combination of your Server and Dispatcher. Connection is probably an equivalent to your Client. I don't believe I have any chained calls. If you right click, view image and zoom, it's readable.
I guess that kind of mimics exceptions, come to think of it. Well, if every layer caught and re-threw the exceptions, anyhow.
Yes it's extremely similar, if you consider exceptions to have the default "event handler" of invoking the destructor on each object on the stack and then passing it up (implicitly).
p.s. I agree that the chain isn't good, I was asking more of a rhetorical question, my apologies. The usefulness of the question is if you're thinking about performance, for example, where you might want to take a shortcut not for design but for speed.
@David: I'd be lying if I said I wasn't a little concerned about performance for a lot of the methods and design patterns I'm using to make the code more modular, easier to maintain/understand, etc. However, I keep reminding myself that the game isn't being played by anyone, and likely won't ever have more than a handful of players even when it's "done" (if any!), so they aren't really justified concerns, methinks. I'm rather new to the "program to make it easier for the programmer, not the computer" methodology. :-p
@Tyche: Your design is very similar to mine, except the IO abstraction. Is your TerminalFilter for VT100?