Skip to main content

Hot-Reloading

In this chapter you will learn about Jenova Framework Hot-Reloading System.

Banner_SakuraFeature

What is Hot-Reloading?

C++ is a pre-compiled language, meaning it runs from pre-built bytecode. When you build your C++ code, it is compiled into machine code and the linker packs it into a module. Once a module is loaded, it cannot be modified unless the process it's running in is closed. This means that any changes to C++ code require a rebuild while the application is not running, followed by a restart.

On the other hand, scripting languages allow for quicker iteration, letting you apply changes and immediately see their effects without restarting the game engine. This makes development more efficient but also highlights a bottleneck when using C/C++ for game development.

Hot-reloading allows developers to modify code without restarting the application. In Jenova Framework, you can Hot-Reload your C++ code just like a scripting language thanks to the powerful hot-reloading system called Sakura.

Caution

Jenova Hot-Reloading is supported on all Godot forks. However, Hot-Reloading Nested Extensions requires a compatible distribution. Currently, Jenova Hot-Reloading system with Nested Extensions is supported only on the following engine distributions :

Sakura System

Sakura Hot-Reloading System (SHS) allows you to edit C++ scripts and see changes on-the-fly. This powerful system brings real scripting flexibility to your pipeline, accelerating development workflows. Sakura offers hot-reloading for both Active and Passive C++ Scripts across all engine distributions. However, hot-reloading Nested Extensions requires a compatible build.

Sakura provides multiple hot-reloading methods :

  • Script Change Trigger Mode : In this mode, every change to C++ scripts from any external or internal editor triggers a system-wide build and hot-reload. This mode is the best choice for Visual Studio Code and Neovim workflow.
  • Script Change Reload Mode : Similar to the first mode, but only gets triggered when the engine issues a reload on script files.
  • Watchdog Invoke/Bootstrap Mode : Designed for custom build systems such as Visual Studio IDE. In this mode, scripts are compiled outside the Jenova build system. After a successful compilation, the compiled binary is parsed and only a hot-reload is issued.

📖 Learn about External Changes Trigger Modes

Enabling Hot-Reload

By default, Hot-Reloading is disabled. To enable it, navigate to Editor > Editor Settings... > Jenova

GuideImage_VisualStudioExporterPrompt

Jenova Editor Settings, Enabling Sakura Hot-Reloading System.

In Jenova settings, change External Changes Trigger Mode to one of the hot-reloading methods explained earlier.
Additionally, to enable hot-reloading at runtime activate the Use Hot-Reload at Runtime setting.

📝 Note : If you're planning to use Visual Studio for builds, set the trigger mode to Bootstrap Project on Watchdog Invoke.

Using Hot-Reload

To use the hot-reloading feature in C++ scripts, simply run your game in debug mode using the Play Button, make changes to the scripts and save them. You will see the updates instantly. To use hot-reloading in Nested Extensions, you need to use Sakura functions from JenovaSDK.
This topic is covered in the Nested Extensions chapter.

Important Notes
  • If you're developing Nested Extensions that only work at runtime without hot-reloading, avoid using Sakura functions.
  • If you're working on Nested Resource Extensions assigned to a Nested Node Extension, close scenes, build, and reopen them.

Global Memory/Variables

While using the Sakura Hot-Reload System in C++ Scripts, all allocated objects and variables defined in C++ scripts will be lost. To solve this issue, JenovaSDK provides a persistent cross-reload storage system that allows allocation of global pointers, memory regions, and variables.

Using global memory/variables is possible through the following JenovaSDK functions :

// Global Memory API
void* GetGlobalPointer(MemoryID id);
void* SetGlobalPointer(MemoryID id, void* ptr);
void DeleteGlobalPointer(MemoryID id);
void* AllocateGlobalMemory(MemoryID id, size_t size);
void FreeGlobalMemory(MemoryID id);
template <typename T> T* GlobalPointer(MemoryID id);
template <typename T> T GlobalGet(MemoryID id);
template <typename T> void GlobalSet(MemoryID id, const T& newValue);

// Global Variables API
Variant GetGlobalVariable(VariableID id);
void SetGlobalVariable(VariableID id, Variant var);
template <typename T> T GlobalVariable(VariableID id);

Cross-Reload API

  • Global Memory storage uses a MemoryID unique identifier to manage global pointers and memory regions.
  • Global Variable storage uses a VariableID unique identifier to store global variables.
  • Global Variables only support standard engine types compatible with Variant.
  • Functions GetGlobalPointer, SetGlobalPointer, and DeleteGlobalPointer allow storing, retrieving, and deleting object pointers in global storage. When using SetGlobalPointer, the previous stored pointer is returned, allowing it to be freed.
  • Functions AllocateGlobalMemory and FreeGlobalMemory provide global memory allocation and deallocation for raw data.
  • Template functions GlobalPointer, GlobalGet, and GlobalSet simplify memory access. GlobalPointer retrieves a global pointer and casts it to a specified type. GlobalGet and GlobalSet directly access and modify objects by dereferencing global pointers.
  • Functions GetGlobalVariable and SetGlobalVariable store and retrieve a Variant in global storage.
  • Template function GlobalVariable enables quick access to a global variable by automatically casting it to a specified type.

Using Global Storage

Here's an example demonstrating usage of the Global Storage feature :

Jenova C++ Script
// Define Static Global Storage IDs
constexpr const char* playerHandleID = "player-handle";
constexpr const char* isControllerActiveID = "is-controller-active";

// Global Pointers
lobby::PlayerHandle* playerHandle = nullptr;

// Script Block
JENOVA_SCRIPT_BEGIN

// Node Events
void OnAwake()
{
// Create/Allocate Global Player Handle
playerHandle = GlobalPointer<lobby::PlayerHandle>(playerHandleID);
if (playerHandle == nullptr)
{
playerHandle = CreateNewPlayer();
SetGlobalPointer(playerHandleID, playerHandle);
}

// Create Global Variables
SetGlobalVariable(isControllerActiveID, false);
}
void OnDestroy(Caller* instance)
{
// Release Global Player Handle
if (playerHandle)
{
if (!playerHandle->Release()) Alert("Error : Failed to Release Player Handle.");
delete playerHandle;
DeleteGlobalPointer(playerHandle);
}
}
void OnReady()
{
// Retrive Global Player Handle
playerHandle = GlobalPointer<lobby::PlayerHandle>(playerHandleID);

// Initialize Player
if (playerHandle->Initialize(lobby::GetPlayeName()))
{
SetGlobalVariable(isControllerActiveID, true);
};

}
void OnProcess(double delta)
{
// Handle Player Data If It's Valid
if (GlobalVariable<bool>(isControllerActiveID))
{
...
}
}

JENOVA_SCRIPT_END