OPifex Engine - Part 2
Previously we discussed an overview of the OPifex Engine.
The Core
The Core Layer is meant to provide the simplest elements of any game. This means no graphical interfaces, no gamepad input, no audio systems, just the basics.
- System Types
- Game Loop (Initialize, Update, Destroy)
- Basic Math (absolute, floor, ceiling, etc)
- Memory Allocation
- Error Logging
System Types
The Core is the first layer of the engine, which is the foundation for every layer after it. To make it work we've implemented defines to handle all of our targeted platforms.
#define OPIFEX_WIN32
#define OPIFEX_WIN64
#define OPIFEX_LINUX32
#define OPIFEX_LINUX64
#define OPIFEX_OSX32
#define OPIFEX_OSX64
#define OPIFEX_ANDROID
The operating system defines are created when generating the project with cmake (we'll cover this later). Using these we can generate the appropriate system types but to make things easier, we defined a few more.
#define OPIFEX_UNIX
#define OPIFEX_WINDOWS
#define OPIFEX_OS32
#define OPIFEX_OS64
Most of the code doesn't care whether we're on a 32 bit or 64 bit architecture. Also, very little changes from Linux to OSX since they're both based on Unix.
Using these generic defines keeps the code clean while still allowing us to fine tune each operating system as needed.
Once the operating system is chosen the base system types get type defined.
typedef char i8; // sizeof(1)
typedef short i16; // sizeof(2)
typedef int i32; // sizeof(4)
typedef long i64; // sizeof(8)
typedef unsigned short ui16; // sizeof(2)
typedef unsigned int ui32; // sizeof(4)
typedef unsigned long ui64; // sizeof(8)
typedef float f32; // sizeof(4)
typedef double d64; // sizeof(8)
These types make cross platform coding easier. For example, on Android a jchar is not the same size as a char. It's the size of an unsigned short, while a jbyte is the size of a char. In our engine we define these types like this:
typedef jchar ui16;
typedef jbyte i8;
Since our defined types are used in the engine instead of the platform specific types, we don't have to change anything in the above layers.
We've also added additional types which fluctuate based on the architecture type specifed (32 bit vs 64 bit).
// 32 bit architecture
OPint // sizeof(4)
OPuint // sizeof(4)
// 64 bit architecture
OPint // sizeof(8)
OPunit // sizeof(8)
These are very useful with pointers, which change size based on the architecture the engine is built for.
The Game Loop
The Game Loop in Core provides 3 functions that every game will need.
- Initialize
- Update
- Destroy
Each function is a function pointer.
extern void (*OPinitialize) ();
extern int (*OPupdate) (OPtimer*);
extern void (*OPdestroy) ();
By providing function pointers for the game loop we're inverting the process. Your application no longer worries about handling the game loop or the timer, the engine does. Your application just provides a pointer to the functions that the engine will call when appropriate.
The Game Loop then manages the timer which we call OPtimer. Every time the loop completes a cycle the global timer is updated and then the update function passed from the Application starts all over again with the now updated timer passed to it.
Math & Memory
We're only creating a basic layer for games to run on so the math and memory functions are very simple in the Core.
For memory management we only define simple wrappers around the normal c calls.
OPalloc() // malloc
OPrealloc() // realloc
OPfree() // free
Our math operations only cover the most common operations that could be needed which don't require a structure (Vectors, Matrices, etc)
OPint OPceil(OPfloat f);
OPint OPfloor(OPfloat f);
OPfloat OPabs(OPfloat f);
OPfloat OPabsf(OPfloat f);
OPint OPabsi(OPint i);
OPfloat OPsin(OPfloat f);
OPfloat OPcos(OPfloat f);
OPfloat OPtan(OPfloat f);
OPfloat OPasin(OPfloat f);
OPfloat OPacos(OPfloat f);
OPfloat OPatan(OPfloat f);
OPfloat OPpow(OPfloat b, OPfloat exp);
OPfloat OPsqrt(OPfloat f);
OPfloat OPlog10(OPfloat f);
OPfloat OPlog2(OPfloat f);
OPfloat OPln(OPfloat f);
OPfloat OPround(OPfloat f);
OPfloat OPnear(OPfloat value, OPfloat target, OPfloat threshhold);
OPfloat OPrandom();
OPfloat OPrandRange(OPfloat min, OPfloat max);
Error Logging
Nothing complicated here. We're using a very standard ASSERT method along with a variable argument logging method to output information to the console.
ASSERT( TRUE , "Much Error, Such Logging, Wow." );
OPlog( "Running %d threads.", 3 );
At its core
Its the most straight forward layer in the engine. It's small, light weight, and provides a minimal feature set. Every game that will use the OPifex Engine will include the Core Layer no matter what the game is.
This covers the direction of the Core Layer as the foundation for the engine. In the next post we'll go over our Data Layer.