1//! A subsecond hot-patch guinea pig: no game, no window, just a loop that 2//! calls one function through `subsecond::call` once a second and watches 3//! for a patch to land. See `../README.md` for the file it watches and what 4//! a successful run looks like. 5 6use std::io::Write; 7use std::path::Path; 8use std::sync::atomic::{AtomicU64, Ordering}; 9use std::time::Duration; 10 11/// The counter `tick` prints and increments itself. A plain static, not a 12/// parameter threaded through a closure - see `../CLAUDE.md` for why 13/// `subsecond::call` needs a bare `fn` item passed directly 14/// (`subsecond::call(tick)`), never `|| tick(n)`. 15static TICKS: AtomicU64 = AtomicU64::new(0); 16 17/// The function `tools/hotpatch` patches. The string it prints is the only 18/// thing that changes between v1/v2/v3 - the loop, the pid, and the process 19/// itself never restart. `#[inline(never)]` is load-bearing, not tidiness: 20/// see `../CLAUDE.md`. 21#[inline(never)] 22fn tick() { 23 let n = TICKS.fetch_add(1, Ordering::Relaxed); 24 println!("hotdemo v1 tick={n}"); 25} 26 27fn main() { 28 // A patcher needs both of these before it can build anything: the pid to 29 // address the running process's MCP-equivalent (here, just a log line a 30 // human/agent reads), and the ASLR reference `subsecond::apply_patch` 31 // rebases the jump table against (subsecond/src/lib.rs:716-750, "main"'s 32 // live address in *this* process). 33 println!( 34 "hotdemo pid={} aslr_reference={:#x}", 35 std::process::id(), 36 subsecond::aslr_reference() 37 ); 38 std::io::stdout().flush().ok(); 39 40 let patch_path = Path::new("run/hotdemo.patch.json"); 41 loop { 42 subsecond::call(tick); 43 std::io::stdout().flush().ok(); 44 45 if patch_path.exists() { 46 apply_pending_patch(patch_path); 47 } 48 49 std::thread::sleep(Duration::from_secs(1)); 50 } 51} 52 53/// Read `run/hotdemo.patch.json` (a serialized `subsecond_types::JumpTable`), 54/// apply it, and delete the file - the same drop-a-file protocol `dx`'s 55/// devtools websocket exists to avoid needing here (research/ 56/// subsecond-patch-build.md §4: jev's MCP server is the in-process 57/// equivalent for `apps/native`; hotdemo does not even need that much). 58fn apply_pending_patch(patch_path: &Path) { 59 let bytes = match std::fs::read(patch_path) { 60 Ok(bytes) => bytes, 61 Err(e) => { 62 eprintln!("hotdemo: failed to read {}: {e}", patch_path.display()); 63 return; 64 } 65 }; 66 67 match serde_json::from_slice::<subsecond_types::JumpTable>(&bytes) { 68 Ok(table) => match unsafe { subsecond::apply_patch(table) } { 69 Ok(()) => println!("hotdemo: patch applied"), 70 Err(e) => eprintln!("hotdemo: patch apply failed: {e}"), 71 }, 72 Err(e) => eprintln!("hotdemo: bad patch json in {}: {e}", patch_path.display()), 73 } 74 75 if let Err(e) = std::fs::remove_file(patch_path) { 76 eprintln!("hotdemo: failed to remove {}: {e}", patch_path.display()); 77 } 78 std::io::stdout().flush().ok(); 79}