crux_core/core/
mod.rs

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