Skip to content

AHotCodeReload#

Hot code reload and rapid development

Header:#include <AUI/Remote/AHotCodeReload.h>
CMake:aui_link(my_target PUBLIC aui::remote_tools)

Detailed Description#

The development workflow for UI applications can be repetitive and slow, commonly requiring you to:

  1. Write or modify code.
  2. Compile.
  3. Run the application.
  4. Navigate to the affected UI or feature.
  5. Check for expected results.
  6. Repeat as needed.

Steps 2-5 are very slow and exhausting. This page briefly describes ways to reduce turnaround time during development of your application.

Precompiled Headers#

Precompiled headers (PCH) can significantly speed up C++ compilation by parsing common headers only once. They are especially beneficial for large projects with many dependencies.

How to use precompiled headers with CMake:

  • Create a header file (e.g. pch.h) containing your frequently used includes:
pch.h
#include <AUI/Platform/AWindow.h>
#include <AUI/Util/UIBuildingHelpers.h>
// Add other common headers here
  • In your CMakeLists.txt:
CMakeLists.txt
# CMake 3.16 or newer recommended for target_precompile_headers
add_library(my_target ...)
target_precompile_headers(my_target PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/pch.h")

Make sure all source files include pch.h (either directly or via your main header). For more complex setups, review CMake’s documentation on precompiled headers.

The AHotCodeReload Class#

AHotCodeReload enables runtime reloading of object files, injecting new function code into a live application. This gives rapid feedback and reduces iteration time during development.

Figure 1. Hot reload with state preservation.
Platform Supported
Windows No
Linux Yes
macOS No
Android No
iOS No

It works by observing changes on object files, which are generated by compiler and picked up by linker. AHotCodeReload implements its own linker designed specifically to load these intermediate build files and link them against a program which is already running. Then, it scans for newly loaded functions and places hooks on old versions of these functions, so the newer version is called.

Unlike classic way of implementing hot code reload with dynamic libraries, approach implemented in AUI does not interfere with build process nor require special project structure with complicated state management. It does not replace older symbols with newer ones, it keeps them both in memory, so referencing both is safe.

Setup#

Link to remote_tools module: aui_link(my_target PUBLIC aui::remote_tools)

Add object files to track:

1
2
3
4
5
6
7
8
aui_executable(aui.example.hot_code_reload)
aui_link(aui.example.hot_code_reload PRIVATE aui::core aui::views)

if (CMAKE_BUILD_TYPE STREQUAL "Debug")
    # recommended: restrict remote_tools to debug builds only
    aui_link(aui.example.hot_code_reload PRIVATE aui::remote_tools)
    aui_enable_hotswap(aui.example.hot_code_reload)
endif()
#include <AUI/Platform/Entry.h>
#if __has_include(<AUI/Remote/AHotCodeReload.h>)
#include <AUI/Remote/AHotCodeReload.h>
#endif
#include "MyWindow.h"

AUI_ENTRY {
#if __has_include(<AUI/Remote/AHotCodeReload.h>)
    // hardcoded path
    AHotCodeReload::inst().addFile("/home/projects/aui/cmake-build-debug/CMakeFiles/aui.example.hot_code_reload.dir/src/MyWindow.cpp.o");
#endif

    _new<MyWindow>()->show();
    return 0;
}

Observe AHotCodeReload::inst().patchEnd to re-call patched functions to see the effect. For UI, extract setContents call to a member function and call it in constructor. Also, connect to AHotCodeReload::inst().patchEnd to re-initialize UI components after the patch. Use __has_include to check if aui::remote_tools was added in your CMakeLists.txt.

#if __has_include(<AUI/Remote/AHotCodeReload.h>)
#include <AUI/Remote/AHotCodeReload.h>
#endif


MyWindow::MyWindow() : AWindow("Hot code reload", 600_dp, 300_dp) {
    inflate();
#if __has_include(<AUI/Remote/AHotCodeReload.h>)
    AObject::connect(AHotCodeReload::inst().patchEnd, me::inflate);
#endif
}

void MyWindow::inflate() {
    // you can update any things here because inflate is connected to AHotCodeReload::inst().patchEnd.
    setContents(
        Vertical {
          _new<TestRender>(),
          Label { "Hello, world" },
          _new<AButton>("Click me") AUI_LET {
                  AObject::connect(it->clicked, AObject::GENERIC_OBSERVER, [] {
                      AMessageBox::show(nullptr, "Hello", "Hello, world!");
                  });
              },
          CheckBox {
            .checked = AUI_REACT(mChecked),
            .onCheckedChange = [this](bool v) { mChecked = v; },
            .content = Label { "Try check me" },
          },
        } AUI_WITH_STYLE { LayoutSpacing { 4_dp } });
}

Usage#

  1. Launch your application.
  2. Edit your source code.
  3. Compile. (You may skip the linking step.). In IDEs, it can be accomplished by shortcuts. CLion: Ctrl+9.
  4. Wait for patching to complete.
  5. Repeat steps 2–4 as needed.

Limitations#

  1. AHotCodeReload performs some safety checks, but a patch could still break your application.
  2. Only functions are hooked. New function versions will reflect changes to certain variables (see below)
  3. For changes to become effective, the patched functions need to be called (e.g. by re-triggering relevant UI actions or re-calling UI inflate routines setContents). See setup.
  4. Do not modify struct or class layouts, function signatures; the system cannot reliably detect or adjust for such changes.

What will change (in the new version of function)

  • function logic
  • static/thread_local/global constants, literals ("string literals", 123, true, nullptr)
  • static/thread_local/global non-constant variables initialized with zeroes, which will be reinitialized with zeroes at the time of patch, i.e., static int counter = 0; will reset to zero. (because those are stored in .bss which linker has to create a new instance of)

Think of it this way: when you update the source code of a function, these changes will be reflected in the new version of the function and take effect the next time that function is called. The modification is as direct as changing the text of the function itself.

What will not change

  • static/thread_local/global non-constant variables initialized with non-zero values, which will use the values prior patch. static int counter = 1; will not be reset to 1
  • any values that were allocated on the heap or stack, including class fields. Exploit this to preserve state of your application

Best Practices#

  • Use hot code reloading for iterative development on function logic and UI changes.
  • Do not rely on this system for data or memory layout changes.
  • Consider combining hot reload with precompiled headers to further minimize turnaround time.

Public fields and Signals#


patchBegin#

emits<> patchBegin

Signal emitted when a binary patch operation begins.

This signal is emitted before the hot code reload system starts patching the application with new code. Can be used to prepare the application state for the upcoming changes.


patchEnd#

emits<> patchEnd

Signal emitted when a binary patch operation completes.

This signal is emitted after the hot code reload system has finished patching the application. Can be used to refresh UI or reinitialize components after the changes have been applied.

Public Methods#

addFile#


void AHotCodeReload::addFile(AString path)

Add a single object file to be observed.

Arguments
path
Path to the object file to watch for changes.

Adds a single object file to the hot code reload system's watch list. When changes are detected in this file, it will be automatically reloaded.

This method will log errors if file watching fails rather than throwing exceptions.

addFiles#


void AHotCodeReload::addFiles(AStringView paths)

Add object files to be observed.

Arguments
paths
paths to object files. Multiple paths can be specified by separating them with a semicolon.

Object files are generated by the compiler and picked up by the linker.

Example:

AHotCodeReload::inst().addFiles("C:/path/to/object1.obj;C:/path/to/object2.obj");

Normally, you do not need to call this method directly. It is called automatically by aui_enable_hotswap CMake command.

inst#


static AHotCodeReload& AHotCodeReload::inst()

Gets the singleton instance of AHotCodeReload.

Returns
Reference to the singleton instance.

This method implements the singleton pattern, ensuring only one instance of AHotCodeReload exists. The instance is created on first access and persists throughout the application lifetime.

Example:

AHotCodeReload::inst().addFiles("path/to/object.o");

loadBinary#


void AHotCodeReload::loadBinary(const APath& path)

Load and patch a binary object file at runtime.

Arguments
path
Path to the object file to load.

This method loads a binary object file and patches the running application with the new code. It's typically used internally by the hot code reload system but can be called manually if needed.

The method is thread-safe and will schedule the reload on the main thread.