1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
use crate::{snapshot_v2, thread_state};
/// Runs the provided closure inside a mutable snapshot and applies the result.
///
/// Use this function when you need to update `MutableState` from outside the
/// composition or layout phase, typically in event handlers or async tasks.
///
/// # Why is this needed?
/// Cranpose uses a snapshot system (MVCC) to isolate state changes. Modifications
/// made to `MutableState` are only visible to the current thread's active snapshot.
/// To make changes visible to the rest of the system (and trigger recomposition),
/// they must be "applied" by committing the snapshot. This helper handles that
/// lifecycle for you.
///
/// # Example
///
/// ```ignore
/// // Inside a button click handler
/// run_in_mutable_snapshot(|| {
/// count.set(count.value() + 1);
/// });
/// ```
///
/// # Important
/// ALL UI event handlers (keyboard, mouse, touch, animations) that modify state
/// MUST use this function or [`dispatch_ui_event`].
pub fn run_in_mutable_snapshot<T>(block: impl FnOnce() -> T) -> Result<T, &'static str> {
let snapshot = snapshot_v2::take_mutable_snapshot(None, None);
let _applied_guard = AppliedSnapshotFlagGuard::enter();
let value = snapshot.enter(block);
match snapshot.apply() {
snapshot_v2::SnapshotApplyResult::Success => Ok(value),
snapshot_v2::SnapshotApplyResult::Failure => Err("Snapshot apply failed"),
}
}
struct AppliedSnapshotFlagGuard {
previous: bool,
}
impl AppliedSnapshotFlagGuard {
fn enter() -> Self {
let previous = thread_state::cells(|cells| cells.in_applied_snapshot.replace(true));
Self { previous }
}
}
impl Drop for AppliedSnapshotFlagGuard {
fn drop(&mut self) {
thread_state::cells(|cells| cells.in_applied_snapshot.set(self.previous));
}
}
/// Dispatches a UI event in a proper snapshot context.
///
/// This is a convenience wrapper around [`run_in_mutable_snapshot`] that returns
/// `Option<T>` instead of `Result<T, &str>`.
///
/// # Example
/// ```ignore
/// // In a keyboard event handler:
/// dispatch_ui_event(|| {
/// text_field_state.edit(|buffer| {
/// buffer.insert("a");
/// });
/// });
/// ```
pub fn dispatch_ui_event<T>(block: impl FnOnce() -> T) -> Option<T> {
run_in_mutable_snapshot(block).ok()
}
/// Ends the event handler scope when dropped, restoring the setting that held before it.
#[must_use]
pub struct EventHandlerScopeGuard {
previous: bool,
}
impl Drop for EventHandlerScopeGuard {
fn drop(&mut self) {
thread_state::cells(|cells| cells.in_event_handler.set(self.previous));
}
}
/// Marks the thread as inside an event handler until the returned guard is dropped. In debug
/// builds, a state write there that is not inside a snapshot logs a warning.
pub fn enter_event_handler_scope() -> EventHandlerScopeGuard {
let previous = thread_state::cells(|cells| cells.in_event_handler.replace(true));
EventHandlerScopeGuard { previous }
}
/// Returns true if currently in an event handler context.
pub fn in_event_handler() -> bool {
thread_state::cells(|cells| cells.in_event_handler.get())
}
/// Returns true if currently in an applied snapshot context.
pub fn in_applied_snapshot() -> bool {
thread_state::cells(|cells| cells.in_applied_snapshot.get())
}