# Hot-Reload and Hot-Patching Architecture Dioxus supports two complementary hot-reload systems: RSX template hot-reload for UI changes, and Subsecond hot-patching for Rust code changes. ## Subsecond Hot-Patching ### Jump Table Architecture Subsecond implements hot-patching through **jump table indirection**: 1. All hot-reloadable functions called through `subsecond::call()` or `HotFn::current()` 2. Runtime looks up function pointer in global `APP_JUMP_TABLE` 3. Jump table points to latest compiled version 4. When patch applied, only jump table is updated 5. Original executable remains untouched **Advantage**: Decouples running binary from patched code. Safe memory model. ### Patch Application Flow ``` 1. ThinLink compiles only modified functions → patch dylib 2. Patch sent via devtools WebSocket 3. subsecond::apply_patch() loads via libloading::Library 4. Base address calculated using main as anchor 5. Jump table updated with old→new address mappings ``` ### ASLR Handling Operating systems randomize memory addresses (ASLR), so compiled addresses don't match runtime. **Solution**: 1. Running app captures real address of `main` via `dlsym()`/`GetProcAddress()` 2. ASLR reference sent to devserver 3. Devserver computes offset: `old_offset = aslr_reference - table.aslr_reference` 4. All jump table addresses adjusted by offset 5. Patch library base address calculated similarly 6. Final: `(old_address + old_offset) → (new_address + new_offset)` ### Thread-Local Storage (TLS) TLS presents challenges for hot-patching: - **Globals and statics**: Generally supported - **Thread-locals in tip crate**: Reset to initial value on new patches - **Thread-locals in dependencies**: Work correctly (not re-patched) **Why**: New thread-local variables exist in patch library's TLS segment, separate from main executable's TLS. Subsecond doesn't rebind TLS accesses. ### Limitations 1. **Struct changes**: Not supported - size/alignment changes cause crashes 2. **Pointer versioning**: Not implemented - all function pointers considered "new" 3. **Workspace support**: Only tip crate patches, library crates ignored 4. **Static initializers**: Changes not observed 5. **Global destructors**: Newly added globals have destructors that never run ### Platform Support - **Desktop**: Linux, macOS, Windows (x86_64, aarch64) - **Mobile**: Android (arm64-v8a, armeabi-v7a), iOS Simulator - **Web**: WASM32 (limited module reloading) - **Not supported**: iOS device (code-signing) ## RSX Hot Reload ### Hot Literal System RSX hot reload is **orthogonal** to subsecond. While subsecond reloads Rust functions, RSX hot reload handles template literal values: **Hot-reloadable**: - Formatted segments: `"{variable}"` - Literal component properties: `Component { value: 123 }` - Dynamic text nodes: `"{expression}"` **Not hot-reloadable**: - Rust code changes - Component structure changes - Control flow changes ### Template Diffing Algorithm **Conservative approach**: If Rust code changes, not hot-reloadable. ``` 1. Parse old and new files 2. Extract all rsx! macro invocations 3. Replace all rsx! bodies with empty rsx! {} 4. Remove doc comments 5. Compare modified files 6. If identical → Rust unchanged → proceed with template diff 7. If different → requires full rebuild ``` ### Dynamic Pool System Three pools of reusable items from last build: 1. **Dynamic text segments**: `"{class}"`, `"{id}"` 2. **Dynamic nodes**: Components, for loops, if chains 3. **Dynamic attributes**: Spread operators, dynamic values Each item tracks usage with `Cell` for waste scoring. ### Greedy Matching Algorithm For each new dynamic node: 1. Find all candidates from last build with compatible structure 2. Attempt to create hot-reloaded template using candidate's pool 3. Score: Count unused dynamic items remaining 4. Select candidate with least unused items 5. Greedy approach optimal because it maximizes reuse ### Change Detection **NOT possible**: - Number of `rsx!` macros changes - Rust expressions change - Component structure changes - Control flow conditions change **IS possible**: - Component children content - Reordering attributes - Adding dynamic text segments from pool - Changing literal values - Shuffling template structure ## Devtools Protocol ### WebSocket Communication Bidirectional WebSocket between app and devserver. **Connection**: - URL: `ws://localhost:3000/_dioxus` - Query params: `build_id`, `pid`, `aslr_reference` - Persistent during development ### Message Types ```rust pub enum DevserverMsg { HotReload(HotReloadMsg), // Templates + optional jump table HotPatchStart, // Binary patching starting FullReloadStart, // Rebuilding entire app FullReloadFailed, // Build failed FullReloadCommand, // Full page reload needed Shutdown, // Devserver shutting down } ``` ### HotReloadMsg Structure ```rust pub struct HotReloadMsg { pub templates: Vec, pub assets: Vec, pub ms_elapsed: u64, pub jump_table: Option, pub for_build_id: Option, pub for_pid: Option, } ``` ### Message Processing 1. `connect(callback)` - Generic connection 2. `connect_subsecond()` - Subsecond-specific with jump tables 3. `apply_changes(dom, msg)`: - Updates signal-based template cache - Applies jump table if PID matches - Marks all components dirty ### WASM-Specific Handling - Connection at `ws://host/_dioxus?build_id={build_id}` - Supports playground mode (iframe via postMessage) - Console logging sent to devserver - Page reload on `FullReloadCommand` - Asset cache invalidation via `dx_force_reload` query params ## Integration Flow ### Subsecond in Dioxus Apps ```rust fn main() { dioxus::launch(app); // Devtools automatically connects during init } ``` For non-Dioxus apps: ```rust fn main() { dioxus_devtools::connect_subsecond(); loop { dioxus_devtools::subsecond::call(|| { handle_request() }); } } ``` ### Async Integration ```rust #[tokio::main] async fn main() { dioxus_devtools::serve_subsecond_with_args( state, |state| async { app_main(state).await } ).await; } ``` **Process**: 1. Catches patch message 2. Applies jump table 3. Drops current future 4. Creates new future with hot function 5. Continues execution ## Build System Integration ### Fat vs Thin Builds **Fat Build** (initial): - Full build with all symbols - Required for initial symbol table - Creates `HotpatchModuleCache` **Thin Build** (patches): - Compiles only modified functions - Uses cached dependency symbols - Produces minimal patch dylib ### JumpTable Structure ```rust pub struct JumpTable { pub lib: PathBuf, // Path to patch dylib pub map: HashMap, // old_addr → new_addr pub aslr_reference: u64, pub new_base_address: u64, pub ifunc_count: u32, } ``` ### Platform-Specific Patching - `create_windows_jump_table()` - x86/x64 jump stubs - `create_native_jump_table()` - macOS/Linux function overrides - `create_wasm_jump_table()` - WASM indirect call table updates ## Key Design Decisions 1. **Jump table indirection**: Safe, no memory corruption 2. **Conservative RSX diffing**: Rust changes trigger rebuild 3. **Greedy pool matching**: Optimal template reuse 4. **WebSocket protocol**: Real-time bidirectional updates 5. **PID filtering**: Correct process receives patches 6. **Dual system**: RSX for templates, Subsecond for logic ## Future Considerations ### Workspace Support - Dependency graph analysis for affected crates - Incremental library crate compilation - Cross-crate function pointer resolution ### Remote Hot-Reloading - SCP transport for bandwidth efficiency - Binary diff for minimal transfers - Cryptographic verification ### CLI Tunnels - SSH/TCP protocol wrapping - Connection persistence - Latency-tolerant queueing