From e337eb35e71b1a5732cd0282bd08c73e47b902a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Forment?= Date: Tue, 3 Feb 2026 03:25:31 +0100 Subject: [PATCH] Again --- crates/forth/Cargo.toml | 1 + crates/forth/src/lib.rs | 1 + crates/forth/src/types.rs | 4 +- crates/forth/src/vm.rs | 95 ++++++++++++++++++++++++++++++++------- src/app.rs | 5 ++- src/engine/sequencer.rs | 45 ++++++++++++++----- src/views/render.rs | 3 +- tests/forth/harness.rs | 3 +- 8 files changed, 125 insertions(+), 32 deletions(-) diff --git a/crates/forth/Cargo.toml b/crates/forth/Cargo.toml index d3d7a82..7e65ef9 100644 --- a/crates/forth/Cargo.toml +++ b/crates/forth/Cargo.toml @@ -14,3 +14,4 @@ desktop = [] [dependencies] rand = "0.8" parking_lot = "0.12" +arc-swap = "1" diff --git a/crates/forth/src/lib.rs b/crates/forth/src/lib.rs index d05fc1b..4ce27f5 100644 --- a/crates/forth/src/lib.rs +++ b/crates/forth/src/lib.rs @@ -7,6 +7,7 @@ mod words; pub use types::{ CcAccess, Dictionary, ExecutionTrace, Rng, SourceSpan, StepContext, Value, Variables, + VariablesMap, }; pub use vm::Forth; pub use words::{lookup_word, Word, WordCompile, WORDS}; diff --git a/crates/forth/src/types.rs b/crates/forth/src/types.rs index 57ae2c2..9ae89b5 100644 --- a/crates/forth/src/types.rs +++ b/crates/forth/src/types.rs @@ -1,3 +1,4 @@ +use arc_swap::ArcSwap; use parking_lot::Mutex; use rand::rngs::StdRng; use std::collections::HashMap; @@ -53,7 +54,8 @@ impl StepContext<'_> { } } -pub type Variables = Arc>>; +pub type VariablesMap = HashMap; +pub type Variables = Arc>; pub type Dictionary = Arc>>>; pub type Rng = Arc>; pub type Stack = Arc>>; diff --git a/crates/forth/src/vm.rs b/crates/forth/src/vm.rs index 27f0b5e..cc66df3 100644 --- a/crates/forth/src/vm.rs +++ b/crates/forth/src/vm.rs @@ -2,12 +2,14 @@ use parking_lot::Mutex; use rand::rngs::StdRng; use rand::{Rng as RngTrait, SeedableRng}; use std::borrow::Cow; +use std::collections::HashMap; use std::sync::Arc; use super::compiler::compile_script; use super::ops::Op; use super::types::{ CmdRegister, Dictionary, ExecutionTrace, Rng, Stack, StepContext, Value, Variables, + VariablesMap, }; pub struct Forth { @@ -38,7 +40,9 @@ impl Forth { } pub fn evaluate(&self, script: &str, ctx: &StepContext) -> Result, String> { - self.evaluate_impl(script, ctx, None) + let (outputs, var_writes) = self.evaluate_impl(script, ctx, None)?; + self.apply_var_writes(var_writes); + Ok(outputs) } pub fn evaluate_with_trace( @@ -47,15 +51,37 @@ impl Forth { ctx: &StepContext, trace: &mut ExecutionTrace, ) -> Result, String> { + let (outputs, var_writes) = self.evaluate_impl(script, ctx, Some(trace))?; + self.apply_var_writes(var_writes); + Ok(outputs) + } + + pub fn evaluate_raw( + &self, + script: &str, + ctx: &StepContext, + trace: &mut ExecutionTrace, + ) -> Result<(Vec, HashMap), String> { self.evaluate_impl(script, ctx, Some(trace)) } + fn apply_var_writes(&self, writes: HashMap) { + if writes.is_empty() { + return; + } + let mut new_vars = (**self.vars.load()).clone(); + for (k, v) in writes { + new_vars.insert(k, v); + } + self.vars.store(Arc::new(new_vars)); + } + fn evaluate_impl( &self, script: &str, ctx: &StepContext, trace: Option<&mut ExecutionTrace>, - ) -> Result, String> { + ) -> Result<(Vec, HashMap), String> { if script.trim().is_empty() { return Err("empty script".into()); } @@ -69,14 +95,25 @@ impl Forth { ops: &[Op], ctx: &StepContext, trace: Option<&mut ExecutionTrace>, - ) -> Result, String> { + ) -> Result<(Vec, HashMap), String> { let mut stack = self.stack.lock(); let mut outputs: Vec = Vec::with_capacity(8); let mut cmd = CmdRegister::new(); + let vars_snapshot = self.vars.load(); + let mut var_writes: HashMap = HashMap::new(); - self.execute_ops(ops, ctx, &mut stack, &mut outputs, &mut cmd, trace)?; + self.execute_ops( + ops, + ctx, + &mut stack, + &mut outputs, + &mut cmd, + trace, + &vars_snapshot, + &mut var_writes, + )?; - Ok(outputs) + Ok((outputs, var_writes)) } #[allow(clippy::too_many_arguments)] @@ -89,9 +126,12 @@ impl Forth { outputs: &mut Vec, cmd: &mut CmdRegister, trace: Option<&mut ExecutionTrace>, + vars_snapshot: &VariablesMap, + var_writes: &mut HashMap, ) -> Result<(), String> { let mut pc = 0; let trace_cell = std::cell::RefCell::new(trace); + let var_writes_cell = std::cell::RefCell::new(Some(var_writes)); let run_quotation = |quot: Value, stack: &mut Vec, @@ -106,6 +146,8 @@ impl Forth { } } let mut trace_opt = trace_cell.borrow_mut().take(); + let mut var_writes_guard = var_writes_cell.borrow_mut(); + let vw = var_writes_guard.as_mut().expect("var_writes taken"); self.execute_ops( "_ops, ctx, @@ -113,7 +155,10 @@ impl Forth { outputs, cmd, trace_opt.as_deref_mut(), + vars_snapshot, + vw, )?; + drop(var_writes_guard); *trace_cell.borrow_mut() = trace_opt; Ok(()) } @@ -475,15 +520,25 @@ impl Forth { Op::Get => { let name = stack.pop().ok_or("stack underflow")?; let name = name.as_str()?; - let vars = self.vars.lock(); - let val = vars.get(name).cloned().unwrap_or(Value::Int(0, None)); + let vw = var_writes_cell.borrow(); + let vw_ref = vw.as_ref().expect("var_writes taken"); + let val = vw_ref + .get(name) + .or_else(|| vars_snapshot.get(name)) + .cloned() + .unwrap_or(Value::Int(0, None)); + drop(vw); stack.push(val); } Op::Set => { let name = stack.pop().ok_or("stack underflow")?; let name = name.as_str()?.to_string(); let val = stack.pop().ok_or("stack underflow")?; - self.vars.lock().insert(name, val); + var_writes_cell + .borrow_mut() + .as_mut() + .expect("var_writes taken") + .insert(name, val); } Op::GetContext(name) => { @@ -710,16 +765,20 @@ impl Forth { Op::SetTempo => { let tempo = stack.pop().ok_or("stack underflow")?.as_float()?; let clamped = tempo.clamp(20.0, 300.0); - self.vars - .lock() + var_writes_cell + .borrow_mut() + .as_mut() + .expect("var_writes taken") .insert("__tempo__".to_string(), Value::Float(clamped, None)); } Op::SetSpeed => { let speed = stack.pop().ok_or("stack underflow")?.as_float()?; let clamped = speed.clamp(0.125, 8.0); - self.vars - .lock() + var_writes_cell + .borrow_mut() + .as_mut() + .expect("var_writes taken") .insert(ctx.speed_key.to_string(), Value::Float(clamped, None)); } @@ -735,7 +794,11 @@ impl Forth { use std::fmt::Write; let mut val = String::with_capacity(8); let _ = write!(&mut val, "{bank}:{pattern}"); - self.vars.lock().insert(ctx.chain_key.to_string(), Value::Str(Arc::from(val), None)); + var_writes_cell + .borrow_mut() + .as_mut() + .expect("var_writes taken") + .insert(ctx.chain_key.to_string(), Value::Str(Arc::from(val), None)); } } @@ -849,8 +912,10 @@ impl Forth { return Err("times count must be >= 0".into()); } for i in 0..count { - self.vars - .lock() + var_writes_cell + .borrow_mut() + .as_mut() + .expect("var_writes taken") .insert("i".to_string(), Value::Int(i, None)); run_quotation(quot.clone(), stack, outputs, cmd)?; } diff --git a/src/app.rs b/src/app.rs index 8ca19a1..08dea1e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,3 +1,4 @@ +use arc_swap::ArcSwap; use parking_lot::Mutex; use rand::rngs::StdRng; use rand::SeedableRng; @@ -60,7 +61,7 @@ impl Default for App { impl App { pub fn new() -> Self { - let variables = Arc::new(Mutex::new(HashMap::new())); + let variables = Arc::new(ArcSwap::from_pointee(HashMap::new())); let dict = Arc::new(Mutex::new(HashMap::new())); let rng = Arc::new(Mutex::new(StdRng::seed_from_u64(0))); let script_engine = @@ -606,7 +607,7 @@ impl App { link.set_tempo(tempo); self.playback.clear_queues(); - self.variables.lock().clear(); + self.variables.store(Arc::new(HashMap::new())); self.dict.lock().clear(); for (bank, pattern) in playing { diff --git a/src/engine/sequencer.rs b/src/engine/sequencer.rs index 6c223f7..f0bdc8a 100644 --- a/src/engine/sequencer.rs +++ b/src/engine/sequencer.rs @@ -810,10 +810,10 @@ impl SequencerState { self.speed_overrides.clear(); { - let vars = self.variables.lock(); + let vars = self.variables.load(); for id in self.audio_state.active_patterns.keys() { let key = self.key_cache.speed_key(id.bank, id.pattern); - if let Some(v) = vars.get(key).and_then(|v| v.as_float().ok()) { + if let Some(v) = vars.get(key).and_then(|v: &Value| v.as_float().ok()) { self.speed_overrides.insert((id.bank, id.pattern), v); } } @@ -931,8 +931,8 @@ impl SequencerState { }; } - let mut vars = self.variables.lock(); - let new_tempo = vars.remove("__tempo__").and_then(|v| v.as_float().ok()); + let vars = self.variables.load(); + let new_tempo = vars.get("__tempo__").and_then(|v: &Value| v.as_float().ok()); let mut chain_transitions = Vec::new(); for id in completed { @@ -942,12 +942,29 @@ impl SequencerState { chain_transitions.push((*id, target)); } } - vars.remove(chain_key); } - for id in stopped { - let chain_key = self.key_cache.chain_key(id.bank, id.pattern); - vars.remove(chain_key); + // Remove consumed variables (tempo and chain keys) + let needs_removal = new_tempo.is_some() + || completed.iter().any(|id| { + let chain_key = self.key_cache.chain_key(id.bank, id.pattern); + vars.contains_key(chain_key) + }) + || stopped.iter().any(|id| { + let chain_key = self.key_cache.chain_key(id.bank, id.pattern); + vars.contains_key(chain_key) + }); + + if needs_removal { + let mut new_vars = (**vars).clone(); + new_vars.remove("__tempo__"); + for id in completed { + new_vars.remove(self.key_cache.chain_key(id.bank, id.pattern)); + } + for id in stopped { + new_vars.remove(self.key_cache.chain_key(id.bank, id.pattern)); + } + self.variables.store(Arc::new(new_vars)); } VariableReads { @@ -1316,10 +1333,11 @@ fn parse_midi_command(cmd: &str) -> Option<(MidiCommand, Option)> { #[cfg(test)] mod tests { use super::*; + use arc_swap::ArcSwap; use parking_lot::Mutex; fn make_state() -> SequencerState { - let variables: Variables = Arc::new(Mutex::new(HashMap::new())); + let variables: Variables = Arc::new(ArcSwap::from_pointee(HashMap::new())); let dict: Dictionary = Arc::new(Mutex::new(HashMap::new())); let rng: Rng = Arc::new(Mutex::new( ::seed_from_u64(0), @@ -1496,11 +1514,12 @@ mod tests { // Set chain variable { - let mut vars = state.variables.lock(); + let mut vars = (**state.variables.load()).clone(); vars.insert( "__chain_0_0__".to_string(), Value::Str(std::sync::Arc::from("0:1"), None), ); + state.variables.store(Arc::new(vars)); } // Pattern 0 completes iteration AND gets stopped immediately in the same tick. @@ -1745,11 +1764,12 @@ mod tests { // Set chain: 0:0 -> 0:1 { - let mut vars = state.variables.lock(); + let mut vars = (**state.variables.load()).clone(); vars.insert( "__chain_0_0__".to_string(), Value::Str(std::sync::Arc::from("0:1"), None), ); + state.variables.store(Arc::new(vars)); } // Pattern 0 (length 1) completes iteration at beat=1.0 AND @@ -1996,8 +2016,9 @@ mod tests { // Script fires at beat 1.0 (step 0). Set __tempo__ as if the script did. { - let mut vars = state.variables.lock(); + let mut vars = (**state.variables.load()).clone(); vars.insert("__tempo__".to_string(), Value::Float(140.0, None)); + state.variables.store(Arc::new(vars)); } let output = state.tick(tick_at(1.0, true)); diff --git a/src/views/render.rs b/src/views/render.rs index 97c2ec2..f0579bf 100644 --- a/src/views/render.rs +++ b/src/views/render.rs @@ -1,3 +1,4 @@ +use arc_swap::ArcSwap; use parking_lot::Mutex; use std::collections::hash_map::DefaultHasher; use std::collections::HashMap; @@ -63,7 +64,7 @@ fn compute_stack_display( let result = if script.trim().is_empty() { "Stack: []".to_string() } else { - let vars = Arc::new(Mutex::new(HashMap::new())); + let vars = Arc::new(ArcSwap::from_pointee(HashMap::new())); let dict = Arc::new(Mutex::new(HashMap::new())); let rng = Arc::new(Mutex::new(StdRng::seed_from_u64(42))); let forth = Forth::new(vars, dict, rng); diff --git a/tests/forth/harness.rs b/tests/forth/harness.rs index 8be9b6a..c0cacb7 100644 --- a/tests/forth/harness.rs +++ b/tests/forth/harness.rs @@ -1,3 +1,4 @@ +use arc_swap::ArcSwap; use cagire::forth::{Dictionary, Forth, Rng, StepContext, Value, Variables}; use parking_lot::Mutex; use rand::rngs::StdRng; @@ -32,7 +33,7 @@ pub fn ctx_with(f: impl FnOnce(&mut StepContext<'static>)) -> StepContext<'stati } pub fn new_vars() -> Variables { - Arc::new(Mutex::new(HashMap::new())) + Arc::new(ArcSwap::from_pointee(HashMap::new())) } pub fn new_dict() -> Dictionary {