anathema_runtime/
builder.rs

1use std::sync::atomic::Ordering;
2
3use anathema_backend::Backend;
4use anathema_default_widgets::register_default_widgets;
5use anathema_geometry::Size;
6use anathema_templates::{Document, ToSourceKind};
7use anathema_value_resolver::{Function, FunctionTable};
8use anathema_widgets::components::deferred::DeferredComponents;
9use anathema_widgets::components::events::Event;
10use anathema_widgets::components::{Component, ComponentId, ComponentRegistry, Emitter, ViewMessage};
11use anathema_widgets::tabindex::TabIndex;
12use anathema_widgets::{Factory, Widget};
13use notify::{Event as NotifyEvent, RecommendedWatcher, RecursiveMode, Watcher, recommended_watcher};
14
15use crate::REBUILD;
16pub use crate::error::{Error, Result};
17use crate::events::GlobalEventHandler;
18use crate::runtime::{Runtime, show_error};
19
20pub struct Builder<G> {
21    factory: Factory,
22    document: Document,
23    component_registry: ComponentRegistry,
24    emitter: Emitter,
25    message_receiver: flume::Receiver<ViewMessage>,
26    fps: u32,
27    size: Size,
28    global_event_handler: G,
29    hot_reload: bool,
30    function_table: FunctionTable,
31}
32
33impl<G: GlobalEventHandler> Builder<G> {
34    /// Create a new runtime builder
35    pub(super) fn new(document: Document, size: Size, global_event_handler: G) -> Self {
36        let mut factory = Factory::new();
37        register_default_widgets(&mut factory);
38
39        let (tx, message_receiver) = flume::unbounded();
40        let emitter = tx.into();
41
42        Self {
43            factory,
44            document,
45            component_registry: ComponentRegistry::new(),
46            emitter,
47            message_receiver,
48            fps: 30,
49            size,
50            global_event_handler,
51            hot_reload: true,
52            function_table: FunctionTable::new(),
53        }
54    }
55
56    /// Disable hot reloading
57    pub fn disable_hot_reload(&mut self) {
58        self.hot_reload = false;
59    }
60
61    /// Register a new widget
62    pub fn register_widget<T: Widget + Default + 'static>(&mut self, ident: &'static str) {
63        self.factory.register_default::<T>(ident);
64    }
65
66    /// Set the expected frame rate
67    pub fn fps(&mut self, fps: u32) {
68        self.fps = fps;
69    }
70
71    /// Returns an [Emitter] to send messages to components
72    pub fn emitter(&self) -> Emitter {
73        self.emitter.clone()
74    }
75
76    /// Registers a component as a template-only component.
77    ///
78    /// This component has no state or reacts to any events
79    pub fn template(&mut self, ident: impl Into<String>, template: impl ToSourceKind) -> Result<()> {
80        self.prototype(ident, template, || (), || ())
81    }
82
83    /// Registers a [Component] with the runtime.
84    /// This returns a unique [ComponentId] that is used to send messages to the component.
85    ///
86    /// A component can only be used once in a template.
87    /// If you want multiple instances, register the component as a prototype instead,
88    /// see [RuntimeBuilder::prototype].
89    pub fn component<C: Component>(
90        &mut self,
91        ident: impl Into<String>,
92        template: impl ToSourceKind,
93        component: C,
94        state: C::State,
95    ) -> Result<ComponentId<C::Message>> {
96        let id = self.document.add_component(ident, template.to_source_kind())?;
97        self.component_registry.add_component(id, component, state);
98        Ok(id.into())
99    }
100
101    /// Registers a [Component] with the runtime as long as the component and the associated state
102    /// implements the `Default` trait.
103    /// This returns a unique [ComponentId] that is used to send messages to the component.
104    pub fn default<C>(
105        &mut self,
106        ident: impl Into<String>,
107        template: impl ToSourceKind,
108    ) -> Result<ComponentId<C::Message>>
109    where
110        C: Component + Default,
111        C::State: Default,
112    {
113        let component = C::default();
114        let state = C::State::default();
115        let id = self.document.add_component(ident, template.to_source_kind())?;
116        self.component_registry.add_component(id, component, state);
117        Ok(id.into())
118    }
119
120    /// Registers a [Component] as a prototype with the [Runtime],
121    /// which allows for multiple instances of the component to exist the templates.
122    pub fn prototype<FC, FS, C>(
123        &mut self,
124        ident: impl Into<String>,
125        template: impl ToSourceKind,
126        proto: FC,
127        state: FS,
128    ) -> Result<()>
129    where
130        FC: 'static + Fn() -> C,
131        FS: 'static + FnMut() -> C::State,
132        C: Component + 'static,
133    {
134        let id = self.document.add_component(ident, template.to_source_kind())?;
135        self.component_registry.add_prototype(id, proto, state);
136        Ok(())
137    }
138
139    /// Assign a new event handler (make sure not to forget to add some mechanism to stop the
140    /// runtime)
141    pub fn with_global_event_handler<Eh>(self, global_event_handler: Eh) -> Builder<Eh>
142    where
143        Eh: Fn(Event, &mut TabIndex<'_, '_>, &mut DeferredComponents) -> Option<Event>,
144    {
145        Builder {
146            factory: self.factory,
147            document: self.document,
148            component_registry: self.component_registry,
149            emitter: self.emitter,
150            message_receiver: self.message_receiver,
151            fps: self.fps,
152            size: self.size,
153            global_event_handler,
154            hot_reload: self.hot_reload,
155            function_table: self.function_table,
156        }
157    }
158
159    pub fn finish<F, B>(mut self, backend: &mut B, mut f: F) -> Result<()>
160    where
161        F: FnMut(&mut Runtime<G>, &mut B) -> Result<()>,
162        B: Backend,
163    {
164        #[cfg(feature = "profile")]
165        let _puffin_server = {
166            let server_addr = format!("127.0.0.1:{}", puffin_http::DEFAULT_PORT);
167            let server = puffin_http::Server::new(&server_addr).unwrap();
168            puffin::set_scopes_on(true);
169            server
170        };
171
172        let (blueprint, globals) = loop {
173            match self.document.compile() {
174                Ok(val) => break val,
175                Err(error) => {
176                    show_error(error, backend, &mut self.document)?;
177                }
178            }
179        };
180
181        let watcher = self.set_watcher(self.hot_reload)?;
182
183        let mut inst = Runtime::new(
184            blueprint,
185            globals,
186            self.component_registry,
187            self.document,
188            self.factory,
189            self.message_receiver,
190            self.emitter,
191            watcher,
192            self.size,
193            self.fps,
194            self.global_event_handler,
195            self.function_table,
196        );
197
198        // NOTE:
199        // this enables hot reload,
200        // however with this enabled the `with_frame` function
201        // on the runtime will repeat
202        loop {
203            match f(&mut inst, backend) {
204                Ok(()) => (),
205                e => match e {
206                    Ok(_) => continue,
207                    Err(Error::Stop) => break Ok(()),
208                    Err(Error::Template(error)) => match show_error(error, backend, &mut inst.document) {
209                        Ok(_) => continue,
210                        Err(err) => panic!("error console failed: {err}"),
211                    },
212                    Err(Error::Widget(err)) => panic!("this should not panic in the future: {err}"),
213                    Err(e) => break Err(e),
214                },
215            }
216
217            if !self.hot_reload {
218                break Ok(());
219            }
220
221            match inst.reload() {
222                Ok(()) => continue,
223                Err(Error::Stop) => todo!(),
224                Err(Error::Template(error)) => match show_error(error, backend, &mut inst.document) {
225                    Ok(_) => continue,
226                    Err(err) => panic!("error console failed: {err}"),
227                },
228                Err(Error::Widget(_error)) => todo!(),
229                Err(e) => break Err(e),
230            }
231        }
232    }
233
234    fn set_watcher(&mut self, hot_reload: bool) -> Result<Option<RecommendedWatcher>> {
235        if !hot_reload {
236            return Ok(None);
237        }
238
239        let paths = self
240            .document
241            .template_paths()
242            .filter_map(|p| p.canonicalize().ok())
243            .collect::<Vec<_>>();
244
245        let mut watcher = recommended_watcher(move |event: std::result::Result<NotifyEvent, _>| match event {
246            Ok(event) => match event.kind {
247                notify::EventKind::Create(_) | notify::EventKind::Remove(_) | notify::EventKind::Modify(_) => {
248                    if paths.iter().any(|p| event.paths.contains(p)) {
249                        REBUILD.store(true, Ordering::Relaxed);
250                    }
251                }
252                notify::EventKind::Any | notify::EventKind::Access(_) | notify::EventKind::Other => (),
253            },
254            Err(_err) => (),
255        })?;
256
257        for path in self.document.template_paths() {
258            let path = path.canonicalize().unwrap();
259
260            if let Some(parent) = path.parent() {
261                watcher.watch(parent, RecursiveMode::NonRecursive)?;
262            }
263        }
264
265        Ok(Some(watcher))
266    }
267
268    pub fn add_function(&mut self, ident: impl Into<String>, f: impl Into<Function>) {
269        self.function_table.insert(ident, f)
270    }
271}