1use std::cell::Cell;
7
8use crate::optics::Casepath;
9use crate::store::{ScopedStateSubscriber, StateSubscriber};
10
11thread_local! {
12 static CLONE_BUDGET: Cell<usize> = const { Cell::new(0) };
13}
14
15pub fn on_state_clone(type_name: &str) {
17 CLONE_BUDGET.with(|budget| {
18 let remaining = budget.get();
19 assert!(
20 remaining > 0,
21 "unexpected clone of {type_name} (clone budget exhausted)"
22 );
23 budget.set(remaining - 1);
24 });
25}
26
27pub fn allow_state_clones<T>(count: usize, f: impl FnOnce() -> T) -> T {
29 CLONE_BUDGET.with(|budget| {
30 let prev = budget.get();
31 budget.set(prev + count);
32 let out = f();
33 budget.set(prev);
34 out
35 })
36}
37
38pub fn store_state<S, M>(store: &crate::Store<S, M>) -> S
40where
41 S: Send + Sync + Clone + 'static,
42 M: Send + 'static,
43{
44 allow_state_clones(1, || store.state())
45}
46
47pub fn subscribe_state<S, M>(
49 store: &crate::Store<S, M>,
50) -> crate::StateSubscriber<S>
51where
52 S: Clone + PartialEq + Send + Sync + 'static,
53 M: Send + 'static,
54{
55 allow_state_clones(1, || store.subscribe_state())
56}
57
58pub fn scoped_child_state<S, M, CS, CM, AK, SK>(
60 scoped: &crate::ScopedStore<S, M, CS, CM, AK, SK>,
61) -> Option<CS>
62where
63 S: Send + Sync + Clone,
64 M: Send,
65 CS: Clone + PartialEq + Send + Sync,
66 CM: Clone + Send,
67 AK: Casepath<M, CM> + Clone + Send + Sync + 'static,
68 SK: crate::StateLens<S, CS> + Clone,
69{
70 allow_state_clones(1, || scoped.child_state())
71}
72
73pub fn scoped_subscribe_state<S, M, CS, CM, AK, SK>(
75 scoped: &crate::ScopedStore<S, M, CS, CM, AK, SK>,
76) -> ScopedStateSubscriber<S, CS, SK>
77where
78 S: Send + Sync + Clone + PartialEq,
79 M: Send,
80 CS: Clone + PartialEq + Send + Sync,
81 CM: Clone + Send,
82 AK: Casepath<M, CM> + Clone + Send + Sync + 'static,
83 SK: crate::StateLens<S, CS> + Clone,
84{
85 allow_state_clones(1, || scoped.subscribe_state())
86}
87
88pub fn subscriber_wait_next<S>(
90 sub: &mut StateSubscriber<S>,
91 timeout: std::time::Duration,
92) -> Option<std::sync::Arc<S>>
93where
94 S: PartialEq + Clone,
95{
96 allow_state_clones(1, || sub.wait_next(timeout))
97}
98
99pub fn scoped_subscriber_next<S, CS, SK>(
101 sub: &mut ScopedStateSubscriber<S, CS, SK>,
102) -> Option<CS>
103where
104 S: PartialEq + Clone,
105 CS: Clone + PartialEq,
106 SK: crate::StateLens<S, CS> + Clone,
107{
108 allow_state_clones(1, || sub.next())
109}
110
111pub fn replay_snapshot<S, M>(harness: &crate::ReplayHarness<S, M>) -> S
113where
114 S: Clone,
115 M: Clone,
116{
117 allow_state_clones(1, || harness.snapshot())
118}
119
120pub fn shared_with_mut<T, R>(shared: &crate::Shared<T>, f: impl FnOnce(&mut T) -> R) -> R
122where
123 T: Clone + PartialEq,
124{
125 allow_state_clones(1, || shared.with_mut(f))
126}
127
128pub fn shared_get<S>(shared: &crate::Shared<S>) -> S
130where
131 S: Clone,
132{
133 allow_state_clones(1, || shared.get())
134}
135
136#[macro_export]
138macro_rules! panic_on_state_clone {
139 (
140 $(#[$meta:meta])*
141 $vis:vis struct $name:ident {
142 $($field:ident: $ty:ty),* $(,)?
143 }
144 ) => {
145 $(#[$meta])*
146 $vis struct $name {
147 $($field: $ty),*
148 }
149
150 impl Clone for $name {
151 fn clone(&self) -> Self {
152 $crate::test_support::on_state_clone(concat!(module_path!(), "::", stringify!($name)));
153 Self {
154 $($field: self.$field.clone()),*
155 }
156 }
157 }
158 };
159}
160
161pub use panic_on_state_clone;