Clean Space - The Engine
This article is a work in progress!
If you have been given this url, I hope you can provide some early feedback for an article that is work in progress. I may also link to an incomplete page (to help me not forget), so apologies if you've arrived here by accident.
This draft was published: October 24, 2016
Check back soon for the complete article!
Last year I spent some time studying Game Development, trying to understand where differences exist in practices, and why. I was less than impressed with what I found, but decided I should walk a mile in a game developer’s shoes by not only writing a game, but by creating one of the most complex games in terms of content.
Considering all the amazing advancements in graphics over the years, I’m amazed at how poor tooling, frameworks, and the entire game development eco-system appears to be right now. Whilst there has probably never been so many indie developers, and access to graphics engines is possibly the best it’s ever been, these resources feel about a decade behind that which is available to other software industries. The emphasis on these technologies is forever pushing towards the frontiers of improvements to hardware and graphics, with usability, user experience, and user support taking a back seat.
This article will discuss some of the problems I’ve found with regards to traditional game development using Unity and MonoGame, and how Clean Engine goes about rectifying issues such as Dependency Injection and inherent async
support.
Limitations
Clean Space intends to not only provide me with a great learning resource and enjoyable gameplay, but to make a few statements. For one, I want to show that clean coding is not only possible in game development, but should be preferable. In addition, I want to show that you don’t need to write your games in C++, nor do you need to make concessions in higher level languages like C# by, for example, substituting properties for fields, all in the name of performance.
To that end, Clean Space has the self-imposed limitation of being entirely written in C# (plus Gherkin if you include the tests, xaml
if you include the UWP forms, etc). So to, is Clean Engine (the game engine used). The graphics engine used, be it Unity, MonoGame, or similar, of course may use other languages, but they’re not my concern.
Engines
Some of you may have done a double-take of that last paragraph. Most people consider Unity and MonoGame to be Game Engines, certainly if you navigate to the Unity3D Home Page, it says “Unity - Game Engine” in the page title. So why do I need Clean Engine ontop of a game engine?
The problem with traditional game engines is the game loop. If you follow the tutorials for any of these engines, you’ll see that they try to push you down the route of placing your game logic within the game loop. You need to react to user input, amend your state, load or locate content, and render accordingly, all within this loop. Fair enough, but what if my game uses I/O? I need to read/write a file or send/receive data over a network? I can’t just block the thread within the game loop (any game where the UI freezes while your saving a game decided otherwise).
So, I need to consider asynchrony. But as the .Net async/await
keywords prove, asynchrony does not simply mean multi-threaded. There’s more to it than that.
Async/Await
Contrary to what seems to be popular misunderstanding, the async
and await
keywords are not about creating new threads to run tasks. Whilst Task.Run
can queue work on the ThreadPool, that’s not where the power of these keywords exist. Lower level Windows API’s for I/O use completion callback mechanisms, which allow us to yield threads instead of wasting them waiting for I/O. So long as you understand the potential pitfalls, it can be immensely powerful, and simple.
The model is so widely adopted across the .Net ecosystem, even technologies as old as WinForms now allow async UI event handlers. But, Game Loops in Unity and MonoGame do not. It’s somewhat baffling as to why such widely adopted frameworks don’t support async/await
. Certainly, these models may not have been around or widely adopted when the frameworks first hit the market, but the same can be said for WinForms. As will be explained in this and future articles, the mechanisms used by Clean Engine and Clean Space rely heavily, where possible, on eventual consistency. That the code is designed to facilitate and encourage event sourcing and CQRS (Command Query Responsiblity Segregation), makes having access to asynchronous API’s all but crucial.
Dependency Injection
//TODO: link to ioc shizzle, brief desc, etc.
Layering
For a long time I was unable to decide which of the traditional C# graphics engines I wanted to use for the rendering of Clean Space. I still find myself switching between Unity and Monogame as one or t’other drives me into a rage with a lack of documentation, poor tooling, broken and confusing APIs… The practice of switching between these frameworks however has been very beneficial. Where possible, Clean Engine has evolved to provide abstractions to which Clean Space can be written against, with pluggable implementations available for the underlying rendering engines. This has had the added bonus of allowing me to improve upon those APIs, hiding away noise and workarounds.
It is upon this layer that Clean Engine provides it’s improved game design model.
However, I have not abstracted the underlying frameworks completely, just for the sake of having done so. Almost all of the modelling, textures, lighting, cameras, and general rendering concepts are left untouched as these tend to be the most volatile components. I don’t believe there would be significant benefit to having abstracted both frameworks, especially given the ongoing maintenance cost of having done so, not to mention the lowest common denominator issues such abstraction would cause. The time spent switching between these frameworks, trying to realign and reimplement the UI, I believe, would be greatly exceeded by maintaining said abstractions, plus suffer from typical disadvantages of LoD (law of demeter, resulting from Interface Segregation - SOLID).