1use crate::prelude::*;
20use alloc::collections::BTreeMap;
21use core::any::{Any, TypeId, type_name};
22use core::fmt;
23
24use shuyi_assets::hot::Reload;
25
26use crate::ctx::{Ctx, GameData};
27use crate::debug::DebugEntry;
28use crate::frame::glyph_buffer::GlyphBuffer;
29
30pub trait Module<G: GameData>: 'static {
34 fn name(&self) -> &'static str;
36
37 fn install(&mut self, _install: &mut Install<'_, G>) {}
40
41 fn before_update(&mut self, _ctx: &mut Ctx<G>, _dt: f32) {}
44
45 fn draw_over(&self, _ctx: &Ctx<G>, _buf: &mut GlyphBuffer) {}
47
48 fn reloaded(&mut self, _ctx: &mut Ctx<G>, _changed: &[String]) {}
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58#[non_exhaustive]
59pub enum Hook {
60 BeforeUpdate,
62 DrawOver,
64 Reloaded,
66}
67
68impl Hook {
69 pub const fn name(self) -> &'static str {
71 match self {
72 Self::BeforeUpdate => "before_update",
73 Self::DrawOver => "draw_over",
74 Self::Reloaded => "reloaded",
75 }
76 }
77}
78
79#[derive(Debug, Default)]
82pub(crate) struct ReloadErrors {
83 by_id: BTreeMap<String, Vec<String>>,
84 lines: Vec<String>,
85}
86
87impl ReloadErrors {
88 pub(crate) fn note(&mut self, id: &str, reload: &Reload) {
90 match reload {
91 Reload::Failed(errors) => {
92 let errors = errors.iter().map(ToString::to_string).collect();
93 self.by_id.insert(id.to_owned(), errors);
94 }
95 Reload::Reloaded => {
96 self.by_id.remove(id);
97 }
98 _ => return,
99 }
100 self.lines = self.by_id.values().flatten().cloned().collect();
101 }
102
103 pub(crate) fn lines(&self) -> &[String] {
105 &self.lines
106 }
107}
108
109#[derive(Debug, Clone, PartialEq, Eq)]
112#[non_exhaustive]
113pub struct InstalledModule {
114 pub name: &'static str,
116 pub hooks: Vec<Hook>,
118 pub states: Vec<&'static str>,
120 pub debug_entries: Vec<String>,
123}
124
125impl InstalledModule {
126 pub fn new(name: &'static str) -> Self {
128 Self {
129 name,
130 hooks: Vec::new(),
131 states: Vec::new(),
132 debug_entries: Vec::new(),
133 }
134 }
135}
136
137#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
139#[non_exhaustive]
140pub enum ModuleError {
141 #[error("the module {name} is installed twice")]
143 SameName {
144 name: &'static str,
146 },
147 #[error("the modules {other} and {module} both put a {state} on the context")]
149 SameState {
150 state: &'static str,
152 module: &'static str,
154 other: &'static str,
156 },
157}
158
159pub struct Install<'a, G: GameData> {
161 ctx: &'a mut Ctx<G>,
162 installed: InstalledModule,
163 error: Option<ModuleError>,
165}
166
167impl<G: GameData> fmt::Debug for Install<'_, G> {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 f.debug_struct("Install")
170 .field("installed", &self.installed)
171 .finish_non_exhaustive()
172 }
173}
174
175impl<'a, G: GameData> Install<'a, G> {
176 pub(crate) fn new(ctx: &'a mut Ctx<G>, name: &'static str) -> Self {
178 Self {
179 ctx,
180 installed: InstalledModule::new(name),
181 error: None,
182 }
183 }
184
185 pub fn hook(&mut self, hook: Hook) {
187 if !self.installed.hooks.contains(&hook) {
188 self.installed.hooks.push(hook);
189 }
190 }
191
192 pub fn state<T: Any>(&mut self, value: T) {
196 let module = self.installed.name;
197 match self.ctx.modules.put(module, value) {
198 Ok(()) => self.installed.states.push(type_name::<T>()),
199 Err(other) => {
200 let state = type_name::<T>();
201 self.error.get_or_insert(ModuleError::SameState {
202 state,
203 module,
204 other,
205 });
206 }
207 }
208 }
209
210 pub fn debug_entry(&mut self, make: fn() -> DebugEntry<G>) {
214 self.installed.debug_entries.push(make().label(self.ctx));
215 self.ctx.modules.makers.push(make);
216 }
217
218 pub(crate) fn finish(self) -> Result<InstalledModule, ModuleError> {
220 match self.error {
221 Some(error) => Err(error),
222 None => Ok(self.installed),
223 }
224 }
225}
226
227pub(crate) fn is_snake_case(name: &str) -> bool {
229 name.starts_with(|c: char| c.is_ascii_lowercase())
230 && name
231 .bytes()
232 .all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'_')
233}
234
235struct State {
237 id: TypeId,
238 type_name: &'static str,
239 module: &'static str,
241 value: Box<dyn Any>,
242}
243
244pub(crate) struct States<G: GameData> {
247 states: Vec<State>,
248 makers: Vec<fn() -> DebugEntry<G>>,
249}
250
251impl<G: GameData> Default for States<G> {
252 fn default() -> Self {
253 Self {
254 states: Vec::new(),
255 makers: Vec::new(),
256 }
257 }
258}
259
260impl<G: GameData> fmt::Debug for States<G> {
261 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
262 f.debug_list()
263 .entries(self.states.iter().map(|s| s.type_name))
264 .finish()
265 }
266}
267
268impl<G: GameData> States<G> {
269 fn put<T: Any>(&mut self, module: &'static str, value: T) -> Result<(), &'static str> {
272 if let Some(other) = self.find(TypeId::of::<T>()) {
273 return Err(other.module);
274 }
275 self.states.push(State {
276 id: TypeId::of::<T>(),
277 type_name: type_name::<T>(),
278 module,
279 value: Box::new(value),
280 });
281 Ok(())
282 }
283
284 fn find(&self, id: TypeId) -> Option<&State> {
285 self.states.iter().find(|s| s.id == id)
286 }
287
288 pub(crate) fn get<T: Any>(&self) -> Option<&T> {
290 self.find(TypeId::of::<T>())?.value.downcast_ref()
291 }
292
293 pub(crate) fn get_mut<T: Any>(&mut self) -> Option<&mut T> {
295 let state = self.states.iter_mut().find(|s| s.id == TypeId::of::<T>())?;
296 state.value.downcast_mut()
297 }
298
299 pub(crate) fn debug_entries(&self) -> impl Iterator<Item = DebugEntry<G>> + '_ {
301 self.makers.iter().map(|make| make())
302 }
303}
304
305#[cfg(test)]
306mod tests;