Again
This commit is contained in:
@@ -14,3 +14,4 @@ desktop = []
|
||||
[dependencies]
|
||||
rand = "0.8"
|
||||
parking_lot = "0.12"
|
||||
arc-swap = "1"
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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<Mutex<HashMap<String, Value>>>;
|
||||
pub type VariablesMap = HashMap<String, Value>;
|
||||
pub type Variables = Arc<ArcSwap<VariablesMap>>;
|
||||
pub type Dictionary = Arc<Mutex<HashMap<String, Vec<Op>>>>;
|
||||
pub type Rng = Arc<Mutex<StdRng>>;
|
||||
pub type Stack = Arc<Mutex<Vec<Value>>>;
|
||||
|
||||
@@ -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<Vec<String>, 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<Vec<String>, 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<String>, HashMap<String, Value>), String> {
|
||||
self.evaluate_impl(script, ctx, Some(trace))
|
||||
}
|
||||
|
||||
fn apply_var_writes(&self, writes: HashMap<String, Value>) {
|
||||
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<Vec<String>, String> {
|
||||
) -> Result<(Vec<String>, HashMap<String, Value>), 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<Vec<String>, String> {
|
||||
) -> Result<(Vec<String>, HashMap<String, Value>), String> {
|
||||
let mut stack = self.stack.lock();
|
||||
let mut outputs: Vec<String> = Vec::with_capacity(8);
|
||||
let mut cmd = CmdRegister::new();
|
||||
let vars_snapshot = self.vars.load();
|
||||
let mut var_writes: HashMap<String, Value> = 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<String>,
|
||||
cmd: &mut CmdRegister,
|
||||
trace: Option<&mut ExecutionTrace>,
|
||||
vars_snapshot: &VariablesMap,
|
||||
var_writes: &mut HashMap<String, Value>,
|
||||
) -> 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<Value>,
|
||||
@@ -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)?;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
// 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.remove(chain_key);
|
||||
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<f64>)> {
|
||||
#[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(
|
||||
<rand::rngs::StdRng as rand::SeedableRng>::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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user