Skip to content

Hot Reload Overview

github-actions[bot] edited this page Sep 6, 2026 · 2 revisions

Hot Reload Overview

SparkEngine contains hot-reload code for several subsystems: shaders, scripts, game modules, materials, and assets. This page provides a unified overview of those capabilities, how they work, and which ones are actually driven by the shipped engine. Shader hot reload is not active in production: the ShaderHotReload watcher is never initialized or enabled by the engine (only registered during compilation), so its per-frame Update() is a no-op.

Relevant sources:

  • SparkEngine/Source/Graphics/ShaderHotReload.h (dedicated shader hot-reload with file watcher)
  • SparkEngine/Source/Graphics/Shader.h (shader management and compilation)
  • SparkEngine/Source/Engine/Scripting/ScriptHotReload.h (script hot-reload)
  • SparkEngine/Source/Core/ModuleHotReload.h (module hot-reload: Spark::ModuleHotReloadManager, polled by the platform frame loops; the older Engine/HotReload/ModuleHotReload singleton is no longer in the lifecycle)
  • SparkEngine/Source/Graphics/MaterialSystem.h (material hot-reload)
  • SparkEngine/Source/Graphics/AssetPipeline.h (asset hot-reload)

Table of Contents


Overview

Hot-reloading allows developers to modify assets, code, and data while the engine is running, seeing changes immediately without restarting. Each subsystem uses a similar pattern: monitor files for changes, detect modifications, reload the resource, and swap it into the running engine.

┌──────────────────────────────────────────────────────────────┐
│                     Hot Reload Systems                        │
│                                                              │
│  ┌─────────────┐  ┌──────────────┐  ┌───────────────────┐   │
│  │ Shader       │  │ Script        │  │ Module            │   │
│  │ Hot Reload   │  │ Hot Reload    │  │ Hot Reload        │   │
│  │              │  │               │  │                   │   │
│  │ FILETIME     │  │ File watcher  │  │ DLL timestamp     │   │
│  │ polling      │  │ + debounce    │  │ polling           │   │
│  │              │  │               │  │                   │   │
│  │ Recompile    │  │ Recompile     │  │ Unload → Reload   │   │
│  │ HLSL → swap  │  │ AS → swap     │  │ DLL → reinit      │   │
│  └─────────────┘  └──────────────┘  └───────────────────┘   │
│                                                              │
│  ┌─────────────┐  ┌──────────────┐                           │
│  │ Material     │  │ Asset         │                          │
│  │ Hot Reload   │  │ Hot Reload    │                          │
│  │              │  │               │                          │
│  │ Material     │  │ File change   │                          │
│  │ file watch   │  │ detection     │                          │
│  │ → reparse    │  │ → reimport    │                          │
│  └─────────────┘  └──────────────┘                           │
└──────────────────────────────────────────────────────────────┘

Shader Hot Reload

Not enabled in the shipped engine (stable-v1). ShaderHotReload::Initialize() / SetEnabled() have no production caller; the watcher only registers directories while shaders are compiled. When driven by a host or test, a modified file is now really recompiled through D3DCompile (failures are reported instead of the old fake success), but no consumer yet recreates the D3D11 shader object from the reloaded bytecode. The description below is the intended flow, not shipped behavior.

How it works:

  1. Shader tracks each shader file's FILETIME (last write time)
  2. Each frame (or on demand), HotReloadShaders() checks if any file has been modified
  3. Modified shaders are recompiled via D3DCompile
  4. On success, the old compiled shader is replaced with the new one
  5. On failure, the old shader remains active and an error is logged

Usage:

// Automatic: happens each frame if enabled
graphicsEngine.SetShaderHotReloadEnabled(true);

// Manual trigger:
graphicsEngine.HotReloadShaders();

State preservation (design intent): constant buffers, render targets, and pipeline state would be unaffected; only the compiled shader bytecode is swapped. Nothing in the shipped engine performs that swap today.


Script Hot Reload

Monitors AngelScript files for changes and recompiles modified scripts.

