Skip to main content

pfx_mod/
gameplay.rs

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}