Skip to main content

gpui_rhai/
reload.rs

1use crate::{CompiledUi, RuntimeEngine, RuntimeError, ScriptGeneration, UiContext, UiNode};
2
3#[derive(Clone)]
4pub struct LiveScript {
5    active: CompiledUi,
6    root: UiNode,
7}
8
9impl LiveScript {
10    /// Compile, render, and activate the initial script.
11    ///
12    /// # Errors
13    ///
14    /// Returns [`RuntimeError`] when compilation or initial rendering fails.
15    pub fn load(runtime: &mut RuntimeEngine, source: &str) -> Result<Self, RuntimeError> {
16        let compiled = runtime.compile(source)?;
17        let root = runtime.render(&compiled)?;
18        Ok(Self {
19            active: compiled,
20            root,
21        })
22    }
23
24    /// Compile, render, and activate an initial script using `view(ctx)`.
25    ///
26    /// # Errors
27    ///
28    /// Returns [`RuntimeError`] when compilation or initial rendering fails.
29    pub fn load_with_context(
30        runtime: &mut RuntimeEngine,
31        source: &str,
32        context: UiContext,
33    ) -> Result<Self, RuntimeError> {
34        let compiled = runtime.compile(source)?;
35        let root = runtime.render_with_context(&compiled, context)?;
36        Ok(Self {
37            active: compiled,
38            root,
39        })
40    }
41
42    #[must_use]
43    pub fn root(&self) -> &UiNode {
44        &self.root
45    }
46
47    #[must_use]
48    pub fn generation(&self) -> ScriptGeneration {
49        self.active.generation()
50    }
51
52    pub fn reload(&mut self, runtime: &mut RuntimeEngine, source: &str) -> ReloadOutcome {
53        let candidate = match runtime.compile(source) {
54            Ok(candidate) => candidate,
55            Err(error) => return ReloadOutcome::Rejected { error },
56        };
57        match runtime.render(&candidate) {
58            Ok(root) => {
59                self.active = candidate;
60                self.root = root;
61                ReloadOutcome::Applied {
62                    generation: self.active.generation(),
63                }
64            }
65            Err(error) => ReloadOutcome::Rejected { error },
66        }
67    }
68
69    pub fn reload_with_context(
70        &mut self,
71        runtime: &mut RuntimeEngine,
72        source: &str,
73        context: UiContext,
74    ) -> ReloadOutcome {
75        let candidate = match runtime.compile(source) {
76            Ok(candidate) => candidate,
77            Err(error) => return ReloadOutcome::Rejected { error },
78        };
79        match runtime.render_with_context(&candidate, context) {
80            Ok(root) => {
81                self.active = candidate;
82                self.root = root;
83                ReloadOutcome::Applied {
84                    generation: self.active.generation(),
85                }
86            }
87            Err(error) => ReloadOutcome::Rejected { error },
88        }
89    }
90}
91
92#[derive(Debug)]
93pub enum ReloadOutcome {
94    Applied { generation: ScriptGeneration },
95    Rejected { error: RuntimeError },
96}
97
98impl ReloadOutcome {
99    #[must_use]
100    pub const fn is_applied(&self) -> bool {
101        matches!(self, Self::Applied { .. })
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn failed_reload_preserves_last_good_tree_and_generation() {
111        let mut runtime = RuntimeEngine::new();
112        let mut live = LiveScript::load(&mut runtime, "fn view() { text(\"good\") }").unwrap();
113        let generation = live.generation();
114        let root = live.root().clone();
115
116        assert!(matches!(
117            live.reload(&mut runtime, "fn view() { text("),
118            ReloadOutcome::Rejected { .. }
119        ));
120        assert_eq!(live.generation(), generation);
121        assert_eq!(live.root(), &root);
122
123        assert!(matches!(
124            live.reload(&mut runtime, "fn view() { throw \"bad render\"; }"),
125            ReloadOutcome::Rejected { .. }
126        ));
127        assert_eq!(live.generation(), generation);
128        assert_eq!(live.root(), &root);
129        assert!(runtime.is_current(generation));
130    }
131
132    #[test]
133    fn successful_reload_atomically_replaces_tree() {
134        let mut runtime = RuntimeEngine::new();
135        let mut live = LiveScript::load(&mut runtime, "fn view() { text(\"one\") }").unwrap();
136        let first = live.generation();
137        assert!(
138            live.reload(&mut runtime, "fn view() { text(\"two\") }")
139                .is_applied()
140        );
141        assert!(live.generation() > first);
142        assert!(matches!(
143            live.root().kind(),
144            crate::UiNodeKind::Text { text } if text == "two"
145        ));
146        assert!(live.root().source().is_some());
147    }
148}