Skip to main content

crux_core/core/
mod.rs

1mod effect;
2mod request;
3mod resolve;
4
5use std::collections::VecDeque;
6use std::sync::{Mutex, RwLock};
7
8pub use effect::{Effect, EffectFFI};
9pub use request::Request;
10pub use resolve::{RequestHandle, Resolvable, ResolveError};
11
12use crate::{App, Command};
13
14/// The Crux core. Create an instance of this type with your App type as the type parameter
15///
16/// The core interface allows passing in events of type `A::Event` using [`Core::process_event`].
17/// It will return back an effect of type `Ef`, containing an effect request, with the input needed for processing
18/// the effect. the `Effect` type can be used by shells to dispatch to the right capability implementation.
19///
20/// The result of the capability's work can then be sent back to the core using [`Core::resolve`], passing
21/// in the request and the corresponding capability output type.
22// used in docs/internals/runtime.md
23// ANCHOR: core
24pub struct Core<A>
25where
26    A: App,
27{
28    // WARNING: The user controlled types _must_ be defined first
29    // so that they are dropped first, in case they contain coordination
30    // primitives which attempt to wake up a future when dropped. For that
31    // reason the executor _must_ outlive the user type instances
32
33    // user types
34    model: RwLock<A::Model>,
35    app: A,
36
37    // internals
38    root_command: Mutex<Command<A::Effect, A::Event>>,
39}
40// ANCHOR_END: core
41
42impl<A> Core<A>
43where
44    A: App + Default,
45    A::Model: Default,
46{
47    /// Create an instance of the Crux core to start a Crux application, e.g.
48    ///
49    /// If you want to construct [App] and [`App::Model`] yourself instead of using default, call [`Self::new_with`]
50    ///
51    /// ```rust,ignore
52    /// let core: Core<MyApp> = Core::new();
53    /// ```
54    ///
55    #[must_use]
56    pub fn new() -> Self {
57        Self {
58            model: RwLock::default(),
59            app: A::default(),
60            root_command: Mutex::new(Command::done()),
61        }
62    }
63}
64
65impl<A> Core<A>
66where
67    A: App,
68{
69    /// Create an instance of the Crux core to start a Crux application with a given [App] and [`App::Model`]
70    ///
71    /// ```rust,ignore
72    /// let app = MyApp::new();
73    /// let model = MyModel::new();
74    /// let core = Core::new_with(app, model);
75    /// ```
76    ///
77    #[must_use]
78    pub fn new_with(app: A, model: A::Model) -> Self {
79        Self {
80            model: RwLock::new(model),
81            app,
82            root_command: Mutex::new(Command::done()),
83        }
84    }
85
86    /// Run the app's `update` function with a given `event`, returning a vector of
87    /// effect requests.
88    ///
89    /// # Panics
90    ///
91    /// Panics if the model `RwLock` was poisoned.
92    // used in docs/internals/runtime.md
93    // ANCHOR: process_event
94    pub fn process_event(&self, event: A::Event) -> Vec<A::Effect> {
95        let mut model = self.model.write().expect("Model RwLock was poisoned.");
96
97        let command = self.app.update(event, &mut model);
98
99        // drop the model here, we don't want to hold the lock for the process() call
100        drop(model);
101
102        let mut root_command = self
103            .root_command
104            .lock()
105            .expect("Capability runtime lock was poisoned");
106        root_command.spawn(|ctx| command.into_future(ctx));
107
108        drop(root_command);
109
110        self.process()
111    }
112    // ANCHOR_END: process_event
113
114    /// Resolve an effect `request` for operation `Op` with the corresponding result.
115    ///
116    /// Note that the `request` is borrowed mutably. When a request that is expected to
117    /// only be resolved once is passed in, it will be consumed and changed to a request
118    /// which can no longer be resolved.
119    ///
120    /// # Errors
121    ///
122    /// Errors if the request cannot (or should not) be resolved.
123    // used in docs/internals/runtime.md and docs/internals/bridge.md
124    // ANCHOR: resolve
125    // ANCHOR: resolve_sig
126    pub fn resolve<Output>(
127        &self,
128        request: &mut impl Resolvable<Output>,
129        result: Output,
130    ) -> Result<Vec<A::Effect>, ResolveError>
131// ANCHOR_END: resolve_sig
132    {
133        let resolve_result = request.resolve(result);
134        debug_assert!(resolve_result.is_ok());
135
136        resolve_result?;
137
138        Ok(self.process())
139    }
140    // ANCHOR_END: resolve
141
142    // used in docs/internals/runtime.md
143    // ANCHOR: process
144    pub(crate) fn process(&self) -> Vec<A::Effect> {
145        let mut root_command = self
146            .root_command
147            .lock()
148            .expect("Capability runtime lock was poisoned");
149
150        let mut events: VecDeque<_> = root_command.events().collect();
151
152        while let Some(event_from_commands) = events.pop_front() {
153            let mut model = self.model.write().expect("Model RwLock was poisoned.");
154            let command = self.app.update(event_from_commands, &mut model);
155            drop(model);
156
157            root_command.spawn(|ctx| command.into_future(ctx));
158
159            events.extend(root_command.events());
160        }
161
162        root_command.effects().collect()
163    }
164    // ANCHOR_END: process
165
166    /// Get the current state of the app's view model.
167    ///
168    /// # Panics
169    ///
170    /// Panics if the model lock was poisoned.
171    pub fn view(&self) -> A::ViewModel {
172        let model = self.model.read().expect("Model RwLock was poisoned.");
173
174        self.app.view(&model)
175    }
176}
177
178impl<A> Default for Core<A>
179where
180    A: App + Default,
181    A::Model: Default,
182{
183    fn default() -> Self {
184        Self::new()
185    }
186}