Imagine being able to modify C++ game code and have it take effect without even restarting the game. That’s the motivating idea behind today’s article. Read on to see how this works and how to use it to really speed up iteration times.

Table of Contents

Way back in part two, we added in the ability to make C++ changes without restarting the editor. We did this by using OS functions to manually load a DLL instead of relying on [DllImport]. By loading on startup and unloading on shutdown, we gained the ability to change the C++ plugin DLL between runs of the game.

This same ability to load and unload the C++ plugin is at the core of “hot reloading.” The “hot” part means we’re loading while the game is running—”hot”—as opposed to stopped: “cold”. So the first step is to add a function to Bindings to reload the plugin. All this Reload function needs to do is unload the current plugin and load the new plugin. However, this triggers a couple of issues that we’ll need to work around.

The first issue is easy to solve. When the plugin gets loaded, PluginMain gets called in C++ as the “entry point” for the plugin. Since it can now be called either at the first boot of the plugin or for a reload of the plugin, we need to pass a parameter to PluginMain indicating which scenario it’s being called for. This allows it to skip any one-time setup work.

The second issue is a bit tougher. The C++ plugin has its own memory and that will go away when it’s unloaded. This means a reloaded plugin will start fresh and not be able to continue where the previous plugin left off. What we need is a way to keep the C++ plugin’s memory around between reloads of the plugin. Fortunately, the plugin’s host—the C# side—is easily capable of this. We just need the C# side to call Marshal.AllocHGlobal to allocate unmanaged memory and pass this to the plugin on both the first boot and on reloads. As long as the C++ code only uses this memory and never allocates its own memory (e.g. with malloc) then this will work out just fine. Here are some examples:

// Define a struct for whatever memory the game needs to hold
struct GameState
{
	int32_t Score;
	int32_t Level;
	// ...
};
 
// Keep a pointer to the game state.
// It doesn't need to be global like this.
GameState* gameState;
 
void PluginMain(void* memory, int32_t memorySize, bool isFirstBoot)
{
	// Treat the memory as a game state
	gameState = (GameState*)memory;
 
	// Only do startup tasks on first boot
	if (isFirstBoot)
	{
		GameObject go;
		go.AddComponent<MyGame::TargetScript>();
	}
}
 
void MyGame::TargetScript::OnCollisionEnter(Collision& param0)
{
	// Score a point in the game state when the target is hit
	gameState->Score++;
 
	// It's OK to have constants outside of the game state
	const int32_t levelThreshold = 100;
 
	// Scoring enough points goes to the next level
	if (gameState->Score > levelThreshold)
	{
		gameState->Score -= levelThreshold;
		gameState->Level++;
	}
}

Calling memory allocation functions like malloc or using static local variables isn’t allowed with this approach, but we can still use a special form of the new operator called placement new. This form skips the memory allocation part of new and just calls the constructor:

// Normal, non-placement 'new' operator
// Allocates memory then calls constructor
void PluginMain(void* memory, int32_t memorySize, bool isFirstBoot)
{
	GameState* gs = new GameState();
}
 
// Placement 'new'
// Just calls constructor
#include <new>
void PluginMain(void* memory, int32_t memorySize, bool isFirstBoot)
{
	GameState* gs = new (memory) GameState();
}

To use dynamic memory allocation with this approach, we’ll have to use an allocator that allocates only out of the memory passed to PluginMain. The global new operator and functions like malloc don’t have support for this and they’re used by the C++ standard library (a.k.a. STL), so we’ll need to avoid this if we want to reload our plugin. One alternative is Electronic Arts’ EASTL replacement which allows us to control memory allocation while still using container types like vector and list.

Because the C++ plugin is going to need plenty of memory to store all of its data, we need a way to specify how much memory for C# to allocate and give it. So we’ll change Bindings.Open to specify a memory size. The bindings layer in C++ will use the first part of this to keep track of handles and then pass the rest of it to PluginMain. BootScript now allows us to specify the memory size we want to allocate for the C++ plugin.

So now that we can hot reload the C++ plugin, how do we use it? There are two options: manual and automatic. There’s now a NativeScript > Reload menu item in the editor to reload the plugin on-demand. There’s also an “Auto Reload” checkbox on BootScript alongside an “Auto Reload Poll Time” field. Checking this box will start a coroutine on the BootScript that polls the C++ plugin file (e.g. DLL) for it’s last write time. The write time will change when the C++ plugin is compiled. This will cause the plugin to be reloaded at that point.

Manual Plugin Reload Controls
Automatic Plugin Reload Controls

Now we have the ability to modify our C++ game code and have it take effect right away in the game. For example, I’ve been testing this out by changing the bouncing ball in the example code on GitHub. I can tweak the speed or even change the code that makes it move around and it takes effect immediately in the editor window. If we can live with the memory allocation limitations, we can really cut down on the time we spend reloading the game and then playing through until we get to just the part we’re working on.

Hot reloading support is now available on the GitHub project. Feel free to let me know what you think about it or any other aspects of the project in the comments.