1use std::hash::Hash;
38use std::marker::PhantomData;
39
40use serde::Serialize;
41use serde::de::DeserializeOwned;
42
43use crate::live_collections::boundedness::{Boundedness, Unbounded};
44use crate::live_collections::stream::{
45 AtLeastOnce, ExactlyOnce, NoOrder, Ordering, Retries, TotalOrder,
46};
47
48pub trait SimHook {
84 fn create(next_id: &mut dyn FnMut() -> usize) -> Self;
86}
87
88impl<H: SimHook> SimHook for Option<H> {
89 fn create(next_id: &mut dyn FnMut() -> usize) -> Self {
90 Some(H::create(next_id))
91 }
92}
93
94pub struct OnProcess<P = ()> {
100 _phantom: PhantomData<fn(P)>,
101}
102
103pub struct OnCluster<C = ()> {
111 _phantom: PhantomData<fn(C)>,
112}
113
114pub struct OnMember<C = ()> {
122 _phantom: PhantomData<fn(C)>,
123}
124
125#[diagnostic::on_unimplemented(
129 message = "`{Self}` is not a scope that sim hook handles can be created with",
130 note = "handles are created with the `OnProcess<P>` or `OnCluster<C>` scope of the operator they will be bound to; `OnMember` handles only arise from `.on(member_id)` at scripting time"
131)]
132#[sealed::sealed]
133pub trait BindableHookScope {}
134#[sealed::sealed]
135impl<P> BindableHookScope for OnProcess<P> {}
136#[sealed::sealed]
137impl<C> BindableHookScope for OnCluster<C> {}
138
139#[diagnostic::on_unimplemented(
143 message = "a `{Self}`-scoped sim hook handle cannot script decisions",
144 note = "a hook bound to an operator running on a cluster has one independent instance per member; select the member to script with `.on(member_id)`"
145)]
146#[sealed::sealed]
147pub trait ScriptableHookScope {}
148#[sealed::sealed]
149impl<P> ScriptableHookScope for OnProcess<P> {}
150#[sealed::sealed]
151impl<C> ScriptableHookScope for OnMember<C> {}
152
153macro_rules! on_member_method {
156 ($handle:ident < $($param:ident),* >) => {
157 pub fn on(&self, member_id: u32) -> $handle<$($param,)* OnMember<C>> {
166 $handle {
167 id: self.id,
168 member: Some(member_id),
169 _phantom: PhantomData,
170 }
171 }
172 };
173}
174
175pub struct BatchHook<T, O: Ordering = TotalOrder, R: Retries = ExactlyOnce, S = OnProcess> {
182 pub(crate) id: usize,
183 #[cfg_attr(
185 not(feature = "sim"),
186 expect(dead_code, reason = "only read by the `sim`-gated scripting API")
187 )]
188 pub(crate) member: Option<u32>,
189 pub(crate) _phantom: PhantomData<fn(T, O, R, S)>,
190}
191
192impl<T, O: Ordering, R: Retries, C> BatchHook<T, O, R, OnCluster<C>> {
193 on_member_method!(BatchHook<T, O, R>);
194}
195
196impl<T, O: Ordering, R: Retries, S> Clone for BatchHook<T, O, R, S> {
197 fn clone(&self) -> Self {
198 *self
199 }
200}
201impl<T, O: Ordering, R: Retries, S> Copy for BatchHook<T, O, R, S> {}
202
203impl<T, O: Ordering, R: Retries, S> std::fmt::Debug for BatchHook<T, O, R, S> {
204 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205 f.debug_struct("BatchHook").field("id", &self.id).finish()
206 }
207}
208
209impl<T, O: Ordering, R: Retries, S: BindableHookScope> SimHook for BatchHook<T, O, R, S>
210where
211 T: Serialize + DeserializeOwned + PartialEq,
212{
213 fn create(next_id: &mut dyn FnMut() -> usize) -> Self {
214 BatchHook {
215 id: next_id(),
216 member: None,
217 _phantom: PhantomData,
218 }
219 }
220}
221
222pub struct SnapshotHook<T, S = OnProcess> {
228 pub(crate) id: usize,
229 #[cfg_attr(
231 not(feature = "sim"),
232 expect(dead_code, reason = "only read by the `sim`-gated scripting API")
233 )]
234 pub(crate) member: Option<u32>,
235 pub(crate) _phantom: PhantomData<fn(T, S)>,
236}
237
238impl<T, C> SnapshotHook<T, OnCluster<C>> {
239 on_member_method!(SnapshotHook<T>);
240}
241
242impl<T, S> Clone for SnapshotHook<T, S> {
243 fn clone(&self) -> Self {
244 *self
245 }
246}
247impl<T, S> Copy for SnapshotHook<T, S> {}
248
249impl<T, S> std::fmt::Debug for SnapshotHook<T, S> {
250 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251 f.debug_struct("SnapshotHook")
252 .field("id", &self.id)
253 .finish()
254 }
255}
256
257impl<T, S: BindableHookScope> SimHook for SnapshotHook<T, S>
258where
259 T: Clone + PartialEq + Serialize + DeserializeOwned,
260{
261 fn create(next_id: &mut dyn FnMut() -> usize) -> Self {
262 SnapshotHook {
263 id: next_id(),
264 member: None,
265 _phantom: PhantomData,
266 }
267 }
268}
269
270pub struct OrderingHook<T, B: Boundedness = Unbounded, R: Retries = ExactlyOnce, S = OnProcess> {
282 pub(crate) id: usize,
283 #[cfg_attr(
285 not(feature = "sim"),
286 expect(dead_code, reason = "only read by the `sim`-gated scripting API")
287 )]
288 pub(crate) member: Option<u32>,
289 pub(crate) _phantom: PhantomData<fn(T, B, R, S)>,
290}
291
292impl<T, B: Boundedness, R: Retries, C> OrderingHook<T, B, R, OnCluster<C>> {
293 on_member_method!(OrderingHook<T, B, R>);
294}
295
296impl<T, B: Boundedness, R: Retries, S> Clone for OrderingHook<T, B, R, S> {
297 fn clone(&self) -> Self {
298 *self
299 }
300}
301impl<T, B: Boundedness, R: Retries, S> Copy for OrderingHook<T, B, R, S> {}
302
303impl<T, B: Boundedness, R: Retries, S> std::fmt::Debug for OrderingHook<T, B, R, S> {
304 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
305 f.debug_struct("OrderingHook")
306 .field("id", &self.id)
307 .finish()
308 }
309}
310
311impl<T, B: Boundedness, S: BindableHookScope> SimHook for OrderingHook<T, B, ExactlyOnce, S>
312where
313 T: Serialize + DeserializeOwned + PartialEq,
314{
315 fn create(next_id: &mut dyn FnMut() -> usize) -> Self {
316 OrderingHook {
317 id: next_id(),
318 member: None,
319 _phantom: PhantomData,
320 }
321 }
322}
323
324impl<T, B: Boundedness, S: BindableHookScope> SimHook for OrderingHook<T, B, AtLeastOnce, S>
325where
326 T: Clone + Serialize + DeserializeOwned + PartialEq,
327{
328 fn create(next_id: &mut dyn FnMut() -> usize) -> Self {
329 OrderingHook {
330 id: next_id(),
331 member: None,
332 _phantom: PhantomData,
333 }
334 }
335}
336
337pub struct RetriesHook<T, O: Ordering = NoOrder, B: Boundedness = Unbounded, S = OnProcess> {
348 pub(crate) id: usize,
349 #[cfg_attr(
353 not(feature = "sim"),
354 expect(
355 dead_code,
356 reason = "read only by the `sim`-gated scripting machinery; handles are plain data usable without it"
357 )
358 )]
359 pub(crate) member: Option<u32>,
360 pub(crate) _phantom: PhantomData<fn(T, O, B, S)>,
361}
362
363impl<T, O: Ordering, B: Boundedness, C> RetriesHook<T, O, B, OnCluster<C>> {
364 on_member_method!(RetriesHook<T, O, B>);
365}
366
367impl<T, O: Ordering, B: Boundedness, S> Clone for RetriesHook<T, O, B, S> {
368 fn clone(&self) -> Self {
369 *self
370 }
371}
372impl<T, O: Ordering, B: Boundedness, S> Copy for RetriesHook<T, O, B, S> {}
373
374impl<T, O: Ordering, B: Boundedness, S> std::fmt::Debug for RetriesHook<T, O, B, S> {
375 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
376 f.debug_struct("RetriesHook").field("id", &self.id).finish()
377 }
378}
379
380impl<T, O: Ordering, B: Boundedness, S: BindableHookScope> SimHook for RetriesHook<T, O, B, S>
381where
382 T: Clone + Serialize + DeserializeOwned + PartialEq,
383{
384 fn create(next_id: &mut dyn FnMut() -> usize) -> Self {
385 RetriesHook {
386 id: next_id(),
387 member: None,
388 _phantom: PhantomData,
389 }
390 }
391}
392
393pub struct KeyedBatchHook<K, V, O: Ordering = TotalOrder, R: Retries = ExactlyOnce, S = OnProcess> {
405 pub(crate) id: usize,
406 #[cfg_attr(
408 not(feature = "sim"),
409 expect(dead_code, reason = "only read by the `sim`-gated scripting API")
410 )]
411 pub(crate) member: Option<u32>,
412 pub(crate) _phantom: PhantomData<fn(K, V, O, R, S)>,
413}
414
415impl<K, V, O: Ordering, R: Retries, C> KeyedBatchHook<K, V, O, R, OnCluster<C>> {
416 on_member_method!(KeyedBatchHook<K, V, O, R>);
417}
418
419impl<K, V, O: Ordering, R: Retries, S> Clone for KeyedBatchHook<K, V, O, R, S> {
420 fn clone(&self) -> Self {
421 *self
422 }
423}
424impl<K, V, O: Ordering, R: Retries, S> Copy for KeyedBatchHook<K, V, O, R, S> {}
425
426impl<K, V, O: Ordering, R: Retries, S> std::fmt::Debug for KeyedBatchHook<K, V, O, R, S> {
427 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
428 f.debug_struct("KeyedBatchHook")
429 .field("id", &self.id)
430 .finish()
431 }
432}
433
434impl<K, V, O: Ordering, R: Retries, S: BindableHookScope> SimHook for KeyedBatchHook<K, V, O, R, S>
435where
436 K: Hash + Eq + Clone + Serialize + DeserializeOwned,
437 V: Serialize + DeserializeOwned + PartialEq,
438{
439 fn create(next_id: &mut dyn FnMut() -> usize) -> Self {
440 KeyedBatchHook {
441 id: next_id(),
442 member: None,
443 _phantom: PhantomData,
444 }
445 }
446}
447
448pub struct KeyedSnapshotHook<K, V, S = OnProcess> {
455 pub(crate) id: usize,
456 #[cfg_attr(
458 not(feature = "sim"),
459 expect(dead_code, reason = "only read by the `sim`-gated scripting API")
460 )]
461 pub(crate) member: Option<u32>,
462 pub(crate) _phantom: PhantomData<fn(K, V, S)>,
463}
464
465impl<K, V, C> KeyedSnapshotHook<K, V, OnCluster<C>> {
466 on_member_method!(KeyedSnapshotHook<K, V>);
467}
468
469impl<K, V, S> Clone for KeyedSnapshotHook<K, V, S> {
470 fn clone(&self) -> Self {
471 *self
472 }
473}
474impl<K, V, S> Copy for KeyedSnapshotHook<K, V, S> {}
475
476impl<K, V, S> std::fmt::Debug for KeyedSnapshotHook<K, V, S> {
477 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
478 f.debug_struct("KeyedSnapshotHook")
479 .field("id", &self.id)
480 .finish()
481 }
482}
483
484impl<K, V, S: BindableHookScope> SimHook for KeyedSnapshotHook<K, V, S>
485where
486 K: Hash + Eq + Clone + Serialize + DeserializeOwned,
487 V: Clone + PartialEq + Serialize + DeserializeOwned,
488{
489 fn create(next_id: &mut dyn FnMut() -> usize) -> Self {
490 KeyedSnapshotHook {
491 id: next_id(),
492 member: None,
493 _phantom: PhantomData,
494 }
495 }
496}
497
498pub struct KeyedOrderingHook<K, V, B: Boundedness = Unbounded, S = OnProcess> {
505 pub(crate) id: usize,
506 #[cfg_attr(
508 not(feature = "sim"),
509 expect(dead_code, reason = "only read by the `sim`-gated scripting API")
510 )]
511 pub(crate) member: Option<u32>,
512 pub(crate) _phantom: PhantomData<fn(K, V, B, S)>,
513}
514
515impl<K, V, B: Boundedness, C> KeyedOrderingHook<K, V, B, OnCluster<C>> {
516 on_member_method!(KeyedOrderingHook<K, V, B>);
517}
518
519impl<K, V, B: Boundedness, S> Clone for KeyedOrderingHook<K, V, B, S> {
520 fn clone(&self) -> Self {
521 *self
522 }
523}
524impl<K, V, B: Boundedness, S> Copy for KeyedOrderingHook<K, V, B, S> {}
525
526impl<K, V, B: Boundedness, S> std::fmt::Debug for KeyedOrderingHook<K, V, B, S> {
527 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
528 f.debug_struct("KeyedOrderingHook")
529 .field("id", &self.id)
530 .finish()
531 }
532}
533
534impl<K, V, B: Boundedness, S: BindableHookScope> SimHook for KeyedOrderingHook<K, V, B, S>
535where
536 K: Hash + Eq + Clone + Serialize + DeserializeOwned,
537 V: Serialize + DeserializeOwned + PartialEq,
538{
539 fn create(next_id: &mut dyn FnMut() -> usize) -> Self {
540 KeyedOrderingHook {
541 id: next_id(),
542 member: None,
543 _phantom: PhantomData,
544 }
545 }
546}
547
548pub struct PartialOrderingHook<K, V, B: Boundedness = Unbounded, S = OnProcess> {
556 pub(crate) id: usize,
557 #[cfg_attr(
559 not(feature = "sim"),
560 expect(dead_code, reason = "only read by the `sim`-gated scripting API")
561 )]
562 pub(crate) member: Option<u32>,
563 pub(crate) _phantom: PhantomData<fn(K, V, B, S)>,
564}
565
566impl<K, V, B: Boundedness, C> PartialOrderingHook<K, V, B, OnCluster<C>> {
567 on_member_method!(PartialOrderingHook<K, V, B>);
568}
569
570impl<K, V, B: Boundedness, S> Clone for PartialOrderingHook<K, V, B, S> {
571 fn clone(&self) -> Self {
572 *self
573 }
574}
575impl<K, V, B: Boundedness, S> Copy for PartialOrderingHook<K, V, B, S> {}
576
577impl<K, V, B: Boundedness, S> std::fmt::Debug for PartialOrderingHook<K, V, B, S> {
578 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
579 f.debug_struct("PartialOrderingHook")
580 .field("id", &self.id)
581 .finish()
582 }
583}
584
585impl<K, V, B: Boundedness, S: BindableHookScope> SimHook for PartialOrderingHook<K, V, B, S>
586where
587 K: Hash + Eq + Clone + Serialize + DeserializeOwned,
588 V: Serialize + DeserializeOwned + PartialEq,
589{
590 fn create(next_id: &mut dyn FnMut() -> usize) -> Self {
591 PartialOrderingHook {
592 id: next_id(),
593 member: None,
594 _phantom: PhantomData,
595 }
596 }
597}
598
599pub struct MergeOrderedHook<T, B: Boundedness = Unbounded, S = OnProcess> {
607 pub(crate) id: usize,
608 #[cfg_attr(
610 not(feature = "sim"),
611 expect(dead_code, reason = "only read by the `sim`-gated scripting API")
612 )]
613 pub(crate) member: Option<u32>,
614 pub(crate) _phantom: PhantomData<fn(T, B, S)>,
615}
616
617impl<T, B: Boundedness, C> MergeOrderedHook<T, B, OnCluster<C>> {
618 on_member_method!(MergeOrderedHook<T, B>);
619}
620
621impl<T, B: Boundedness, S> Clone for MergeOrderedHook<T, B, S> {
622 fn clone(&self) -> Self {
623 *self
624 }
625}
626impl<T, B: Boundedness, S> Copy for MergeOrderedHook<T, B, S> {}
627
628impl<T, B: Boundedness, S> std::fmt::Debug for MergeOrderedHook<T, B, S> {
629 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
630 f.debug_struct("MergeOrderedHook")
631 .field("id", &self.id)
632 .finish()
633 }
634}
635
636impl<T, B: Boundedness, S: BindableHookScope> SimHook for MergeOrderedHook<T, B, S>
637where
638 T: Serialize + DeserializeOwned + PartialEq,
639{
640 fn create(next_id: &mut dyn FnMut() -> usize) -> Self {
641 MergeOrderedHook {
642 id: next_id(),
643 member: None,
644 _phantom: PhantomData,
645 }
646 }
647}
648
649pub struct KeyedMergeOrderedHook<K, V, B: Boundedness = Unbounded, S = OnProcess> {
658 pub(crate) id: usize,
659 #[cfg_attr(
661 not(feature = "sim"),
662 expect(dead_code, reason = "only read by the `sim`-gated scripting API")
663 )]
664 pub(crate) member: Option<u32>,
665 pub(crate) _phantom: PhantomData<fn(K, V, B, S)>,
666}
667
668impl<K, V, B: Boundedness, C> KeyedMergeOrderedHook<K, V, B, OnCluster<C>> {
669 on_member_method!(KeyedMergeOrderedHook<K, V, B>);
670}
671
672impl<K, V, B: Boundedness, S> Clone for KeyedMergeOrderedHook<K, V, B, S> {
673 fn clone(&self) -> Self {
674 *self
675 }
676}
677impl<K, V, B: Boundedness, S> Copy for KeyedMergeOrderedHook<K, V, B, S> {}
678
679impl<K, V, B: Boundedness, S> std::fmt::Debug for KeyedMergeOrderedHook<K, V, B, S> {
680 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
681 f.debug_struct("KeyedMergeOrderedHook")
682 .field("id", &self.id)
683 .finish()
684 }
685}
686
687impl<K, V, B: Boundedness, S: BindableHookScope> SimHook for KeyedMergeOrderedHook<K, V, B, S>
688where
689 K: Hash + Eq + Clone + Serialize + DeserializeOwned,
690 V: Serialize + DeserializeOwned + PartialEq,
691{
692 fn create(next_id: &mut dyn FnMut() -> usize) -> Self {
693 KeyedMergeOrderedHook {
694 id: next_id(),
695 member: None,
696 _phantom: PhantomData,
697 }
698 }
699}