From c62ef343f0351893e291601cd70d52f295d1a1d5 Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Tue, 28 Apr 2026 14:37:19 -0500 Subject: [PATCH] add state_get/state_set builtins for frame-persistent key-value store --- bin/el/src/main.rs | 25 ++++++++++++++++++++++++- crates/el-types/src/types.rs | 2 ++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/bin/el/src/main.rs b/bin/el/src/main.rs index acd3114..ce547a4 100644 --- a/bin/el/src/main.rs +++ b/bin/el/src/main.rs @@ -2322,7 +2322,9 @@ fn dispatch_builtin( "Content-Type", content_type ).unwrap(); if let Some(dir) = html_dir { - let asset_path = dir.join(file_name); + // Try src/assets/ first, then src/ as fallback + let asset_path = dir.join("assets").join(file_name); + let asset_path = if asset_path.exists() { asset_path } else { dir.join(file_name) }; if let Ok(bytes) = std::fs::read(&asset_path) { let _ = request.respond( tiny_http::Response::from_data(bytes) @@ -4551,6 +4553,27 @@ fn dispatch_builtin( BuiltinResult::Handled } + // ── Frame-persistent key-value state ──────────────────────────────── + // state_set / state_get use GLOBAL_STATE so values survive between frames + // when canvas_run_loop calls the draw function repeatedly. + + "state_set" => { + // state_set(key: String, val: String) -> Void + let val = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; + let key = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; + GLOBAL_STATE.with(|gs| gs.borrow_mut().insert(key, val)); + stack.push(Value::Nil); + BuiltinResult::Handled + } + + "state_get" => { + // state_get(key: String) -> String + let key = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => String::new() }; + let val = GLOBAL_STATE.with(|gs| gs.borrow().get(&key).cloned().unwrap_or_default()); + stack.push(Value::Str(val)); + BuiltinResult::Handled + } + "canvas_run_loop" => { // canvas_run_loop(draw_fn: String) -> Void (never returns) let draw_fn = match stack.pop().unwrap_or(Value::Nil) { Value::Str(s) => s, _ => "draw".into() }; diff --git a/crates/el-types/src/types.rs b/crates/el-types/src/types.rs index 900e785..cb21d15 100644 --- a/crates/el-types/src/types.rs +++ b/crates/el-types/src/types.rs @@ -278,6 +278,8 @@ impl TypeEnv { env.functions.insert("canvas_events".into(), str_fn(vec![], s.clone())); env.functions.insert("canvas_swap".into(), str_fn(vec![], Type::Void)); env.functions.insert("canvas_run_loop".into(), str_fn(vec![s.clone()], Type::Void)); + env.functions.insert("state_set".into(), str_fn(vec![s.clone(), s.clone()], Type::Void)); + env.functions.insert("state_get".into(), str_fn(vec![s.clone()], s.clone())); // Math for name in &["math_abs","math_floor","math_ceil","math_round","math_sqrt"] {