What should be public in an API?
When you design a framework/engine/library, there is always the fact that you have to think about the “surface area” of the API: how many classes, methods,… the user will see and be able to use.
In Wave, we have divided the engine in several assemblies:
- WaveEngine.Common: this assembly contains the most basic functionality of the engine. For example, the math library sits here. It also contains a set of core interfaces that represent the engine in its most barebones state: IAdapter, IGraphicDevice, IIOManager, IInput,…
- WaveEngine.Adapter: this is not really an assembly, but a set of assemblies with the same name, one for each platform we support. An adapter is simply the code that is needed for the engine to work on a given platform. These Adapters implement the core interfaces defined in WaveEngine.Common (IAdapter,…).
- WaveEngine.Framework: this is the assembly the users of Wave Engine will be using to code. Framework wraps over the low level operations defined in the core interfaces, and presents the end user with a higher level API that is more powerful and simpler to use.
- There are a few other assemblies, but they are not more extra sugar candy and not mandatory really (WaveEngine.Components, WaveEngine.Materials,…).
So, in a perfect world, when a user starts creating a project with Wave Engine, he will just reference Framework and start working happily. In reality, that person will probably also need to reference Common, as some types that sit there (specially the math types) are commonly needed in a normal project.
Once you add a reference to Common, there is a problem. All the core interfaces (and a few other things) are public, but they are not designed to be used when making a game, they are designed to be used when writting an adapter to support a platform on where to run the engine. But the user will see them nevertheless, as they are public because they have to be consumed for the Adapter assemblies.
This is somewhat problematic. If by mistake we expose one of those levels interfaces in Framework (for example we used to have quite a few references to IAdapter), the user can bypass the high level API (by mistake or by his own choice) and then start using things that are not designed to be used in his scenario (making a game). A solution would be to split the Common assembly, one with really basic types, and another with the types used in the adapters, but the problem is that those assemblies would be quite small if separated.
In the end, we have made Framework so none of this classes and interfaces are exposed publicly anywhere, so the user doesn’t have a way to get a reference to an object implementing them (they are always interfaces or abstract classes). The user could implement them himself, but that doesn’t make much sense, so we do not worry about that case. The user could also get them using reflection, but we do not think that’s an interesting scenario to take into account either.
So, the main place where we should spend more time (and where we have spent more time by far) making sure the public API was solid is WaveEngine.Framework. Take into account that Wave Engine was developed at the same time as our first game, ByeByeBrain (BBB), was developed, so both evolved quite a lot during their main development (which lasted more or less a year). It was an interesting thing for me to see how the olders part of the code where for example accessing Common, while the newer ones were using Framework, it shows how the engine evolved, providing only very basic services at the start, and then growing to provide more higher level and powerful classes to simplify daily scenarios, or handle some platform differences that even with Common couldn’t be abstracted. For example serialization works in some minor details differently in .NET (and different between the PC and the Xbox360) and Monotouch/Monodroid.
As we dediced to review Framework, we went through all public attributes, properties and methods and start deciding what to do with every one of them. Some things were public, but were not used outside Framework (maybe it was thought they would be useful but they were not in the end), and were made internal. Others were public and where used, but the same could be achieved in the same way and we did not want to expose two ways of doing the same thing. And so on and on. In general, we tried to make as much as possible private/protected/internal, yet it is hard sometimes to gauge if you have done it right as we have only BBB as a case study (and some internal samples, but they are fairly simple). Did we make something internal because it was not useful or it was just not useful in BBB? That was a very important question for us, and sometimes we did not have a clear idea of what was the answer, so in those cases we decided to go with hiding things, as making something public has less chances of breaking things than making something private.
We think in the end we have ended with a very clean API, with enough power to be usable but not overly complex, but we need to validate it ourselves first. Right now the current Wave Engine build and BBB sit in a Stable branch, while this new rewrite sits in a Development branch, which does not compile BBB yet. We have now some work to migrate BBB to this new branch and see how it performs in a “real scenario”. BBB is a very graphic intensive game and during its original fast development there were some design decissions that are lost today. For example, did we expose a low level operation from the IAdapter because using the high level version was too slow for a common scenario? Or more importantly: have we introduced some subtle bug without realizing? Those are the questions we will be able to answer when we port BBB and validate our work. We have ported the samples and they work nicely, but switching BBB while we finished its release in iOS and Android was simply not feasible for a small team like us. Now that the iOS has been released and the Android version is coming to an end, we can start with this final validation and fix any pending issues we find.