1use std::fmt;
2use std::path::{Path, PathBuf};
3
4use pfx_core::modules::{Level, Notice, Reload};
5use wasmtime::Store;
6
7use crate::ctx::ModCtx;
8use crate::error::{Cause, ModError, Refusal};
9use crate::fingerprint::Fingerprint;
10use crate::host::{Host, read_capped};
11use crate::set::{Bind, Called, Mod, Swapped};
12
13#[derive(Clone, Debug, PartialEq, Eq)]
14pub enum What {
15 Started,
16 Missing(Refusal),
17 Refused(Refusal),
18 Swapped(Swapped),
19 Failed(ModError),
20}
21
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct Event {
24 pub tick: u64,
25 pub id: String,
26 pub what: What,
27}
28
29impl Event {
30 pub fn level(&self) -> Level {
31 match &self.what {
32 What::Started | What::Swapped(Swapped::Kept { .. }) => Level::Info,
33 What::Missing(_) | What::Refused(_) | What::Swapped(Swapped::Fresh(_)) => Level::Warn,
34 What::Failed(_) => Level::Error,
35 }
36 }
37}
38
39impl fmt::Display for Event {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 let (id, tick) = (&self.id, self.tick);
42 match &self.what {
43 What::Started => write!(f, "module `{id}` started at tick {tick}"),
44 What::Missing(refusal) => write!(
45 f,
46 "module `{id}` {refusal}; it stays off until its file loads"
47 ),
48 What::Refused(refusal) => write!(
49 f,
50 "module `{id}`: the rebuilt module {refusal}; the running one carries on"
51 ),
52 What::Swapped(swapped) => {
53 write!(f, "module `{id}` swapped at tick {tick} and {swapped}")
54 }
55 What::Failed(error) => write!(f, "at tick {tick}, {error}"),
56 }
57 }
58}
59
60struct Slot<T: 'static, B> {
61 id: String,
62 path: PathBuf,
63 module: Option<Mod<T, B>>,
64 queued: Option<Vec<u8>>,
65 loaded: Option<Fingerprint>,
66}
67
68pub struct Modules<T: 'static, B> {
69 host: Host<T>,
70 bind: Bind<T, B>,
71 make: Box<dyn FnMut(&str) -> T>,
72 slots: Vec<Slot<T, B>>,
73 events: Vec<Event>,
74 now: u64,
75 ticks: u64,
76}
77
78impl<T: 'static, B> Modules<T, B> {
79 pub fn open(
80 host: Host<T>,
81 bind: Bind<T, B>,
82 files: impl IntoIterator<Item = (String, PathBuf)>,
83 make: impl FnMut(&str) -> T + 'static,
84 ) -> Self {
85 let mut modules = Self {
86 host,
87 bind,
88 make: Box::new(make),
89 slots: Vec::new(),
90 events: Vec::new(),
91 now: 0,
92 ticks: 0,
93 };
94 for (id, path) in files {
95 if modules.slots.iter().any(|slot| slot.id == id) {
96 let refusal = Refusal::Invalid(format!(
97 "is named twice; a second module at {} needs another file name",
98 path.display()
99 ));
100 modules.event(&id, What::Missing(refusal));
101 continue;
102 }
103 let mut slot = Slot {
104 id,
105 path,
106 module: None,
107 queued: None,
108 loaded: None,
109 };
110 match read_capped(
111 &slot.path,
112 "component",
113 modules.host.limits().component_bytes,
114 ) {
115 Ok(bytes) => slot.queued = Some(bytes),
116 Err(refusal) => modules.event(&slot.id, What::Missing(refusal)),
117 }
118 modules.slots.push(slot);
119 }
120 modules.apply();
121 modules
122 }
123
124 fn event(&mut self, id: &str, what: What) {
125 self.events.push(Event {
126 tick: self.now,
127 id: id.to_string(),
128 what,
129 });
130 }
131
132 fn refuse(&mut self, index: usize, refusal: Refusal) {
133 let running = self.slots[index].module.as_ref().is_some_and(Mod::running);
134 let what = if running {
135 What::Refused(refusal)
136 } else {
137 What::Missing(refusal)
138 };
139 let id = self.slots[index].id.clone();
140 self.event(&id, what);
141 }
142
143 fn apply(&mut self) {
144 for index in 0..self.slots.len() {
145 let Some(bytes) = self.slots[index].queued.take() else {
146 continue;
147 };
148 let id = self.slots[index].id.clone();
149 let package = match self.host.load_module(&id, &bytes) {
150 Ok(package) => package,
151 Err(refusal) => {
152 self.refuse(index, refusal);
153 continue;
154 }
155 };
156 if self.slots[index].loaded == Some(package.fingerprint)
157 && self.slots[index].module.as_ref().is_some_and(Mod::running)
158 {
159 continue;
160 }
161 self.slots[index].loaded = Some(package.fingerprint);
162 let what = match self.slots[index].module.as_mut() {
163 Some(module) => match module.reload(&self.host, package) {
164 Ok(swapped) => What::Swapped(swapped),
165 Err(error) => What::Failed(error),
166 },
167 None => {
168 let game = (self.make)(&id);
169 let module = Mod::start(&self.host, package, self.bind, game);
170 let what = match module.stopped() {
171 Some(error) => What::Failed(error.clone()),
172 None => What::Started,
173 };
174 self.slots[index].module = Some(module);
175 what
176 }
177 };
178 self.event(&id, what);
179 }
180 }
181
182 pub fn tick(&mut self) {
183 self.now = self.ticks;
184 self.ticks += 1;
185 self.apply();
186 for slot in &mut self.slots {
187 if let Some(module) = slot.module.as_mut() {
188 module.tick();
189 }
190 }
191 }
192
193 pub fn ticks(&self) -> u64 {
194 self.ticks
195 }
196
197 pub fn call<R>(
198 &mut self,
199 id: &str,
200 f: impl FnOnce(&B, &mut Store<ModCtx<T>>) -> wasmtime::Result<R>,
201 ) -> Option<Result<Called<R>, ModError>> {
202 let module = self
203 .slots
204 .iter_mut()
205 .find(|slot| slot.id == id)?
206 .module
207 .as_mut()?;
208 let result = module.call(f);
209 if let Err(error) = &result
210 && error.cause != Cause::Disabled
211 {
212 self.event(id, What::Failed(error.clone()));
213 }
214 Some(result)
215 }
216
217 pub fn each<R>(
218 &mut self,
219 mut f: impl FnMut(&str, &B, &mut Store<ModCtx<T>>) -> wasmtime::Result<R>,
220 ) -> Vec<(String, Called<R>)> {
221 let mut done = Vec::new();
222 let mut failed = Vec::new();
223 for slot in &mut self.slots {
224 let Slot { id, module, .. } = slot;
225 let Some(module) = module.as_mut().filter(|module| module.running()) else {
226 continue;
227 };
228 match module.call(|bindings, store| f(id, bindings, store)) {
229 Ok(called) => done.push((id.clone(), called)),
230 Err(error) => failed.push((id.clone(), error)),
231 }
232 }
233 for (id, error) in failed {
234 self.event(&id, What::Failed(error));
235 }
236 done
237 }
238
239 pub fn swap(&mut self, id: &str, bytes: Vec<u8>) -> bool {
240 match self.slots.iter_mut().find(|slot| slot.id == id) {
241 Some(slot) => {
242 slot.queued = Some(bytes);
243 true
244 }
245 None => false,
246 }
247 }
248
249 pub fn get(&self, id: &str) -> Option<&Mod<T, B>> {
250 self.slots
251 .iter()
252 .find(|slot| slot.id == id)
253 .and_then(|slot| slot.module.as_ref())
254 }
255
256 pub fn get_mut(&mut self, id: &str) -> Option<&mut Mod<T, B>> {
257 self.slots
258 .iter_mut()
259 .find(|slot| slot.id == id)
260 .and_then(|slot| slot.module.as_mut())
261 }
262
263 pub fn running(&self, id: &str) -> bool {
264 self.get(id).is_some_and(Mod::running)
265 }
266
267 pub fn ids(&self) -> Vec<&str> {
268 self.slots.iter().map(|slot| slot.id.as_str()).collect()
269 }
270
271 pub fn host(&self) -> &Host<T> {
272 &self.host
273 }
274
275 pub fn fingerprint(&self) -> Fingerprint {
276 Fingerprint::of_set(self.slots.iter().filter_map(|slot| {
277 slot.module
278 .as_ref()
279 .map(|module| (slot.id.as_str(), module.fingerprint()))
280 }))
281 }
282
283 pub fn take_events(&mut self) -> Vec<Event> {
284 std::mem::take(&mut self.events)
285 }
286}
287
288impl<T: 'static, B> Reload for Modules<T, B> {
289 fn files(&self) -> Vec<PathBuf> {
290 self.slots.iter().map(|slot| slot.path.clone()).collect()
291 }
292
293 fn changed(&mut self, file: &Path) {
294 let cap = self.host.limits().component_bytes;
295 let Some(index) = self.slots.iter().position(|slot| slot.path == file) else {
296 return;
297 };
298 match read_capped(file, "component", cap) {
299 Ok(bytes) => self.slots[index].queued = Some(bytes),
300 Err(refusal) => self.refuse(index, refusal),
301 }
302 }
303
304 fn notices(&mut self) -> Vec<Notice> {
305 self.take_events()
306 .into_iter()
307 .map(|event| Notice {
308 level: event.level(),
309 text: event.to_string(),
310 })
311 .collect()
312 }
313}