Project 4: Hot-Reload Development Server

Build a C application that reloads its logic from a shared library without restarting.

Quick Reference

Attribute Value
Difficulty Expert
Time Estimate 2-4 weeks
Language C
Prerequisites Strong C, dynamic loading, build tooling
Key Topics dlopen lifecycle, state preservation, file watching

1. Learning Objectives

By completing this project, you will:

  1. Separate runtime state from reloadable logic.
  2. Implement a hot-reload loop using dlclose and dlopen.
  3. Detect source changes and rebuild shared libraries automatically.
  4. Handle API changes and versioning safely.

2. Theoretical Foundation

2.1 Core Concepts

  • State vs logic: Persistent state must live outside the reloadable library.
  • Library lifecycle: Unload, rebuild, and reload without leaking resources.
  • ABI compatibility: The host and library must agree on structures and function signatures.

2.2 Why This Matters

Hot-reload is how game engines and live systems iterate quickly. It tests your understanding of dynamic loading under stress.

2.3 Historical Context / Background

Live code editing became popular through game development workflows such as Handmade Hero. It depends on predictable shared library behavior.

2.4 Common Misconceptions

  • “dlclose frees everything”: Only the library code is unloaded; state leaks remain.
  • “Any change is safe”: Changing struct layouts can corrupt running state.

3. Project Specification

3.1 What You Will Build

A server-like application that loads its game logic from a shared library, monitors source changes, recompiles, and reloads new logic without restarting.

3.2 Functional Requirements

  1. Core state: Keep state in the host binary.
  2. Reloadable module: Export init, update, and shutdown functions.
  3. File watch: Detect changes and rebuild the .so/.dylib.
  4. Hot reload: Swap in new code while preserving state.

3.3 Non-Functional Requirements

  • Reliability: Reload should not crash the host.
  • Usability: Clear logging when reload happens.
  • Performance: Reload latency should be acceptable (<1s for small builds).

3.4 Example Usage / Output

$ ./hotreload-server
[server] state: score=0
[hot-reload] change detected, rebuilding...
[hot-reload] loaded game_logic.so
[server] state: score=1

3.5 Real World Outcome

You edit game_logic.c, save, and see the running server reload logic while keeping state intact:

$ ./hotreload-server
[server] state: score=0
[hot-reload] change detected, rebuilding...
[hot-reload] loaded game_logic.so
[server] state: score=0  # state preserved

4. Solution Architecture

4.1 High-Level Design

┌──────────────┐     ┌─────────────────┐     ┌──────────────┐
│ host process │────▶│ reloadable .so  │────▶│ update loop  │
└──────────────┘     └─────────────────┘     └──────────────┘
        ▲                    │
        └──── state (heap) ──┘

4.2 Key Components

Component Responsibility Key Decisions
Host core Owns state and reload loop Stable ABI
Reloadable lib Implements logic Fixed entry points
File watcher Detects changes inotify/polling

4.3 Data Structures

typedef struct {
    int score;
    float delta_time;
} game_state_t;

typedef struct {
    int api_version;
    void (*init)(game_state_t *state);
    void (*update)(game_state_t *state);
    void (*shutdown)(game_state_t *state);
} game_api_t;

4.4 Algorithm Overview

Key Algorithm: Hot reload loop

  1. Load library and get API.
  2. Run update loop, monitor timestamps.
  3. On change: unload, rebuild, reload, rebind API.

Complexity Analysis:

  • Time: O(1) per update; reload cost depends on build time.
  • Space: O(1) for state and function pointers.

5. Implementation Guide

5.1 Development Environment Setup

gcc --version
make --version

5.2 Project Structure

project-root/
├── host/
│   ├── main.c
│   ├── reload.c
│   └── state.c
├── game_logic/
│   └── game_logic.c
└── Makefile

5.3 The Core Question You’re Answering

“How can I replace code while a program keeps running and state stays intact?”

5.4 Concepts You Must Understand First

Stop and research these before coding:

  1. Library lifecycle
    • dlopen, dlsym, dlclose
  2. State ownership
    • State must live in the host, not the library
  3. ABI changes
    • Add fields with version checks

