Skip to main content

widgetkit_runtime/
app.rs

1use crate::{
2    context::{DisposeCtx, MountCtx, RenderCtx, StartCtx, StopCtx, UpdateCtx},
3    event::Event,
4    host::HostRunner,
5    internal::{DispatchToken, Dispatcher, RuntimeEvent, RuntimeServices, WakeHandle},
6    widget::Widget,
7};
8use crossbeam_channel::{Receiver, TryRecvError, unbounded};
9use widgetkit_core::{Error, HostEvent, InstanceId, Result, Size, WidgetId};
10use widgetkit_render::{Canvas, RenderSurface, Renderer};
11
12/// v0.1 application bootstrap for a single widget instance bound to one host and one renderer.
13/// The runtime is demand-driven and only redraws when widget code requests it.
14pub struct WidgetApp<W = (), H = (), R = ()> {
15    widget_name: Option<String>,
16    widget: Option<W>,
17    host: Option<H>,
18    renderer: Option<R>,
19}
20
21impl WidgetApp<(), (), ()> {
22    pub fn new() -> Self {
23        Self {
24            widget_name: None,
25            widget: None,
26            host: None,
27            renderer: None,
28        }
29    }
30}
31
32impl<H, R> WidgetApp<(), H, R> {
33    pub fn widget<W>(self, name: impl Into<String>, widget: W) -> WidgetApp<W, H, R>
34    where
35        W: Widget,
36    {
37        WidgetApp {
38            widget_name: Some(name.into()),
39            widget: Some(widget),
40            host: self.host,
41            renderer: self.renderer,
42        }
43    }
44}
45
46impl<W, R> WidgetApp<W, (), R>
47where
48    W: Widget,
49{
50    pub fn host<H>(self, host: H) -> WidgetApp<W, H, R> {
51        WidgetApp {
52            widget_name: self.widget_name,
53            widget: self.widget,
54            host: Some(host),
55            renderer: self.renderer,
56        }
57    }
58}
59
60impl<W, H> WidgetApp<W, H, ()>
61where
62    W: Widget,
63{
64    pub fn renderer<R>(self, renderer: R) -> WidgetApp<W, H, R> {
65        WidgetApp {
66            widget_name: self.widget_name,
67            widget: self.widget,
68            host: self.host,
69            renderer: Some(renderer),
70        }
71    }
72}
73
74impl<W, H, R> WidgetApp<W, H, R>
75where
76    W: Widget,
77    H: HostRunner<W, R>,
78    R: Renderer,
79{
80    pub fn run(self) -> Result<()> {
81        let runner = AppRunner::new(
82            self.widget_name.expect("widget name must be configured"),
83            self.widget.expect("widget must be configured"),
84            self.renderer.expect("renderer must be configured"),
85        );
86        self.host.expect("host must be configured").run(runner)
87    }
88}
89
90impl Default for WidgetApp<(), (), ()> {
91    fn default() -> Self {
92        Self::new()
93    }
94}
95
96/// v0.1 runtime runner. It intentionally manages exactly one widget instance lifetime.
97/// Timers and tasks are instance-owned and are shut down with `stop`/`dispose`.
98pub struct AppRunner<W, R>
99where
100    W: Widget,
101    R: Renderer,
102{
103    widget_name: String,
104    widget_id: WidgetId,
105    instance_id: InstanceId,
106    instance_generation: u64,
107    widget: W,
108    state: Option<W::State>,
109    renderer: R,
110    receiver: Receiver<RuntimeEvent<W::Message>>,
111    services: RuntimeServices<W::Message>,
112    surface_size: Size,
113    initialized: bool,
114    shut_down: bool,
115}
116
117impl<W, R> AppRunner<W, R>
118where
119    W: Widget,
120    R: Renderer,
121{
122    pub fn new(widget_name: impl Into<String>, widget: W, renderer: R) -> Self {
123        let (sender, receiver) = unbounded();
124        let wake = WakeHandle::default();
125        let instance_id = InstanceId::new();
126        let instance_generation = 1;
127        let dispatcher = Dispatcher {
128            sender,
129            wake,
130            token: DispatchToken::new(instance_id, instance_generation),
131        };
132        let services = RuntimeServices::new(dispatcher);
133        Self {
134            widget_name: widget_name.into(),
135            widget_id: WidgetId::new(),
136            instance_id,
137            instance_generation,
138            widget,
139            state: None,
140            renderer,
141            receiver,
142            services,
143            surface_size: Size::new(320.0, 120.0),
144            initialized: false,
145            shut_down: false,
146        }
147    }
148
149    pub fn widget_name(&self) -> &str {
150        &self.widget_name
151    }
152
153    pub fn surface_size(&self) -> Size {
154        self.surface_size
155    }
156
157    pub fn set_surface_size(&mut self, size: Size) {
158        if !size.is_empty() {
159            self.surface_size = size;
160            self.request_render();
161        }
162    }
163
164    pub fn attach_waker<F>(&mut self, wake: F)
165    where
166        F: Fn() + Send + Sync + 'static,
167    {
168        self.services.dispatcher.wake.set(wake);
169    }
170
171    pub fn initialize(&mut self, surface_size: Size) -> Result<()> {
172        if self.shut_down {
173            return Err(Error::message(
174                "widgetkit v0.1 AppRunner supports a single widget instance lifetime",
175            ));
176        }
177        if self.initialized {
178            return Ok(());
179        }
180        if !surface_size.is_empty() {
181            self.surface_size = surface_size;
182        }
183
184        let mut mount_ctx = MountCtx::new(self.widget_id, self.instance_id);
185        let state = self.widget.mount(&mut mount_ctx);
186        self.state = Some(state);
187        self.with_state_mut(|widget, state, services, widget_id, instance_id| {
188            let mut ctx = StartCtx::new(widget_id, instance_id, std::ptr::NonNull::from(services));
189            widget.start(state, &mut ctx);
190        });
191        self.initialized = true;
192        self.request_render();
193        self.process_pending()
194    }
195
196    pub fn process_pending(&mut self) -> Result<()> {
197        loop {
198            match self.receiver.try_recv() {
199                Ok(RuntimeEvent::Message(envelope)) => {
200                    if self.accepts_token(envelope.token) {
201                        self.dispatch_event(Event::Message(envelope.message));
202                    }
203                }
204                Ok(RuntimeEvent::TaskFinished { token, task_id }) => {
205                    if self.matches_token(token) {
206                        self.services.tasks.reap(task_id);
207                    }
208                }
209                Ok(RuntimeEvent::TimerFinished { token, timer_id }) => {
210                    if self.matches_token(token) {
211                        self.services.scheduler.reap(timer_id);
212                    }
213                }
214                Err(TryRecvError::Empty) => break,
215                Err(TryRecvError::Disconnected) => break,
216            }
217        }
218        Ok(())
219    }
220
221    pub fn handle_host_event(&mut self, event: HostEvent) -> Result<()> {
222        if let HostEvent::Resized(size) = event.clone() {
223            self.set_surface_size(size);
224        }
225        self.dispatch_event(Event::Host(event));
226        self.process_pending()
227    }
228
229    pub fn needs_redraw(&self) -> bool {
230        self.services.render_requested
231    }
232
233    pub fn render(&mut self, surface: &mut dyn RenderSurface) -> Result<()> {
234        let (width, height) = surface.size();
235        self.surface_size = Size::new(width as f32, height as f32);
236        if let Some(state) = self.state.as_ref() {
237            let mut canvas = Canvas::new(self.surface_size);
238            let ctx = RenderCtx::new(self.widget_id, self.instance_id, self.surface_size);
239            self.widget.render(state, &mut canvas, &ctx);
240            self.renderer.render_canvas(canvas, surface)?;
241            self.services.render_requested = false;
242        }
243        Ok(())
244    }
245
246    pub fn shutdown(&mut self) -> Result<()> {
247        if self.shut_down {
248            return Ok(());
249        }
250        self.with_state_mut(|widget, state, services, widget_id, instance_id| {
251            let mut ctx = StopCtx::new(widget_id, instance_id, std::ptr::NonNull::from(services));
252            widget.stop(state, &mut ctx);
253        });
254        self.services.scheduler.shutdown();
255        self.services.tasks.shutdown();
256        if let Some(state) = self.state.take() {
257            let mut ctx = DisposeCtx::new(self.widget_id, self.instance_id);
258            self.widget.dispose(state, &mut ctx);
259        }
260        self.instance_generation += 1;
261        self.initialized = false;
262        self.shut_down = true;
263        Ok(())
264    }
265
266    fn request_render(&mut self) {
267        self.services.render_requested = true;
268        self.services.dispatcher.wake.wake();
269    }
270
271    fn dispatch_event(&mut self, event: Event<W::Message>) {
272        self.with_state_mut(|widget, state, services, widget_id, instance_id| {
273            let mut ctx = UpdateCtx::new(widget_id, instance_id, std::ptr::NonNull::from(services));
274            widget.update(state, event, &mut ctx);
275        });
276    }
277
278    fn accepts_token(&self, token: DispatchToken) -> bool {
279        self.initialized && !self.shut_down && self.matches_token(token)
280    }
281
282    fn matches_token(&self, token: DispatchToken) -> bool {
283        token == self.current_dispatch_token()
284    }
285
286    fn current_dispatch_token(&self) -> DispatchToken {
287        DispatchToken::new(self.instance_id, self.instance_generation)
288    }
289
290    #[cfg(test)]
291    pub(crate) fn scheduler_active_count(&self) -> usize {
292        self.services.scheduler.active_count()
293    }
294
295    #[cfg(test)]
296    pub(crate) fn task_active_count(&self) -> usize {
297        self.services.tasks.active_count()
298    }
299
300    #[cfg(test)]
301    pub(crate) fn test_token(&self) -> DispatchToken {
302        self.services.dispatcher.token
303    }
304
305    #[cfg(test)]
306    pub(crate) fn dispatch_test_message(&self, token: DispatchToken, message: W::Message) {
307        let _ = self
308            .services
309            .dispatcher
310            .sender
311            .send(RuntimeEvent::Message(crate::internal::MessageEnvelope { token, message }));
312    }
313
314    fn with_state_mut(
315        &mut self,
316        f: impl FnOnce(&mut W, &mut W::State, &mut RuntimeServices<W::Message>, WidgetId, InstanceId),
317    ) {
318        if let Some(state) = self.state.as_mut() {
319            f(&mut self.widget, state, &mut self.services, self.widget_id, self.instance_id);
320        }
321    }
322}
323
324impl<W, R> Drop for AppRunner<W, R>
325where
326    W: Widget,
327    R: Renderer,
328{
329    fn drop(&mut self) {
330        let _ = self.shutdown();
331    }
332}