1use std::any::Any;
4use std::panic::Location;
5use std::sync::{Arc, Mutex, MutexGuard};
6
7use crate::cx::{Cx, location_key};
8use crate::state::State;
9
10pub struct Dispatch<M> {
15 queue: Arc<Mutex<Vec<M>>>,
16 ctx: egui::Context,
17}
18
19impl<M> Clone for Dispatch<M> {
20 fn clone(&self) -> Self {
21 Self {
22 queue: Arc::clone(&self.queue),
23 ctx: self.ctx.clone(),
24 }
25 }
26}
27
28impl<M> std::fmt::Debug for Dispatch<M> {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 f.debug_struct("Dispatch")
31 .field("pending", &self.queue.lock().map(|q| q.len()).unwrap_or(0))
32 .finish()
33 }
34}
35
36impl<M> Dispatch<M> {
37 fn new(queue: Arc<Mutex<Vec<M>>>, ctx: egui::Context) -> Self {
38 Self { queue, ctx }
39 }
40
41 pub fn send(&self, msg: M) {
46 lock(&self.queue).push(msg);
47 self.ctx.request_repaint();
48 }
49}
50
51fn lock<M>(queue: &Mutex<Vec<M>>) -> MutexGuard<'_, Vec<M>> {
53 queue.lock().unwrap_or_else(|e| e.into_inner())
54}
55
56#[track_caller]
63pub fn use_reducer<'s, S: 'static, M: Send + 'static>(
64 cx: &mut Cx<'s, '_>,
65 mut reducer: impl FnMut(&mut S, M),
66 init: impl FnOnce() -> S,
67) -> (State<'s, S>, Dispatch<M>) {
68 let location = Location::caller();
69 let store = cx.store;
70 let id = cx.scope_id().with(location_key(location));
71
72 let queue_slot = store.slot(id.with("__egui_reactor_reducer_queue"), location, || {
75 Box::new(Arc::new(Mutex::new(Vec::<M>::new()))) as Box<dyn Any>
76 });
77 let queue: Arc<Mutex<Vec<M>>> = Arc::clone(&queue_slot.borrow::<Arc<Mutex<Vec<M>>>>());
78
79 let slot = store.slot(id, location, || Box::new(init()) as Box<dyn Any>);
80 let mut state = State::new(store, slot, location);
81
82 let pending: Vec<M> = std::mem::take(&mut *lock(&queue));
85 for msg in pending {
86 reducer(&mut state, msg);
87 }
88
89 let dispatch = Dispatch::new(queue, store.ctx().clone());
90 (state, dispatch)
91}