# How Shader Hot Reloading Works in Equilibrium Engine Using x-watcher

> Learn how Equilibrium Engine achieves shader hot reloading using x-watcher to detect changes, update entities, and recompile BGFX programs without engine restarts.

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

---

**The Equilibrium Engine implements shader hot reloading by combining ECS components with the x-watcher file monitoring library to detect shader changes, flag entities for updates, and recompile BGFX programs without restarting the engine.**

Shader iteration is a critical workflow in graphics development. The Equilibrium Engine, an open-source ECS-based game engine, solves this by integrating the **x-watcher** library into its component system. This architecture allows vertex and fragment shaders to be recompiled and swapped at runtime while the engine continues rendering, eliminating the need to restart the application for every shader tweak.

## The Three-Subsystem Architecture

The implementation relies on three tightly coupled subsystems working across the main and background threads:

| Subsystem | Role |
|-----------|------|
| **ECS Components** (`HotReloadableShader`, `GfxResource`, `ReloadShader`, `FileWatcher`) | Store shader file paths, BGFX program handles, and flags that signal when a reload is required. |
| **x-watcher** | Runs a background thread monitoring vertex and fragment shader files. When a file is modified, it invokes a C callback on the main thread. |
| **Hot-reload system** (`HotReloadShaders` observer) | Reacts to the `ReloadShader` flag, recompiles the shader, destroys the old BGFX program, and updates the component that uses it. |

## Registering Shader Files for Monitoring

When a shader program is created, the engine must register its source files with the watcher. This happens automatically through the `create_program` macro defined in [`equilibrium/utils/bgfx_utils.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/utils/bgfx_utils.h).

### The create_program Macro

The macro creates a `HotReloadableShader` component that records the absolute paths of the vertex and fragment shader files:

```c
ecs_set(program_entity.world, program_entity.handle,
        HotReloadableShader,
        {(entity_t){entity.handle, entity.world},
         ecs_id(T), ECS_SIZEOF(T), offsetof(T, member_name),
         vertex_handle.file_path, fragment_handle.file_path});

```

*Source:* [`equilibrium/utils/bgfx_utils.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/utils/bgfx_utils.h) – lines 58‑61

### InitHotReloadShaders Observer

When the `HotReloadableShader` component is attached, the `InitHotReloadShaders` observer (triggered by `EcsOnSet`) runs. It creates two `xWatcher_reference` objects and registers them with the global `FileWatcher` component:

```c
xWatcher_reference vertex, fragment;
vertex.path = shader.vertex_shader_name;
fragment.path = shader.fragment_shader_name;
vertex.callback_func = fragment.callback_func = callback_func;
fragment.context = vertex.context = it->entities[i];
fragment.additional_data = vertex.additional_data = it->world;

xWatcher_appendFile(watcher->data, &vertex);
xWatcher_appendFile(watcher->data, &fragment);

```

*Source:* [`equilibrium/systems/rendering/gfx_resource_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/systems/rendering/gfx_resource_system.c) – lines 94‑106

The `FileWatcher` component is created once when the system imports:

```c
FileWatcher watcher = {xWatcher_create()};
ecs_set_ptr(world, ecs_id(FileWatcher), FileWatcher, &watcher);

```

*Source:* [`equilibrium/systems/rendering/gfx_resource_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/systems/rendering/gfx_resource_system.c) – lines 89‑91

## Detecting File Changes with x-watcher

The x-watcher library runs a dedicated background thread (using `pthread` on Linux or `OVERLAPPED` I/O on Windows) that monitors the file system. When a watched shader file is modified, it invokes the registered callback on the main thread.

### The Callback Function

The `callback_func` receives the file event and entity context, then sets the `ReloadShader` component to flag the entity for reloading:

```c
void callback_func(XWATCHER_FILE_EVENT event,
                   const char *path,
                   int context,
                   void *data) {
    if (event == XWATCHER_FILE_MODIFIED) {
        entity_t shader_entity = (entity_t){context, data};
        ecs_set(shader_entity.world,
                shader_entity.handle,
                ReloadShader, {});
    }
}

```

*Source:* [`equilibrium/systems/rendering/gfx_resource_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/systems/rendering/gfx_resource_system.c) – lines 22‑42

This design keeps the hot-reload work on the main thread where the ECS can safely update resources, while the heavy file-system monitoring happens in the background.

## Performing the Hot Reload

The `HotReloadShaders` observer is triggered whenever an entity has the three components `HotReloadableShader`, `GfxResource`, and `ReloadShader` (using `EcsOnSet`):

```c
ECS_OBSERVER(world, HotReloadShaders,
             EcsOnSet,
             HotReloadableShader,
             GfxResource,
             ReloadShader);

```

*Source:* [`equilibrium/systems/rendering/gfx_resource_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/systems/rendering/gfx_resource_system.c) – lines 96‑98

