Tuesday, February 3, 2015

Building your Core library (Part 1)

When you think about a game engine, there are many aspects that probably jump to your mind first: rendering, physics, AI, etc... While these are all of the more "user-facing" parts of an engine, there are many parts which go unseen that are just as (if not more) important. Just to name a few (in no particular order):

  • Debugging Facilities
    • The engine should log messages about what it's currently doing and output it to a log somewhere. This can be an invaluable tool for a programmer trying to understand the flow of events in the engine.
    • Messages are great, but oftentimes to really understand a bug you need to visualize the problem. Debug drawing utilities allow programmers to insert drawing code for simple primitive types into the rendering command stream for easy visualization.
  • Memory management
    • Optimal performance cannot be achieved in a game when you're relying on the operating system's memory management utilities. Every call to new or delete will hang the calling thread until the OS has processed the command. Not only that but your total allocated memory is not guaranteed to be contiguous which can result in many cache misses. There are several allocators that engines use which are all just variants of some kind of memory pool. I'll make sure to cover these in a future post.
  • Math libraries
    • What is a game engine without some math libraries? Depending on the types of games you're targeting with your engine these can definitely vary in type but there are usually three main concepts encapsulated here: vectors, matrices, and quaternions. 
  • Thread systems
    • Every OS represents threads in a slightly different way (pthreads in Unix, thread handles in Windows, etc.). Because of that, it's often convenient to wrap OS-specific threading calls into a class which can be used by higher-level code. If your engine is job-based, you'll also want to create a thread pool to avoid recreating threads over and over again.
  • Timers
    • It goes without saying that your engine will need some way of telling time. Smooth animations and many rendering effects rely on knowing how much time has passed since the last time they were updated. Fixed framerate games need to know how long to wait until triggering a frame redraw. On top of all of this though, a user is going to want to profile parts of the engine. What better way than your already built-in timing code, right? :)
  • File I/O handling
    • Engines are going to spend a good portion of their time reading/writing to files on the disk (log files, saving/loading games, level/asset loads, etc.). Creating a custom file system allows you to abstract away any OS-specific calls you may need to accomplish this. Additionally, you can ensure that the engine has full knowledge of all file operations by not allowing a user to use anything but your system.
  • Custom containers
    • STL is great. It may have a bit of a learning curve but the platform-independent API and many containers is invaluable to many software systems. However, oftentimes a game engine requires more control over how a container is created/managed (most importantly ensuring that your custom memory allocators are used). There are also many structures that STL does not include that an engine may use (kd-trees, quadtrees, hash tables (minus C++11) just to name a few). 
Usually these parts of the engine need to be designed and implemented before you can do anything else. Want to build the renderer? Well you'll need a math library, threading setup, timing for profiling, containers for storing commands, and a memory system for any dynamic memory you may need. Physics, very similar requirements to the renderer. And so on. Therefore, careful thought should go into these systems because they'll be so widely used throughout your codebase. Not only that, but careful design here will result in users having everything they need to tune performance for their game.

Qi builds all of these concepts into one library called Core, which will be linked with the rest of the system. This code isn't really expected to change that much after being initially written (minus bug squashing) so I've elected to link it as a static lib for the time being.

Before going into any detail on these systems, I think it's important to talk about a specific design decision I made early in Qi. I've always been a fan of C#'s delegates, something that has been sorely lacking from C++ (C++11 has added this functionality with std::bind but I prefer to stay away from too many concepts that will make the engine not work on older compilers). To date, the best implementation I've found for C++ is FastDelegates. Qi will make pretty extensive use of them right from the beginning so it may be beneficial to read a bit about how they work. Essentially, they allow a programmer to specify a member function to a particular instance of a class to call from outside. It's a lot like a C function callback but in a more C++ world :) Here's a quick code example:
class Foo
{
   public:
       void bar(int val);
};

Foo f;

// Create a delegate and bind it to the instance of Foo 'f' and 
// the function Foo::bar. The template argument to the delegate
// means the function returns a 'void' type and expects an
// 'int' as an argument. The function bound ('bar' in this case)
// must match the same definition otherwise you'll get a handy
// compilation error :)
FastDelegate <void (int)> delegate(f, &Foo::bar);

// Invoke the delegate (really, just call f.bar())
delegate(10);

Next up, I'll be talking about what I feel is the most important first step you can take in your engine, the logging system.

No comments:

Post a Comment