# How Script Hot Reloading with cr.h Works in the Equilibrium Engine

> Learn how script hot reloading with cr.h works in the Equilibrium Engine. Discover runtime library swapping, static state preservation with CR_STATE, and ECS module lifecycle management.

- Repository: [Alexander/equilibriumengine](https://github.com/clibequilibrium/equilibriumengine)
- Tags: internals
- Published: 2026-02-27

---

**The Equilibrium Engine implements script hot reloading with cr.h by compiling game logic into separate shared libraries that the host application loads, monitors, and swaps at runtime while preserving static state via the `CR_STATE` macro and managing ECS module lifecycle through the `HotReloadableModule` component.**

The Equilibrium Engine (clibequilibrium/equilibriumengine) leverages the single-header cr.h library to enable zero-downtime iteration on C code. This article examines the practical mechanics of script hot reloading with cr.h, from host initialization to state persistence and ECS integration.

## The Architecture of cr.h Hot Reloading

The cr.h library operates on a host-plugin model. The host executable manages the lifecycle of shared libraries (plugins), while each plugin implements a standardized entry point that cr.h invokes during load, update, and unload operations.

### Defining the Host Application

The host program must define `CR_HOST` before including [`cr.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/cr.h) to activate the host-side API. This macro enables functions like `cr_plugin_open`, `cr_plugin_update`, and `cr_plugin_close`, and activates the `CR_STATE` persistence mechanism.

In `launcher/launcher.cc`, the host initialization follows this pattern:

```c
#define CR_HOST
#include <cr.h>
#include <engine.h>

int main() {
    cr_plugin sandbox;
    world_t *world = engine_init(...).world;
    
    sandbox.userdata = world;
    cr_plugin_open(sandbox, CR_PLUGIN("sandbox"));
    
    while (engine_running) {
        cr_plugin_update(sandbox);  // Checks for .so changes
    }
    
    cr_plugin_close(sandbox);
}

```

### Creating Reloadable Plugins

Each script module compiles to a separate shared library (e.g., `libsandbox.so`) that exports a single entry point: `cr_main`. The plugin implements `CR_EXPORT int cr_main(struct cr_plugin *ctx, enum cr_op operation)` to handle lifecycle events.

In [`launcher/sandbox/main.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/launcher/sandbox/main.c), the plugin structure appears as:

```c
#include <cr.h>
#include <equilibrium.h>

static bool CR_STATE initialized = false;

CR_EXPORT int cr_main(struct cr_plugin *ctx, enum cr_op op) {
    if (op == CR_CLOSE) return 0;
    
    world_t *world = ctx->userdata;
    
    if (!initialized) {
        ECS_IMPORT(world, TransformSystem);
        initialized = true;
    } else {
        CHECK_IF_PLUGIN_RELOADED(ctx, op);
        ECS_IMPORT_HOT_RELOADABLE(ctx, MyHotSystem);
    }
    return 0;
}

```

## Preserving State Across Reloads

Static variables declared with the `CR_STATE` macro automatically persist across plugin reloads. When `cr_plugin_update` detects a changed shared library, it unloads the old binary, loads the new version, and copies the previous memory contents back into the new static variables.

In [`launcher/sandbox/main.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/launcher/sandbox/main.c), line 5 demonstrates this pattern:

```c
static bool CR_STATE initialized = false;

```

This ensures that initialization logic runs only once during the first load, not after every reload.

## Integrating with the ECS World

The Equilibrium Engine extends cr.h with ECS-specific cleanup logic through the `HotReloadableModule` component defined in [`equilibrium/components/hot_reloadable_module.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/components/hot_reloadable_module.h).

### Tracking Reloadable Modules

When a plugin uses `ECS_IMPORT_HOT_RELOADABLE(ctx, ModuleName)`, the macro creates an entity with a `HotReloadableModule` component that stores the plugin's `cr_plugin` pointer and module name. This registration allows the engine to identify which ECS entities belong to which shared library.

### Automatic Cleanup on Reload

The `reload_modules` function iterates over all `HotReloadableModule` entities associated with a reloading plugin. It deletes the previous entity (and its resources) via `ecs_delete` before the new plugin version re-registers its components. This prevents stale pointers from persisting in the ECS world.

From [`equilibrium/components/hot_reloadable_module.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/components/hot_reloadable_module.h) (lines 21-57), the cleanup logic ensures that the ECS world remains consistent even as code modules are swapped at runtime.

## Practical Implementation: Code Examples

### Minimal Hot-Reloadable Plugin

This example from [`launcher/sandbox/main.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/launcher/sandbox/main.c) demonstrates the complete lifecycle of a reloadable script:

```c
/* sandbox/main.c */
#include <equilibrium.h>
#include <cr.h>

static bool CR_STATE initialized = false;

CR_EXPORT int cr_main(struct cr_plugin *ctx, enum cr_op op) {
    if (op == CR_CLOSE) return 0;
    
    world_t *world = ctx->userdata;
    
    if (!initialized) {
        ECS_IMPORT(world, TransformSystem);
        ECS_IMPORT(world, SdlSystem);
        initialized = true;
    } else {
        CHECK_IF_PLUGIN_RELOADED(ctx, op);
        ECS_IMPORT_HOT_RELOADABLE(ctx, MyHotSystem);
    }
    return 0;
}

```

### Declaring a Hot-Reloadable ECS System

Module authors use the `ECS_IMPORT_HOT_RELOADABLE` macro to register systems that require cleanup on reload:

```c
/* my_system.c */
#include <cr.h>
#include "hot_reloadable_module.h"

CR_EXPORT void cr_main(struct cr_plugin *ctx, enum cr_op op) {
    if (op == CR_LOAD) {
        ECS_IMPORT_HOT_RELOADABLE(ctx, MySystem);
    }
}

```

The macro automatically creates the tracking entity necessary for the `reload_modules` cleanup routine.

### Host-Side Plugin Management

The host application in `launcher/launcher.cc` orchestrates the reload cycle:

```c
/* launcher/launcher.cc */
#define CR_HOST
#include <cr.h>
#include <engine.h>

int main() {
    cr_plugin sandbox;
    world_t *world = engine_init(...).world;
    
    sandbox.userdata = world;
    cr_plugin_open(sandbox, CR_PLUGIN("sandbox"));
    
    while (engine_running) {
        cr_plugin_update(sandbox);
        engine_tick();
    }
    
    cr_plugin_close(sandbox);
    return 0;
}

```

## Key Source Files

- **`launcher/launcher.cc`** – Host program that defines `CR_HOST`, initializes `cr_plugin` objects, and drives the update loop via `cr_plugin_update`.

- **[`launcher/sandbox/main.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/launcher/sandbox/main.c)** – Reference implementation of a reloadable plugin demonstrating `CR_STATE`, `CHECK_IF_PLUGIN_RELOADED`, and `ECS_IMPORT_HOT_RELOADABLE`.

- **[`equilibrium/components/hot_reloadable_module.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/components/hot_reloadable_module.h)** – Defines the `HotReloadableModule` component and the `reload_modules` function that manages ECS cleanup during script reloads.

- **[`3rdparty/cr/cr.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/3rdparty/cr/cr.h)** – The single-header hot-reload runtime providing `cr_plugin_open`, `cr_plugin_update`, `CR_STATE`, and the plugin/host API.

## Summary

- **Script hot reloading with cr.h** requires compiling game logic as separate shared libraries that export a `cr_main` entry point.

- The **host application** defines `CR_HOST` before including [`cr.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/cr.h) and manages plugin lifecycle through `cr_plugin_open`, `cr_plugin_update`, and `cr_plugin_close`.

- **State persistence** across reloads is achieved via the `CR_STATE` macro, which automatically preserves static variable values when the shared library is swapped.

- The **ECS integration** uses the `HotReloadableModule` component to track which entities belong to which plugin, ensuring `reload_modules` can delete stale resources before the new code re-registers its systems.

- Developers trigger reloads by overwriting the shared library file; `cr_plugin_update` detects the change and performs the swap without restarting the host process.

## Frequently Asked Questions

### How does cr.h detect when a shared library has changed?

The `cr_plugin_update` function checks the file modification time of the plugin's shared library on disk. If the timestamp differs from the currently loaded version, [`cr.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/cr.h) unloads the old library, loads the new binary, restores `CR_STATE` variables, and invokes `cr_main` with the `CR_LOAD` operation.

### What happens to static variables that are not marked with CR_STATE?

Static variables without the `CR_STATE` macro are reset to their initial values when the plugin reloads. Only variables explicitly declared with `CR_STATE` (e.g., `static int CR_STATE counter = 0;`) have their memory preserved across the unload/load cycle.

### Can multiple plugins be hot-reloaded simultaneously?

Yes. The host application can create multiple `cr_plugin` instances (e.g., for `sandbox`, `editor`, and `engine-simulation` modules). Each plugin is updated independently via `cr_plugin_update`, allowing developers to reload specific systems without affecting others.

### How does the ECS world prevent stale pointers after a reload?

The `HotReloadableModule` component tracks which ECS entities belong to which plugin. When `cr_plugin_update` triggers a reload, the `reload_modules` function iterates over these entities and calls `ecs_delete` to remove the old module's resources before the new plugin version re-registers its components.