Deprecated: Assigning the return value of new by reference is deprecated in /usr/share/php/HTTP/Request.php on line 412

Deprecated: Assigning the return value of new by reference is deprecated in /usr/share/php/HTTP/Request.php on line 736

Deprecated: Assigning the return value of new by reference is deprecated in /usr/share/php/HTTP/Request.php on line 749

Deprecated: Assigning the return value of new by reference is deprecated in /usr/share/php/HTTP/Request.php on line 794
☢ One Qwerki Website ☢ - The BatEngine
The background!
Title background
 

jamie-thompson.co.uk

The home of Jamie Thompson on the web

The BatEngine

- A second attempt at a game engine.

Preamble

Ahh. The mighty batengine. This project was born out of the embryonic stubs I created for the planned Jonny Man project. The premise of the engine was that it would implement the major design elements I had learned from my experiences with my previous engine, D2X. These were firstly a platform-agnostic aproach to the design, to ease porting at a later date, and secondarily a highly modular and extensible approach to building the engine as a whole, utilising dynamic libraries to implement the base components so that they could be replaced independently by 3rd parties easily at a later date.

This was designed, developed and documented in great detail as part of a university project, and I have no intention of restating 150 pages of information (well, maybe only half of that is actually relevant to the code's design). I may publish my Independent Study project at a later date, but for now you'll have to make do with the summary given here.

Highlights

Platform Independence & Modularity

The modular design of the engine allowed the entirety of the client code to remain completely platform independent, whilst still allowing the flexibility to utilise platform specific APIs, should the developer so choose. In fact, the modular nature of the engine was so successful that it allowed me to branch the code in CVS and remove the 3D module written by my group mate for our group project and use a 2D module written by me (thus making the code base entirely my own work as per the demands of that project) whilst maintaining full code compatibility with the rest of the engine, allowing me to work on both projects at once. A very useful trait indeed.

Whilst it has long been a goal I strove towards, it was the experience I gained from my Game Server project that finally spurred me towards a workable abstraction of the fundamental elements of processes, threads, timers and their ilk, though in this project I only stubbed the implementations for other platforms.

Graphical Elements

I based my paradigm around the concept of independent elements combining to create graphical images. Thus I built a tree of interfaces describing the different types I would need, roughly akin to:

The BatEngine's Graphical Elements

The BatEngine's Graphical Elements

This allowed me to construct the graphical output of the program by creating lists or elements akin to OpenGL display lists, or at least, that was the intention. As currently implemented you had to call each element to be rendered, but due to the interface's design this simply consisted of a series of nested loops. This simple concept allowed development of client application logic using little more than coloured blocks which could easily be altered to bitmaps and animations once the important code was complete. Here's a excerpt showing it's use:

rendering.cpp

Graphics->output().clear();

CurrentBattle->render(96, 79, Graphics->output());

GUI->getFPSMonitor()->setCaption("");
GUI->getFPSMonitor()->caption() << "FPS: " << FPS;
GUI->render(0, 0, Graphics->output());

Graphics->render();

Basically, the way this works is that you either add a render method to the class a la iGraphic::iElement & render(unsigned int _X, unsigned int _Y, iGraphic::iImage &Output); to any object that you want to be renderable. If you want to be able to store it with other graphic elements (although it's a good idea regardless) then you simply inherit from the iGraphic::iElement interface, and thats that. As rendering is performed from source to target (the last argument to the render function), this allows custom renderings to be abstract and simple, and also allows graphic elements that are generated at render-time, such as solid-colour blocks, gradients and text output.

Design Patterns

Part of the research I conducted during the development was in the area of design patterns. Evaluating those described in my reference materials, I found a few design patterns that I was already fairly close to re-implementing myself, the bridge, the facade, and the abstract factory. Which reminds me...I read somewhere that the great thing about patterns is that when explaining them to those unfamiliar with them they often rubbish the idea, but when you examine their code you can often find numerious examples of where they have come close to or even succeeded in re-creating many patterns themselves. Much like history, those who do not use patterns are doomed to repeat their design.

The problem I found with the bridge pattern is that it is designed to separate the implementation of the interface from the implementation of the functionality, rather than achieving what I wanted, which essentially was the ability to have multiple inherited interfaces bound robustly to an inherited implementation. I tried to avoid multiple inheritance initially, as it's generally reviled by many members of the OOP community, much as the GOTO statement is with the imperative community. This additional separation of the implementation whilst beneficial in many respects, has a severe performance cost associated with it, not something you want associated with any graphics code, so I took the multiple inheritance approach, which after some initial convulsions worked wonderfully. In fact, this use is so good that when Sun Microsystems designed Java they explictly added support for Multiple Inheritance, though only for interfaces, as I've used it in this situation.

My mentality is that features like Multiple Inheritance and GOTO are in languages for a reason, as long as they are used responsibly when they're the best solution to the problem, then they're there to be used, as if they were not then they wouldn't be there!

Critique

Extensibility

The C++ ABI

First, a little rant. C++ sucks horribly for runtime binding, whoever thought that not mandating a standard ABI was a good idea should be shot. Instead we're left with horrible hacks for runtime binding like declaring stub functions using the C calling convention then passing pointers back and forth. You CAN put C++ classes in dynamic libraries of course, but then you have to use the same version of both the compiler and all libraries used, and even then it's not 100% safe. The only real solution to this is the use a methodology like COM or CORBA to handle these external objects, but even with those the overhead is enormous when all you want to do is load a known class from a dynamic library.

I can only speak of COM, for I'm (as of writing this) still investigating CORBA, but COM's solution is to use interfaces (good idea) that are implemented as virtual function tables (v-tables). It's not too bad a solution given the power of modern computers these days, but aside from the performance penalties of virtual functions, you still loose one of the most valuable assets in your toolbox, the STL. As templates are compiled as needed, if you pass objects back and forth as parameters (say, a std::string for example), then the object will obviously be different in the external code. This means that in order to pass strings back and forth you have to wrap them in a wrapper class that you can then put in a library that the external code can link with, but even then you need to make every single operation pass through the virtualised interface, which needless to say is awful for your performance.

I would think the solution is probably to either restrict the compiler potential 3rd parties can use (not so good), or to use some other language that has better runtime binding, such as Java or one of the .NET languages. These have their own problems though, in that in order to guarantee compatibility they must suffer the performance penalties of being managed code. It seems work is underway for standardising the ABI, but the chances of Visual C++ conforming to this are minimal at best, so we'll just have to see how things go I guess.

The only reason developing COM objects with GCC and its derivatives is possible is because in later versions they emulated Microsoft Visual C++'s v-table layout as thats (funniliy enough) what Microsoft mandate as required for COM objects. Sickeningly enough, Microsoft has even patented part of this (as is par for their usual tricks), though luckily it only covers enough that GCC is still able to create code that works correctly. Software patents being used to hamper the development of unrelated inventions, indeed. As much as I'd like to mandate the use of Visual C++ for plugin development, I cannot expect other developers to pick up the tab for a decision when gratis alternatives such as GCC are availible, although there is also now the free edition of the Visual C++ tools to consider.

...as I was saying

Anyway, back to the batengine. The batengine was developed as part of a couple of university projects, so whilst that decision gave me more time to work on it that prehaps I would have otherwise, it was still a project with a pressing deadline amongst several others. The net effect of this was that much that I had planned was not finished, and of what was, much was rushed. The first thing that was dropped was the dynamic plugins, for the reasons explained above. The second difficulty was the inherent problems with using components in a platform independent manner. Case in point, the cWin32Core class (a implementation of the iCore interface) manages the Window handle required by all of the DirectX components. Communicating this value between the components via the interface is a no-no as it would pollute the interface with platform specific data. The easist method would be using a void-typed parameter to pass arbitrary structures, but this is neither type-safe nor robust (though seems to be the most prevalent way of handling these things in the code and APIs I have seen over the years).

The method I chose to resolve this was at first a custom primitive form of RTTI (of which I hadn't yet explored at all by that point), but realising the maintenance nightmare awaiting me I looked for a more...autonomous solution, and researching RTTI I found it was probably the best option to go with, though again I was frustrated by the fact that each compiler has their own way of implementing RTTI (the ABI all over again), making it only truly useful for either passing anonymous objects around or storing them in huge base-classed collections and all but useless for use in dynamic libraries. No wonder it's turned off by default in most compilers. It'd be nice if the pain ended there...but no, its the same situation with exception handlers.

Error Handling

Robust error handling has never been the highest priority with my work, as it has generally only ever been for my use, and given that my testing is fairly thorough, few major error lurk in the important bit of my code (as far as I know :) ). Nevertheless, it's an important thing to implement, so I had a stab at doing things properly in this project. The concept I was aiming for was hierarchical family of exceptions specific to each class which passed back unhandled exceptions to their base classes. I also had a single rooted class hierarchy, so more controversy there.

What I intended was for the implementations to throw specific exceptions as required, these would then be translated into common exceptions by the interface and passed up the tree of exception classes until it was recognised or until it reached the base class, in which case it was a fatal exception.

The problem I foud was that the amount of boilerplate header code for these exception classes was immense and very annoying. This was required because exceptions in C++ are differentiated by type, so different exceptions require different classes, even if the only difference is the data pertaining to errors they handle. The single-rooted class hierarchy work very well for an indirect purpose I concieved for it, debug logging. Using the root class I was able to add functionality to every object in the system in one fell swoop, though I only actually used this for the debug console. This console allowed every class to log messages to a redirectable std::ostream seperate from the std::cout and std::cerr streams. Ensuring this stream was initialised correctly whilst still allowing for redirection made for some messy code however, and I'll be exploring alternatives the next time I try to implement something similar.

Mr DataStrip

  • Time:
    06:55:54 pm
  • Date:
    12/03/2010
  • Visitors:
    3
  • Max Visitors:
    108 on 2005-08-27 01:34:45
  • Uptime:
    0 days, 00:00:00
  • Really need to think of more things...