use std::cell::Cell;
thread_local! {
static CLONE_BUDGET: Cell<usize> = const { Cell::new(0) };
}
pub fn on_state_clone(type_name: &str) {
CLONE_BUDGET.with(|budget| {
let remaining = budget.get();
assert!(
remaining > 0,
"unexpected clone of {type_name} (clone budget exhausted)"
);
budget.set(remaining - 1);
});
}
pub fn allow_state_clones<T>(count: usize, f: impl FnOnce() -> T) -> T {
CLONE_BUDGET.with(|budget| {
let prev = budget.get();
budget.set(prev + count);
let out = f();
budget.set(prev);
out
})
}
#[cfg(feature = "runtime")]
mod store_helpers {
use super::allow_state_clones;
use crate::optics::Casepath;
use crate::store::{ScopedStateSubscriber, StateSubscriber};
pub fn store_state<S, M>(store: &crate::Store<S, M>) -> S
where
S: Send + Sync + Clone + 'static,
M: Send + 'static,
{
allow_state_clones(1, || store.state())
}
pub fn subscribe_state<S, M>(store: &crate::Store<S, M>) -> crate::StateSubscriber<S>
where
S: Clone + PartialEq + Send + Sync + 'static,
M: Send + 'static,
{
allow_state_clones(1, || store.subscribe_state())
}
pub fn scoped_child_state<S, M, CS, CM, AK, SK>(
scoped: &crate::ScopedStore<S, M, CS, CM, AK, SK>,
) -> Option<CS>
where
S: Send + Sync + Clone,
M: Send,
CS: Clone + PartialEq + Send + Sync,
CM: Clone + Send,
AK: Casepath<M, CM> + Clone + Send + Sync + 'static,
SK: crate::keypath::RefKpTrait<S, CS> + Clone,
{
allow_state_clones(1, || scoped.child_state())
}
pub fn scoped_subscribe_state<S, M, CS, CM, AK, SK>(
scoped: &crate::ScopedStore<S, M, CS, CM, AK, SK>,
) -> ScopedStateSubscriber<S, CS, SK>
where
S: Send + Sync + Clone + PartialEq,
M: Send,
CS: Clone + PartialEq + Send + Sync,
CM: Clone + Send,
AK: Casepath<M, CM> + Clone + Send + Sync + 'static,
SK: crate::keypath::RefKpTrait<S, CS> + Clone,
{
allow_state_clones(1, || scoped.subscribe_state())
}
pub fn subscriber_wait_next<S>(
sub: &mut StateSubscriber<S>,
timeout: std::time::Duration,
) -> Option<std::sync::Arc<S>>
where
S: PartialEq + Clone,
{
allow_state_clones(1, || sub.wait_next(timeout))
}
pub fn scoped_subscriber_next<S, CS, SK>(
sub: &mut ScopedStateSubscriber<S, CS, SK>,
) -> Option<CS>
where
S: PartialEq + Clone,
CS: Clone + PartialEq,
SK: crate::keypath::RefKpTrait<S, CS> + Clone,
{
allow_state_clones(1, || sub.next())
}
}
#[cfg(feature = "runtime")]
pub use store_helpers::{
scoped_child_state, scoped_subscribe_state, scoped_subscriber_next, store_state,
subscribe_state, subscriber_wait_next,
};
pub fn replay_snapshot<S, M>(harness: &crate::ReplayHarness<S, M>) -> S
where
S: Clone,
M: Clone,
{
allow_state_clones(1, || harness.snapshot())
}
pub fn shared_with_mut<T, R>(shared: &crate::Shared<T>, f: impl FnOnce(&mut T) -> R) -> R
where
T: Clone + PartialEq,
{
allow_state_clones(1, || shared.with_mut(f))
}
pub fn shared_get<S>(shared: &crate::Shared<S>) -> S
where
S: Clone,
{
allow_state_clones(1, || shared.get())
}
#[macro_export]
macro_rules! panic_on_state_clone {
(
$(#[$meta:meta])*
$vis:vis struct $name:ident {
$($field:ident: $ty:ty),* $(,)?
}
) => {
$(#[$meta])*
$vis struct $name {
$($field: $ty),*
}
impl Clone for $name {
fn clone(&self) -> Self {
$crate::test_support::on_state_clone(concat!(module_path!(), "::", stringify!($name)));
Self {
$($field: self.$field.clone()),*
}
}
}
};
}