Monday, June 20, 2011

Program Design, Class Structure

Last post, I had decided on a basic structure for the program. Something that looked like this:

 Basic program structure

With that done, we can start thinking of what actually needs to be done, and create a hierarchy of classes that will get everything working.

In the Engine Layer, for example, we'll be having a Rendering Engine, a Physics Engine, a Gameplay Engine, AI Engine, etc. Each of them will drive one of the game's subsystems, called in turn by the Game. We want these to have a common interface where it comes to communicating with the Resource Layer. They'll generally want to ask for the same information, and trying to standardize how they do it can save us work in the long term.

For now I'll concentrate on getting the Rendering Engine working, which is the shiniest part of the project. Getting it working can help us see what is going on, and it'll make it obvious later on if one of the other parts of the game is acting oddly.

So, we start with a RenderEngine class. This class is going to inherit from an Abstract Base Class (ABC) called Engine. Doing this will allow us to refer to it as "a kind of" Engine, same as any other Classes we derive from it.

Engine inheritance graph

In inheritance graphs, arrows point from the child to the parent. What inheritance means, in essence, is that the child has all the same methods and data members as the father, as well as any defined in itself. The child can be handled by pointers of the parent's type, and any methods or data members in the parent can be safely accessed through it. In short, you could treat an object of the child's type through a pointer of the parent's type as if it were an object of the parent's type.

So, what does the rendering engine need? It needs access to the list of entities it must draw (ideally sorted or culled in some way, though we'll start with just getting it working with all of them), it needs access to the models that represents each entity, it needs the ModelViewProjection matrix and it needs the shaders that will be in charge rendering to the screen. It would also need textures, but not just yet.

These are a lot of different resources that must be handled. So how do we get a hold of them?

Well, we need a way of storing them in the Resource Layer. We can have a vector for each different kind of resource, for example. We can then use it's position in the vector as a unique identifier for each.

However, each kind of resource is fundamentally different. The way to prepare a shader for use is very different from the way a model or a texture are prepared, for example. We need specialized functions for how to store each. What we can do is create several manager classes, each specialized in handling a specific kind of resource, but with a common interface for delivering them in a predictable manner.

Certain functions will be common to all the managers, even if their implementation is different in each. This again is a reasonable situation to use inheritance. We can have a Manager ABC, from which several different managers will inherit (ShaderManager, ModelManager, EntityManager, etc).

Manager inheritance graph

Then each manager would have a structure to hold the associated resource. It can be a vector in some cases, but for others it may eventually be best to use some other kind of structure.

So the rendering engine would then need only call on the shader manager to provide a given shader when needed, and the model manager for models, etc. However, we can see two problems with this. One, our engine would need to know how to talk to each manager. And since the managers are external to the engine classes, each engine will need to store the address of each manager.

This is a lot of duplicated information, not all of which would be of use. Instead, we could have a single system in charge of interfacing with the resource layer, and all the different engines would instead talk to it. Then, we'd only need to store it's location in each, and each engine could just focus on asking this interface for a kind of resource, instead of knowing how to talk to each manager.

Link between the Engine and Resource Layers

This is starting to look decent. However, there's another issue to deal with. Each of these resources is of a different type, and both the handling and storage of them breaks the uniformity we'd rather have. Models, for example, are being stored as GLTriangleBatch objects, whereas Shaders are stored as GLuint types and Entities as Entity objects. When trying to draw the models, the Rendering engine has to call a method of the GLTriangleBatch class, and to use the Shader it has to use an OpenGL API call with a GLuint parameter.

This means that while we went some ways into simplifying the connection to the Resource Layer, we have to have several different types of call methods in our interface. This is a heavy burden, and if we change the way we store any of them, it means changing the interface. The interface shouldn't concern itself with the implementation of the resources, it should just be getting them for the Engines to use.

We can solve this by standardizing the way we refer to our resources. We can, in effect, create a Resource ABC all other resource types inherit from. We can then store and pass along Resource type objects, and it will work equally well for shaders or models or entities. Only we can't pass along the Resource object (which, since it's an abstract class, can't be instantiated), but instead we'll be storing and passing along pointers to them.

This is so because while we can point to something that inherits from Resource with a Resource pointer, if we try to set up storage for a Resource object, objects derived from it won't fit in it. So we must store pointers to them instead.

This means our storage structures will no longer be holding the information. Instead, they'll be holding pointers to where the information we want is. It'll be up to the person asking for it to know what kind of information they asked for, however, to avoid calling members or methods from another type, which could kill the program.

Next post I'll look into this in more detail.

No comments:

Post a Comment