1use std::collections::HashMap;
2use std::fs;
3use std::fs::File;
4use std::path::PathBuf;
5use std::sync::mpsc::Sender;
6use std::time::{Duration, Instant};
7
8use log::{debug, error, info, warn};
9
10use crate::error::{RuntimeError, RuntimeResult};
11use crate::events::{AppEvent, AppEventReceivingSystem, Events};
12use crate::plugins::{Plugin, PluginBuildContext, PluginUpdateContext};
13use crate::scenes::{
14 ActiveScene, Scene, SceneCommand, SceneCommands, SceneLibrary, SceneLoader, SceneSystem,
15};
16use crate::time;
17use crate::time::Time;
18use furmint_registry::{ComponentFactory, ComponentRegistry};
19use furmint_resources::assets::{AssetServer, Handle};
20use serde::{Deserialize, Serialize};
21use specs::{Component, Dispatcher, DispatcherBuilder, World, WorldExt};
22
23pub type SystemRegistration = Box<dyn FnOnce(&mut DispatcherBuilder<'static, 'static>) + Send>;
25
26pub struct App {
29 world: World,
31 dispatchers: HashMap<Stage, Dispatcher<'static, 'static>>,
33 plugins: Vec<Box<dyn Plugin + 'static + Send + Sync>>,
35 running: bool,
37}
38
39#[derive(Default)]
41pub struct AppBuilder {
42 config: &'static str,
43 world: World,
44 component_registry: ComponentRegistry,
45 dispatcher_builders: HashMap<Stage, DispatcherBuilder<'static, 'static>>,
46 dispatcher_config: HashMap<Stage, Vec<SystemRegistration>>,
47 plugins: Vec<Box<dyn Plugin + Send + Sync>>,
48}
49
50#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
55pub enum Stage {
56 PreUpdate,
58 Update,
60 PostUpdate,
62 Runtime,
64}
65
66impl Stage {
67 pub const ORDER: [Stage; 4] = [
69 Stage::PreUpdate,
70 Stage::Update,
71 Stage::PostUpdate,
72 Stage::Runtime,
73 ];
74}
75
76#[derive(Deserialize, Serialize, Debug)]
78struct Config {
79 assets_path: String,
81 initial_scene: String,
83 tick_period: u64,
85}
86
87impl App {
88 pub fn run(mut self) -> RuntimeResult<()> {
90 self.running = true;
91 while self.running {
92 self.run_frame()?;
93 }
94 Ok(())
95 }
96
97 fn run_frame(&mut self) -> RuntimeResult<()> {
98 let frame_start = Instant::now();
99
100 self.handle_app_events();
101 self.dispatch_stage(Stage::PreUpdate);
102 self.dispatch_stage(Stage::Update);
103 self.dispatch_stage(Stage::PostUpdate);
104
105 self.handle_scene_commands();
106 self.dispatch_stage(Stage::Runtime);
107
108 self.world.maintain();
109
110 let dt = self.finish_tick(frame_start.elapsed());
111
112 self.update_plugins(dt);
113
114 Ok(())
115 }
116
117 fn handle_app_events(&mut self) {
118 let mut receiver = self.world.write_resource::<Events<AppEvent>>();
119 receiver.update();
120 for ev in receiver.iter() {
121 debug!("app event: {:?}", ev);
122 match ev {
123 AppEvent::ExitRequested => {
124 info!("got ExitRequested, exiting app");
125 self.running = false;
126 }
127 }
128 }
129 }
130
131 fn dispatch_stage(&mut self, stage: Stage) {
132 self.dispatchers
133 .get_mut(&stage)
134 .expect("stage dispatcher missing")
135 .dispatch(&self.world);
136 }
137
138 fn update_plugins(&mut self, dt_seconds: f64) {
139 let mut ctx = PluginUpdateContext::new(dt_seconds, &mut self.world);
140 for plugin in &mut self.plugins {
141 if let Err(e) = plugin.update(&mut ctx) {
142 error!("failed to update plugin: {e}");
143 }
144 }
145 }
146
147 fn finish_tick(&mut self, elapsed: Duration) -> f64 {
148 let elapsed_nanos = elapsed.as_nanos() as u64;
149 let mut time = self.world.write_resource::<Time>();
150
151 let frame_nanos = if elapsed_nanos < time.tick_period {
152 let remaining = time.tick_period - elapsed_nanos;
153 std::thread::sleep(Duration::from_nanos(remaining));
154 time.tick_period
155 } else {
156 warn!(
157 "cannot keep up, tick took {} > {} nanoseconds",
158 elapsed_nanos, time.tick_period
159 );
160 elapsed_nanos
161 };
162
163 let dt = frame_nanos as f64 / time::NANOS_PER_SECOND;
164 time.ticks += 1;
165 time.delta_seconds = dt;
166 time.total_elapsed_time += dt;
167
168 dt
169 }
170
171 fn handle_scene_commands(&mut self) {
172 let commands = {
173 let mut scene_commands = self.world.write_resource::<SceneCommands>();
174 scene_commands.drain().collect::<Vec<_>>()
175 };
176
177 for command in commands {
178 match command {
179 SceneCommand::SwitchTo(new_scene) => {
180 let old_entities = {
181 let mut active_scene = self.world.write_resource::<ActiveScene>();
182 std::mem::take(&mut active_scene.entities)
183 };
184 for entity in old_entities {
185 let _ = self.world.delete_entity(entity);
186 }
187 let mut active_scene = self.world.write_resource::<ActiveScene>();
188 active_scene.set(new_scene);
189 }
190 _ => todo!(),
191 }
192 }
193 }
194}
195
196impl AppBuilder {
197 pub fn new() -> AppBuilder {
199 let mut dispatcher_builders = HashMap::new();
200 let mut dispatcher_config = HashMap::new();
201
202 for stage in Stage::ORDER {
203 dispatcher_builders.insert(stage, DispatcherBuilder::new());
204 dispatcher_config.insert(stage, Vec::new());
205 }
206
207 Self {
208 config: "",
209 world: World::new(),
210 component_registry: ComponentRegistry::new(),
211 dispatcher_builders,
212 dispatcher_config,
213 plugins: Vec::new(),
214 }
215 }
216
217 pub fn with_config(mut self, path: &'static str) -> AppBuilder {
219 self.config = path;
220 self
221 }
222
223 pub fn with_system<S>(self, sys: S) -> Self
225 where
226 for<'a> S: specs::System<'a> + Send + 'static,
227 {
228 self.with_system_in_stage(Stage::Update, sys)
229 }
230
231 pub fn with_system_in_stage<S>(mut self, stage: Stage, sys: S) -> Self
233 where
234 for<'a> S: specs::System<'a> + Send + 'static,
235 {
236 push_system_registration(
237 self.dispatcher_config
238 .get_mut(&stage)
239 .expect("stage config missing"),
240 sys,
241 &[],
242 );
243
244 self
245 }
246
247 pub fn with_system_deps<S>(self, sys: S, deps: &'static [&'static str]) -> Self
249 where
250 for<'a> S: specs::System<'a> + Send + 'static,
251 {
252 self.with_system_deps_in_stage(Stage::Update, sys, deps)
253 }
254
255 pub fn with_system_deps_in_stage<S>(
257 mut self,
258 stage: Stage,
259 sys: S,
260 deps: &'static [&'static str],
261 ) -> Self
262 where
263 for<'a> S: specs::System<'a> + Send + 'static,
264 {
265 push_system_registration(
266 self.dispatcher_config
267 .get_mut(&stage)
268 .expect("stage config missing"),
269 sys,
270 deps,
271 );
272
273 self
274 }
275
276 pub fn with_component<C: Component, F: ComponentFactory + Default + 'static>(
278 mut self,
279 ) -> AppBuilder
280 where
281 <C as Component>::Storage: Default,
282 {
283 self.world.register::<C>();
284 self.component_registry
285 .register(F::name(), Box::new(F::default()));
286 self
287 }
288
289 pub fn with_plugin<C: Plugin + 'static + Send + Sync>(mut self, plugin: C) -> AppBuilder {
291 self.plugins.push(Box::new(plugin));
292 self
293 }
294
295 pub fn build(mut self) -> RuntimeResult<App> {
297 if self.config.is_empty() {
298 return Err(RuntimeError::EmptyConfigPath);
299 }
300
301 let config_reader =
302 fs::read(self.config).map_err(|source| RuntimeError::ConfigIo { source })?;
303
304 let config: Config = toml::from_slice(&config_reader)?;
305
306 let mut asset_server = AssetServer::with_root(config.assets_path.clone());
307 asset_server.register_loader::<Scene>(Box::new(SceneLoader));
308
309 self.world.register::<Handle<Scene>>();
310
311 self.world.insert(Time {
312 ticks: 0,
313 tick_period: config.tick_period,
314 total_elapsed_time: 0.0,
315 delta_seconds: 0.0,
316 });
317
318 self.world.insert(SceneCommands::default());
319
320 self.load_scenes(&config, &mut asset_server)?;
321
322 self.world.insert(ActiveScene {
323 name: Some(config.initial_scene),
324 loaded: false,
325 entities: Vec::new(),
326 });
327
328 self.world.insert(asset_server);
329 self.world.insert(Events::<AppEvent>::default());
330
331 self.register_internal_systems();
332 self.build_plugins()?;
333 self.build_dispatcher_configs();
334
335 let dispatchers = self.build_dispatchers();
336 let sender = self.world.write_resource::<Sender<AppEvent>>().clone();
337 ctrlc::set_handler(move || {
338 sender
339 .send(AppEvent::ExitRequested)
340 .expect("failed to send exit request message");
341 })
342 .expect("error setting Ctrl-C handler");
343
344 self.world.insert(self.component_registry);
345 self.world.remove::<Sender<AppEvent>>();
347
348 Ok(App {
349 world: self.world,
350 dispatchers,
351 plugins: self.plugins,
352 running: false,
353 })
354 }
355
356 fn load_scenes(
357 &mut self,
358 config: &Config,
359 asset_server: &mut AssetServer,
360 ) -> RuntimeResult<()> {
361 let scenes_path = PathBuf::from(&config.assets_path).join("scenes");
362
363 debug!("config.assets_path={}", config.assets_path);
364 debug!("scenes_path={}", scenes_path.display());
365
366 let mut library = SceneLibrary::default();
367
368 for entry in fs::read_dir(scenes_path)? {
369 let entry = entry?;
370 let mut reader = File::open(entry.path())?;
371
372 let scene = asset_server.load_reader::<Scene>(
373 entry
374 .file_name()
375 .to_str()
376 .expect("failed to convert scene path to str"),
377 &mut reader,
378 )?;
379 let name = scene.read().name.clone();
380 library.scenes.insert(name.clone(), scene);
381 }
382
383 self.world.insert(library);
384
385 Ok(())
386 }
387
388 fn build_plugins(&mut self) -> RuntimeResult<()> {
389 for plugin in self.plugins.iter_mut() {
390 let event_sender = self.world.write_resource::<Sender<AppEvent>>().clone();
391 let mut ctx = PluginBuildContext {
392 world: &mut self.world,
393 dispatcher_config: &mut self.dispatcher_config,
394 component_registry: &mut self.component_registry,
395 event_sender,
396 };
397
398 plugin.build(&mut ctx).map_err(|source| {
399 error!("failed to build plugin `{}`: {}", plugin.name(), source);
400
401 RuntimeError::PluginSetup {
402 plugin: plugin.name().to_string(),
403 source,
404 }
405 })?;
406
407 debug!("registered plugin {}", plugin.name());
408 }
409
410 Ok(())
411 }
412
413 fn register_internal_systems(&mut self) {
414 let (sender, receiver) = std::sync::mpsc::channel::<AppEvent>();
415 self.world.insert(sender);
416
417 let runtime = self
418 .dispatcher_builders
419 .get_mut(&Stage::Runtime)
420 .expect("runtime dispatcher builder missing");
421
422 runtime.add(SceneSystem, "furmint.scene_system", &[]);
423
424 let pre_update = self
425 .dispatcher_builders
426 .get_mut(&Stage::PreUpdate)
427 .expect("pre-update dispatcher builder missing");
428 pre_update.add(
429 AppEventReceivingSystem::new(receiver),
430 "furmint.app_event_receiving",
431 &[],
432 );
433 }
434
435 fn build_dispatcher_configs(&mut self) {
436 for stage in Stage::ORDER {
437 let builder = self
438 .dispatcher_builders
439 .get_mut(&stage)
440 .expect("dispatcher builder missing");
441
442 let regs = self
443 .dispatcher_config
444 .get_mut(&stage)
445 .expect("dispatcher config missing");
446
447 for reg in regs.drain(..) {
448 reg(builder);
449 }
450
451 #[cfg(debug_assertions)]
452 {
453 debug!("system graph for stage {:?}:", stage);
454 builder.print_par_seq();
455 }
456 }
457 }
458
459 fn build_dispatchers(&mut self) -> HashMap<Stage, Dispatcher<'static, 'static>> {
460 let mut dispatchers = HashMap::new();
461
462 for stage in Stage::ORDER {
463 let builder = self
464 .dispatcher_builders
465 .remove(&stage)
466 .expect("dispatcher builder missing");
467
468 dispatchers.insert(stage, builder.build());
469 }
470
471 dispatchers
472 }
473}
474
475pub(crate) fn push_system_registration<S>(
476 config: &mut Vec<SystemRegistration>,
477 sys: S,
478 deps: &'static [&'static str],
479) where
480 for<'a> S: specs::System<'a> + Send + 'static,
481{
482 config.push(Box::new(move |builder| {
483 let name = std::any::type_name::<S>();
484 builder.add(sys, name, deps);
485 }));
486}