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}