CRM64Pro GDK  v0.97
A free cross-platform game development kit built on top of SDL 2.0
Game development with CRM64Pro GDK

Introduction

The computational power required for executing a traditional game can, at least, be divided on the following categories:

  • Audio
  • Graphics
  • Logic (physics, AI, game logic, etc.)
  • Network

Nowadays, in the multicore CPU era, using multiple threads is, on some cases, a really needed scenario. The "easy" way is to run the different computational categories on different threads that will run on the CPU cores, I said "easy" because multithreading development is all but easy task and good skills and planification are needed.

Fortunately, this GDK facilitates the usage of multithreading development:

  • Audio: thanks to SDL, the audio processing is executed on separated thread and it's done automatically
  • Graphics: it is a limitation of SDL, the graphics must be executed on the main thread
  • Logic: the GDK provides methods for running the logic code separated of the Graphics code. Once the logic and graphics code is separated, nothing stop you to setup the logic on different threads
  • Network: the GDK use automatically different threads for the client and server code

In the following sections, we will focus on the Graphics/Logic categories, starting from simpliest to more advanced methods.


Standard Graphics/Logic method

This is the simpliest method which can be used on small demos or applications, while you are learning the GDK or if you are implementing a custom graphics/logic/timing method.
Logic and Graphics updates are executed in a sequential and synchronized way.

Code example:

// Initialization stuff
Main &mC64 = Main::Instance();
...
// Main loop
SDL_Event myEvent;
Uint8 bRunning = 1;
while(bRunning)
{
// My logic stuff
...
// My graphics stuff
...
// GDK main governor
while(mC64.update(&myEvent)
{
switch(myEvent.type)
{
case SDL_QUIT:
bRunning = 0;
break;
}
}
}

The "Main loop" will run at the maximum speed provided by the CPU (assuming ConfigMgr::iMTFriendly = 0), the "logic" will be executed, then the "graphics stuff" and then the "main governor loop" will do its tasks (Main::update()).

Although you can use ConfigMgr::iMTFriendly with a minimum of milliseconds to wait while Main::update() is executed, this is useful for giving back the execution control to the operating system and not eat the whole CPU probably doing a lot of unnecesary work.
Do not try to get a kind of "time control" using this parameter, let's say you use a value of 10ms, so 1000/10 = 100 executions of the "main loop" (in the best case), but these executions will not be equally distributed and also, not guaranted, as some iterations could take 10ms and others more, breaking the "smoothness" of the "main loop".


Advanced Graphics/Logic method I

This method can be used on menu screens or when you dont need to have a perfect "smoothness".
Logic and Graphics updates are executed in a sequential but asynchronized way.

Code example:

// Initialization stuff
Main &mC64 = Main::Instance();
mC64.ITimer().init();
mC64.ITimer().setRate(0,20); // Set the Logic Frame Rate to 20
...
// Main loop
SDL_Event myEvent;
Uint8 bRunning = 1;
while(bRunning)
{
// My logic stuff
...
// GDK main governor
while(mC64.update(&myEvent)
{
switch(myEvent.type)
{
case SDL_QUIT:
bRunning = 0;
break;
case EVENT_C64:
if(myEvent.user.code == EVENT_C64_RENDER)
{
// My graphics stuff
...
}
break;
}
}
}

The "Main loop" and "logic stuff" are running at the Logic Frame Rate of 20 executions per second (they are equally distributed across the timeline).
In this case, the "graphics stuff" is executed without limit (maximum CPU/GPU speed) using a custom event returned when a Render Frame occurred.
On fast systems, ConfigMgr::iMTFriendly can still be used in order to give the execution back to the operating system for avoiding to eat the whole CPU doing plenty of unnecesary graphics rendering.


Advanced Graphics/Logic method II

Along the GDK, this method is know as the fixed virtual logic frame rate with interpolation and can be used on the game itself as provides the most advanced "smoothness".
Logic and Graphics updates are executed in a sequential but asynchronized way.

Code example:

// Initialization stuff
Main &mC64 = Main::Instance();
mC64.ITimer().init();
mC64.ITimer().setRate(0,20); // Set the Logic Frame Rate to 20
...
// Set a render callback for our main screen
Screen *mScreen = mC64.IConfigMgr().get();
mScreen->setRenderCallback(myRenderFunc);
// Main loop
SDL_Event myEvent;
Uint8 bRunning = 1;
while(bRunning)
{
// My logic stuff
...
// GDK main governor
while(mC64.update(&myEvent)
{
switch(myEvent.type)
{
case SDL_QUIT:
bRunning = 0;
break;
}
}
}
Sint32 myRenderFunc(Sint32 iMode)
{
// My graphics stuff
...
}

The "Main loop" and "logic stuff" are running at the Logic Frame Rate of 20 executions per second (they are equally distributed across the timeline).
In this case, the "graphics stuff" is executed without limit (maximum CPU/GPU speed) using a render callback function.
Using the callback function will produce a very smooth graphics output with some GDK modules (Sprite or the TileEngine). This callback function can be changed by other one or disabled using a nullptr function.
On fast systems, ConfigMgr::iMTFriendly can still be used in order to give the execution back to the operating system for avoiding to eat the whole CPU doing plenty of unnecesary graphics rendering.