1use std::cell::RefCell;
14use std::collections::VecDeque;
15use std::hash::Hash;
16use std::rc::Rc;
17
18use bolero::generator::bolero_generator::driver::object::Borrowed;
19use bolero::{ValueGenerator, produce};
20use dfir_rs::rustc_hash::FxHashMap;
21use dfir_rs::util::unsync::mpsc::Sender;
22
23use super::{
24 HookLocationMeta, ObservationHook, RuntimeHook, ScriptDecision, ScriptableHook,
25 ScriptableObservationHook, TruncatedLabeledVecDebug, TruncatedVecDebug, abort,
26 describe_keyed_pending, keyed_buffer_len, log_release,
27};
28
29pub struct TopLevelStreamOrderHook<T> {
48 pub input: Rc<RefCell<VecDeque<T>>>,
49 pub to_release: Option<Vec<T>>,
50 pub output: Sender<T>,
51 pub location: HookLocationMeta,
52 pub format_item_debug: fn(&T) -> Option<String>,
53}
54
55impl<T> RuntimeHook for TopLevelStreamOrderHook<T> {
56 fn has_pending_input(&self) -> bool {
57 !self.input.borrow().is_empty()
58 }
59
60 fn only_one_possible_decision(&self) -> bool {
61 self.input.borrow().len() <= 1
64 }
65
66 fn release_decision(&mut self, log_writer: Option<&mut dyn std::fmt::Write>) {
67 if let Some(to_release) = self.to_release.take() {
68 if !to_release.is_empty()
69 && let Some(log_writer) = log_writer
70 {
71 let HookLocationMeta {
72 location: batch_location,
73 line,
74 caret_indent,
75 } = self.location;
76 let note_str = format!(
77 "^ observed non-deterministic order: {:?}",
78 TruncatedVecDebug(
79 RefCell::new(Some(to_release.iter())),
80 8,
81 self.format_item_debug
82 )
83 );
84
85 let _ = writeln!(log_writer);
86 log_release(
87 log_writer,
88 batch_location,
89 line,
90 caret_indent,
91 ¬e_str,
92 colored::Color::Green,
93 );
94 }
95
96 for item in to_release {
97 self.output.try_send(item).unwrap();
98 }
99 } else {
100 panic!("No decision to release");
101 }
102 }
103
104 fn location_meta(&self) -> HookLocationMeta {
105 self.location
106 }
107}
108
109impl<T> ObservationHook for TopLevelStreamOrderHook<T> {
110 fn autonomous_decision<'a>(&mut self, driver: &mut Borrowed<'a>) {
111 let mut current_input = self.input.borrow_mut();
112 let idx = (0..current_input.len()).generate(driver).unwrap();
115 let item = current_input.remove(idx).unwrap();
116 self.to_release = Some(vec![item]);
117 }
118}
119
120#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
122pub enum TopLevelOrderingDecision<T> {
123 Next(T),
124}
125
126impl<T> ScriptDecision for TopLevelOrderingDecision<T>
127where
128 T: serde::Serialize + serde::de::DeserializeOwned,
129{
130 fn describe(&self) -> String {
131 "next(value)".to_owned()
132 }
133}
134
135#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
136pub struct OrderingStatus {
137 pub buffered: usize,
138}
139
140impl<T> ScriptableHook for TopLevelStreamOrderHook<T>
141where
142 T: serde::Serialize + serde::de::DeserializeOwned + PartialEq,
143{
144 type Decision = TopLevelOrderingDecision<T>;
145 type Status = OrderingStatus;
146
147 fn is_honorable(&self, decision: &Self::Decision) -> Result<bool, String> {
148 let TopLevelOrderingDecision::Next(expected) = decision;
149 Ok(self.input.borrow().iter().any(|item| item == expected))
150 }
151
152 fn apply(&mut self, decision: Self::Decision) {
153 let mut input = self.input.borrow_mut();
154 let TopLevelOrderingDecision::Next(expected) = decision;
155 let index = input.iter().position(|item| item == &expected).unwrap();
156 self.to_release = Some(vec![input.remove(index).unwrap()]);
157 }
158
159 fn implicit(&mut self) {
160 abort!("implicit decision invoked on a top-level ordering hook");
164 }
165
166 fn status(&self) -> Self::Status {
167 OrderingStatus {
168 buffered: self.input.borrow().len(),
169 }
170 }
171
172 fn describe_pending(&self) -> Option<String> {
173 let input = self.input.borrow();
174 (!input.is_empty()).then(|| {
175 format!(
176 "{} buffered ordering item(s): {:?}",
177 input.len(),
178 TruncatedVecDebug(RefCell::new(Some(input.iter())), 8, self.format_item_debug)
179 )
180 })
181 }
182}
183
184impl<T> ScriptableObservationHook for TopLevelStreamOrderHook<T> where
185 T: serde::Serialize + serde::de::DeserializeOwned + PartialEq
186{
187}
188
189pub struct TopLevelFoldHook<T> {
193 pub input: Rc<RefCell<VecDeque<T>>>,
194 pub to_release: Option<Vec<T>>,
195 pub output: Sender<Vec<T>>,
196 pub location: HookLocationMeta,
197 pub format_item_debug: fn(&T) -> Option<String>,
198}
199
200impl<T> RuntimeHook for TopLevelFoldHook<T> {
201 fn has_pending_input(&self) -> bool {
202 !self.input.borrow().is_empty()
203 }
204
205 fn only_one_possible_decision(&self) -> bool {
206 self.input.borrow().len() <= 1
209 }
210
211 fn release_decision(&mut self, log_writer: Option<&mut dyn std::fmt::Write>) {
212 if let Some(to_release) = self.to_release.take() {
213 if !to_release.is_empty()
214 && let Some(log_writer) = log_writer
215 {
216 let HookLocationMeta {
217 location: batch_location,
218 line,
219 caret_indent,
220 } = self.location;
221 let note_str = format!(
222 "^ fold input batch (permuted): {:?}",
223 TruncatedVecDebug(
224 RefCell::new(Some(to_release.iter())),
225 8,
226 self.format_item_debug
227 )
228 );
229
230 let _ = writeln!(log_writer);
231 log_release(
232 log_writer,
233 batch_location,
234 line,
235 caret_indent,
236 ¬e_str,
237 colored::Color::Green,
238 );
239 }
240
241 self.output.try_send(to_release).unwrap();
242 } else {
243 panic!("No decision to release");
244 }
245 }
246
247 fn location_meta(&self) -> HookLocationMeta {
248 self.location
249 }
250}
251
252impl<T> ObservationHook for TopLevelFoldHook<T> {
253 fn autonomous_decision<'a>(&mut self, driver: &mut Borrowed<'a>) {
254 let mut current_input = self.input.borrow_mut();
255
256 let mut selected = Vec::new();
259 let mut remaining = VecDeque::new();
260
261 let len = current_input.len();
262 for (i, item) in current_input.drain(..).enumerate() {
263 let is_last = i == len - 1;
264 let must_include = is_last && selected.is_empty();
265 if must_include || produce().generate(driver).unwrap() {
266 selected.push(item);
267 } else {
268 remaining.push_back(item);
269 }
270 }
271
272 *current_input = remaining;
274
275 {
279 let slen = selected.len();
280 for i in (1..slen).rev() {
281 let j = (0..=i).generate(driver).unwrap();
282 selected.swap(i, j);
283 }
284 }
285
286 self.to_release = Some(selected);
287 }
288}
289
290impl<T> ScriptableHook for TopLevelFoldHook<T>
295where
296 T: serde::Serialize + serde::de::DeserializeOwned + PartialEq,
297{
298 type Decision = TopLevelOrderingDecision<T>;
299 type Status = OrderingStatus;
300
301 fn is_honorable(&self, decision: &Self::Decision) -> Result<bool, String> {
302 let TopLevelOrderingDecision::Next(expected) = decision;
303 Ok(self.input.borrow().iter().any(|item| item == expected))
304 }
305
306 fn apply(&mut self, decision: Self::Decision) {
307 let TopLevelOrderingDecision::Next(expected) = decision;
308 let mut input = self.input.borrow_mut();
309 let index = input.iter().position(|item| item == &expected).unwrap();
310 self.to_release = Some(vec![input.remove(index).unwrap()]);
311 }
312
313 fn implicit(&mut self) {
314 abort!("implicit decision invoked on a top-level fold hook");
318 }
319
320 fn status(&self) -> Self::Status {
321 OrderingStatus {
322 buffered: self.input.borrow().len(),
323 }
324 }
325
326 fn describe_pending(&self) -> Option<String> {
327 let input = self.input.borrow();
328 (!input.is_empty()).then(|| {
329 format!(
330 "{} buffered fold input(s): {:?}",
331 input.len(),
332 TruncatedVecDebug(RefCell::new(Some(input.iter())), 8, self.format_item_debug)
333 )
334 })
335 }
336}
337
338impl<T> ScriptableObservationHook for TopLevelFoldHook<T> where
339 T: serde::Serialize + serde::de::DeserializeOwned + PartialEq
340{
341}
342
343pub struct TopLevelKeyedStreamOrderHook<K: Hash + Eq + Clone, V> {
347 pub input: Rc<RefCell<FxHashMap<K, VecDeque<V>>>>,
348 pub to_release: Option<Vec<(K, V)>>,
349 pub output: Sender<(K, V)>,
350 pub location: HookLocationMeta,
351 pub format_item_debug: fn(&(K, V)) -> Option<String>,
352}
353
354impl<K: Hash + Eq + Clone, V> RuntimeHook for TopLevelKeyedStreamOrderHook<K, V> {
355 fn has_pending_input(&self) -> bool {
356 #[expect(clippy::disallowed_methods, reason = "FxHasher is deterministic")]
357 !self.input.borrow().values().all(|q| q.is_empty())
358 }
359
360 fn only_one_possible_decision(&self) -> bool {
361 #[expect(clippy::disallowed_methods, reason = "FxHasher is deterministic")]
363 let total: usize = self.input.borrow().values().map(|q| q.len()).sum();
364 total <= 1
365 }
366
367 fn release_decision(&mut self, log_writer: Option<&mut dyn std::fmt::Write>) {
368 if let Some(to_release) = self.to_release.take() {
369 if !to_release.is_empty()
370 && let Some(log_writer) = log_writer
371 {
372 let HookLocationMeta {
373 location: batch_location,
374 line,
375 caret_indent,
376 } = self.location;
377 let note_str = format!(
378 "^ observed non-deterministic order: {:?}",
379 TruncatedVecDebug(
380 RefCell::new(Some(to_release.iter())),
381 8,
382 self.format_item_debug
383 )
384 );
385
386 let _ = writeln!(log_writer);
387 log_release(
388 log_writer,
389 batch_location,
390 line,
391 caret_indent,
392 ¬e_str,
393 colored::Color::Green,
394 );
395 }
396
397 for item in to_release {
398 self.output.try_send(item).unwrap();
399 }
400 } else {
401 panic!("No decision to release");
402 }
403 }
404
405 fn location_meta(&self) -> HookLocationMeta {
406 self.location
407 }
408}
409
410impl<K: Hash + Eq + Clone, V> ObservationHook for TopLevelKeyedStreamOrderHook<K, V> {
411 fn autonomous_decision<'a>(&mut self, driver: &mut Borrowed<'a>) {
412 let mut current_input = self.input.borrow_mut();
413
414 #[expect(clippy::disallowed_methods, reason = "FxHasher is deterministic")]
416 let nonempty_keys: Vec<(K, usize)> = current_input
417 .iter()
418 .filter(|(_, q)| !q.is_empty())
419 .map(|(k, q)| (k.clone(), q.len()))
420 .collect();
421
422 let key_idx = (0..nonempty_keys.len()).generate(driver).unwrap();
424 let (key, queue_len) = &nonempty_keys[key_idx];
425
426 let item_idx = (0..*queue_len).generate(driver).unwrap();
428 let item = current_input
429 .get_mut(key)
430 .unwrap()
431 .remove(item_idx)
432 .unwrap();
433
434 self.to_release = Some(vec![(key.clone(), item)]);
435 }
436}
437
438impl<K, V> ScriptableHook for TopLevelKeyedStreamOrderHook<K, V>
442where
443 K: Hash + Eq + Clone + serde::Serialize + serde::de::DeserializeOwned,
444 V: serde::Serialize + serde::de::DeserializeOwned + PartialEq,
445{
446 type Decision = TopLevelOrderingDecision<(K, V)>;
447 type Status = OrderingStatus;
448
449 fn is_honorable(&self, decision: &Self::Decision) -> Result<bool, String> {
450 let TopLevelOrderingDecision::Next((key, expected)) = decision;
451 Ok(self
452 .input
453 .borrow()
454 .get(key)
455 .is_some_and(|queue| queue.iter().any(|item| item == expected)))
456 }
457
458 fn apply(&mut self, decision: Self::Decision) {
459 let TopLevelOrderingDecision::Next((key, expected)) = decision;
460 let mut input = self.input.borrow_mut();
461 let queue = input.get_mut(&key).unwrap();
462 let index = queue.iter().position(|item| item == &expected).unwrap();
463 let item = queue.remove(index).unwrap();
464 self.to_release = Some(vec![(key, item)]);
465 }
466
467 fn implicit(&mut self) {
468 abort!("implicit decision invoked on a top-level keyed ordering hook");
472 }
473
474 fn status(&self) -> Self::Status {
475 OrderingStatus {
476 buffered: keyed_buffer_len(&self.input.borrow()),
477 }
478 }
479
480 fn describe_pending(&self) -> Option<String> {
481 describe_keyed_pending(&self.input.borrow())
482 }
483}
484
485impl<K, V> ScriptableObservationHook for TopLevelKeyedStreamOrderHook<K, V>
486where
487 K: Hash + Eq + Clone + serde::Serialize + serde::de::DeserializeOwned,
488 V: serde::Serialize + serde::de::DeserializeOwned + PartialEq,
489{
490}
491
492pub struct TopLevelPartiallyOrderedStreamHook<K: Hash + Eq + Clone, V> {
496 pub input: Rc<RefCell<FxHashMap<K, VecDeque<V>>>>,
497 pub to_release: Option<Vec<(K, V)>>,
498 pub output: Sender<(K, V)>,
499 pub location: HookLocationMeta,
500 pub format_item_debug: fn(&(K, V)) -> Option<String>,
501}
502
503impl<K: Hash + Eq + Clone, V> RuntimeHook for TopLevelPartiallyOrderedStreamHook<K, V> {
504 fn has_pending_input(&self) -> bool {
505 #[expect(clippy::disallowed_methods, reason = "FxHasher is deterministic")]
506 !self.input.borrow().values().all(|q| q.is_empty())
507 }
508
509 fn only_one_possible_decision(&self) -> bool {
510 #[expect(clippy::disallowed_methods, reason = "FxHasher is deterministic")]
513 let nonempty_keys = self
514 .input
515 .borrow()
516 .values()
517 .filter(|q| !q.is_empty())
518 .count();
519 nonempty_keys <= 1
520 }
521
522 fn release_decision(&mut self, log_writer: Option<&mut dyn std::fmt::Write>) {
523 if let Some(to_release) = self.to_release.take() {
524 if !to_release.is_empty()
525 && let Some(log_writer) = log_writer
526 {
527 let HookLocationMeta {
528 location: batch_location,
529 line,
530 caret_indent,
531 } = self.location;
532 let note_str = format!(
533 "^ observed partially-ordered interleaving: {:?}",
534 TruncatedVecDebug(
535 RefCell::new(Some(to_release.iter())),
536 8,
537 self.format_item_debug
538 )
539 );
540
541 let _ = writeln!(log_writer);
542 log_release(
543 log_writer,
544 batch_location,
545 line,
546 caret_indent,
547 ¬e_str,
548 colored::Color::Green,
549 );
550 }
551
552 for item in to_release {
553 self.output.try_send(item).unwrap();
554 }
555 } else {
556 panic!("No decision to release");
557 }
558 }
559
560 fn location_meta(&self) -> HookLocationMeta {
561 self.location
562 }
563}
564
565impl<K: Hash + Eq + Clone, V> ObservationHook for TopLevelPartiallyOrderedStreamHook<K, V> {
566 fn autonomous_decision<'a>(&mut self, driver: &mut Borrowed<'a>) {
567 let mut current_input = self.input.borrow_mut();
568
569 #[expect(clippy::disallowed_methods, reason = "FxHasher is deterministic")]
570 let nonempty_keys: Vec<K> = current_input
571 .iter()
572 .filter(|(_, q)| !q.is_empty())
573 .map(|(k, _)| k.clone())
574 .collect();
575
576 let key_idx = (0..nonempty_keys.len()).generate(driver).unwrap();
578 let key = &nonempty_keys[key_idx];
579
580 let item = current_input.get_mut(key).unwrap().pop_front().unwrap();
582
583 self.to_release = Some(vec![(key.clone(), item)]);
584 }
585}
586
587impl<K, V> ScriptableHook for TopLevelPartiallyOrderedStreamHook<K, V>
591where
592 K: Hash + Eq + Clone + serde::Serialize + serde::de::DeserializeOwned,
593 V: serde::Serialize + serde::de::DeserializeOwned + PartialEq,
594{
595 type Decision = TopLevelOrderingDecision<(K, V)>;
596 type Status = OrderingStatus;
597
598 fn is_honorable(&self, decision: &Self::Decision) -> Result<bool, String> {
599 let TopLevelOrderingDecision::Next((key, expected)) = decision;
600 match self.input.borrow().get(key).and_then(VecDeque::front) {
601 Some(front) if front == expected => Ok(true),
602 Some(_) => Err(
605 "next: the named value did not match the front of its key's buffered queue \
606 (within-key order is preserved by this operator)"
607 .to_owned(),
608 ),
609 None => Ok(false),
610 }
611 }
612
613 fn apply(&mut self, decision: Self::Decision) {
614 let TopLevelOrderingDecision::Next((key, _expected)) = decision;
615 let mut input = self.input.borrow_mut();
616 let item = input.get_mut(&key).unwrap().pop_front().unwrap();
617 self.to_release = Some(vec![(key, item)]);
618 }
619
620 fn implicit(&mut self) {
621 abort!("implicit decision invoked on a top-level partially-ordered hook");
625 }
626
627 fn status(&self) -> Self::Status {
628 OrderingStatus {
629 buffered: keyed_buffer_len(&self.input.borrow()),
630 }
631 }
632
633 fn describe_pending(&self) -> Option<String> {
634 describe_keyed_pending(&self.input.borrow())
635 }
636}
637
638impl<K, V> ScriptableObservationHook for TopLevelPartiallyOrderedStreamHook<K, V>
639where
640 K: Hash + Eq + Clone + serde::Serialize + serde::de::DeserializeOwned,
641 V: serde::Serialize + serde::de::DeserializeOwned + PartialEq,
642{
643}
644
645pub struct TopLevelMergeOrderedHook<T> {
649 pub first: Rc<RefCell<VecDeque<T>>>,
650 pub second: Rc<RefCell<VecDeque<T>>>,
651 pub to_release: Option<Vec<T>>,
652 pub release_source: Option<&'static str>,
653 pub output: Sender<T>,
654 pub location: HookLocationMeta,
655 pub format_item_debug: fn(&T) -> Option<String>,
656}
657
658impl<T> RuntimeHook for TopLevelMergeOrderedHook<T> {
659 fn has_pending_input(&self) -> bool {
660 !self.first.borrow().is_empty() || !self.second.borrow().is_empty()
661 }
662
663 fn only_one_possible_decision(&self) -> bool {
664 self.first.borrow().is_empty() || self.second.borrow().is_empty()
667 }
668
669 fn release_decision(&mut self, log_writer: Option<&mut dyn std::fmt::Write>) {
670 if let Some(to_release) = self.to_release.take() {
671 let source = self.release_source.take();
672 if !to_release.is_empty()
673 && let Some(log_writer) = log_writer
674 {
675 let HookLocationMeta {
676 location: batch_location,
677 line,
678 caret_indent,
679 } = self.location;
680 let source_label = source.unwrap_or("?");
681
682 let labeled_iter = to_release.iter().map(|item| (source_label, item));
683
684 let note_str = format!(
685 "^ observed non-deterministic merge order: {:?}",
686 TruncatedLabeledVecDebug(
687 RefCell::new(Some(labeled_iter)),
688 8,
689 self.format_item_debug
690 )
691 );
692
693 let _ = writeln!(log_writer);
694 log_release(
695 log_writer,
696 batch_location,
697 line,
698 caret_indent,
699 ¬e_str,
700 colored::Color::Green,
701 );
702 }
703
704 for item in to_release {
705 self.output.try_send(item).unwrap();
706 }
707 } else {
708 panic!("No decision to release");
709 }
710 }
711
712 fn location_meta(&self) -> HookLocationMeta {
713 self.location
714 }
715}
716
717impl<T> ObservationHook for TopLevelMergeOrderedHook<T> {
718 fn autonomous_decision<'a>(&mut self, driver: &mut Borrowed<'a>) {
719 let first_empty = self.first.borrow().is_empty();
720 let second_empty = self.second.borrow().is_empty();
721
722 let (item, source) = if first_empty {
723 (self.second.borrow_mut().pop_front().unwrap(), "r")
724 } else if second_empty {
725 (self.first.borrow_mut().pop_front().unwrap(), "l")
726 } else {
727 let take_second: bool = produce().generate(driver).unwrap();
728 if take_second {
729 (self.second.borrow_mut().pop_front().unwrap(), "r")
730 } else {
731 (self.first.borrow_mut().pop_front().unwrap(), "l")
732 }
733 };
734
735 self.to_release = Some(vec![item]);
736 self.release_source = Some(source);
737 }
738}
739
740#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
747pub enum MergeDecision<T, S = ()> {
748 First(T),
750 Second(T),
752 FirstNext(S),
754 SecondNext(S),
756}
757
758impl<T, S> ScriptDecision for MergeDecision<T, S>
759where
760 T: serde::Serialize + serde::de::DeserializeOwned,
761 S: serde::Serialize + serde::de::DeserializeOwned,
762{
763 fn describe(&self) -> String {
764 match self {
765 MergeDecision::First(_) => "next_first(value)".to_owned(),
766 MergeDecision::Second(_) => "next_second(value)".to_owned(),
767 MergeDecision::FirstNext(_) => "advance_first(..)".to_owned(),
768 MergeDecision::SecondNext(_) => "advance_second(..)".to_owned(),
769 }
770 }
771}
772
773#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
776pub struct MergeStatus {
777 pub first_buffered: usize,
779 pub second_buffered: usize,
781}
782
783impl<T> ScriptableHook for TopLevelMergeOrderedHook<T>
784where
785 T: serde::Serialize + serde::de::DeserializeOwned + PartialEq,
786{
787 type Decision = MergeDecision<T>;
788 type Status = MergeStatus;
789
790 fn is_honorable(&self, decision: &MergeDecision<T>) -> Result<bool, String> {
791 let (buffer, expected) = match decision {
792 MergeDecision::First(expected) => (&self.first, Some(expected)),
793 MergeDecision::Second(expected) => (&self.second, Some(expected)),
794 MergeDecision::FirstNext(()) => (&self.first, None),
795 MergeDecision::SecondNext(()) => (&self.second, None),
796 };
797 match (buffer.borrow().front(), expected) {
798 (Some(_), None) => Ok(true),
799 (Some(front), Some(expected)) if front == expected => Ok(true),
800 (Some(_), Some(_)) => Err(
803 "next_first/next_second: the named value did not match the front of that \
804 input's buffer (per-input order is preserved by merge_ordered)"
805 .to_owned(),
806 ),
807 (None, _) => Ok(false),
808 }
809 }
810
811 fn apply(&mut self, decision: MergeDecision<T>) {
812 let (item, source) = match decision {
813 MergeDecision::First(_) | MergeDecision::FirstNext(()) => {
814 (self.first.borrow_mut().pop_front().unwrap(), "l")
815 }
816 MergeDecision::Second(_) | MergeDecision::SecondNext(()) => {
817 (self.second.borrow_mut().pop_front().unwrap(), "r")
818 }
819 };
820 self.to_release = Some(vec![item]);
821 self.release_source = Some(source);
822 }
823
824 fn implicit(&mut self) {
825 abort!("implicit decision invoked on a top-level merge-ordered hook");
829 }
830
831 fn status(&self) -> MergeStatus {
832 MergeStatus {
833 first_buffered: self.first.borrow().len(),
834 second_buffered: self.second.borrow().len(),
835 }
836 }
837
838 fn describe_pending(&self) -> Option<String> {
839 let first = self.first.borrow();
840 let second = self.second.borrow();
841 (!first.is_empty() || !second.is_empty()).then(|| {
842 format!(
843 "{} + {} buffered item(s) (first + second input)",
844 first.len(),
845 second.len()
846 )
847 })
848 }
849}
850
851impl<T> ScriptableObservationHook for TopLevelMergeOrderedHook<T> where
852 T: serde::Serialize + serde::de::DeserializeOwned + PartialEq
853{
854}
855
856pub struct TopLevelKeyedMergeOrderedHook<K: Hash + Eq + Clone, V> {
863 pub first: Rc<RefCell<FxHashMap<K, VecDeque<V>>>>,
864 pub second: Rc<RefCell<FxHashMap<K, VecDeque<V>>>>,
865 pub to_release: Option<Vec<(K, V)>>,
866 pub release_source: Option<&'static str>,
867 pub output: Sender<(K, V)>,
868 pub location: HookLocationMeta,
869 pub format_item_debug: fn(&(K, V)) -> Option<String>,
870}
871
872impl<K: Hash + Eq + Clone, V> RuntimeHook for TopLevelKeyedMergeOrderedHook<K, V> {
873 fn has_pending_input(&self) -> bool {
874 #[expect(clippy::disallowed_methods, reason = "FxHasher is deterministic")]
875 let first_nonempty = !self.first.borrow().values().all(|q| q.is_empty());
876 #[expect(clippy::disallowed_methods, reason = "FxHasher is deterministic")]
877 let second_nonempty = !self.second.borrow().values().all(|q| q.is_empty());
878 first_nonempty || second_nonempty
879 }
880
881 fn only_one_possible_decision(&self) -> bool {
882 #[expect(clippy::disallowed_methods, reason = "FxHasher is deterministic")]
885 let first_count = self
886 .first
887 .borrow()
888 .values()
889 .filter(|q| !q.is_empty())
890 .count();
891 #[expect(clippy::disallowed_methods, reason = "FxHasher is deterministic")]
892 let second_count = self
893 .second
894 .borrow()
895 .values()
896 .filter(|q| !q.is_empty())
897 .count();
898 first_count + second_count <= 1
899 }
900
901 fn release_decision(&mut self, log_writer: Option<&mut dyn std::fmt::Write>) {
902 if let Some(to_release) = self.to_release.take() {
903 let source = self.release_source.take();
904 if !to_release.is_empty()
905 && let Some(log_writer) = log_writer
906 {
907 let HookLocationMeta {
908 location: batch_location,
909 line,
910 caret_indent,
911 } = self.location;
912 let source_label = source.unwrap_or("?");
913
914 let labeled_iter = to_release.iter().map(|item| (source_label, item));
915
916 let note_str = format!(
917 "^ observed non-deterministic merge order: {:?}",
918 TruncatedLabeledVecDebug(
919 RefCell::new(Some(labeled_iter)),
920 8,
921 self.format_item_debug
922 )
923 );
924
925 let _ = writeln!(log_writer);
926 log_release(
927 log_writer,
928 batch_location,
929 line,
930 caret_indent,
931 ¬e_str,
932 colored::Color::Green,
933 );
934 }
935
936 for item in to_release {
937 self.output.try_send(item).unwrap();
938 }
939 } else {
940 panic!("No decision to release");
941 }
942 }
943
944 fn location_meta(&self) -> HookLocationMeta {
945 self.location
946 }
947}
948
949impl<K: Hash + Eq + Clone, V> ObservationHook for TopLevelKeyedMergeOrderedHook<K, V> {
950 fn autonomous_decision<'a>(&mut self, driver: &mut Borrowed<'a>) {
951 let mut candidates: Vec<(bool, K)> = Vec::new();
954 {
955 #[expect(clippy::disallowed_methods, reason = "FxHasher is deterministic")]
956 for (k, q) in self.first.borrow().iter() {
957 if !q.is_empty() {
958 candidates.push((false, k.clone()));
959 }
960 }
961 #[expect(clippy::disallowed_methods, reason = "FxHasher is deterministic")]
962 for (k, q) in self.second.borrow().iter() {
963 if !q.is_empty() {
964 candidates.push((true, k.clone()));
965 }
966 }
967 }
968
969 let idx = (0..candidates.len()).generate(driver).unwrap();
970 let (take_second, key) = &candidates[idx];
971 let take_second = *take_second;
972
973 let item = if take_second {
974 self.second
975 .borrow_mut()
976 .get_mut(key)
977 .unwrap()
978 .pop_front()
979 .unwrap()
980 } else {
981 self.first
982 .borrow_mut()
983 .get_mut(key)
984 .unwrap()
985 .pop_front()
986 .unwrap()
987 };
988
989 self.to_release = Some(vec![(key.clone(), item)]);
990 self.release_source = Some(if take_second { "r" } else { "l" });
991 }
992}
993
994impl<K, V> ScriptableHook for TopLevelKeyedMergeOrderedHook<K, V>
995where
996 K: Hash + Eq + Clone + serde::Serialize + serde::de::DeserializeOwned,
997 V: serde::Serialize + serde::de::DeserializeOwned + PartialEq,
998{
999 type Decision = MergeDecision<(K, V), K>;
1000 type Status = MergeStatus;
1001
1002 fn is_honorable(&self, decision: &MergeDecision<(K, V), K>) -> Result<bool, String> {
1003 let (buffer, key, expected) = match decision {
1004 MergeDecision::First((key, expected)) => (&self.first, key, Some(expected)),
1005 MergeDecision::Second((key, expected)) => (&self.second, key, Some(expected)),
1006 MergeDecision::FirstNext(key) => (&self.first, key, None),
1007 MergeDecision::SecondNext(key) => (&self.second, key, None),
1008 };
1009 match (buffer.borrow().get(key).and_then(VecDeque::front), expected) {
1010 (Some(_), None) => Ok(true),
1011 (Some(front), Some(expected)) if front == expected => Ok(true),
1012 (Some(_), Some(_)) => Err(
1016 "next_first/next_second: the named value did not match the front of its \
1017 key's buffer in that input (per-input within-key order is preserved by \
1018 merge_ordered)"
1019 .to_owned(),
1020 ),
1021 (None, _) => Ok(false),
1022 }
1023 }
1024
1025 fn apply(&mut self, decision: MergeDecision<(K, V), K>) {
1026 let (buffer, key, source) = match decision {
1027 MergeDecision::First((key, _)) => (&self.first, key, "l"),
1028 MergeDecision::Second((key, _)) => (&self.second, key, "r"),
1029 MergeDecision::FirstNext(key) => (&self.first, key, "l"),
1030 MergeDecision::SecondNext(key) => (&self.second, key, "r"),
1031 };
1032 let item = buffer
1033 .borrow_mut()
1034 .get_mut(&key)
1035 .unwrap()
1036 .pop_front()
1037 .unwrap();
1038 self.to_release = Some(vec![(key, item)]);
1039 self.release_source = Some(source);
1040 }
1041
1042 fn implicit(&mut self) {
1043 abort!("implicit decision invoked on a top-level keyed merge-ordered hook");
1047 }
1048
1049 fn status(&self) -> MergeStatus {
1050 MergeStatus {
1051 first_buffered: keyed_buffer_len(&self.first.borrow()),
1052 second_buffered: keyed_buffer_len(&self.second.borrow()),
1053 }
1054 }
1055
1056 fn describe_pending(&self) -> Option<String> {
1057 let first = keyed_buffer_len(&self.first.borrow());
1058 let second = keyed_buffer_len(&self.second.borrow());
1059 (first + second > 0).then(|| {
1060 format!(
1061 "{} + {} buffered item(s) (first + second input)",
1062 first, second
1063 )
1064 })
1065 }
1066}
1067
1068impl<K, V> ScriptableObservationHook for TopLevelKeyedMergeOrderedHook<K, V>
1069where
1070 K: Hash + Eq + Clone + serde::Serialize + serde::de::DeserializeOwned,
1071 V: serde::Serialize + serde::de::DeserializeOwned + PartialEq,
1072{
1073}
1074
1075pub struct TopLevelNoOrderStreamRetriesHook<T> {
1093 pub input: Rc<RefCell<VecDeque<T>>>,
1094 pub to_release: Option<Vec<T>>,
1095 pub output: Sender<T>,
1096 pub location: HookLocationMeta,
1097 pub format_item_debug: fn(&T) -> Option<String>,
1098}
1099
1100pub struct TopLevelOrderedStreamRetriesHook<T> {
1136 pub input: Rc<RefCell<VecDeque<T>>>,
1137 pub to_release: Option<Vec<T>>,
1138 pub release_provisional: bool,
1141 pub front_started: bool,
1145 pub output: Sender<T>,
1146 pub location: HookLocationMeta,
1147 pub format_item_debug: fn(&T) -> Option<String>,
1148}
1149
1150impl<T> RuntimeHook for TopLevelNoOrderStreamRetriesHook<T> {
1151 fn has_pending_input(&self) -> bool {
1152 !self.input.borrow().is_empty()
1153 }
1154
1155 fn only_one_possible_decision(&self) -> bool {
1156 self.input.borrow().is_empty()
1159 }
1160
1161 fn release_decision(&mut self, log_writer: Option<&mut dyn std::fmt::Write>) {
1162 if let Some(to_release) = self.to_release.take() {
1163 if !to_release.is_empty()
1164 && let Some(log_writer) = log_writer
1165 {
1166 let HookLocationMeta {
1167 location: batch_location,
1168 line,
1169 caret_indent,
1170 } = self.location;
1171 let note_str = format!(
1172 "^ observed non-deterministic retries: {:?}",
1173 TruncatedVecDebug(
1174 RefCell::new(Some(to_release.iter())),
1175 8,
1176 self.format_item_debug
1177 )
1178 );
1179
1180 let _ = writeln!(log_writer);
1181 log_release(
1182 log_writer,
1183 batch_location,
1184 line,
1185 caret_indent,
1186 ¬e_str,
1187 colored::Color::Green,
1188 );
1189 }
1190
1191 for item in to_release {
1192 self.output.try_send(item).unwrap();
1193 }
1194 } else {
1195 panic!("No decision to release");
1196 }
1197 }
1198
1199 fn location_meta(&self) -> HookLocationMeta {
1200 self.location
1201 }
1202}
1203
1204impl<T> RuntimeHook for TopLevelOrderedStreamRetriesHook<T> {
1205 fn has_pending_input(&self) -> bool {
1206 !self.input.borrow().is_empty()
1207 }
1208
1209 fn only_one_possible_decision(&self) -> bool {
1210 self.input.borrow().is_empty()
1213 }
1214
1215 fn release_decision(&mut self, log_writer: Option<&mut dyn std::fmt::Write>) {
1216 if let Some(to_release) = self.to_release.take() {
1217 let provisional = self.release_provisional;
1218 if !to_release.is_empty()
1219 && let Some(log_writer) = log_writer
1220 {
1221 let HookLocationMeta {
1222 location: batch_location,
1223 line,
1224 caret_indent,
1225 } = self.location;
1226 let note_str = format!(
1227 "^ observed non-deterministic retries: {:?}{}",
1228 TruncatedVecDebug(
1229 RefCell::new(Some(to_release.iter())),
1230 8,
1231 self.format_item_debug
1232 ),
1233 if provisional {
1234 " (last value may repeat)"
1235 } else {
1236 ""
1237 }
1238 );
1239
1240 let _ = writeln!(log_writer);
1241 log_release(
1242 log_writer,
1243 batch_location,
1244 line,
1245 caret_indent,
1246 ¬e_str,
1247 colored::Color::Green,
1248 );
1249 }
1250
1251 for item in to_release {
1252 self.output.try_send(item).unwrap();
1253 }
1254 } else {
1255 panic!("No decision to release");
1256 }
1257 }
1258
1259 fn location_meta(&self) -> HookLocationMeta {
1260 self.location
1261 }
1262}
1263
1264#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
1267pub enum NoOrderRetriesDecision<T> {
1268 Release(T, usize),
1269}
1270
1271impl<T> ScriptDecision for NoOrderRetriesDecision<T>
1272where
1273 T: serde::Serialize + serde::de::DeserializeOwned,
1274{
1275 fn describe(&self) -> String {
1276 let NoOrderRetriesDecision::Release(_, copies) = self;
1277 format!("release(value, {})", copies)
1278 }
1279}
1280
1281#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
1286pub enum OrderedRetriesDecision<T> {
1287 Release(Vec<T>),
1290 ReleaseFinal(Vec<T>),
1294 ReleaseNext(usize),
1297}
1298
1299impl<T> ScriptDecision for OrderedRetriesDecision<T>
1300where
1301 T: serde::Serialize + serde::de::DeserializeOwned,
1302{
1303 fn describe(&self) -> String {
1304 match self {
1305 OrderedRetriesDecision::Release(values) => {
1306 format!("release({} value(s))", values.len())
1307 }
1308 OrderedRetriesDecision::ReleaseFinal(values) => {
1309 format!("release_final({} value(s))", values.len())
1310 }
1311 OrderedRetriesDecision::ReleaseNext(copies) => format!("release_next({})", copies),
1312 }
1313 }
1314}
1315
1316fn expand_retries<T: Clone>(item: T, copies: usize) -> Vec<T> {
1319 let mut to_release = Vec::with_capacity(copies);
1320 for _ in 1..copies {
1321 to_release.push(item.clone());
1322 }
1323 to_release.push(item);
1324 to_release
1325}
1326
1327fn check_copies_at_least_one(copies: usize) -> Result<(), String> {
1330 if copies == 0 {
1331 Err("the number of copies must be at least 1 (at-least-once permits duplicates, not losses)".to_owned())
1332 } else {
1333 Ok(())
1334 }
1335}
1336
1337impl<T> ScriptableHook for TopLevelNoOrderStreamRetriesHook<T>
1338where
1339 T: Clone + serde::Serialize + serde::de::DeserializeOwned + PartialEq,
1340{
1341 type Decision = NoOrderRetriesDecision<T>;
1342 type Status = OrderingStatus;
1343
1344 fn is_honorable(&self, decision: &Self::Decision) -> Result<bool, String> {
1345 let NoOrderRetriesDecision::Release(expected, copies) = decision;
1346 check_copies_at_least_one(*copies)?;
1347 Ok(self.input.borrow().iter().any(|item| item == expected))
1348 }
1349
1350 fn apply(&mut self, decision: Self::Decision) {
1351 let NoOrderRetriesDecision::Release(expected, copies) = decision;
1352 let mut input = self.input.borrow_mut();
1353 let index = input.iter().position(|item| item == &expected).unwrap();
1354 let item = input.remove(index).unwrap();
1355 self.to_release = Some(expand_retries(item, copies));
1356 }
1357
1358 fn implicit(&mut self) {
1359 abort!("implicit decision invoked on a top-level retries hook");
1363 }
1364
1365 fn status(&self) -> Self::Status {
1366 OrderingStatus {
1367 buffered: self.input.borrow().len(),
1368 }
1369 }
1370
1371 fn describe_pending(&self) -> Option<String> {
1372 let input = self.input.borrow();
1373 (!input.is_empty()).then(|| {
1374 format!(
1375 "{} buffered item(s) awaiting a retries decision: {:?}",
1376 input.len(),
1377 TruncatedVecDebug(RefCell::new(Some(input.iter())), 8, self.format_item_debug)
1378 )
1379 })
1380 }
1381}
1382
1383impl<T> ScriptableObservationHook for TopLevelNoOrderStreamRetriesHook<T> where
1384 T: Clone + serde::Serialize + serde::de::DeserializeOwned + PartialEq
1385{
1386}
1387
1388enum PrefixWalk {
1391 Complete { consumed: usize },
1395 NeedMoreInput,
1398 Mismatch,
1401}
1402
1403fn walk_ordered_retries<T: PartialEq>(
1411 input: &VecDeque<T>,
1412 front_started: bool,
1413 values: &[T],
1414) -> PrefixWalk {
1415 let mut consumed = 0;
1416 let mut started = front_started;
1417 for value in values {
1418 if !started {
1419 match input.get(consumed) {
1420 Some(front) if front == value => started = true,
1421 Some(_) => return PrefixWalk::Mismatch,
1422 None => return PrefixWalk::NeedMoreInput,
1423 }
1424 } else if input.get(consumed + 1).is_some_and(|next| next == value) {
1425 consumed += 1;
1427 } else if input.get(consumed).is_some_and(|current| current == value) {
1428 } else if consumed + 1 >= input.len() {
1430 return PrefixWalk::NeedMoreInput;
1431 } else {
1432 return PrefixWalk::Mismatch;
1433 }
1434 }
1435 PrefixWalk::Complete { consumed }
1436}
1437
1438impl<T> ScriptableHook for TopLevelOrderedStreamRetriesHook<T>
1439where
1440 T: Clone + serde::Serialize + serde::de::DeserializeOwned + PartialEq,
1441{
1442 type Decision = OrderedRetriesDecision<T>;
1443 type Status = OrderingStatus;
1444
1445 fn is_honorable(&self, decision: &Self::Decision) -> Result<bool, String> {
1446 let values = match decision {
1447 OrderedRetriesDecision::Release(values) => {
1448 if values.is_empty() {
1449 return Err(
1452 "a non-final release must contain at least one value (declare \
1453 intentional buffering with the pause family instead)"
1454 .to_owned(),
1455 );
1456 }
1457 values
1458 }
1459 OrderedRetriesDecision::ReleaseFinal(values) => {
1460 if values.is_empty() {
1461 return if self.front_started {
1462 Ok(true)
1463 } else {
1464 Err(
1465 "release_final with no values requires an earlier release to \
1466 have left the front element's copies open"
1467 .to_owned(),
1468 )
1469 };
1470 }
1471 values
1472 }
1473 OrderedRetriesDecision::ReleaseNext(copies) => {
1474 check_copies_at_least_one(*copies)?;
1475 return Ok(!self.input.borrow().is_empty());
1476 }
1477 };
1478 match walk_ordered_retries(&self.input.borrow(), self.front_started, values) {
1479 PrefixWalk::Complete { .. } => Ok(true),
1480 PrefixWalk::NeedMoreInput => Ok(false),
1481 PrefixWalk::Mismatch => Err(
1484 "the scripted values did not match the buffered queue (input order is \
1485 preserved, so values are released in arrival order and repeat only \
1486 back-to-back)"
1487 .to_owned(),
1488 ),
1489 }
1490 }
1491
1492 fn apply(&mut self, decision: Self::Decision) {
1493 match decision {
1494 OrderedRetriesDecision::Release(values) => {
1495 let PrefixWalk::Complete { consumed } =
1496 walk_ordered_retries(&self.input.borrow(), self.front_started, &values)
1497 else {
1498 abort!("ordered retries decision applied while not honorable");
1499 };
1500 self.input.borrow_mut().drain(..consumed);
1501 self.to_release = Some(values);
1502 self.release_provisional = true;
1503 self.front_started = true;
1504 }
1505 OrderedRetriesDecision::ReleaseFinal(values) => {
1506 if values.is_empty() {
1507 self.input.borrow_mut().pop_front().unwrap();
1510 self.to_release = Some(vec![]);
1511 } else {
1512 let PrefixWalk::Complete { consumed } =
1513 walk_ordered_retries(&self.input.borrow(), self.front_started, &values)
1514 else {
1515 abort!("ordered retries decision applied while not honorable");
1516 };
1517 self.input.borrow_mut().drain(..=consumed);
1519 self.to_release = Some(values);
1520 }
1521 self.release_provisional = false;
1522 self.front_started = false;
1523 }
1524 OrderedRetriesDecision::ReleaseNext(copies) => {
1525 let item = self.input.borrow_mut().pop_front().unwrap();
1526 self.to_release = Some(expand_retries(item, copies));
1527 self.release_provisional = false;
1528 self.front_started = false;
1529 }
1530 }
1531 }
1532
1533 fn implicit(&mut self) {
1534 abort!("implicit decision invoked on a top-level retries hook");
1538 }
1539
1540 fn status(&self) -> Self::Status {
1541 OrderingStatus {
1542 buffered: self.input.borrow().len(),
1543 }
1544 }
1545
1546 fn describe_pending(&self) -> Option<String> {
1547 let input = self.input.borrow();
1548 (!input.is_empty()).then(|| {
1549 format!(
1550 "{} buffered item(s) awaiting a retries decision{}: {:?}",
1551 input.len(),
1552 if self.front_started {
1553 " (the front element's copies are still open)"
1554 } else {
1555 ""
1556 },
1557 TruncatedVecDebug(RefCell::new(Some(input.iter())), 8, self.format_item_debug)
1558 )
1559 })
1560 }
1561}
1562
1563impl<T> ScriptableObservationHook for TopLevelOrderedStreamRetriesHook<T> where
1564 T: Clone + serde::Serialize + serde::de::DeserializeOwned + PartialEq
1565{
1566}
1567
1568pub struct TopLevelAtLeastOnceOrderHook<T> {
1595 pub input: Rc<RefCell<VecDeque<T>>>,
1596 pub to_release: Option<Vec<T>>,
1597 pub release_provisional: bool,
1600 pub last_emitted: Option<T>,
1607 pub emitted_counts: Vec<(T, usize)>,
1611 pub output: Sender<T>,
1612 pub location: HookLocationMeta,
1613 pub format_item_debug: fn(&T) -> Option<String>,
1614}
1615
1616impl<T> RuntimeHook for TopLevelAtLeastOnceOrderHook<T> {
1617 fn has_pending_input(&self) -> bool {
1618 !self.input.borrow().is_empty()
1619 }
1620
1621 fn only_one_possible_decision(&self) -> bool {
1622 self.input.borrow().is_empty()
1625 }
1626
1627 fn release_decision(&mut self, log_writer: Option<&mut dyn std::fmt::Write>) {
1628 if let Some(to_release) = self.to_release.take() {
1629 let provisional = std::mem::take(&mut self.release_provisional);
1630 if !to_release.is_empty()
1631 && let Some(log_writer) = log_writer
1632 {
1633 let HookLocationMeta {
1634 location: batch_location,
1635 line,
1636 caret_indent,
1637 } = self.location;
1638 let note_str = format!(
1639 "^ observed non-deterministic order: {:?}{}",
1640 TruncatedVecDebug(
1641 RefCell::new(Some(to_release.iter())),
1642 8,
1643 self.format_item_debug
1644 ),
1645 if provisional { " (may re-emit)" } else { "" }
1646 );
1647
1648 let _ = writeln!(log_writer);
1649 log_release(
1650 log_writer,
1651 batch_location,
1652 line,
1653 caret_indent,
1654 ¬e_str,
1655 colored::Color::Green,
1656 );
1657 }
1658
1659 for item in to_release {
1660 self.output.try_send(item).unwrap();
1661 }
1662 } else {
1663 panic!("No decision to release");
1664 }
1665 }
1666
1667 fn location_meta(&self) -> HookLocationMeta {
1668 self.location
1669 }
1670}
1671
1672#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
1676pub enum AtLeastOnceOrderingDecision<T> {
1677 Emit(T),
1678 EmitFinal(T),
1679}
1680
1681impl<T> ScriptDecision for AtLeastOnceOrderingDecision<T>
1682where
1683 T: serde::Serialize + serde::de::DeserializeOwned,
1684{
1685 fn describe(&self) -> String {
1686 match self {
1687 AtLeastOnceOrderingDecision::Emit(_) => "emit(value)".to_owned(),
1688 AtLeastOnceOrderingDecision::EmitFinal(_) => "emit_final(value)".to_owned(),
1689 }
1690 }
1691}
1692
1693impl<T> TopLevelAtLeastOnceOrderHook<T>
1694where
1695 T: PartialEq,
1696{
1697 fn buffered_count(&self, value: &T) -> usize {
1699 self.input
1700 .borrow()
1701 .iter()
1702 .filter(|item| *item == value)
1703 .count()
1704 }
1705
1706 fn emitted_count(&self, value: &T) -> usize {
1708 self.emitted_counts
1709 .iter()
1710 .find(|(v, _)| v == value)
1711 .map_or(0, |(_, count)| *count)
1712 }
1713
1714 fn check_decision(&self, value: &T, is_final: bool) -> Result<bool, String> {
1718 let buffered = self.buffered_count(value);
1719 if buffered == 0 {
1720 return Ok(false);
1721 }
1722 if self.last_emitted.as_ref() == Some(value) && buffered < 2 {
1723 return Err(
1724 "the same value cannot be released twice in a row when only one buffered \
1725 element matches it (back-to-back duplicates are the downstream \
1726 `assume_retries`'s decision)"
1727 .to_owned(),
1728 );
1729 }
1730 if is_final && self.emitted_count(value) + 1 < buffered {
1731 return Err(format!(
1732 "emit_final consumes all {} buffered element(s) equal to the value, but \
1733 only {} would have been emitted (every element must be emitted at least \
1734 once)",
1735 buffered,
1736 self.emitted_count(value) + 1,
1737 ));
1738 }
1739 Ok(true)
1740 }
1741}
1742
1743impl<T> ScriptableHook for TopLevelAtLeastOnceOrderHook<T>
1744where
1745 T: Clone + serde::Serialize + serde::de::DeserializeOwned + PartialEq,
1746{
1747 type Decision = AtLeastOnceOrderingDecision<T>;
1748 type Status = OrderingStatus;
1749
1750 fn is_honorable(&self, decision: &Self::Decision) -> Result<bool, String> {
1751 match decision {
1752 AtLeastOnceOrderingDecision::Emit(expected) => self.check_decision(expected, false),
1753 AtLeastOnceOrderingDecision::EmitFinal(expected) => self.check_decision(expected, true),
1754 }
1755 }
1756
1757 fn apply(&mut self, decision: Self::Decision) {
1758 match decision {
1759 AtLeastOnceOrderingDecision::Emit(expected) => {
1760 if let Some((_, count)) =
1761 self.emitted_counts.iter_mut().find(|(v, _)| *v == expected)
1762 {
1763 *count += 1;
1764 } else {
1765 self.emitted_counts.push((expected.clone(), 1));
1766 }
1767 self.to_release = Some(vec![expected.clone()]);
1768 self.release_provisional = true;
1769 self.last_emitted = Some(expected);
1770 }
1771 AtLeastOnceOrderingDecision::EmitFinal(expected) => {
1772 self.input.borrow_mut().retain(|item| item != &expected);
1776 self.emitted_counts.retain(|(v, _)| v != &expected);
1777 self.to_release = Some(vec![expected]);
1778 self.release_provisional = false;
1779 self.last_emitted = None;
1780 }
1781 }
1782 }
1783
1784 fn implicit(&mut self) {
1785 abort!("implicit decision invoked on a top-level at-least-once ordering hook");
1789 }
1790
1791 fn status(&self) -> Self::Status {
1792 OrderingStatus {
1793 buffered: self.input.borrow().len(),
1794 }
1795 }
1796
1797 fn describe_pending(&self) -> Option<String> {
1798 let input = self.input.borrow();
1799 (!input.is_empty()).then(|| {
1800 format!(
1801 "{} buffered ordering item(s) (each may be emitted into several slots): {:?}",
1802 input.len(),
1803 TruncatedVecDebug(RefCell::new(Some(input.iter())), 8, self.format_item_debug)
1804 )
1805 })
1806 }
1807}
1808
1809impl<T> ScriptableObservationHook for TopLevelAtLeastOnceOrderHook<T> where
1810 T: Clone + serde::Serialize + serde::de::DeserializeOwned + PartialEq
1811{
1812}