1use std::collections::VecDeque;
2use std::sync::Arc;
3
4use ad_core_rs::ndarray::NDArray;
5use ad_core_rs::ndarray_pool::NDArrayPool;
6use ad_core_rs::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) => match op {
72 CalcOp::Not => {
73 let v = stack.pop().unwrap_or(0.0);
74 stack.push(if v == 0.0 { 1.0 } else { 0.0 });
75 }
76 _ => {
77 let rhs = stack.pop().unwrap_or(0.0);
78 let lhs = stack.pop().unwrap_or(0.0);
79 let result = match op {
80 CalcOp::Gt => {
81 if lhs > rhs {
82 1.0
83 } else {
84 0.0
85 }
86 }
87 CalcOp::Lt => {
88 if lhs < rhs {
89 1.0
90 } else {
91 0.0
92 }
93 }
94 CalcOp::Ge => {
95 if lhs >= rhs {
96 1.0
97 } else {
98 0.0
99 }
100 }
101 CalcOp::Le => {
102 if lhs <= rhs {
103 1.0
104 } else {
105 0.0
106 }
107 }
108 CalcOp::Eq => {
109 if (lhs - rhs).abs() < f64::EPSILON {
110 1.0
111 } else {
112 0.0
113 }
114 }
115 CalcOp::Ne => {
116 if (lhs - rhs).abs() >= f64::EPSILON {
117 1.0
118 } else {
119 0.0
120 }
121 }
122 CalcOp::And => {
123 if lhs != 0.0 && rhs != 0.0 {
124 1.0
125 } else {
126 0.0
127 }
128 }
129 CalcOp::Or => {
130 if lhs != 0.0 || rhs != 0.0 {
131 1.0
132 } else {
133 0.0
134 }
135 }
136 CalcOp::Not => unreachable!(),
137 };
138 stack.push(result);
139 }
140 },
141 }
142 }
143 stack.pop().unwrap_or(0.0)
144 }
145
146 fn precedence(op: &CalcOp) -> u8 {
147 match op {
148 CalcOp::Or => 1,
149 CalcOp::And => 2,
150 CalcOp::Eq | CalcOp::Ne => 3,
151 CalcOp::Gt | CalcOp::Lt | CalcOp::Ge | CalcOp::Le => 4,
152 CalcOp::Not => 5,
153 }
154 }
155
156 fn is_right_assoc(op: &CalcOp) -> bool {
157 matches!(op, CalcOp::Not)
158 }
159
160 fn tokenize(expr: &str) -> Option<Vec<RawToken>> {
161 use RawToken as RT;
162 let chars: Vec<char> = expr.chars().collect();
163 let mut tokens = Vec::new();
164 let mut i = 0;
165
166 while i < chars.len() {
167 match chars[i] {
168 ' ' | '\t' => {
169 i += 1;
170 }
171 '(' => {
172 tokens.push(RT::LParen);
173 i += 1;
174 }
175 ')' => {
176 tokens.push(RT::RParen);
177 i += 1;
178 }
179 'A' | 'a' => {
180 tokens.push(RT::VarA);
181 i += 1;
182 }
183 'B' | 'b' => {
184 tokens.push(RT::VarB);
185 i += 1;
186 }
187 '>' => {
188 if i + 1 < chars.len() && chars[i + 1] == '=' {
189 tokens.push(RT::Op(CalcOp::Ge));
190 i += 2;
191 } else {
192 tokens.push(RT::Op(CalcOp::Gt));
193 i += 1;
194 }
195 }
196 '<' => {
197 if i + 1 < chars.len() && chars[i + 1] == '=' {
198 tokens.push(RT::Op(CalcOp::Le));
199 i += 2;
200 } else {
201 tokens.push(RT::Op(CalcOp::Lt));
202 i += 1;
203 }
204 }
205 '=' => {
206 if i + 1 < chars.len() && chars[i + 1] == '=' {
207 tokens.push(RT::Op(CalcOp::Eq));
208 i += 2;
209 } else {
210 return None; }
212 }
213 '!' => {
214 if i + 1 < chars.len() && chars[i + 1] == '=' {
215 tokens.push(RT::Op(CalcOp::Ne));
216 i += 2;
217 } else {
218 tokens.push(RT::Op(CalcOp::Not));
219 i += 1;
220 }
221 }
222 '&' => {
223 if i + 1 < chars.len() && chars[i + 1] == '&' {
224 tokens.push(RT::Op(CalcOp::And));
225 i += 2;
226 } else {
227 return None;
228 }
229 }
230 '|' => {
231 if i + 1 < chars.len() && chars[i + 1] == '|' {
232 tokens.push(RT::Op(CalcOp::Or));
233 i += 2;
234 } else {
235 return None;
236 }
237 }
238 c if c.is_ascii_digit() || c == '.' => {
239 let start = i;
240 while i < chars.len() && (chars[i].is_ascii_digit() || chars[i] == '.') {
241 i += 1;
242 }
243 let num_str: String = chars[start..i].iter().collect();
244 let num: f64 = num_str.parse().ok()?;
245 tokens.push(RT::Num(num));
246 }
247 '-' => {
248 let is_unary_minus = tokens.is_empty()
250 || matches!(tokens.last(), Some(RT::LParen) | Some(RT::Op(_)));
251 if is_unary_minus
252 && i + 1 < chars.len()
253 && (chars[i + 1].is_ascii_digit() || chars[i + 1] == '.')
254 {
255 i += 1; let start = i;
257 while i < chars.len() && (chars[i].is_ascii_digit() || chars[i] == '.') {
258 i += 1;
259 }
260 let num_str: String = chars[start..i].iter().collect();
261 let num: f64 = num_str.parse().ok()?;
262 tokens.push(RT::Num(-num));
263 } else {
264 return None; }
266 }
267 _ => return None,
268 }
269 }
270
271 Some(tokens)
272 }
273
274 fn shunting_yard(raw: Vec<RawToken>) -> Option<Vec<CalcToken>> {
275 use RawToken as RT;
276 let mut output: Vec<CalcToken> = Vec::new();
277 let mut op_stack: Vec<RawToken> = Vec::new();
278
279 for tok in raw {
280 match tok {
281 RT::Num(n) => output.push(CalcToken::Num(n)),
282 RT::VarA => output.push(CalcToken::VarA),
283 RT::VarB => output.push(CalcToken::VarB),
284 RT::Op(ref op) => {
285 while let Some(RT::Op(top_op)) = op_stack.last() {
286 let top_prec = Self::precedence(top_op);
287 let cur_prec = Self::precedence(op);
288 if (!Self::is_right_assoc(op) && cur_prec <= top_prec)
289 || (Self::is_right_assoc(op) && cur_prec < top_prec)
290 {
291 if let Some(RT::Op(o)) = op_stack.pop() {
292 output.push(CalcToken::Op(o));
293 }
294 } else {
295 break;
296 }
297 }
298 op_stack.push(tok);
299 }
300 RT::LParen => op_stack.push(tok),
301 RT::RParen => {
302 loop {
303 match op_stack.pop() {
304 Some(RT::LParen) => break,
305 Some(RT::Op(o)) => output.push(CalcToken::Op(o)),
306 _ => return None, }
308 }
309 }
310 }
311 }
312
313 while let Some(tok) = op_stack.pop() {
315 match tok {
316 RT::Op(o) => output.push(CalcToken::Op(o)),
317 RT::LParen => return None, _ => return None,
319 }
320 }
321
322 Some(output)
323 }
324}
325
326#[derive(Debug, Clone)]
328pub enum TriggerCondition {
329 AttributeThreshold { name: String, threshold: f64 },
331 External,
333 Calc {
335 attr_a: String,
336 attr_b: String,
337 expression: CalcExpression,
338 },
339}
340
341#[derive(Debug, Clone, Copy, PartialEq, Eq)]
343pub enum BufferStatus {
344 Idle,
345 BufferFilling,
346 Flushing,
347 AcquisitionCompleted,
348}
349
350pub struct CircularBuffer {
352 pub(crate) pre_count: usize,
353 pub(crate) post_count: usize,
354 buffer: VecDeque<Arc<NDArray>>,
355 pub(crate) trigger_condition: TriggerCondition,
356 triggered: bool,
357 post_remaining: usize,
358 captured: Vec<Arc<NDArray>>,
359 preset_trigger_count: usize,
361 trigger_count: usize,
363 flush_on_soft_trigger: bool,
365 pub(crate) status: BufferStatus,
367}
368
369impl CircularBuffer {
370 pub fn new(pre_count: usize, post_count: usize, condition: TriggerCondition) -> Self {
371 Self {
372 pre_count,
373 post_count,
374 buffer: VecDeque::with_capacity(pre_count + 1),
375 trigger_condition: condition,
376 triggered: false,
377 post_remaining: 0,
378 captured: Vec::new(),
379 preset_trigger_count: 0,
380 trigger_count: 0,
381 flush_on_soft_trigger: false,
382 status: BufferStatus::Idle,
383 }
384 }
385
386 pub fn set_preset_trigger_count(&mut self, count: usize) {
388 self.preset_trigger_count = count;
389 }
390
391 pub fn trigger_count(&self) -> usize {
393 self.trigger_count
394 }
395
396 pub fn status(&self) -> BufferStatus {
398 self.status
399 }
400
401 pub fn set_flush_on_soft_trigger(&mut self, flush: bool) {
403 self.flush_on_soft_trigger = flush;
404 }
405
406 pub fn push(&mut self, array: Arc<NDArray>) -> bool {
409 if self.status == BufferStatus::AcquisitionCompleted {
411 return false;
412 }
413
414 if self.status == BufferStatus::Idle {
416 self.status = BufferStatus::BufferFilling;
417 }
418
419 if self.triggered {
420 self.captured.push(array);
422 self.post_remaining -= 1;
423 if self.post_remaining == 0 {
424 self.triggered = false;
425 if self.preset_trigger_count > 0 && self.trigger_count >= self.preset_trigger_count
427 {
428 self.status = BufferStatus::AcquisitionCompleted;
429 } else {
430 self.status = BufferStatus::BufferFilling;
431 }
432 return true;
433 }
434 return false;
435 }
436
437 let trigger = match &self.trigger_condition {
439 TriggerCondition::AttributeThreshold { name, threshold } => array
440 .attributes
441 .get(name)
442 .and_then(|a| a.value.as_f64())
443 .map(|v| v >= *threshold)
444 .unwrap_or(false),
445 TriggerCondition::External => false,
446 TriggerCondition::Calc {
447 attr_a,
448 attr_b,
449 expression,
450 } => {
451 let a = array
452 .attributes
453 .get(attr_a)
454 .and_then(|a| a.value.as_f64())
455 .unwrap_or(0.0);
456 let b = array
457 .attributes
458 .get(attr_b)
459 .and_then(|a| a.value.as_f64())
460 .unwrap_or(0.0);
461 expression.evaluate(a, b) != 0.0
462 }
463 };
464
465 self.buffer.push_back(array);
467 if self.buffer.len() > self.pre_count {
468 self.buffer.pop_front();
469 }
470
471 if trigger {
472 self.trigger();
473 }
474
475 false
476 }
477
478 pub fn trigger(&mut self) {
480 if self.status == BufferStatus::AcquisitionCompleted {
482 return;
483 }
484
485 self.triggered = true;
486 self.post_remaining = self.post_count;
487 self.trigger_count += 1;
488 self.status = BufferStatus::Flushing;
489 self.captured.clear();
491 self.captured.extend(self.buffer.drain(..));
492 }
493
494 pub fn take_captured(&mut self) -> Vec<Arc<NDArray>> {
496 std::mem::take(&mut self.captured)
497 }
498
499 pub fn is_triggered(&self) -> bool {
500 self.triggered
501 }
502
503 pub fn pre_buffer_len(&self) -> usize {
504 self.buffer.len()
505 }
506
507 pub fn reset(&mut self) {
508 self.buffer.clear();
509 self.captured.clear();
510 self.triggered = false;
511 self.post_remaining = 0;
512 self.trigger_count = 0;
513 self.status = BufferStatus::Idle;
514 }
515}
516
517#[derive(Default)]
521struct CBParamIndices {
522 control: Option<usize>,
523 status: Option<usize>,
524 trigger_a: Option<usize>,
525 trigger_b: Option<usize>,
526 trigger_a_val: Option<usize>,
527 trigger_b_val: Option<usize>,
528 trigger_calc: Option<usize>,
529 trigger_calc_val: Option<usize>,
530 pre_trigger: Option<usize>,
531 post_trigger: Option<usize>,
532 current_image: Option<usize>,
533 post_count: Option<usize>,
534 soft_trigger: Option<usize>,
535 triggered: Option<usize>,
536 preset_trigger_count: Option<usize>,
537 actual_trigger_count: Option<usize>,
538 flush_on_soft_trigger: Option<usize>,
539}
540
541pub struct CircularBuffProcessor {
542 buffer: CircularBuffer,
543 params: CBParamIndices,
544 trigger_a_name: String,
546 trigger_b_name: String,
547 trigger_calc_expr: String,
548}
549
550impl CircularBuffProcessor {
551 pub fn new(pre_count: usize, post_count: usize, condition: TriggerCondition) -> Self {
552 Self {
553 buffer: CircularBuffer::new(pre_count, post_count, condition),
554 params: CBParamIndices::default(),
555 trigger_a_name: String::new(),
556 trigger_b_name: String::new(),
557 trigger_calc_expr: String::new(),
558 }
559 }
560
561 pub fn trigger(&mut self) {
562 self.buffer.trigger();
563 }
564
565 pub fn buffer(&self) -> &CircularBuffer {
566 &self.buffer
567 }
568
569 fn rebuild_trigger_condition(&mut self) {
571 if !self.trigger_calc_expr.is_empty() {
572 if let Some(expr) = CalcExpression::parse(&self.trigger_calc_expr) {
573 self.buffer.trigger_condition = TriggerCondition::Calc {
574 attr_a: self.trigger_a_name.clone(),
575 attr_b: self.trigger_b_name.clone(),
576 expression: expr,
577 };
578 return;
579 }
580 }
581 if !self.trigger_a_name.is_empty() {
582 self.buffer.trigger_condition = TriggerCondition::AttributeThreshold {
583 name: self.trigger_a_name.clone(),
584 threshold: 0.5,
585 };
586 } else {
587 self.buffer.trigger_condition = TriggerCondition::External;
588 }
589 }
590}
591
592impl NDPluginProcess for CircularBuffProcessor {
593 fn process_array(&mut self, array: &NDArray, _pool: &NDArrayPool) -> ProcessResult {
594 use ad_core_rs::plugin::runtime::ParamUpdate;
595
596 let done = self.buffer.push(Arc::new(array.clone()));
597
598 let mut updates = Vec::new();
599 if let Some(idx) = self.params.status {
600 let status_val = match self.buffer.status() {
601 BufferStatus::Idle => 0,
602 BufferStatus::BufferFilling => 1,
603 BufferStatus::Flushing => 2,
604 BufferStatus::AcquisitionCompleted => 3,
605 };
606 updates.push(ParamUpdate::int32(idx, status_val));
607 }
608 if let Some(idx) = self.params.current_image {
609 updates.push(ParamUpdate::int32(idx, self.buffer.pre_buffer_len() as i32));
610 }
611 if let Some(idx) = self.params.triggered {
612 updates.push(ParamUpdate::int32(
613 idx,
614 if self.buffer.is_triggered() { 1 } else { 0 },
615 ));
616 }
617 if let Some(idx) = self.params.actual_trigger_count {
618 updates.push(ParamUpdate::int32(idx, self.buffer.trigger_count() as i32));
619 }
620
621 if done {
622 let mut result = ProcessResult::arrays(self.buffer.take_captured());
623 result.param_updates = updates;
624 result
625 } else {
626 ProcessResult::sink(updates)
627 }
628 }
629
630 fn plugin_type(&self) -> &str {
631 "NDPluginCircularBuff"
632 }
633
634 fn register_params(
635 &mut self,
636 base: &mut asyn_rs::port::PortDriverBase,
637 ) -> asyn_rs::error::AsynResult<()> {
638 use asyn_rs::param::ParamType;
639 base.create_param("CIRC_BUFF_CONTROL", ParamType::Int32)?;
640 base.create_param("CIRC_BUFF_STATUS", ParamType::Int32)?;
641 base.create_param("CIRC_BUFF_TRIGGER_A", ParamType::Octet)?;
642 base.create_param("CIRC_BUFF_TRIGGER_B", ParamType::Octet)?;
643 base.create_param("CIRC_BUFF_TRIGGER_A_VAL", ParamType::Float64)?;
644 base.create_param("CIRC_BUFF_TRIGGER_B_VAL", ParamType::Float64)?;
645 base.create_param("CIRC_BUFF_TRIGGER_CALC", ParamType::Octet)?;
646 base.create_param("CIRC_BUFF_TRIGGER_CALC_VAL", ParamType::Float64)?;
647 base.create_param("CIRC_BUFF_PRE_TRIGGER", ParamType::Int32)?;
648 base.create_param("CIRC_BUFF_POST_TRIGGER", ParamType::Int32)?;
649 base.create_param("CIRC_BUFF_CURRENT_IMAGE", ParamType::Int32)?;
650 base.create_param("CIRC_BUFF_POST_COUNT", ParamType::Int32)?;
651 base.create_param("CIRC_BUFF_SOFT_TRIGGER", ParamType::Int32)?;
652 base.create_param("CIRC_BUFF_TRIGGERED", ParamType::Int32)?;
653 base.create_param("CIRC_BUFF_PRESET_TRIGGER_COUNT", ParamType::Int32)?;
654 base.create_param("CIRC_BUFF_ACTUAL_TRIGGER_COUNT", ParamType::Int32)?;
655 base.create_param("CIRC_BUFF_FLUSH_ON_SOFTTRIGGER", ParamType::Int32)?;
656
657 self.params.control = base.find_param("CIRC_BUFF_CONTROL");
658 self.params.status = base.find_param("CIRC_BUFF_STATUS");
659 self.params.trigger_a = base.find_param("CIRC_BUFF_TRIGGER_A");
660 self.params.trigger_b = base.find_param("CIRC_BUFF_TRIGGER_B");
661 self.params.trigger_a_val = base.find_param("CIRC_BUFF_TRIGGER_A_VAL");
662 self.params.trigger_b_val = base.find_param("CIRC_BUFF_TRIGGER_B_VAL");
663 self.params.trigger_calc = base.find_param("CIRC_BUFF_TRIGGER_CALC");
664 self.params.trigger_calc_val = base.find_param("CIRC_BUFF_TRIGGER_CALC_VAL");
665 self.params.pre_trigger = base.find_param("CIRC_BUFF_PRE_TRIGGER");
666 self.params.post_trigger = base.find_param("CIRC_BUFF_POST_TRIGGER");
667 self.params.current_image = base.find_param("CIRC_BUFF_CURRENT_IMAGE");
668 self.params.post_count = base.find_param("CIRC_BUFF_POST_COUNT");
669 self.params.soft_trigger = base.find_param("CIRC_BUFF_SOFT_TRIGGER");
670 self.params.triggered = base.find_param("CIRC_BUFF_TRIGGERED");
671 self.params.preset_trigger_count = base.find_param("CIRC_BUFF_PRESET_TRIGGER_COUNT");
672 self.params.actual_trigger_count = base.find_param("CIRC_BUFF_ACTUAL_TRIGGER_COUNT");
673 self.params.flush_on_soft_trigger = base.find_param("CIRC_BUFF_FLUSH_ON_SOFTTRIGGER");
674 Ok(())
675 }
676
677 fn on_param_change(
678 &mut self,
679 reason: usize,
680 params: &ad_core_rs::plugin::runtime::PluginParamSnapshot,
681 ) -> ad_core_rs::plugin::runtime::ParamChangeResult {
682 use ad_core_rs::plugin::runtime::{ParamChangeResult, ParamChangeValue};
683
684 if Some(reason) == self.params.control {
685 let v = params.value.as_i32();
686 if v == 1 {
687 self.buffer.reset();
689 self.buffer.status = BufferStatus::BufferFilling;
690 } else {
691 self.buffer.status = BufferStatus::Idle;
693 }
694 } else if Some(reason) == self.params.pre_trigger {
695 self.buffer.pre_count = params.value.as_i32().max(0) as usize;
696 } else if Some(reason) == self.params.post_trigger {
697 self.buffer.post_count = params.value.as_i32().max(0) as usize;
698 } else if Some(reason) == self.params.preset_trigger_count {
699 self.buffer
700 .set_preset_trigger_count(params.value.as_i32().max(0) as usize);
701 } else if Some(reason) == self.params.flush_on_soft_trigger {
702 self.buffer
703 .set_flush_on_soft_trigger(params.value.as_i32() != 0);
704 } else if Some(reason) == self.params.soft_trigger {
705 if params.value.as_i32() != 0 {
706 self.buffer.trigger();
707 }
708 } else if Some(reason) == self.params.trigger_a {
709 if let ParamChangeValue::Octet(s) = ¶ms.value {
710 self.trigger_a_name = s.clone();
711 self.rebuild_trigger_condition();
712 }
713 } else if Some(reason) == self.params.trigger_b {
714 if let ParamChangeValue::Octet(s) = ¶ms.value {
715 self.trigger_b_name = s.clone();
716 self.rebuild_trigger_condition();
717 }
718 } else if Some(reason) == self.params.trigger_calc {
719 if let ParamChangeValue::Octet(s) = ¶ms.value {
720 self.trigger_calc_expr = s.clone();
721 self.rebuild_trigger_condition();
722 }
723 }
724
725 ParamChangeResult::updates(vec![])
726 }
727}
728
729#[cfg(test)]
730mod tests {
731 use super::*;
732 use ad_core_rs::attributes::{NDAttrSource, NDAttrValue, NDAttribute};
733 use ad_core_rs::ndarray::{NDDataType, NDDimension};
734
735 fn make_array(id: i32) -> Arc<NDArray> {
736 let mut arr = NDArray::new(vec![NDDimension::new(4)], NDDataType::UInt8);
737 arr.unique_id = id;
738 Arc::new(arr)
739 }
740
741 fn make_array_with_attr(id: i32, attr_val: f64) -> Arc<NDArray> {
742 let mut arr = NDArray::new(vec![NDDimension::new(4)], NDDataType::UInt8);
743 arr.unique_id = id;
744 arr.attributes.add(NDAttribute {
745 name: "trigger".into(),
746 description: "".into(),
747 source: NDAttrSource::Driver,
748 value: NDAttrValue::Float64(attr_val),
749 });
750 Arc::new(arr)
751 }
752
753 fn make_array_with_attrs(id: i32, a_val: f64, b_val: f64) -> Arc<NDArray> {
754 let mut arr = NDArray::new(vec![NDDimension::new(4)], NDDataType::UInt8);
755 arr.unique_id = id;
756 arr.attributes.add(NDAttribute {
757 name: "attr_a".into(),
758 description: "".into(),
759 source: NDAttrSource::Driver,
760 value: NDAttrValue::Float64(a_val),
761 });
762 arr.attributes.add(NDAttribute {
763 name: "attr_b".into(),
764 description: "".into(),
765 source: NDAttrSource::Driver,
766 value: NDAttrValue::Float64(b_val),
767 });
768 Arc::new(arr)
769 }
770
771 #[test]
772 fn test_pre_trigger_buffering() {
773 let mut cb = CircularBuffer::new(3, 2, TriggerCondition::External);
774
775 for i in 0..5 {
776 cb.push(make_array(i));
777 }
778 assert_eq!(cb.pre_buffer_len(), 3);
780 }
781
782 #[test]
783 fn test_external_trigger() {
784 let mut cb = CircularBuffer::new(2, 2, TriggerCondition::External);
785
786 cb.push(make_array(1));
787 cb.push(make_array(2));
788 cb.push(make_array(3));
789 cb.trigger();
792 assert!(cb.is_triggered());
793
794 cb.push(make_array(4));
795 let done = cb.push(make_array(5));
796 assert!(done);
797
798 let captured = cb.take_captured();
799 assert_eq!(captured.len(), 4); assert_eq!(captured[0].unique_id, 2);
801 assert_eq!(captured[1].unique_id, 3);
802 assert_eq!(captured[2].unique_id, 4);
803 assert_eq!(captured[3].unique_id, 5);
804 }
805
806 #[test]
807 fn test_attribute_trigger() {
808 let mut cb = CircularBuffer::new(
809 1,
810 1,
811 TriggerCondition::AttributeThreshold {
812 name: "trigger".into(),
813 threshold: 5.0,
814 },
815 );
816
817 cb.push(make_array_with_attr(1, 1.0));
818 cb.push(make_array_with_attr(2, 2.0));
819 assert!(!cb.is_triggered());
820
821 cb.push(make_array_with_attr(3, 5.0));
823 assert!(cb.is_triggered());
824
825 let done = cb.push(make_array(4));
826 assert!(done);
827
828 let captured = cb.take_captured();
829 assert_eq!(captured.len(), 2); }
831
832 #[test]
835 fn test_calc_trigger() {
836 let expr = CalcExpression::parse("A>5").unwrap();
838 let mut cb = CircularBuffer::new(
839 1,
840 1,
841 TriggerCondition::Calc {
842 attr_a: "attr_a".into(),
843 attr_b: "attr_b".into(),
844 expression: expr,
845 },
846 );
847
848 cb.push(make_array_with_attrs(1, 3.0, 0.0));
850 assert!(!cb.is_triggered());
851
852 cb.push(make_array_with_attrs(2, 6.0, 0.0));
854 assert!(cb.is_triggered());
855
856 let done = cb.push(make_array(3));
857 assert!(done);
858
859 let captured = cb.take_captured();
860 assert_eq!(captured.len(), 2); }
862
863 #[test]
864 fn test_calc_expression_parse() {
865 let expr = CalcExpression::parse("A>5").unwrap();
867 assert_eq!(expr.evaluate(6.0, 0.0), 1.0);
868 assert_eq!(expr.evaluate(4.0, 0.0), 0.0);
869 assert_eq!(expr.evaluate(5.0, 0.0), 0.0); let expr = CalcExpression::parse("A>=5").unwrap();
873 assert_eq!(expr.evaluate(5.0, 0.0), 1.0);
874 assert_eq!(expr.evaluate(4.9, 0.0), 0.0);
875
876 let expr = CalcExpression::parse("A>3&&B<10").unwrap();
878 assert_eq!(expr.evaluate(4.0, 5.0), 1.0);
879 assert_eq!(expr.evaluate(2.0, 5.0), 0.0);
880 assert_eq!(expr.evaluate(4.0, 15.0), 0.0);
881
882 let expr = CalcExpression::parse("(A>10)||(B>10)").unwrap();
884 assert_eq!(expr.evaluate(11.0, 0.0), 1.0);
885 assert_eq!(expr.evaluate(0.0, 11.0), 1.0);
886 assert_eq!(expr.evaluate(0.0, 0.0), 0.0);
887
888 let expr = CalcExpression::parse("A!=0").unwrap();
890 assert_eq!(expr.evaluate(1.0, 0.0), 1.0);
891 assert_eq!(expr.evaluate(0.0, 0.0), 0.0);
892
893 let expr = CalcExpression::parse("A==B").unwrap();
895 assert_eq!(expr.evaluate(5.0, 5.0), 1.0);
896 assert_eq!(expr.evaluate(5.0, 6.0), 0.0);
897
898 let expr = CalcExpression::parse("!A").unwrap();
900 assert_eq!(expr.evaluate(0.0, 0.0), 1.0);
901 assert_eq!(expr.evaluate(1.0, 0.0), 0.0);
902
903 assert!(CalcExpression::parse("A=5").is_none()); assert!(CalcExpression::parse("A&B").is_none()); }
907
908 #[test]
909 fn test_preset_trigger_count() {
910 let mut cb = CircularBuffer::new(1, 1, TriggerCondition::External);
911 cb.set_preset_trigger_count(2);
912
913 assert_eq!(cb.status(), BufferStatus::Idle);
914
915 cb.push(make_array(1));
917 assert_eq!(cb.status(), BufferStatus::BufferFilling);
918
919 cb.trigger();
921 assert_eq!(cb.trigger_count(), 1);
922 assert_eq!(cb.status(), BufferStatus::Flushing);
923
924 let done = cb.push(make_array(2));
925 assert!(done);
926 assert_eq!(cb.status(), BufferStatus::BufferFilling); cb.take_captured();
929
930 cb.push(make_array(3));
932
933 cb.trigger();
935 assert_eq!(cb.trigger_count(), 2);
936 assert_eq!(cb.status(), BufferStatus::Flushing);
937
938 let done = cb.push(make_array(4));
939 assert!(done);
940 assert_eq!(cb.status(), BufferStatus::AcquisitionCompleted);
941
942 cb.take_captured();
943
944 let done = cb.push(make_array(5));
946 assert!(!done);
947 assert_eq!(cb.status(), BufferStatus::AcquisitionCompleted);
948
949 cb.trigger();
951 assert_eq!(cb.trigger_count(), 2); }
953
954 #[test]
955 fn test_buffer_status_transitions() {
956 let mut cb = CircularBuffer::new(2, 1, TriggerCondition::External);
957
958 assert_eq!(cb.status(), BufferStatus::Idle);
960
961 cb.push(make_array(1));
963 assert_eq!(cb.status(), BufferStatus::BufferFilling);
964
965 cb.push(make_array(2));
966 assert_eq!(cb.status(), BufferStatus::BufferFilling);
967
968 cb.trigger();
970 assert_eq!(cb.status(), BufferStatus::Flushing);
971
972 let done = cb.push(make_array(3));
974 assert!(done);
975 assert_eq!(cb.status(), BufferStatus::BufferFilling);
976
977 cb.reset();
979 assert_eq!(cb.status(), BufferStatus::Idle);
980 assert_eq!(cb.trigger_count(), 0);
981 }
982}