1use evalexpr::{
4 Context, ContextWithMutableVariables, DefaultNumericTypes, EvalexprError, EvalexprResult,
5 HashMapContext, Value, error::EvalexprResultValue,
6};
7
8pub const HOT_SAMPLE: usize = 0;
22pub const HOT_VALUE1: usize = 1;
23pub const HOT_VALUE2: usize = 2;
24pub const HOT_X: usize = 3;
25pub const HOT_Y: usize = 4;
26pub const HOT_RAD: usize = 5;
27pub const HOT_ANG: usize = 6;
28pub const HOT_R: usize = 7;
29pub const HOT_G: usize = 8;
30pub const HOT_B: usize = 9;
31pub const HOT_A: usize = 10;
32pub const HOT_INSTANCE: usize = 11;
33pub const HOT_NUM_INST: usize = 12;
34pub const HOT_SIDES: usize = 13;
35pub const HOT_TEX_ZOOM: usize = 14;
36pub const HOT_TEX_ANG: usize = 15;
37pub const HOT_R2: usize = 16;
38pub const HOT_G2: usize = 17;
39pub const HOT_B2: usize = 18;
40pub const HOT_A2: usize = 19;
41pub const HOT_BORDER_R: usize = 20;
42pub const HOT_BORDER_G: usize = 21;
43pub const HOT_BORDER_B: usize = 22;
44pub const HOT_BORDER_A: usize = 23;
45pub const HOT_THICK: usize = 24;
46pub const HOT_ADDITIVE: usize = 25;
47pub const HOT_COUNT: usize = 26;
48
49#[inline]
54pub fn hot_index_of(name: &str) -> Option<usize> {
55 Some(match name {
56 "sample" => HOT_SAMPLE,
57 "value1" => HOT_VALUE1,
58 "value2" => HOT_VALUE2,
59 "x" => HOT_X,
60 "y" => HOT_Y,
61 "rad" => HOT_RAD,
62 "ang" => HOT_ANG,
63 "r" => HOT_R,
64 "g" => HOT_G,
65 "b" => HOT_B,
66 "a" => HOT_A,
67 "instance" => HOT_INSTANCE,
68 "num_inst" => HOT_NUM_INST,
69 "sides" => HOT_SIDES,
70 "tex_zoom" => HOT_TEX_ZOOM,
71 "tex_ang" => HOT_TEX_ANG,
72 "r2" => HOT_R2,
73 "g2" => HOT_G2,
74 "b2" => HOT_B2,
75 "a2" => HOT_A2,
76 "border_r" => HOT_BORDER_R,
77 "border_g" => HOT_BORDER_G,
78 "border_b" => HOT_BORDER_B,
79 "border_a" => HOT_BORDER_A,
80 "thick" => HOT_THICK,
81 "additive" => HOT_ADDITIVE,
82 _ => return None,
83 })
84}
85
86#[derive(Debug, Clone)]
92struct HotVars {
93 slots: [f64; HOT_COUNT],
94 slots_value: [Value; HOT_COUNT],
97}
98
99impl Default for HotVars {
100 fn default() -> Self {
101 let mut slots = [0.0f64; HOT_COUNT];
102 slots[HOT_X] = 0.5;
103 slots[HOT_Y] = 0.5;
104 slots[HOT_R] = 1.0;
105 slots[HOT_G] = 1.0;
106 slots[HOT_B] = 1.0;
107 slots[HOT_A] = 1.0;
108 slots[HOT_NUM_INST] = 1.0;
109 slots[HOT_SIDES] = 4.0;
110 slots[HOT_TEX_ZOOM] = 1.0;
111 slots[HOT_BORDER_R] = 1.0;
112 slots[HOT_BORDER_G] = 1.0;
113 slots[HOT_BORDER_B] = 1.0;
114 let slots_value: [Value; HOT_COUNT] = std::array::from_fn(|i| Value::Float(slots[i]));
115 Self { slots, slots_value }
116 }
117}
118
119impl HotVars {
120 #[inline]
121 fn slot(&self, name: &str) -> Option<&Value> {
122 hot_index_of(name).map(|idx| &self.slots_value[idx])
123 }
124
125 #[inline]
129 fn set_by_name(&mut self, name: &str, value: f64) -> bool {
130 match hot_index_of(name) {
131 Some(idx) => {
132 self.slots[idx] = value;
133 self.slots_value[idx] = Value::Float(value);
134 true
135 }
136 None => false,
137 }
138 }
139}
140
141pub const Q_CHANNEL_NAMES_32: [&str; 32] = [
148 "q1", "q2", "q3", "q4", "q5", "q6", "q7", "q8", "q9", "q10", "q11", "q12", "q13", "q14", "q15",
149 "q16", "q17", "q18", "q19", "q20", "q21", "q22", "q23", "q24", "q25", "q26", "q27", "q28",
150 "q29", "q30", "q31", "q32",
151];
152
153#[inline]
159pub fn q_index_of(name: &str) -> Option<usize> {
160 let bytes = name.as_bytes();
161 if bytes.first()? != &b'q' || bytes.len() < 2 {
162 return None;
163 }
164 let idx: usize = name[1..].parse().ok()?;
165 if (1..=64).contains(&idx) {
166 Some(idx - 1)
167 } else {
168 None
169 }
170}
171
172pub fn is_hot_var(name: &str) -> bool {
177 matches!(
178 name,
179 "sample"
180 | "value1"
181 | "value2"
182 | "x"
183 | "y"
184 | "rad"
185 | "ang"
186 | "r"
187 | "g"
188 | "b"
189 | "a"
190 | "instance"
191 | "num_inst"
192 | "sides"
193 | "tex_zoom"
194 | "tex_ang"
195 | "r2"
196 | "g2"
197 | "b2"
198 | "a2"
199 | "border_r"
200 | "border_g"
201 | "border_b"
202 | "border_a"
203 | "thick"
204 | "additive"
205 )
206}
207
208#[derive(Debug, Clone, Default)]
226struct ColdSlab {
227 values: Vec<f64>,
228 values_mirror: Vec<Value>,
229 name_to_idx: std::collections::HashMap<String, usize>,
230}
231
232impl ColdSlab {
233 fn intern(&mut self, name: &str) -> usize {
236 if let Some(&idx) = self.name_to_idx.get(name) {
237 return idx;
238 }
239 let idx = self.values.len();
240 self.values.push(0.0);
241 self.values_mirror.push(Value::Float(0.0));
242 self.name_to_idx.insert(name.to_string(), idx);
243 idx
244 }
245
246 #[inline]
247 fn get_idx(&self, idx: usize) -> f64 {
248 self.values[idx]
249 }
250
251 #[inline]
252 fn set_idx(&mut self, idx: usize, val: f64) {
253 self.values[idx] = val;
254 self.values_mirror[idx] = Value::Float(val);
255 }
256
257 fn get_by_name(&self, name: &str) -> Option<f64> {
258 self.name_to_idx.get(name).map(|&i| self.values[i])
259 }
260
261 fn get_value_by_name(&self, name: &str) -> Option<&Value> {
262 self.name_to_idx.get(name).map(|&i| &self.values_mirror[i])
263 }
264
265 fn set_by_name(&mut self, name: &str, val: f64) {
266 let idx = self.intern(name);
267 self.set_idx(idx, val);
268 }
269
270 fn set_value_by_name(&mut self, name: &str, value: Value) {
271 let idx = self.intern(name);
272 let f = match &value {
273 Value::Float(f) => *f,
274 Value::Int(i) => *i as f64,
275 Value::Boolean(b) => {
276 if *b {
277 1.0
278 } else {
279 0.0
280 }
281 }
282 _ => 0.0,
283 };
284 self.values[idx] = f;
285 self.values_mirror[idx] = value;
286 }
287}
288
289#[derive(Debug, Clone)]
291pub struct MilkContext {
292 context: HashMapContext,
296
297 hot: HotVars,
299
300 q_vars: [f64; 64],
304 q_values_mirror: [Value; 64],
305
306 cold: ColdSlab,
310}
311
312impl MilkContext {
313 pub fn new() -> Self {
315 let mut context = HashMapContext::new();
316
317 crate::math_functions::register_math_functions(&mut context);
321
322 crate::math_functions::gmegabuf::reset();
327
328 let mut ctx = Self {
329 context,
330 hot: HotVars::default(),
331 q_vars: [0.0f64; 64],
332 q_values_mirror: std::array::from_fn(|_| Value::Float(0.0)),
333 cold: ColdSlab::default(),
334 };
335 Self::seed_cold_defaults(&mut ctx.cold);
339 ctx
340 }
341
342 pub fn cold_intern(&mut self, name: &str) -> usize {
346 self.cold.intern(name)
347 }
348
349 #[inline]
351 pub fn cold_get_idx(&self, idx: usize) -> f64 {
352 self.cold.get_idx(idx)
353 }
354
355 #[inline]
357 pub fn cold_set_idx(&mut self, idx: usize, val: f64) {
358 self.cold.set_idx(idx, val);
359 }
360
361 fn seed_cold_defaults(cold: &mut ColdSlab) {
367 cold.set_by_name("time", 0.0);
369 cold.set_by_name("frame", 0.0);
370 cold.set_by_name("fps", 60.0);
371 cold.set_by_name("progress", 0.0);
372
373 cold.set_by_name("bass", 0.0);
375 cold.set_by_name("mid", 0.0);
376 cold.set_by_name("treb", 0.0);
377 cold.set_by_name("bass_att", 0.0);
378 cold.set_by_name("mid_att", 0.0);
379 cold.set_by_name("treb_att", 0.0);
380
381 cold.set_by_name("zoom", 1.0);
383 cold.set_by_name("zoomexp", 1.0);
384 cold.set_by_name("rot", 0.0);
385 cold.set_by_name("warp", 0.0);
386 cold.set_by_name("cx", 0.5);
387 cold.set_by_name("cy", 0.5);
388 cold.set_by_name("dx", 0.0);
389 cold.set_by_name("dy", 0.0);
390 cold.set_by_name("sx", 1.0);
391 cold.set_by_name("sy", 1.0);
392
393 cold.set_by_name("wave_r", 1.0);
395 cold.set_by_name("wave_g", 1.0);
396 cold.set_by_name("wave_b", 1.0);
397 cold.set_by_name("wave_a", 1.0);
398 cold.set_by_name("wave_x", 0.5);
399 cold.set_by_name("wave_y", 0.5);
400 cold.set_by_name("wave_mystery", 0.0);
401 cold.set_by_name("wave_mode", 0.0);
402
403 cold.set_by_name("ob_size", 0.0);
405 cold.set_by_name("ob_r", 0.0);
406 cold.set_by_name("ob_g", 0.0);
407 cold.set_by_name("ob_b", 0.0);
408 cold.set_by_name("ob_a", 0.0);
409 cold.set_by_name("ib_size", 0.0);
410 cold.set_by_name("ib_r", 0.0);
411 cold.set_by_name("ib_g", 0.0);
412 cold.set_by_name("ib_b", 0.0);
413 cold.set_by_name("ib_a", 0.0);
414
415 cold.set_by_name("mv_x", 12.0);
417 cold.set_by_name("mv_y", 9.0);
418 cold.set_by_name("mv_dx", 0.0);
419 cold.set_by_name("mv_dy", 0.0);
420 cold.set_by_name("mv_l", 0.9);
421 cold.set_by_name("mv_r", 1.0);
422 cold.set_by_name("mv_g", 1.0);
423 cold.set_by_name("mv_b", 1.0);
424 cold.set_by_name("mv_a", 0.0);
425
426 cold.set_by_name("decay", 0.98);
428 cold.set_by_name("echo_zoom", 1.0);
429 cold.set_by_name("echo_alpha", 0.0);
430 cold.set_by_name("echo_orient", 0.0);
431 }
432
433 pub fn set(&mut self, name: &str, value: f64) {
435 if self.hot.set_by_name(name, value) {
437 return;
438 }
439
440 if let Some(idx) = q_index_of(name) {
443 self.q_vars[idx] = value;
444 self.q_values_mirror[idx] = Value::Float(value);
445 return;
446 }
447
448 self.cold.set_by_name(name, value);
451 }
452
453 pub fn get(&self, name: &str) -> Option<f64> {
455 if let Some(idx) = hot_index_of(name) {
457 return Some(self.hot.slots[idx]);
458 }
459
460 if let Some(idx) = q_index_of(name) {
462 return Some(self.q_vars[idx]);
463 }
464
465 self.cold.get_by_name(name)
470 }
471
472 #[inline]
475 pub fn hot_get_idx(&self, idx: usize) -> f64 {
476 self.hot.slots[idx]
477 }
478
479 #[inline]
483 pub fn hot_set_idx(&mut self, idx: usize, value: f64) {
484 self.hot.slots[idx] = value;
485 self.hot.slots_value[idx] = Value::Float(value);
486 }
487
488 #[inline]
491 pub fn q_get_idx(&self, idx: usize) -> f64 {
492 self.q_vars[idx]
493 }
494
495 #[inline]
499 pub fn q_set_idx(&mut self, idx: usize, value: f64) {
500 self.q_vars[idx] = value;
501 self.q_values_mirror[idx] = Value::Float(value);
502 }
503
504 pub fn inner(&self) -> &HashMapContext {
506 &self.context
507 }
508
509 pub fn inner_mut(&mut self) -> &mut HashMapContext {
515 &mut self.context
516 }
517
518 pub fn q_vars(&self) -> [f64; 64] {
522 std::array::from_fn(|i| self.q_get_idx(i))
523 }
524
525 pub fn set_pixel(&mut self, x: f64, y: f64, rad: f64, ang: f64) {
527 self.set("x", x);
528 self.set("y", y);
529 self.set("rad", rad);
530 self.set("ang", ang);
531 }
532
533 pub fn set_var(&mut self, name: &str, value: f64) {
535 self.set(name, value);
536 }
537
538 pub fn set_time(&mut self, time: f64) {
540 self.set("time", time);
541 }
542
543 pub fn set_frame(&mut self, frame: f64) {
545 self.set("frame", frame);
546 }
547
548 pub fn set_audio(&mut self, bass: f64, mid: f64, treb: f64) {
550 self.set("bass", bass);
551 self.set("mid", mid);
552 self.set("treb", treb);
553 }
554
555 pub fn get_var(&self, name: &str) -> Option<f64> {
557 self.get(name)
558 }
559}
560
561impl Default for MilkContext {
562 fn default() -> Self {
563 Self::new()
564 }
565}
566
567impl Context for MilkContext {
577 type NumericTypes = DefaultNumericTypes;
578
579 fn get_value(&self, identifier: &str) -> Option<&Value> {
580 if let Some(v) = self.hot.slot(identifier) {
581 return Some(v);
582 }
583 if let Some(idx) = q_index_of(identifier) {
584 return Some(&self.q_values_mirror[idx]);
585 }
586 self.cold.get_value_by_name(identifier)
587 }
588
589 fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResultValue {
590 self.context.call_function(identifier, argument)
591 }
592
593 fn are_builtin_functions_disabled(&self) -> bool {
594 self.context.are_builtin_functions_disabled()
595 }
596
597 fn set_builtin_functions_disabled(
598 &mut self,
599 disabled: bool,
600 ) -> EvalexprResult<(), Self::NumericTypes> {
601 self.context.set_builtin_functions_disabled(disabled)
602 }
603}
604
605impl ContextWithMutableVariables for MilkContext {
606 fn set_value(
607 &mut self,
608 identifier: String,
609 value: Value,
610 ) -> EvalexprResult<(), Self::NumericTypes> {
611 if let Some(idx) = hot_index_of(&identifier) {
618 let f = match value {
619 Value::Float(f) => f,
620 Value::Int(i) => i as f64,
621 other => return Err(EvalexprError::expected_float(other)),
622 };
623 self.hot.slots[idx] = f;
624 self.hot.slots_value[idx] = Value::Float(f);
625 return Ok(());
626 }
627
628 if let Some(idx) = q_index_of(&identifier) {
631 let f = match value {
632 Value::Float(f) => f,
633 Value::Int(i) => i as f64,
634 other => return Err(EvalexprError::expected_float(other)),
635 };
636 self.q_vars[idx] = f;
637 self.q_values_mirror[idx] = Value::Float(f);
638 return Ok(());
639 }
640
641 self.cold.set_value_by_name(&identifier, value);
645 Ok(())
646 }
647
648 fn remove_value(
649 &mut self,
650 identifier: &str,
651 ) -> EvalexprResult<Option<Value>, Self::NumericTypes> {
652 if self.hot.slot(identifier).is_some() {
656 return Ok(None);
657 }
658 if q_index_of(identifier).is_some() {
659 return Ok(None);
660 }
661 if self.cold.get_value_by_name(identifier).is_some() {
662 return Ok(None);
663 }
664 self.context.remove_value(identifier)
665 }
666}
667
668#[cfg(test)]
669mod tests {
670 use super::*;
671
672 #[test]
673 fn test_create_context() {
674 let ctx = MilkContext::new();
675 assert_eq!(ctx.get("time"), Some(0.0));
676 assert_eq!(ctx.get("fps"), Some(60.0));
677 }
678
679 #[test]
680 fn test_set_get_variable() {
681 let mut ctx = MilkContext::new();
682
683 ctx.set("bass", 1.5);
684 assert_eq!(ctx.get("bass"), Some(1.5));
685
686 ctx.set("custom_var", 42.0);
687 assert_eq!(ctx.get("custom_var"), Some(42.0));
688 }
689
690 #[test]
691 fn test_q_variables() {
692 let mut ctx = MilkContext::new();
693
694 for i in 1..=64 {
696 let name = format!("q{}", i);
697 ctx.set(&name, i as f64);
698 assert_eq!(ctx.get(&name), Some(i as f64));
699 }
700
701 assert_eq!(ctx.q_vars()[0], 1.0);
703 assert_eq!(ctx.q_vars()[63], 64.0);
704 }
705
706 #[test]
707 fn test_custom_variables_round_trip() {
708 let mut ctx = MilkContext::new();
712
713 ctx.set("my_var", 123.0);
714 ctx.set("another_var", 456.0);
715
716 assert_eq!(ctx.get("my_var"), Some(123.0));
717 assert_eq!(ctx.get("another_var"), Some(456.0));
718 }
719
720 #[test]
721 fn test_hot_vars_round_trip() {
722 let mut ctx = MilkContext::new();
723 for (name, val) in [
724 ("x", 0.25),
725 ("y", 0.75),
726 ("r", 0.1),
727 ("g", 0.2),
728 ("b", 0.3),
729 ("a", 0.4),
730 ("sample", 0.5),
731 ("value1", 0.6),
732 ("value2", 0.7),
733 ("rad", 1.5),
734 ("ang", 2.5),
735 ("instance", 7.0),
736 ("sides", 5.0),
737 ("tex_zoom", 1.5),
738 ("border_r", 0.9),
739 ] {
740 ctx.set(name, val);
741 assert_eq!(ctx.get(name), Some(val), "round-trip failed for {name}");
742 }
743 }
744
745 #[test]
746 fn test_hot_vars_visible_to_evalexpr() {
747 use evalexpr::build_operator_tree;
748 let mut ctx = MilkContext::new();
749 ctx.set("x", 3.0);
750 ctx.set("y", 4.0);
751 let tree = build_operator_tree::<DefaultNumericTypes>("x*x + y*y").unwrap();
752 let val = tree.eval_with_context_mut(&mut ctx).unwrap();
753 assert!(matches!(val, Value::Float(f) if (f - 25.0).abs() < 1e-9));
754 }
755
756 #[test]
757 fn test_evalexpr_assignment_lands_in_hot_slot() {
758 use evalexpr::build_operator_tree;
759 let mut ctx = MilkContext::new();
760 let tree = build_operator_tree::<DefaultNumericTypes>("x = 0.123").unwrap();
761 tree.eval_with_context_mut(&mut ctx).unwrap();
762 assert_eq!(ctx.get("x"), Some(0.123));
763 }
764}