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}