Skip to main content

kas_core/runner/
runner.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! [`Runner`] and supporting elements
7
8use super::{AppData, GraphicsInstance, Platform, ProxyAction, Result, Shared};
9use crate::config::{Config, ConfigFactory};
10#[allow(unused)] use crate::event::ConfigCx;
11use crate::theme::Theme;
12use crate::window::{WindowId, WindowIdFactory};
13use std::cell::RefCell;
14use std::rc::Rc;
15use std::sync::mpsc;
16use winit::event_loop::{EventLoop, EventLoopProxy};
17
18/// State used to launch the UI
19///
20/// This is a low-level type; it is recommended to instead use
21/// [`Runner`](https://docs.rs/kas/latest/kas/runner/struct.Runner.html).
22#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
23#[cfg_attr(docsrs, doc(cfg(internal_doc)))]
24pub struct PreLaunchState {
25    config: Rc<RefCell<Config>>,
26    config_writer: Option<Box<dyn FnMut(&Config)>>,
27    el: EventLoop,
28    platform: Platform,
29    proxy_tx: mpsc::SyncSender<ProxyAction>,
30    proxy_rx: mpsc::Receiver<ProxyAction>,
31    window_id_factory: WindowIdFactory,
32}
33
34impl PreLaunchState {
35    /// Construct
36    pub fn new<C: ConfigFactory>(config: C) -> Result<Self> {
37        let mut cf = config;
38        let config = cf.read_config()?;
39        config.borrow_mut().init();
40
41        let el = EventLoop::new()?;
42        let platform = Platform::new(&el);
43
44        let (proxy_tx, proxy_rx) = mpsc::sync_channel(16);
45
46        Ok(PreLaunchState {
47            config,
48            config_writer: cf.writer(),
49            el,
50            platform,
51            proxy_tx,
52            proxy_rx,
53            window_id_factory: Default::default(),
54        })
55    }
56
57    /// Access config
58    #[inline]
59    pub fn config(&self) -> &Rc<RefCell<Config>> {
60        &self.config
61    }
62
63    /// Generate a [`WindowId`]
64    #[inline]
65    pub fn next_window_id(&mut self) -> WindowId {
66        self.window_id_factory.make_next()
67    }
68
69    /// Get the platform
70    #[inline]
71    pub fn platform(&self) -> Platform {
72        self.platform
73    }
74
75    /// Create a proxy which can be used to update the UI from another thread
76    pub fn create_proxy(&self) -> Proxy {
77        Proxy::new(self.proxy_tx.clone(), self.el.create_proxy())
78    }
79
80    /// Run the main loop
81    pub fn run<Data: AppData, G: GraphicsInstance + 'static, T: Theme<G::Shared> + 'static>(
82        self,
83        data: Data,
84        graphical: G,
85        theme: T,
86        windows: Vec<Box<super::Window<Data, G, T>>>,
87    ) -> Result<()> {
88        let shared = Shared::<Data, _, _>::new(
89            self.platform,
90            graphical,
91            theme,
92            self.config,
93            self.config_writer,
94            create_waker(&self.el),
95            self.proxy_rx,
96            self.window_id_factory,
97        )?;
98
99        let l = super::Loop::new(windows, shared, data);
100        self.el.run_app(l)?;
101        Ok(())
102    }
103}
104
105impl Platform {
106    /// Get platform
107    #[allow(clippy::needless_return)]
108    fn new(_el: &EventLoop) -> Platform {
109        // Logic copied from winit::platform_impl module.
110
111        #[cfg(target_os = "windows")]
112        return Platform::Windows;
113
114        #[cfg(any(
115            target_os = "linux",
116            target_os = "dragonfly",
117            target_os = "freebsd",
118            target_os = "netbsd",
119            target_os = "openbsd"
120        ))]
121        {
122            cfg_if::cfg_if! {
123                if #[cfg(all(feature = "wayland", feature = "x11"))] {
124                    use winit::platform::wayland::EventLoopExtWayland;
125                    return if _el.is_wayland() {
126                        Platform::Wayland
127                    } else {
128                        Platform::X11
129                    };
130                } else if #[cfg(feature = "wayland")] {
131                    return Platform::Wayland;
132                } else if #[cfg(feature = "x11")] {
133                    return Platform::X11;
134                } else {
135                    compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
136                }
137            }
138        }
139
140        #[cfg(target_os = "macos")]
141        return Platform::MacOS;
142
143        #[cfg(target_os = "android")]
144        return Platform::Android;
145
146        #[cfg(target_os = "ios")]
147        return Platform::IOS;
148
149        #[cfg(target_arch = "wasm32")]
150        return Platform::Web;
151
152        // Otherwise platform is unsupported!
153    }
154}
155
156/// Create a waker
157///
158/// This waker may be used by a [`Future`](std::future::Future) to revive
159/// event handling.
160fn create_waker(el: &EventLoop) -> std::task::Waker {
161    use std::sync::Arc;
162    use std::task::{RawWaker, RawWakerVTable, Waker};
163
164    // We wrap with Arc which is a Sync type supporting Clone and into_raw.
165    let proxy = el.create_proxy();
166    let a: Arc<EventLoopProxy> = Arc::new(proxy);
167    let data = Arc::into_raw(a);
168
169    fn wake_async(proxy: &EventLoopProxy) {
170        // ignore error: if the loop closed the future has been dropped
171        proxy.wake_up();
172    }
173
174    const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
175
176    unsafe fn clone(data: *const ()) -> RawWaker {
177        unsafe {
178            let a = Arc::from_raw(data as *const EventLoopProxy);
179            let c = Arc::into_raw(a.clone());
180            let _do_not_drop = Arc::into_raw(a);
181            RawWaker::new(c as *const (), &VTABLE)
182        }
183    }
184    unsafe fn wake(data: *const ()) {
185        unsafe {
186            let a = Arc::from_raw(data as *const EventLoopProxy);
187            wake_async(&a);
188        }
189    }
190    unsafe fn wake_by_ref(data: *const ()) {
191        unsafe {
192            let a = Arc::from_raw(data as *const EventLoopProxy);
193            wake_async(&a);
194            let _do_not_drop = Arc::into_raw(a);
195        }
196    }
197    unsafe fn drop(data: *const ()) {
198        unsafe {
199            let _ = Arc::from_raw(data as *const EventLoopProxy);
200        }
201    }
202
203    let raw_waker = RawWaker::new(data as *const (), &VTABLE);
204    unsafe { Waker::from_raw(raw_waker) }
205}
206
207/// A proxy allowing control of a UI from another thread.
208///
209/// Created by [`Runner::create_proxy`](https://docs.rs/kas/latest/kas/runner/struct.Runner.html#method.create_proxy).
210#[derive(Clone)]
211pub struct Proxy {
212    tx: mpsc::SyncSender<ProxyAction>,
213    waker: EventLoopProxy,
214}
215
216/// Error type returned by [`Proxy`] functions.
217///
218/// This error occurs only if the [`Runner`](https://docs.rs/kas/latest/kas/runner/struct.Runner.html) already terminated.
219pub struct ClosedError;
220
221impl Proxy {
222    #[inline]
223    fn new(tx: mpsc::SyncSender<ProxyAction>, waker: EventLoopProxy) -> Self {
224        Proxy { tx, waker }
225    }
226
227    /// Close a specific window.
228    ///
229    /// Fails if the application has exited.
230    pub fn close(&self, id: WindowId) -> std::result::Result<(), ClosedError> {
231        self.tx
232            .send(ProxyAction::Close(id))
233            .map_err(|_| ClosedError)?;
234        self.waker.wake_up();
235        Ok(())
236    }
237
238    /// Close all windows and terminate the UI.
239    ///
240    /// Fails if the application has exited.
241    pub fn close_all(&self) -> std::result::Result<(), ClosedError> {
242        self.tx
243            .send(ProxyAction::CloseAll)
244            .map_err(|_| ClosedError)?;
245        self.waker.wake_up();
246        Ok(())
247    }
248
249    /// Send a message to [`AppData`] or a set recipient
250    ///
251    /// This is similar to [`EventCx::push`](crate::event::EventCx::push),
252    /// but can only be handled by top-level [`AppData`] or a recipient set
253    /// using [`ConfigCx::set_send_target_for`].
254    ///
255    /// Fails if the application has exited.
256    pub fn push<M: std::fmt::Debug + Send + 'static>(
257        &mut self,
258        msg: M,
259    ) -> std::result::Result<(), ClosedError> {
260        self.tx
261            .send(ProxyAction::Message(kas::messages::SendErased::new(msg)))
262            .map_err(|_| ClosedError)?;
263        self.waker.wake_up();
264        Ok(())
265    }
266}