5.5 Questions to Guide Your Design

  1. Which data must survive reloads?
  2. How will you version API changes?
  3. How will you rebuild safely without partial files?

5.6 Thinking Exercise

If you change the layout of game_state_t, how can the host detect and reject the new library?

5.7 The Interview Questions They’ll Ask

  1. Why must state live outside the reloadable library?
  2. How do you avoid crashes if the library fails to load?
  3. What are the risks of changing structs across reloads?

5.8 Hints in Layers

Hint 1: Separate files

  • Keep game_state_t in a shared header.

Hint 2: Version guard

  • Add api_version to the API struct.

Hint 3: Safe reload

  • Load the new library before unloading the old one.

5.9 Books That Will Help

Topic Book Chapter
Hot reload patterns “Game Programming Patterns” architecture sections
Dynamic loading TLPI Ch. 42
File watching TLPI Ch. 19

5.10 Implementation Phases

Phase 1: Foundation (1 week)

Goals:

  • Build a reloadable logic module.

Tasks:

  1. Define game_api_t and entry points.
  2. Load the library and call update.

Checkpoint: The host calls the logic successfully.

Phase 2: Core Functionality (1 week)

Goals:

  • Add reload and file monitoring.

Tasks:

  1. Track file timestamps or use inotify.
  2. Unload and reload the library.

Checkpoint: Logic changes are reflected without restart.

Phase 3: Polish & Edge Cases (1 week)

Goals:

  • Handle failures and compatibility.

Tasks:

  1. Add API version checks.
  2. Preserve state across reloads.

Checkpoint: Reload errors do not crash the host.

5.11 Key Implementation Decisions

Decision Options Recommendation Rationale
File watch inotify vs polling inotify on Linux Low latency
Reload order unload then load load then swap Avoid downtime

6. Testing Strategy

6.1 Test Categories

Category Purpose Examples
Reload Detect change Touch file triggers reload
Compatibility ABI version Reject incompatible module
Stability No crash Reload repeatedly

6.2 Critical Test Cases

  1. Reload occurs after source change.
  2. State remains unchanged across reload.
  3. Failing build does not crash host.

6.3 Test Data

Change score logic from +1 to +2

7. Common Pitfalls & Debugging

7.1 Frequent Mistakes

Pitfall Symptom Solution
State in library Reset on reload Move state to host
Incompatible ABI Crash on call Version checks
File race Partial .so Build to temp then swap

7.2 Debugging Strategies

  • Add verbose logging around reload events.
  • Use dlerror() after each load.

7.3 Performance Traps

Frequent rebuilds can stall the main loop; throttle reload checks.


8. Extensions & Challenges

8.1 Beginner Extensions

  • Add a manual reload command.

8.2 Intermediate Extensions

  • Hot reload multiple modules.

8.3 Advanced Extensions

  • Preserve state across ABI changes with serialization.

9. Real-World Connections

9.1 Industry Applications

  • Game engines: Live code iteration.
  • Dev servers: Hot reload in web tooling.
  • Handmade Hero: Live coding inspiration.
  • Live++: Commercial hot reload tool.

9.3 Interview Relevance

  • Demonstrates understanding of dynamic linking in complex workflows.

10. Resources

10.1 Essential Reading

  • TLPI - Shared libraries advanced features.
  • Game Programming Patterns - Architecture thinking.

10.2 Video Resources

  • Search: “hot reload C dlopen”.

10.3 Tools & Documentation

  • inotify: man inotify
  • dlopen/dlsym: man dlopen

11. Self-Assessment Checklist

11.1 Understanding

  • I can explain why state must live in the host.
  • I can describe the reload lifecycle.

11.2 Implementation

  • Reload happens without restarting.
  • State persists across reload.

11.3 Growth

  • I can apply this to other hot-reload problems.

12. Submission / Completion Criteria

Minimum Viable Completion:

  • Reloadable module updates without restart.

Full Completion:

  • Automatic rebuild and reload with state preserved.

Excellence (Going Above & Beyond):

  • ABI-safe reload with serialization and rollback.

This guide was generated from SHARED_LIBRARIES_LEARNING_PROJECTS.md. For the complete learning path, see the parent directory README.