Inside the observer, the engine performs four critical steps:

1. **Reload shader binaries** using `shader_load` to read the updated files from disk and create new BGFX shader handles.
2. **Destroy the old BGFX program** using `bgfx_destroy_program`.
3. **Create a new program** by linking the new vertex and fragment shaders with `bgfx_create_program`.
4. **Update the component data** by writing the new program handle back into the owning component using pointer arithmetic based on the stored field offset.

```c
ShaderHandle vertex_handle   = shader_load(entity.world,
                                          shader.vertex_shader_name, true);
ShaderHandle fragment_handle = shader_load(entity.world,
                                          shader.fragment_shader_name, true);

bgfx_destroy_program((bgfx_program_handle_t){resource.handle});
resource.handle = bgfx_create_program(vertex_handle.handle,
                                      fragment_handle.handle,
                                      true).idx;

void *component = ecs_get_mut_id(shader.shader_entity.world,
                                 shader.shader_entity.handle,
                                 shader.component_id);
*(bgfx_program_handle_t *)(component + shader.filed_offset) =
    (bgfx_program_handle_t){resource.handle};

```

*Source:* [`equilibrium/systems/rendering/gfx_resource_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/systems/rendering/gfx_resource_system.c) – lines 139‑159

Finally, the observer logs the successful reload:

```c
ecs_trace("Hot reload successful for %s | %s shader program: %s",
          shader.vertex_shader_name,
          shader.fragment_shader_name,
          entity_get_name(entity));

```

*Source:* [`equilibrium/systems/rendering/gfx_resource_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/systems/rendering/gfx_resource_system.c) – lines 167‑168

## Frame-by-Frame Watcher Updates

To ensure file system events are processed, the `UpdateFileWatcher` system runs every frame (tagged with the custom `OnInput` phase). It simply forwards the update call to the underlying `xWatcherUpdate` function:

```c
static void UpdateFileWatcher(ecs_iter_t *it) {
    FileWatcher *watcher = ecs_field(it, FileWatcher, 1);
    xWatcherUpdate(watcher->data);
}

```

*Source:* [`equilibrium/systems/rendering/gfx_resource_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/systems/rendering/gfx_resource_system.c) – lines 74‑76

This polling approach ensures that the background thread's events are dispatched to the main thread callbacks without blocking the render loop.

## Summary

- **ECS-driven architecture**: Shader hot reloading in Equilibrium Engine uses components (`HotReloadableShader`, `ReloadShader`, `FileWatcher`) to track state and trigger updates.
- **x-watcher integration**: The third-party x-watcher library provides cross-platform file monitoring via background threads, invoking callbacks when shader files change.
- **Deferred reload pattern**: File change callbacks only set a flag (`ReloadShader`), keeping heavy recompilation work on the main thread where ECS operations are safe.
- **Automatic handle management**: The `HotReloadShaders` observer destroys old BGFX programs, creates new ones from reloaded binaries, and updates component memory using stored field offsets.
- **Continuous polling**: The `UpdateFileWatcher` system processes file events every frame, ensuring the background thread remains responsive without blocking rendering.

## Frequently Asked Questions

### What is x-watcher and why does Equilibrium Engine use it?

**x-watcher is a lightweight, cross-platform C library for file system monitoring** that abstracts platform-specific APIs like Linux inotify and Windows `OVERLAPPED` I/O. Equilibrium Engine uses it because it provides non-blocking file watching via background threads, allowing the engine to detect shader modifications without freezing the main render loop or implementing platform-specific code.

### How does the engine avoid blocking the render loop during file monitoring?

**File monitoring runs on a background thread managed by x-watcher**, while the main thread continues rendering. When a file changes, x-watcher invokes a callback that merely sets an empty `ReloadShader` component on the affected entity. The actual heavy work—reading files, compiling shaders, and creating new BGFX programs—happens later on the main thread when the `HotReloadShaders` observer runs, ensuring thread safety with ECS operations.

### What happens to existing shader programs when a file is modified?

**The old BGFX program is destroyed and replaced atomically from the perspective of the rendering system.** When the `HotReloadShaders` observer triggers, it calls `bgfx_destroy_program` on the existing handle, loads the new shader binaries using `shader_load`, creates a new program with `bgfx_create_program`, and writes the new handle back into the component's memory. The next frame automatically uses the updated program without requiring entity recreation.

### Can hot reloading be disabled for specific shaders?

**Yes, by omitting the `HotReloadableShader` component during program creation.** The `create_program` macro in [`bgfx_utils.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/bgfx_utils.h) automatically registers shaders for hot reloading, but developers can manually create BGFX programs using `bgfx_create_program` without setting the `HotReloadableShader` component. Without this component, no file watcher references are created, and the shader will not participate in the hot-reload pipeline.