Globals Considered Harmful

My design leftover from the 7DRL challenge made use of a bunch of singletons (i.e. global variables) for things like the screen, player, world, log, and so forth. I think we can all appreciate why global variables (even when disguised as singletons) are bad. They introduce coupling between otherwise unrelated parts of code, they introduce concurrency issues, make testing difficult at best, and increase cognitive load on the programmer due to non-locality. The biggest thing I’ve done recently is to get rid of these globals. While these hacks made throwing together the 7DRL challenge a bit easier, long term these changes will actually make it easier to accomplish some of my design goals.

The first global I got rid of was the global logger. With the global logger, each entity that needed to log something would simply access the global logger and send the log message directly. In place of the global logger, I’ve now switched to using a log component on every entity that might need to log. There are a number of advantages to this (in addition to removing the problems with global variables). For example, I no longer have special cases sprinkled throughout the code to determine whether or not the hero could see the logged event. I just have the component handle this, and the everywhere else the code just sends a log event to the appropriate entity. Using log events instead of directly logging also opens the door for more interesting logic around the log event. As an example, suppose the player becomes blind for some reason. Now instead of directly logging a message like “The wolf bites you”, I could have the blind component intercept the log message and replace the message with something like “Something bites you”.

Next I got rid of the screen. I realized that the only entity that actually dealt with it was the player. More specifically, in the player controller there was a call to update the global screen. Now, I simply added the screen as a component on the player entity which knows to update upon receiving an act event. Consequently, the screen gets updated at exactly the right time (i.e. just before we get player input), and there is no coupling between the screen and the player controller.

Finally, I go rid of the global player and world variables. This one turned out to be easy. The only reason I had to have the player be global before was actually due to the coupling between the global logger and every other entity in the game. With that coupling removed, I no longer needed every entity to have access to the player in order to determine what to log. Now they can go back to treating the player as just another entity on the map. Of course the main game loop still uses the player to determine what is going on with the game, but that is easily done without a global.

Overall I’m pretty happy with these changes, and I’m ready to start adding logic around some of the bump events. Hopefully the next post will talk about the basics of combat!

More Reading
Older// Reviving Stones