feat: AGENTS.md and architecture guides for agents!
This commit is contained in:
1 parent
2ee8ecd477
commit
638c02e15a
12 files changed
+3324
No files matched your search
@@ -0,0 +1,284 @@
|
||||
# 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<bool>` 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<HotReloadTemplateWithLocation>,
|
||||
pub assets: Vec<PathBuf>,
|
||||
pub ms_elapsed: u64,
|
||||
pub jump_table: Option<JumpTable>,
|
||||
pub for_build_id: Option<u64>,
|
||||
pub for_pid: Option<u32>,
|
||||
}
|
||||
```
|
||||
|
||||
### 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<u64, u64>, // 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
|
||||
Reference in new issue
Block a user