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}