1use std::{
2 any::{Any, TypeId},
3 cell::RefCell,
4 rc::Rc,
5};
6
7use crate::reactivity::dispose_computed_scope;
8use crate::{Computed, EffectHandle, HistoryBlock, HistoryEntry, Signal, computed, signal};
9
10pub enum RuntimeRequest<Message> {
11 EmitMessage(Message),
12 CommitHistory(HistoryEntry),
13 SetFocusScope(Option<String>),
14 Quit,
15}
16
17#[derive(Default)]
18pub struct HookStore {
19 slots: Vec<Box<dyn HookSlot>>,
22 cursor: usize,
23}
24
25impl HookStore {
26 pub fn begin_render(&mut self) {
27 self.cursor = 0;
28 }
29
30 pub fn signal<T, F>(&mut self, init: F) -> Signal<T>
31 where
32 T: Clone + 'static,
33 F: FnOnce() -> T,
34 {
35 let slot = self.cursor;
36 self.cursor += 1;
37
38 if slot == self.slots.len() {
39 self.slots.push(Box::new(StateSlot {
40 signal: signal(init()),
41 }));
42 }
43
44 assert!(
45 matches!(
46 self.slots[slot].kind(),
47 HookSlotKind::State(type_id) if type_id == TypeId::of::<T>()
48 ),
49 "hook type mismatch"
50 );
51
52 *self.slots[slot]
53 .clone_state_signal()
54 .expect("state hooks should expose a signal handle")
55 .downcast::<Signal<T>>()
56 .expect("hook type mismatch")
57 }
58
59 pub fn computed<T, F>(&mut self, compute: F) -> Computed<T>
60 where
61 T: Clone + 'static,
62 F: Fn() -> T + 'static,
63 {
64 let slot = self.cursor;
65 self.cursor += 1;
66
67 if slot == self.slots.len() {
68 self.slots.push(Box::new(ComputedSlot {
69 computed: computed(compute),
70 }));
71 }
72
73 assert!(
74 matches!(
75 self.slots[slot].kind(),
76 HookSlotKind::Computed(type_id) if type_id == TypeId::of::<T>()
77 ),
78 "hook type mismatch"
79 );
80
81 *self.slots[slot]
82 .clone_boxed_value()
83 .downcast::<Computed<T>>()
84 .expect("hook type mismatch")
85 }
86
87 pub fn effect<F>(&mut self, effect: F)
88 where
89 F: FnMut() + 'static,
90 {
91 let slot = self.cursor;
92 self.cursor += 1;
93
94 if slot == self.slots.len() {
95 self.slots.push(Box::new(EffectSlot::new(effect)));
96 return;
97 }
98
99 assert!(
100 matches!(self.slots[slot].kind(), HookSlotKind::Effect),
101 "hook type mismatch"
102 );
103 }
104
105 pub fn finish_render(&mut self) {
106 while self.slots.len() > self.cursor {
109 let mut slot = self.slots.pop().expect("slot should exist");
110 slot.teardown();
111 }
112 }
113}
114
115trait HookSlot {
116 fn kind(&self) -> HookSlotKind;
117 fn clone_boxed_value(&self) -> Box<dyn Any>;
118 fn clone_state_signal(&self) -> Option<Box<dyn Any>> {
119 None
120 }
121 fn teardown(&mut self) {}
122}
123
124#[derive(Clone, Copy, Debug, PartialEq, Eq)]
125enum HookSlotKind {
126 State(TypeId),
127 Computed(TypeId),
128 Effect,
129}
130
131struct StateSlot<T> {
132 signal: Signal<T>,
133}
134
135impl<T: Clone + 'static> HookSlot for StateSlot<T> {
136 fn kind(&self) -> HookSlotKind {
137 HookSlotKind::State(TypeId::of::<T>())
138 }
139
140 fn clone_boxed_value(&self) -> Box<dyn Any> {
141 Box::new(self.signal.get())
142 }
143
144 fn clone_state_signal(&self) -> Option<Box<dyn Any>> {
145 Some(Box::new(self.signal.clone()))
146 }
147}
148
149struct ComputedSlot<T> {
150 computed: Computed<T>,
151}
152
153impl<T: Clone + 'static> HookSlot for ComputedSlot<T> {
154 fn kind(&self) -> HookSlotKind {
155 HookSlotKind::Computed(TypeId::of::<T>())
156 }
157
158 fn clone_boxed_value(&self) -> Box<dyn Any> {
159 Box::new(self.computed.clone())
160 }
161
162 fn teardown(&mut self) {
163 dispose_computed_scope(self.computed.scope_id());
164 }
165}
166
167struct EffectSlot {
168 handle: EffectHandle,
169}
170
171impl EffectSlot {
172 fn new<F>(callback_fn: F) -> Self
173 where
174 F: FnMut() + 'static,
175 {
176 let callback = Rc::new(RefCell::new(Box::new(callback_fn) as Box<dyn FnMut()>));
177 Self {
178 handle: crate::effect(move || (callback.borrow_mut())()),
179 }
180 }
181}
182
183impl HookSlot for EffectSlot {
184 fn kind(&self) -> HookSlotKind {
185 HookSlotKind::Effect
186 }
187
188 fn clone_boxed_value(&self) -> Box<dyn Any> {
189 panic!("effect slots do not expose values")
190 }
191
192 fn teardown(&mut self) {
193 self.handle.stop();
194 }
195}
196
197impl Drop for HookStore {
198 fn drop(&mut self) {
199 for slot in &mut self.slots {
200 slot.teardown();
201 }
202 }
203}
204
205pub struct ViewCtx<'a, Message> {
206 hooks: &'a mut HookStore,
207 marker: std::marker::PhantomData<Message>,
208}
209
210pub type Cx<'a, Message> = ViewCtx<'a, Message>;
211
212impl<'a, Message: Send + 'static> ViewCtx<'a, Message> {
213 pub fn new(hooks: &'a mut HookStore) -> Self {
214 Self {
215 hooks,
216 marker: std::marker::PhantomData,
217 }
218 }
219
220 pub fn signal<T, F>(&mut self, init: F) -> Signal<T>
221 where
222 T: Clone + 'static,
223 F: FnOnce() -> T,
224 {
225 self.hooks.signal(init)
226 }
227
228 pub fn effect<F>(&mut self, effect: F)
229 where
230 F: FnMut() + 'static,
231 {
232 self.hooks.effect(effect);
236 }
237
238 pub fn computed<T, F>(&mut self, compute: F) -> Computed<T>
239 where
240 T: Clone + 'static,
241 F: Fn() -> T + 'static,
242 {
243 self.hooks.computed(compute)
244 }
245}
246
247impl<Message> RuntimeRequest<Message> {
248 pub fn commit_text(content: String) -> Self {
249 Self::CommitHistory(HistoryEntry::Text(content))
250 }
251
252 pub fn commit_block(block: HistoryBlock) -> Self {
253 Self::CommitHistory(HistoryEntry::Block(block))
254 }
255}