1use std::cell::Cell;
7
8thread_local! {
9 static CLONE_BUDGET: Cell<usize> = const { Cell::new(0) };
10}
11
12pub fn on_state_clone(type_name: &str) {
14 CLONE_BUDGET.with(|budget| {
15 let remaining = budget.get();
16 assert!(
17 remaining > 0,
18 "unexpected clone of {type_name} (clone budget exhausted)"
19 );
20 budget.set(remaining - 1);
21 });
22}
23
24pub fn allow_state_clones<T>(count: usize, f: impl FnOnce() -> T) -> T {
26 CLONE_BUDGET.with(|budget| {
27 let prev = budget.get();
28 budget.set(prev + count);
29 let out = f();
30 budget.set(prev);
31 out
32 })
33}
34
35#[cfg(feature = "runtime")]
36mod store_helpers {
37 use super::allow_state_clones;
38 use crate::optics::Casepath;
39 use crate::store::{ScopedStateSubscriber, StateSubscriber};
40
41 pub fn store_state<S, M>(store: &crate::Store<S, M>) -> S
43 where
44 S: Send + Sync + Clone + 'static,
45 M: Send + 'static,
46 {
47 allow_state_clones(1, || store.state())
48 }
49
50 pub fn subscribe_state<S, M>(store: &crate::Store<S, M>) -> crate::StateSubscriber<S>
52 where
53 S: Clone + PartialEq + Send + Sync + 'static,
54 M: Send + 'static,
55 {
56 allow_state_clones(1, || store.subscribe_state())
57 }
58
59 pub fn scoped_child_state<S, M, CS, CM, AK, SK>(
61 scoped: &crate::ScopedStore<S, M, CS, CM, AK, SK>,
62 ) -> Option<CS>
63 where
64 S: Send + Sync + Clone,
65 M: Send,
66 CS: Clone + PartialEq + Send + Sync,
67 CM: Clone + Send,
68 AK: Casepath<M, CM> + Clone + Send + Sync + 'static,
69 SK: crate::keypath::RefKpTrait<S, CS> + Clone,
70 {
71 allow_state_clones(1, || scoped.child_state())
72 }
73
74 pub fn scoped_subscribe_state<S, M, CS, CM, AK, SK>(
76 scoped: &crate::ScopedStore<S, M, CS, CM, AK, SK>,
77 ) -> ScopedStateSubscriber<S, CS, SK>
78 where
79 S: Send + Sync + Clone + PartialEq,
80 M: Send,
81 CS: Clone + PartialEq + Send + Sync,
82 CM: Clone + Send,
83 AK: Casepath<M, CM> + Clone + Send + Sync + 'static,
84 SK: crate::keypath::RefKpTrait<S, CS> + Clone,
85 {
86 allow_state_clones(1, || scoped.subscribe_state())
87 }
88
89 pub fn subscriber_wait_next<S>(
91 sub: &mut StateSubscriber<S>,
92 timeout: std::time::Duration,
93 ) -> Option<std::sync::Arc<S>>
94 where
95 S: PartialEq + Clone,
96 {
97 allow_state_clones(1, || sub.wait_next(timeout))
98 }
99
100 pub fn scoped_subscriber_next<S, CS, SK>(
102 sub: &mut ScopedStateSubscriber<S, CS, SK>,
103 ) -> Option<CS>
104 where
105 S: PartialEq + Clone,
106 CS: Clone + PartialEq,
107 SK: crate::keypath::RefKpTrait<S, CS> + Clone,
108 {
109 allow_state_clones(1, || sub.next())
110 }
111}
112
113#[cfg(feature = "runtime")]
114pub use store_helpers::{
115 scoped_child_state, scoped_subscribe_state, scoped_subscriber_next, store_state,
116 subscribe_state, subscriber_wait_next,
117};
118
119pub fn replay_snapshot<S, M>(harness: &crate::ReplayHarness<S, M>) -> S
121where
122 S: Clone,
123 M: Clone,
124{
125 allow_state_clones(1, || harness.snapshot())
126}
127
128pub fn shared_with_mut<T, R>(shared: &crate::Shared<T>, f: impl FnOnce(&mut T) -> R) -> R
130where
131 T: Clone + PartialEq,
132{
133 allow_state_clones(1, || shared.with_mut(f))
134}
135
136pub fn shared_get<S>(shared: &crate::Shared<S>) -> S
138where
139 S: Clone,
140{
141 allow_state_clones(1, || shared.get())
142}
143
144#[macro_export]
146macro_rules! panic_on_state_clone {
147 (
148 $(#[$meta:meta])*
149 $vis:vis struct $name:ident {
150 $($field:ident: $ty:ty),* $(,)?
151 }
152 ) => {
153 $(#[$meta])*
154 $vis struct $name {
155 $($field: $ty),*
156 }
157
158 impl Clone for $name {
159 fn clone(&self) -> Self {
160 $crate::test_support::on_state_clone(concat!(module_path!(), "::", stringify!($name)));
161 Self {
162 $($field: self.$field.clone()),*
163 }
164 }
165 }
166 };
167}
168
169#[cfg(feature = "runtime")]
171pub fn start_runtime<S, M>(
172 program: crate::Program<S, M>,
173 env: crate::Environment,
174 config: crate::RuntimeConfig,
175) -> crate::Runtime<S, M>
176where
177 S: Send + Sync + 'static,
178 M: Send + 'static,
179{
180 match crate::Runtime::from_program(program, env, config) {
181 Ok(runtime) => runtime,
182 Err(err) => panic!("runtime bootstrap failed: {err}"),
183 }
184}
185
186#[cfg(feature = "runtime")]
188pub fn start_reducer_runtime<R>(program: crate::ReducerProgram<R>, env: crate::Environment, config: crate::RuntimeConfig) -> crate::Runtime<R::State, R::Action>
189where
190 R: crate::Reducer + Send + Sync + 'static,
191 R::State: Send + Sync + Clone + 'static,
192 R::Action: Send + 'static,
193{
194 match crate::Runtime::from_reducer_program(program, env, config) {
195 Ok(runtime) => runtime,
196 Err(err) => panic!("runtime bootstrap failed: {err}"),
197 }
198}
199
200#[cfg(feature = "runtime")]
201pub fn start_rw_reducer_runtime<R>(program: crate::ReducerProgram<R>, env: crate::Environment, config: crate::RuntimeConfig) -> crate::RwRuntime<R::State, R::Action>
202where
203 R: crate::Reducer + Send + Sync + 'static,
204 R::State: Send + Sync + 'static,
205 R::Action: Send + 'static,
206{
207 match crate::RwRuntime::from_reducer_program(program, env, config) {
208 Ok(runtime) => runtime,
209 Err(err) => panic!("runtime bootstrap failed: {err}"),
210 }
211}
212
213#[cfg(all(feature = "runtime", feature = "arc-swap"))]
214pub fn start_swap_reducer_runtime<R>(program: crate::ReducerProgram<R>, env: crate::Environment, config: crate::RuntimeConfig) -> crate::SwapRuntime<R::State, R::Action>
215where
216 R: crate::Reducer + Send + Sync + 'static,
217 R::State: Send + Sync + Clone + 'static,
218 R::Action: Send + 'static,
219{
220 match crate::SwapRuntime::from_reducer_program(program, env, config) {
221 Ok(runtime) => runtime,
222 Err(err) => panic!("runtime bootstrap failed: {err}"),
223 }
224}
225
226#[cfg(feature = "runtime")]
227pub fn start_tea_reducer_runtime<R>(program: crate::ReducerProgram<R>, env: crate::Environment, config: crate::RuntimeConfig) -> crate::TeaRuntime<R::State, R::Action>
228where
229 R: crate::Reducer + Send + Sync + 'static,
230 R::State: Send + Sync + Clone + 'static,
231 R::Action: Send + 'static,
232{
233 match crate::TeaRuntime::from_reducer_program(program, env, config) {
234 Ok(runtime) => runtime,
235 Err(err) => panic!("runtime bootstrap failed: {err}"),
236 }
237}