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:
- Separate runtime state from reloadable logic.
- Implement a hot-reload loop using
dlcloseanddlopen. - Detect source changes and rebuild shared libraries automatically.
- 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
- Core state: Keep state in the host binary.
- Reloadable module: Export
init,update, andshutdownfunctions. - File watch: Detect changes and rebuild the
.so/.dylib. - 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
- Load library and get API.
- Run update loop, monitor timestamps.
- 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:
- Library lifecycle
dlopen,dlsym,dlclose
- State ownership
- State must live in the host, not the library
- ABI changes
- Add fields with version checks
5.5 Questions to Guide Your Design
- Which data must survive reloads?
- How will you version API changes?
- 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
- Why must state live outside the reloadable library?
- How do you avoid crashes if the library fails to load?
- What are the risks of changing structs across reloads?
5.8 Hints in Layers
Hint 1: Separate files
- Keep
game_state_tin a shared header.
Hint 2: Version guard
- Add
api_versionto 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:
- Define
game_api_tand entry points. - 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:
- Track file timestamps or use inotify.
- 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:
- Add API version checks.
- 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
- Reload occurs after source change.
- State remains unchanged across reload.
- 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.
9.2 Related Open Source Projects
- 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
10.4 Related Projects in This Series
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.