How it works:

  1. ScriptHotReloadManager watches directories for file changes (Modified, Created, Deleted, Renamed)
  2. A debounce timer (configurable, default ~200ms) prevents recompilation during rapid saves
  3. When the debounce timer expires, the recompile callback fires
  4. The script engine recompiles the modified file
  5. A RecompileResult reports success/failure and error details

Usage:

ScriptHotReloadManager hotReload;
hotReload.AddWatchDirectory("Scripts/");
hotReload.SetRecompileCallback([&](const std::string& file)
{
    scriptEngine.CompileFile(file);
});
hotReload.Start();

// In main loop:
hotReload.PollChanges();

State preservation: Script global variables and object state may be lost on reload. Design scripts to re-initialize from component data rather than relying on persistent script-side state.

File change types:

Type Action
Modified Recompile the changed file
Created Compile and register the new script
Deleted Unregister the script (existing instances keep running)
Renamed Treated as delete + create

Module Hot Reload

Monitors game module DLLs for changes and reloads them via the ModuleManager.

How it works:

  1. ModuleHotReloadManager polls module DLL/SO file timestamps
  2. When a change is detected, the module is unloaded (IModule::Shutdown)
  3. The new DLL is loaded and the module is re-initialized (IModule::Initialize)
  4. A callback reports success or failure per module

Usage:

ModuleHotReloadManager moduleReload;
moduleReload.SetCallback([](const std::string& name, bool success)
{
    if (success)
        LOG_INFO("Module '{}' reloaded", name);
    else
        LOG_ERROR("Module '{}' reload failed", name);
});

// In main loop:
moduleReload.PollChanges();

State preservation: Module state is lost on reload. The module must re-register its systems, components, and commands during Initialize(). Game state stored in ECS components persists across module reloads.


Material Hot Reload

The MaterialSystem monitors material definition files for changes.

How it works:

  1. Material files are tracked by timestamp
  2. On change, the material definition is re-parsed
  3. Shader permutations are recompiled if the material's shader references changed
  4. Texture bindings are updated
  5. Objects using the material see the new appearance immediately

Trigger: Automatic when shader hot-reload detects material-related changes, or via console command.


Asset Hot Reload

The AssetPipeline detects changes to source asset files and re-imports them.

How it works:

  1. Source assets (textures, models, audio) are monitored via the LocalFileCache
  2. When a source file changes, the asset transitions to a "needs reimport" state
  3. The reimport pipeline processes the asset (compression, format conversion, etc.)
  4. The runtime resource is updated with the new data

Best for: Texture iteration, model tweaks, audio adjustments during development.


Comparison

System Detection Debounce State Preserved Latency
Shader FILETIME polling (watcher not enabled in production) No n/a -- no device-shader swap implemented n/a
Script File watcher Yes (~200ms) Partial (globals lost) ~200ms + compile
Module DLL timestamp No None (re-initialize) ~1s (DLL load)
Material File timestamp No Full (seamless) ~1 frame + compile
Asset File cache No Full (resource swap) Varies (reimport)

Console Commands

Command Description
shader_reload Force reload all modified shaders
shader_reload_all Recompile all shaders regardless of timestamp
material_reload <name> Reload a specific material
asset_reimport <path> Force reimport a specific asset

Best Practices

Designing Reload-Safe Code

  1. Store state in ECS components, not in reloadable code. Script variables and module-side state will be lost. ECS components survive script and module reloads.

  2. Use Initialize()/Shutdown() for module lifecycle. Modules must cleanly unregister everything in Shutdown() and re-register in Initialize(). Leaked registrations cause crashes on reload.

  3. Keep shader interfaces stable. Changing constant buffer layouts requires all dependent code to update simultaneously. Add fields at the end of constant buffers when possible.

  4. Test with hot-reload during development. Script and module hot-reload can be exercised in debug builds; shader hot-reload has no production enable path yet.

  5. Don't rely on script constructor order. After script hot-reload, objects may initialize in a different order. Use explicit initialization functions instead.


See Also

SparkEngine Wiki

Website Entry Points

Getting Started

Engine Subsystems

Gameplay & Tools

Platform Support

Graphics

Advanced

Development & Process

Research & Analysis

Engineering Notes & Audits

Specifications

Reference

Clone this wiki locally