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 effect type, and your app type as type parameters
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    pub fn new() -> Self
62    where
63        A::Capabilities: WithContext<A::Event, A::Effect>,
64    {
65        let (request_sender, request_receiver) = capability::channel();
66        let (event_sender, event_receiver) = capability::channel();
67        let (executor, spawner) = capability::executor_and_spawner();
68        let proto_context = ProtoContext::new(request_sender, event_sender, spawner);
69        let command_spawner = CommandSpawner::new(proto_context.clone());
70
71        Self {
72            model: Default::default(),
73            executor,
74            app: Default::default(),
75            capabilities: <<A as App>::Capabilities>::new_with_context(proto_context),
76            requests: request_receiver,
77            capability_events: event_receiver,
78            command_spawner,
79        }
80    }
81
82    /// Run the app's `update` function with a given `event`, returning a vector of
83    /// effect requests.
84    // used in docs/internals/runtime.md
85    // ANCHOR: process_event
86    pub fn process_event(&self, event: A::Event) -> Vec<A::Effect> {
87        let mut model = self.model.write().expect("Model RwLock was poisoned.");
88
89        let command = self.app.update(event, &mut model, &self.capabilities);
90
91        // drop the model here, we don't want to hold the lock for the process() call
92        drop(model);
93
94        self.command_spawner.spawn(command);
95        self.process()
96    }
97    // ANCHOR_END: process_event
98
99    /// Resolve an effect `request` for operation `Op` with the corresponding result.
100    ///
101    /// Note that the `request` is borrowed mutably. When a request that is expected to
102    /// only be resolved once is passed in, it will be consumed and changed to a request
103    /// which can no longer be resolved.
104    // used in docs/internals/runtime.md and docs/internals/bridge.md
105    // ANCHOR: resolve
106    // ANCHOR: resolve_sig
107    pub fn resolve<Op>(
108        &self,
109        request: &mut Request<Op>,
110        result: Op::Output,
111    ) -> Result<Vec<A::Effect>, ResolveError>
112    where
113        Op: Operation,
114        // ANCHOR_END: resolve_sig
115    {
116        let resolve_result = request.resolve(result);
117        debug_assert!(resolve_result.is_ok());
118
119        resolve_result?;
120
121        Ok(self.process())
122    }
123    // ANCHOR_END: resolve
124
125    // used in docs/internals/runtime.md
126    // ANCHOR: process
127    pub(crate) fn process(&self) -> Vec<A::Effect> {
128        self.executor.run_all();
129
130        while let Some(capability_event) = self.capability_events.receive() {
131            let mut model = self.model.write().expect("Model RwLock was poisoned.");
132            let command = self
133                .app
134                .update(capability_event, &mut model, &self.capabilities);
135
136            drop(model);
137
138            self.command_spawner.spawn(command);
139            self.executor.run_all();
140        }
141
142        self.requests.drain().collect()
143    }
144    // ANCHOR_END: process
145
146    /// Get the current state of the app's view model.
147    pub fn view(&self) -> A::ViewModel {
148        let model = self.model.read().expect("Model RwLock was poisoned.");
149
150        self.app.view(&model)
151    }
152}
153
154impl<A> Default for Core<A>
155where
156    A: App,
157    A::Capabilities: WithContext<A::Event, A::Effect>,
158{
159    fn default() -> Self {
160        Self::new()
161    }
162}