1use crate::{
2 assets::{
3 font::FontAssetSubsystem, shader::ShaderAssetSubsystem, sound::SoundAssetSubsystem,
4 texture::TextureAssetSubsystem,
5 },
6 audio::Audio,
7 context::GameContext,
8};
9#[cfg(not(target_arch = "wasm32"))]
10use glutin::{event::Event, window::Window};
11#[cfg(target_arch = "wasm32")]
12use instant::Instant;
13use keket::database::AssetDatabase;
14use spitfire_draw::{
15 context::DrawContext,
16 utils::{ShaderRef, Vertex},
17};
18use spitfire_glow::{app::AppState, graphics::Graphics, renderer::GlowBlending};
19use spitfire_gui::context::GuiContext;
20use spitfire_input::InputContext;
21#[cfg(not(target_arch = "wasm32"))]
22use std::time::Instant;
23use std::{
24 any::{Any, TypeId},
25 cell::{Ref, RefCell, RefMut},
26 collections::HashMap,
27};
28#[cfg(target_arch = "wasm32")]
29use winit::{event::Event, window::Window};
30
31pub trait GameObject {
32 #[allow(unused_variables)]
33 fn activate(&mut self, context: &mut GameContext) {}
34
35 #[allow(unused_variables)]
36 fn deactivate(&mut self, context: &mut GameContext) {}
37
38 #[allow(unused_variables)]
39 fn process(&mut self, context: &mut GameContext, delta_time: f32) {}
40
41 #[allow(unused_variables)]
42 fn draw(&mut self, context: &mut GameContext) {}
43}
44
45#[derive(Default)]
46pub enum GameStateChange {
47 #[default]
48 Continue,
49 Swap(Box<dyn GameState>),
50 Push(Box<dyn GameState>),
51 Pop,
52}
53
54#[allow(unused_variables)]
55pub trait GameState {
56 fn enter(&mut self, context: GameContext) {}
57
58 fn exit(&mut self, context: GameContext) {}
59
60 fn update(&mut self, context: GameContext, delta_time: f32) {}
61
62 fn fixed_update(&mut self, context: GameContext, delta_time: f32) {}
63
64 fn draw(&mut self, context: GameContext) {}
65
66 fn draw_gui(&mut self, context: GameContext) {}
67}
68
69pub trait GameSubsystem {
70 fn run(&mut self, context: GameContext, delta_time: f32);
71}
72
73#[derive(Default)]
74pub struct GameGlobals {
75 globals: HashMap<TypeId, RefCell<Box<dyn Any>>>,
76}
77
78impl GameGlobals {
79 pub fn set<T: 'static>(&mut self, value: T) {
80 self.globals
81 .insert(TypeId::of::<T>(), RefCell::new(Box::new(value)));
82 }
83
84 pub fn unset<T: 'static>(&mut self) {
85 self.globals.remove(&TypeId::of::<T>());
86 }
87
88 pub fn read<T: 'static>(&self) -> Option<Ref<T>> {
89 self.globals
90 .get(&TypeId::of::<T>())
91 .and_then(|v| v.try_borrow().ok())
92 .map(|v| Ref::map(v, |v| v.downcast_ref::<T>().unwrap()))
93 }
94
95 pub fn write<T: 'static>(&self) -> Option<RefMut<T>> {
96 self.globals
97 .get(&TypeId::of::<T>())
98 .and_then(|v| v.try_borrow_mut().ok())
99 .map(|v| RefMut::map(v, |v| v.downcast_mut::<T>().unwrap()))
100 }
101}
102
103pub struct GameInstance {
104 pub fixed_delta_time: f32,
105 pub color_shader: &'static str,
106 pub image_shader: &'static str,
107 pub text_shader: &'static str,
108 pub input_maintain_on_fixed_step: bool,
109 draw: DrawContext,
110 gui: GuiContext,
111 input: InputContext,
112 assets: AssetDatabase,
113 audio: Audio,
114 timer: Instant,
115 fixed_timer: Instant,
116 states: Vec<Box<dyn GameState>>,
117 state_change: GameStateChange,
118 subsystems: Vec<Box<dyn GameSubsystem>>,
119 globals: GameGlobals,
120}
121
122impl Default for GameInstance {
123 fn default() -> Self {
124 Self {
125 fixed_delta_time: 1.0 / 60.0,
126 color_shader: "color",
127 image_shader: "image",
128 text_shader: "text",
129 input_maintain_on_fixed_step: true,
130 draw: Default::default(),
131 gui: Default::default(),
132 input: Default::default(),
133 assets: Default::default(),
134 audio: Default::default(),
135 timer: Instant::now(),
136 fixed_timer: Instant::now(),
137 states: Default::default(),
138 state_change: Default::default(),
139 subsystems: vec![
140 Box::new(ShaderAssetSubsystem),
141 Box::new(TextureAssetSubsystem),
142 Box::new(FontAssetSubsystem),
143 Box::new(SoundAssetSubsystem),
144 ],
145 globals: Default::default(),
146 }
147 }
148}
149
150impl GameInstance {
151 pub fn new(state: impl GameState + 'static) -> Self {
152 Self {
153 state_change: GameStateChange::Push(Box::new(state)),
154 ..Default::default()
155 }
156 }
157
158 pub fn with_fixed_time_step(mut self, value: f32) -> Self {
159 self.fixed_delta_time = value;
160 self
161 }
162
163 pub fn with_fps(mut self, frames_per_second: usize) -> Self {
164 self.set_fps(frames_per_second);
165 self
166 }
167
168 pub fn with_color_shader(mut self, name: &'static str) -> Self {
169 self.color_shader = name;
170 self
171 }
172
173 pub fn with_image_shader(mut self, name: &'static str) -> Self {
174 self.image_shader = name;
175 self
176 }
177
178 pub fn with_text_shader(mut self, name: &'static str) -> Self {
179 self.text_shader = name;
180 self
181 }
182
183 pub fn with_input_maintain_on_fixed_step(mut self, value: bool) -> Self {
184 self.input_maintain_on_fixed_step = value;
185 self
186 }
187
188 pub fn with_subsystem(mut self, subsystem: impl GameSubsystem + 'static) -> Self {
189 self.subsystems.push(Box::new(subsystem));
190 self
191 }
192
193 pub fn with_globals<T: 'static>(mut self, value: T) -> Self {
194 self.globals.set(value);
195 self
196 }
197
198 pub fn setup_assets(mut self, f: impl FnOnce(&mut AssetDatabase)) -> Self {
199 f(&mut self.assets);
200 self
201 }
202
203 pub fn fps(&self) -> usize {
204 (1.0 / self.fixed_delta_time).ceil() as usize
205 }
206
207 pub fn set_fps(&mut self, frames_per_second: usize) {
208 self.fixed_delta_time = 1.0 / frames_per_second as f32;
209 }
210
211 pub fn process_frame(&mut self, graphics: &mut Graphics<Vertex>) {
212 let delta_time = self.timer.elapsed().as_secs_f32();
213
214 for subsystem in &mut self.subsystems {
215 subsystem.run(
216 GameContext {
217 graphics,
218 draw: &mut self.draw,
219 gui: &mut self.gui,
220 input: &mut self.input,
221 state_change: &mut self.state_change,
222 assets: &mut self.assets,
223 audio: &mut self.audio,
224 globals: &mut self.globals,
225 },
226 delta_time,
227 );
228 }
229 self.assets.maintain().unwrap();
230
231 if let Some(state) = self.states.last_mut() {
232 self.timer = Instant::now();
233 state.update(
234 GameContext {
235 graphics,
236 draw: &mut self.draw,
237 gui: &mut self.gui,
238 input: &mut self.input,
239 state_change: &mut self.state_change,
240 assets: &mut self.assets,
241 audio: &mut self.audio,
242 globals: &mut self.globals,
243 },
244 delta_time,
245 );
246 }
247
248 let fixed_delta_time = self.fixed_timer.elapsed().as_secs_f32();
249 let fixed_step = if fixed_delta_time > self.fixed_delta_time {
250 self.fixed_timer = Instant::now();
251 if let Some(state) = self.states.last_mut() {
252 state.fixed_update(
253 GameContext {
254 graphics,
255 draw: &mut self.draw,
256 gui: &mut self.gui,
257 input: &mut self.input,
258 state_change: &mut self.state_change,
259 assets: &mut self.assets,
260 audio: &mut self.audio,
261 globals: &mut self.globals,
262 },
263 fixed_delta_time,
264 );
265 }
266 true
267 } else {
268 false
269 };
270
271 self.draw.begin_frame(graphics);
272 self.draw.push_shader(&ShaderRef::name(self.image_shader));
273 self.draw.push_blending(GlowBlending::Alpha);
274 if let Some(state) = self.states.last_mut() {
275 state.draw(GameContext {
276 graphics,
277 draw: &mut self.draw,
278 gui: &mut self.gui,
279 input: &mut self.input,
280 state_change: &mut self.state_change,
281 assets: &mut self.assets,
282 audio: &mut self.audio,
283 globals: &mut self.globals,
284 });
285 }
286 self.gui.begin_frame();
287 if let Some(state) = self.states.last_mut() {
288 state.draw_gui(GameContext {
289 graphics,
290 draw: &mut self.draw,
291 gui: &mut self.gui,
292 input: &mut self.input,
293 state_change: &mut self.state_change,
294 assets: &mut self.assets,
295 audio: &mut self.audio,
296 globals: &mut self.globals,
297 });
298 }
299 self.gui.end_frame(
300 &mut self.draw,
301 graphics,
302 &ShaderRef::name(self.color_shader),
303 &ShaderRef::name(self.image_shader),
304 &ShaderRef::name(self.text_shader),
305 );
306 self.draw.end_frame();
307 if !self.input_maintain_on_fixed_step || fixed_step {
308 self.input.maintain();
309 }
310
311 match std::mem::take(&mut self.state_change) {
312 GameStateChange::Continue => {}
313 GameStateChange::Swap(mut state) => {
314 if let Some(mut state) = self.states.pop() {
315 state.exit(GameContext {
316 graphics,
317 draw: &mut self.draw,
318 gui: &mut self.gui,
319 input: &mut self.input,
320 state_change: &mut self.state_change,
321 assets: &mut self.assets,
322 audio: &mut self.audio,
323 globals: &mut self.globals,
324 });
325 }
326 state.enter(GameContext {
327 graphics,
328 draw: &mut self.draw,
329 gui: &mut self.gui,
330 input: &mut self.input,
331 state_change: &mut self.state_change,
332 assets: &mut self.assets,
333 audio: &mut self.audio,
334 globals: &mut self.globals,
335 });
336 self.states.push(state);
337 self.timer = Instant::now();
338 }
339 GameStateChange::Push(mut state) => {
340 state.enter(GameContext {
341 graphics,
342 draw: &mut self.draw,
343 gui: &mut self.gui,
344 input: &mut self.input,
345 state_change: &mut self.state_change,
346 assets: &mut self.assets,
347 audio: &mut self.audio,
348 globals: &mut self.globals,
349 });
350 self.states.push(state);
351 self.timer = Instant::now();
352 }
353 GameStateChange::Pop => {
354 if let Some(mut state) = self.states.pop() {
355 state.exit(GameContext {
356 graphics,
357 draw: &mut self.draw,
358 gui: &mut self.gui,
359 input: &mut self.input,
360 state_change: &mut self.state_change,
361 assets: &mut self.assets,
362 audio: &mut self.audio,
363 globals: &mut self.globals,
364 });
365 }
366 self.timer = Instant::now();
367 }
368 }
369 }
370
371 pub fn process_event(&mut self, event: &Event<()>) -> bool {
372 if let Event::WindowEvent { event, .. } = event {
373 self.input.on_event(event);
374 }
375 !self.states.is_empty() || !matches!(self.state_change, GameStateChange::Continue)
376 }
377}
378
379impl AppState<Vertex> for GameInstance {
380 fn on_redraw(&mut self, graphics: &mut Graphics<Vertex>) {
381 self.process_frame(graphics);
382 }
383
384 fn on_event(&mut self, event: Event<()>, _: &mut Window) -> bool {
385 self.process_event(&event)
386 }
387}