1use std::collections::VecDeque;
2use std::sync::Arc;
3
4use ad_core::ndarray::NDArray;
5use ad_core::ndarray_pool::NDArrayPool;
6use ad_core::plugin::runtime::{NDPluginProcess, ProcessResult};
7
8#[derive(Debug, Clone, Copy)]
10enum CalcOp {
11 Gt,
12 Lt,
13 Ge,
14 Le,
15 Eq,
16 Ne,
17 And,
18 Or,
19 Not,
20}
21
22#[derive(Debug, Clone)]
24enum CalcToken {
25 Num(f64),
26 VarA,
27 VarB,
28 Op(CalcOp),
29}
30
31#[derive(Debug, Clone)]
33enum RawToken {
34 Num(f64),
35 VarA,
36 VarB,
37 Op(CalcOp),
38 LParen,
39 RParen,
40}
41
42#[derive(Debug, Clone)]
47pub struct CalcExpression {
48 tokens: Vec<CalcToken>,
49}
50
51impl CalcExpression {
52 pub fn parse(expr: &str) -> Option<CalcExpression> {
57 let raw_tokens = Self::tokenize(expr)?;
58 let rpn = Self::shunting_yard(raw_tokens)?;
59 Some(CalcExpression { tokens: rpn })
60 }
61
62 pub fn evaluate(&self, a: f64, b: f64) -> f64 {
65 let mut stack: Vec<f64> = Vec::new();
66 for tok in &self.tokens {
67 match tok {
68 CalcToken::Num(n) => stack.push(*n),
69 CalcToken::VarA => stack.push(a),
70 CalcToken::VarB => stack.push(b),
71 CalcToken::Op(op) => {
72 match op {
73 CalcOp::Not => {
74 let v = stack.pop().unwrap_or(0.0);
75 stack.push(if v == 0.0 { 1.0 } else { 0.0 });
76 }
77 _ => {
78 let rhs = stack.pop().unwrap_or(0.0);
79 let lhs = stack.pop().unwrap_or(0.0);
80 let result = match op {
81 CalcOp::Gt => if lhs > rhs { 1.0 } else { 0.0 },
82 CalcOp::Lt => if lhs < rhs { 1.0 } else { 0.0 },
83 CalcOp::Ge => if lhs >= rhs { 1.0 } else { 0.0 },
84 CalcOp::Le => if lhs <= rhs { 1.0 } else { 0.0 },
85 CalcOp::Eq => if (lhs - rhs).abs() < f64::EPSILON { 1.0 } else { 0.0 },
86 CalcOp::Ne => if (lhs - rhs).abs() >= f64::EPSILON { 1.0 } else { 0.0 },
87 CalcOp::And => if lhs != 0.0 && rhs != 0.0 { 1.0 } else { 0.0 },
88 CalcOp::Or => if lhs != 0.0 || rhs != 0.0 { 1.0 } else { 0.0 },
89 CalcOp::Not => unreachable!(),
90 };
91 stack.push(result);
92 }
93 }
94 }
95 }
96 }
97 stack.pop().unwrap_or(0.0)
98 }
99
100 fn precedence(op: &CalcOp) -> u8 {
101 match op {
102 CalcOp::Or => 1,
103 CalcOp::And => 2,
104 CalcOp::Eq | CalcOp::Ne => 3,
105 CalcOp::Gt | CalcOp::Lt | CalcOp::Ge | CalcOp::Le => 4,
106 CalcOp::Not => 5,
107 }
108 }
109
110 fn is_right_assoc(op: &CalcOp) -> bool {
111 matches!(op, CalcOp::Not)
112 }
113
114 fn tokenize(expr: &str) -> Option<Vec<RawToken>> {
115 use RawToken as RT;
116 let chars: Vec<char> = expr.chars().collect();
117 let mut tokens = Vec::new();
118 let mut i = 0;
119
120 while i < chars.len() {
121 match chars[i] {
122 ' ' | '\t' => { i += 1; }
123 '(' => { tokens.push(RT::LParen); i += 1; }
124 ')' => { tokens.push(RT::RParen); i += 1; }
125 'A' | 'a' => { tokens.push(RT::VarA); i += 1; }
126 'B' | 'b' => { tokens.push(RT::VarB); i += 1; }
127 '>' => {
128 if i + 1 < chars.len() && chars[i + 1] == '=' {
129 tokens.push(RT::Op(CalcOp::Ge));
130 i += 2;
131 } else {
132 tokens.push(RT::Op(CalcOp::Gt));
133 i += 1;
134 }
135 }
136 '<' => {
137 if i + 1 < chars.len() && chars[i + 1] == '=' {
138 tokens.push(RT::Op(CalcOp::Le));
139 i += 2;
140 } else {
141 tokens.push(RT::Op(CalcOp::Lt));
142 i += 1;
143 }
144 }
145 '=' => {
146 if i + 1 < chars.len() && chars[i + 1] == '=' {
147 tokens.push(RT::Op(CalcOp::Eq));
148 i += 2;
149 } else {
150 return None; }
152 }
153 '!' => {
154 if i + 1 < chars.len() && chars[i + 1] == '=' {
155 tokens.push(RT::Op(CalcOp::Ne));
156 i += 2;
157 } else {
158 tokens.push(RT::Op(CalcOp::Not));
159 i += 1;
160 }
161 }
162 '&' => {
163 if i + 1 < chars.len() && chars[i + 1] == '&' {
164 tokens.push(RT::Op(CalcOp::And));
165 i += 2;
166 } else {
167 return None;
168 }
169 }
170 '|' => {
171 if i + 1 < chars.len() && chars[i + 1] == '|' {
172 tokens.push(RT::Op(CalcOp::Or));
173 i += 2;
174 } else {
175 return None;
176 }
177 }
178 c if c.is_ascii_digit() || c == '.' => {
179 let start = i;
180 while i < chars.len() && (chars[i].is_ascii_digit() || chars[i] == '.') {
181 i += 1;
182 }
183 let num_str: String = chars[start..i].iter().collect();
184 let num: f64 = num_str.parse().ok()?;
185 tokens.push(RT::Num(num));
186 }
187 '-' => {
188 let is_unary_minus = tokens.is_empty()
190 || matches!(tokens.last(), Some(RT::LParen) | Some(RT::Op(_)));
191 if is_unary_minus && i + 1 < chars.len() && (chars[i + 1].is_ascii_digit() || chars[i + 1] == '.') {
192 i += 1; let start = i;
194 while i < chars.len() && (chars[i].is_ascii_digit() || chars[i] == '.') {
195 i += 1;
196 }
197 let num_str: String = chars[start..i].iter().collect();
198 let num: f64 = num_str.parse().ok()?;
199 tokens.push(RT::Num(-num));
200 } else {
201 return None; }
203 }
204 _ => return None,
205 }
206 }
207
208 Some(tokens)
209 }
210
211 fn shunting_yard(raw: Vec<RawToken>) -> Option<Vec<CalcToken>> {
212 use RawToken as RT;
213 let mut output: Vec<CalcToken> = Vec::new();
214 let mut op_stack: Vec<RawToken> = Vec::new();
215
216 for tok in raw {
217 match tok {
218 RT::Num(n) => output.push(CalcToken::Num(n)),
219 RT::VarA => output.push(CalcToken::VarA),
220 RT::VarB => output.push(CalcToken::VarB),
221 RT::Op(ref op) => {
222 while let Some(RT::Op(top_op)) = op_stack.last() {
223 let top_prec = Self::precedence(top_op);
224 let cur_prec = Self::precedence(op);
225 if (!Self::is_right_assoc(op) && cur_prec <= top_prec)
226 || (Self::is_right_assoc(op) && cur_prec < top_prec)
227 {
228 if let Some(RT::Op(o)) = op_stack.pop() {
229 output.push(CalcToken::Op(o));
230 }
231 } else {
232 break;
233 }
234 }
235 op_stack.push(tok);
236 }
237 RT::LParen => op_stack.push(tok),
238 RT::RParen => {
239 loop {
240 match op_stack.pop() {
241 Some(RT::LParen) => break,
242 Some(RT::Op(o)) => output.push(CalcToken::Op(o)),
243 _ => return None, }
245 }
246 }
247 }
248 }
249
250 while let Some(tok) = op_stack.pop() {
252 match tok {
253 RT::Op(o) => output.push(CalcToken::Op(o)),
254 RT::LParen => return None, _ => return None,
256 }
257 }
258
259 Some(output)
260 }
261}
262
263#[derive(Debug, Clone)]
265pub enum TriggerCondition {
266 AttributeThreshold { name: String, threshold: f64 },
268 External,
270 Calc {
272 attr_a: String,
273 attr_b: String,
274 expression: CalcExpression,
275 },
276}
277
278#[derive(Debug, Clone, Copy, PartialEq, Eq)]
280pub enum BufferStatus {
281 Idle,
282 BufferFilling,
283 Flushing,
284 AcquisitionCompleted,
285}
286
287pub struct CircularBuffer {
289 pre_count: usize,
290 post_count: usize,
291 buffer: VecDeque<Arc<NDArray>>,
292 trigger_condition: TriggerCondition,
293 triggered: bool,
294 post_remaining: usize,
295 captured: Vec<Arc<NDArray>>,
296 preset_trigger_count: usize,
298 trigger_count: usize,
300 flush_on_soft_trigger: bool,
302 status: BufferStatus,
304}
305
306impl CircularBuffer {
307 pub fn new(pre_count: usize, post_count: usize, condition: TriggerCondition) -> Self {
308 Self {
309 pre_count,
310 post_count,
311 buffer: VecDeque::with_capacity(pre_count + 1),
312 trigger_condition: condition,
313 triggered: false,
314 post_remaining: 0,
315 captured: Vec::new(),
316 preset_trigger_count: 0,
317 trigger_count: 0,
318 flush_on_soft_trigger: false,
319 status: BufferStatus::Idle,
320 }
321 }
322
323 pub fn set_preset_trigger_count(&mut self, count: usize) {
325 self.preset_trigger_count = count;
326 }
327
328 pub fn trigger_count(&self) -> usize {
330 self.trigger_count
331 }
332
333 pub fn status(&self) -> BufferStatus {
335 self.status
336 }
337
338 pub fn set_flush_on_soft_trigger(&mut self, flush: bool) {
340 self.flush_on_soft_trigger = flush;
341 }
342
343 pub fn push(&mut self, array: Arc<NDArray>) -> bool {
346 if self.status == BufferStatus::AcquisitionCompleted {
348 return false;
349 }
350
351 if self.status == BufferStatus::Idle {
353 self.status = BufferStatus::BufferFilling;
354 }
355
356 if self.triggered {
357 self.captured.push(array);
359 self.post_remaining -= 1;
360 if self.post_remaining == 0 {
361 self.triggered = false;
362 if self.preset_trigger_count > 0 && self.trigger_count >= self.preset_trigger_count {
364 self.status = BufferStatus::AcquisitionCompleted;
365 } else {
366 self.status = BufferStatus::BufferFilling;
367 }
368 return true;
369 }
370 return false;
371 }
372
373 let trigger = match &self.trigger_condition {
375 TriggerCondition::AttributeThreshold { name, threshold } => {
376 array.attributes.get(name)
377 .and_then(|a| a.value.as_f64())
378 .map(|v| v >= *threshold)
379 .unwrap_or(false)
380 }
381 TriggerCondition::External => false,
382 TriggerCondition::Calc { attr_a, attr_b, expression } => {
383 let a = array.attributes.get(attr_a)
384 .and_then(|a| a.value.as_f64()).unwrap_or(0.0);
385 let b = array.attributes.get(attr_b)
386 .and_then(|a| a.value.as_f64()).unwrap_or(0.0);
387 expression.evaluate(a, b) != 0.0
388 }
389 };
390
391 self.buffer.push_back(array);
393 if self.buffer.len() > self.pre_count {
394 self.buffer.pop_front();
395 }
396
397 if trigger {
398 self.trigger();
399 }
400
401 false
402 }
403
404 pub fn trigger(&mut self) {
406 if self.status == BufferStatus::AcquisitionCompleted {
408 return;
409 }
410
411 self.triggered = true;
412 self.post_remaining = self.post_count;
413 self.trigger_count += 1;
414 self.status = BufferStatus::Flushing;
415 self.captured.clear();
417 self.captured.extend(self.buffer.drain(..));
418 }
419
420 pub fn take_captured(&mut self) -> Vec<Arc<NDArray>> {
422 std::mem::take(&mut self.captured)
423 }
424
425 pub fn is_triggered(&self) -> bool {
426 self.triggered
427 }
428
429 pub fn pre_buffer_len(&self) -> usize {
430 self.buffer.len()
431 }
432
433 pub fn reset(&mut self) {
434 self.buffer.clear();
435 self.captured.clear();
436 self.triggered = false;
437 self.post_remaining = 0;
438 self.trigger_count = 0;
439 self.status = BufferStatus::Idle;
440 }
441}
442
443pub struct CircularBuffProcessor {
447 buffer: CircularBuffer,
448}
449
450impl CircularBuffProcessor {
451 pub fn new(pre_count: usize, post_count: usize, condition: TriggerCondition) -> Self {
452 Self {
453 buffer: CircularBuffer::new(pre_count, post_count, condition),
454 }
455 }
456
457 pub fn trigger(&mut self) {
458 self.buffer.trigger();
459 }
460
461 pub fn buffer(&self) -> &CircularBuffer {
462 &self.buffer
463 }
464}
465
466impl NDPluginProcess for CircularBuffProcessor {
467 fn process_array(&mut self, array: &NDArray, _pool: &NDArrayPool) -> ProcessResult {
468 let done = self.buffer.push(Arc::new(array.clone()));
469 if done {
470 ProcessResult::arrays(self.buffer.take_captured())
471 } else {
472 ProcessResult::empty()
473 }
474 }
475
476 fn plugin_type(&self) -> &str {
477 "NDPluginCircularBuff"
478 }
479}
480
481#[cfg(test)]
482mod tests {
483 use super::*;
484 use ad_core::ndarray::{NDDataType, NDDimension};
485 use ad_core::attributes::{NDAttribute, NDAttrSource, NDAttrValue};
486
487 fn make_array(id: i32) -> Arc<NDArray> {
488 let mut arr = NDArray::new(vec![NDDimension::new(4)], NDDataType::UInt8);
489 arr.unique_id = id;
490 Arc::new(arr)
491 }
492
493 fn make_array_with_attr(id: i32, attr_val: f64) -> Arc<NDArray> {
494 let mut arr = NDArray::new(vec![NDDimension::new(4)], NDDataType::UInt8);
495 arr.unique_id = id;
496 arr.attributes.add(NDAttribute {
497 name: "trigger".into(),
498 description: "".into(),
499 source: NDAttrSource::Driver,
500 value: NDAttrValue::Float64(attr_val),
501 });
502 Arc::new(arr)
503 }
504
505 fn make_array_with_attrs(id: i32, a_val: f64, b_val: f64) -> Arc<NDArray> {
506 let mut arr = NDArray::new(vec![NDDimension::new(4)], NDDataType::UInt8);
507 arr.unique_id = id;
508 arr.attributes.add(NDAttribute {
509 name: "attr_a".into(),
510 description: "".into(),
511 source: NDAttrSource::Driver,
512 value: NDAttrValue::Float64(a_val),
513 });
514 arr.attributes.add(NDAttribute {
515 name: "attr_b".into(),
516 description: "".into(),
517 source: NDAttrSource::Driver,
518 value: NDAttrValue::Float64(b_val),
519 });
520 Arc::new(arr)
521 }
522
523 #[test]
524 fn test_pre_trigger_buffering() {
525 let mut cb = CircularBuffer::new(3, 2, TriggerCondition::External);
526
527 for i in 0..5 {
528 cb.push(make_array(i));
529 }
530 assert_eq!(cb.pre_buffer_len(), 3);
532 }
533
534 #[test]
535 fn test_external_trigger() {
536 let mut cb = CircularBuffer::new(2, 2, TriggerCondition::External);
537
538 cb.push(make_array(1));
539 cb.push(make_array(2));
540 cb.push(make_array(3));
541 cb.trigger();
544 assert!(cb.is_triggered());
545
546 cb.push(make_array(4));
547 let done = cb.push(make_array(5));
548 assert!(done);
549
550 let captured = cb.take_captured();
551 assert_eq!(captured.len(), 4); assert_eq!(captured[0].unique_id, 2);
553 assert_eq!(captured[1].unique_id, 3);
554 assert_eq!(captured[2].unique_id, 4);
555 assert_eq!(captured[3].unique_id, 5);
556 }
557
558 #[test]
559 fn test_attribute_trigger() {
560 let mut cb = CircularBuffer::new(1, 1, TriggerCondition::AttributeThreshold {
561 name: "trigger".into(),
562 threshold: 5.0,
563 });
564
565 cb.push(make_array_with_attr(1, 1.0));
566 cb.push(make_array_with_attr(2, 2.0));
567 assert!(!cb.is_triggered());
568
569 cb.push(make_array_with_attr(3, 5.0));
571 assert!(cb.is_triggered());
572
573 let done = cb.push(make_array(4));
574 assert!(done);
575
576 let captured = cb.take_captured();
577 assert_eq!(captured.len(), 2); }
579
580 #[test]
583 fn test_calc_trigger() {
584 let expr = CalcExpression::parse("A>5").unwrap();
586 let mut cb = CircularBuffer::new(1, 1, TriggerCondition::Calc {
587 attr_a: "attr_a".into(),
588 attr_b: "attr_b".into(),
589 expression: expr,
590 });
591
592 cb.push(make_array_with_attrs(1, 3.0, 0.0));
594 assert!(!cb.is_triggered());
595
596 cb.push(make_array_with_attrs(2, 6.0, 0.0));
598 assert!(cb.is_triggered());
599
600 let done = cb.push(make_array(3));
601 assert!(done);
602
603 let captured = cb.take_captured();
604 assert_eq!(captured.len(), 2); }
606
607 #[test]
608 fn test_calc_expression_parse() {
609 let expr = CalcExpression::parse("A>5").unwrap();
611 assert_eq!(expr.evaluate(6.0, 0.0), 1.0);
612 assert_eq!(expr.evaluate(4.0, 0.0), 0.0);
613 assert_eq!(expr.evaluate(5.0, 0.0), 0.0); let expr = CalcExpression::parse("A>=5").unwrap();
617 assert_eq!(expr.evaluate(5.0, 0.0), 1.0);
618 assert_eq!(expr.evaluate(4.9, 0.0), 0.0);
619
620 let expr = CalcExpression::parse("A>3&&B<10").unwrap();
622 assert_eq!(expr.evaluate(4.0, 5.0), 1.0);
623 assert_eq!(expr.evaluate(2.0, 5.0), 0.0);
624 assert_eq!(expr.evaluate(4.0, 15.0), 0.0);
625
626 let expr = CalcExpression::parse("(A>10)||(B>10)").unwrap();
628 assert_eq!(expr.evaluate(11.0, 0.0), 1.0);
629 assert_eq!(expr.evaluate(0.0, 11.0), 1.0);
630 assert_eq!(expr.evaluate(0.0, 0.0), 0.0);
631
632 let expr = CalcExpression::parse("A!=0").unwrap();
634 assert_eq!(expr.evaluate(1.0, 0.0), 1.0);
635 assert_eq!(expr.evaluate(0.0, 0.0), 0.0);
636
637 let expr = CalcExpression::parse("A==B").unwrap();
639 assert_eq!(expr.evaluate(5.0, 5.0), 1.0);
640 assert_eq!(expr.evaluate(5.0, 6.0), 0.0);
641
642 let expr = CalcExpression::parse("!A").unwrap();
644 assert_eq!(expr.evaluate(0.0, 0.0), 1.0);
645 assert_eq!(expr.evaluate(1.0, 0.0), 0.0);
646
647 assert!(CalcExpression::parse("A=5").is_none()); assert!(CalcExpression::parse("A&B").is_none()); }
651
652 #[test]
653 fn test_preset_trigger_count() {
654 let mut cb = CircularBuffer::new(1, 1, TriggerCondition::External);
655 cb.set_preset_trigger_count(2);
656
657 assert_eq!(cb.status(), BufferStatus::Idle);
658
659 cb.push(make_array(1));
661 assert_eq!(cb.status(), BufferStatus::BufferFilling);
662
663 cb.trigger();
665 assert_eq!(cb.trigger_count(), 1);
666 assert_eq!(cb.status(), BufferStatus::Flushing);
667
668 let done = cb.push(make_array(2));
669 assert!(done);
670 assert_eq!(cb.status(), BufferStatus::BufferFilling); cb.take_captured();
673
674 cb.push(make_array(3));
676
677 cb.trigger();
679 assert_eq!(cb.trigger_count(), 2);
680 assert_eq!(cb.status(), BufferStatus::Flushing);
681
682 let done = cb.push(make_array(4));
683 assert!(done);
684 assert_eq!(cb.status(), BufferStatus::AcquisitionCompleted);
685
686 cb.take_captured();
687
688 let done = cb.push(make_array(5));
690 assert!(!done);
691 assert_eq!(cb.status(), BufferStatus::AcquisitionCompleted);
692
693 cb.trigger();
695 assert_eq!(cb.trigger_count(), 2); }
697
698 #[test]
699 fn test_buffer_status_transitions() {
700 let mut cb = CircularBuffer::new(2, 1, TriggerCondition::External);
701
702 assert_eq!(cb.status(), BufferStatus::Idle);
704
705 cb.push(make_array(1));
707 assert_eq!(cb.status(), BufferStatus::BufferFilling);
708
709 cb.push(make_array(2));
710 assert_eq!(cb.status(), BufferStatus::BufferFilling);
711
712 cb.trigger();
714 assert_eq!(cb.status(), BufferStatus::Flushing);
715
716 let done = cb.push(make_array(3));
718 assert!(done);
719 assert_eq!(cb.status(), BufferStatus::BufferFilling);
720
721 cb.reset();
723 assert_eq!(cb.status(), BufferStatus::Idle);
724 assert_eq!(cb.trigger_count(), 0);
725 }
726}