Some time ago I made a post on GameDev talking about my old message passing scheme - a big monolithic Singleton that sat squarely in the middle of practically every subsystem and passed messages back and forth. I was trying to get away from Singletonitis and wondered if there was a better message passing scheme. Someone I had previously identified as a rather talented engineer quickly brought up signals/slots - delegates, essentially - as a more type-safe, more decoupling, easier-to-handle alternative.
I accepted it because, well, I’ve grown a remarkable distaste for Singletons, signals/slots have become first-class citizens in C# in the form of events, and this guy is way smarter than I. Lately, though, I’ve been reevaluating the choice in my head.
The problem, as I see it, is that it does exactly what I wanted it to - decentralizes the mssage passing scheme. Which was great in my head, but leaves certain pragmatic difficulties. For instance, there’s no easy way (that I know of) to keep a log of all events fired, unless of course you implement it yourself in every event firing routine. With a centralized scheme, you can practically get this for free. This is both a boon and a curse during debugging - the log is great in theory and can give useful insight into program flow, but it really doesn’t measure up to a stack trace when something goes terribly wrong, the stack trace being largely meaningless when there are a bunch of events flowing through a central system.
The real benefit to having the system centralized - and what I think is an extremely strong argument - is directly related to logging and when I say the name you might not notice a semantic difference: recording. Assuming you’re running your game at a fixed time step (something I am a strong supporter of), if you have a record of the events that get fired during game execution and during what ‘tick’ they get fired at, you can play that record back later for pretty easy gameplay recording. That right there is an invaluable tool for debugging along with implementing an instant-replay system or a quick demo mode (ala the intro to Quake before you actually started playing). I don’t know how nicely this plays with multicore/multiprocessor systems; I think it might be more complicated but still functional.
There’s another benefit that I’m still up in the air about. With signals/slots, you’re essentially confined to the happy world of type safety. Make a function of a certain type, bind it, no problem. But what about data-driven games? What if you want to provide support for firing events in a script that were hitherto unheard of and listening for them in another script? A centralized system can be designed to pretty easily facilitate this; it can still be done with signals/slots, but it’s a bit more awkward. But, again, at that point you’re leaving the happy world of type safety, which I think might be more important than trying to “design for everything.”
So in summary, I still haven’t made a firm decision. I’ll probably go with signals/slots because they’re the more “preferred” way to do this from what I can tell, but it’d be great to get some feedback.
Think about how stupid the average person is, and then remember that half of them are dumber than that.