use std::collections::HashMap;
use std::fs;
use std::fs::File;
use std::path::PathBuf;
use std::sync::mpsc::Sender;
use std::time::{Duration, Instant};
use log::{debug, error, info, warn};
use crate::error::{RuntimeError, RuntimeResult};
use crate::events::{AppEvent, AppEventReceivingSystem, Events};
use crate::plugins::{Plugin, PluginBuildContext, PluginUpdateContext};
use crate::scenes::{
ActiveScene, Scene, SceneCommand, SceneCommands, SceneLibrary, SceneLoader, SceneSystem,
};
use crate::time;
use crate::time::Time;
use furmint_registry::{ComponentFactory, ComponentRegistry};
use furmint_resources::assets::{AssetServer, Handle};
use serde::{Deserialize, Serialize};
use specs::{Component, Dispatcher, DispatcherBuilder, World, WorldExt};
pub type SystemRegistration = Box<dyn FnOnce(&mut DispatcherBuilder<'static, 'static>) + Send>;
pub struct App {
world: World,
dispatchers: HashMap<Stage, Dispatcher<'static, 'static>>,
plugins: Vec<Box<dyn Plugin + 'static + Send + Sync>>,
running: bool,
}
#[derive(Default)]
pub struct AppBuilder {
config: &'static str,
world: World,
component_registry: ComponentRegistry,
dispatcher_builders: HashMap<Stage, DispatcherBuilder<'static, 'static>>,
dispatcher_config: HashMap<Stage, Vec<SystemRegistration>>,
plugins: Vec<Box<dyn Plugin + Send + Sync>>,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum Stage {
PreUpdate,
Update,
PostUpdate,
Runtime,
}
impl Stage {
pub const ORDER: [Stage; 4] = [
Stage::PreUpdate,
Stage::Update,
Stage::PostUpdate,
Stage::Runtime,
];
}
#[derive(Deserialize, Serialize, Debug)]
struct Config {
assets_path: String,
initial_scene: String,
tick_period: u64,
}
impl App {
pub fn run(mut self) -> RuntimeResult<()> {
self.running = true;
while self.running {
self.run_frame()?;
}
Ok(())
}
fn run_frame(&mut self) -> RuntimeResult<()> {
let frame_start = Instant::now();
self.handle_app_events();
self.dispatch_stage(Stage::PreUpdate);
self.dispatch_stage(Stage::Update);
self.dispatch_stage(Stage::PostUpdate);
self.handle_scene_commands();
self.dispatch_stage(Stage::Runtime);
self.world.maintain();
let dt = self.finish_tick(frame_start.elapsed());
self.update_plugins(dt);
Ok(())
}
fn handle_app_events(&mut self) {
let mut receiver = self.world.write_resource::<Events<AppEvent>>();
receiver.update();
for ev in receiver.iter() {
debug!("app event: {:?}", ev);
match ev {
AppEvent::ExitRequested => {
info!("got ExitRequested, exiting app");
self.running = false;
}
}
}
}
fn dispatch_stage(&mut self, stage: Stage) {
self.dispatchers
.get_mut(&stage)
.expect("stage dispatcher missing")
.dispatch(&self.world);
}
fn update_plugins(&mut self, dt_seconds: f64) {
let mut ctx = PluginUpdateContext::new(dt_seconds, &mut self.world);
for plugin in &mut self.plugins {
if let Err(e) = plugin.update(&mut ctx) {
error!("failed to update plugin: {e}");
}
}
}
fn finish_tick(&mut self, elapsed: Duration) -> f64 {
let elapsed_nanos = elapsed.as_nanos() as u64;
let mut time = self.world.write_resource::<Time>();
let frame_nanos = if elapsed_nanos < time.tick_period {
let remaining = time.tick_period - elapsed_nanos;
std::thread::sleep(Duration::from_nanos(remaining));
time.tick_period
} else {
warn!(
"cannot keep up, tick took {} > {} nanoseconds",
elapsed_nanos, time.tick_period
);
elapsed_nanos
};
let dt = frame_nanos as f64 / time::NANOS_PER_SECOND;
time.ticks += 1;
time.delta_seconds = dt;
time.total_elapsed_time += dt;
dt
}
fn handle_scene_commands(&mut self) {
let commands = {
let mut scene_commands = self.world.write_resource::<SceneCommands>();
scene_commands.drain().collect::<Vec<_>>()
};
for command in commands {
match command {
SceneCommand::SwitchTo(new_scene) => {
let old_entities = {
let mut active_scene = self.world.write_resource::<ActiveScene>();
std::mem::take(&mut active_scene.entities)
};
for entity in old_entities {
let _ = self.world.delete_entity(entity);
}
let mut active_scene = self.world.write_resource::<ActiveScene>();
active_scene.set(new_scene);
}
_ => todo!(),
}
}
}
}
impl AppBuilder {
pub fn new() -> AppBuilder {
let mut dispatcher_builders = HashMap::new();
let mut dispatcher_config = HashMap::new();
for stage in Stage::ORDER {
dispatcher_builders.insert(stage, DispatcherBuilder::new());
dispatcher_config.insert(stage, Vec::new());
}
Self {
config: "",
world: World::new(),
component_registry: ComponentRegistry::new(),
dispatcher_builders,
dispatcher_config,
plugins: Vec::new(),
}
}
pub fn with_config(mut self, path: &'static str) -> AppBuilder {
self.config = path;
self
}
pub fn with_system<S>(self, sys: S) -> Self
where
for<'a> S: specs::System<'a> + Send + 'static,
{
self.with_system_in_stage(Stage::Update, sys)
}
pub fn with_system_in_stage<S>(mut self, stage: Stage, sys: S) -> Self
where
for<'a> S: specs::System<'a> + Send + 'static,
{
push_system_registration(
self.dispatcher_config
.get_mut(&stage)
.expect("stage config missing"),
sys,
&[],
);
self
}
pub fn with_system_deps<S>(self, sys: S, deps: &'static [&'static str]) -> Self
where
for<'a> S: specs::System<'a> + Send + 'static,
{
self.with_system_deps_in_stage(Stage::Update, sys, deps)
}
pub fn with_system_deps_in_stage<S>(
mut self,
stage: Stage,
sys: S,
deps: &'static [&'static str],
) -> Self
where
for<'a> S: specs::System<'a> + Send + 'static,
{
push_system_registration(
self.dispatcher_config
.get_mut(&stage)
.expect("stage config missing"),
sys,
deps,
);
self
}
pub fn with_component<C: Component, F: ComponentFactory + Default + 'static>(
mut self,
) -> AppBuilder
where
<C as Component>::Storage: Default,
{
self.world.register::<C>();
self.component_registry
.register(F::name(), Box::new(F::default()));
self
}
pub fn with_plugin<C: Plugin + 'static + Send + Sync>(mut self, plugin: C) -> AppBuilder {
self.plugins.push(Box::new(plugin));
self
}
pub fn build(mut self) -> RuntimeResult<App> {
if self.config.is_empty() {
return Err(RuntimeError::EmptyConfigPath);
}
let config_reader =
fs::read(self.config).map_err(|source| RuntimeError::ConfigIo { source })?;
let config: Config = toml::from_slice(&config_reader)?;
let mut asset_server = AssetServer::with_root(config.assets_path.clone());
asset_server.register_loader::<Scene>(Box::new(SceneLoader));
self.world.register::<Handle<Scene>>();
self.world.insert(Time {
ticks: 0,
tick_period: config.tick_period,
total_elapsed_time: 0.0,
delta_seconds: 0.0,
});
self.world.insert(SceneCommands::default());
self.load_scenes(&config, &mut asset_server)?;
self.world.insert(ActiveScene {
name: Some(config.initial_scene),
loaded: false,
entities: Vec::new(),
});
self.world.insert(asset_server);
self.world.insert(Events::<AppEvent>::default());
self.register_internal_systems();
self.build_plugins()?;
self.build_dispatcher_configs();
let dispatchers = self.build_dispatchers();
let sender = self.world.write_resource::<Sender<AppEvent>>().clone();
ctrlc::set_handler(move || {
sender
.send(AppEvent::ExitRequested)
.expect("failed to send exit request message");
})
.expect("error setting Ctrl-C handler");
self.world.insert(self.component_registry);
self.world.remove::<Sender<AppEvent>>();
Ok(App {
world: self.world,
dispatchers,
plugins: self.plugins,
running: false,
})
}
fn load_scenes(
&mut self,
config: &Config,
asset_server: &mut AssetServer,
) -> RuntimeResult<()> {
let scenes_path = PathBuf::from(&config.assets_path).join("scenes");
debug!("config.assets_path={}", config.assets_path);
debug!("scenes_path={}", scenes_path.display());
let mut library = SceneLibrary::default();
for entry in fs::read_dir(scenes_path)? {
let entry = entry?;
let mut reader = File::open(entry.path())?;
let scene = asset_server.load_reader::<Scene>(
entry
.file_name()
.to_str()
.expect("failed to convert scene path to str"),
&mut reader,
)?;
let name = scene.read().name.clone();
library.scenes.insert(name.clone(), scene);
}
self.world.insert(library);
Ok(())
}
fn build_plugins(&mut self) -> RuntimeResult<()> {
for plugin in self.plugins.iter_mut() {
let event_sender = self.world.write_resource::<Sender<AppEvent>>().clone();
let mut ctx = PluginBuildContext {
world: &mut self.world,
dispatcher_config: &mut self.dispatcher_config,
component_registry: &mut self.component_registry,
event_sender,
};
plugin.build(&mut ctx).map_err(|source| {
error!("failed to build plugin `{}`: {}", plugin.name(), source);
RuntimeError::PluginSetup {
plugin: plugin.name().to_string(),
source,
}
})?;
debug!("registered plugin {}", plugin.name());
}
Ok(())
}
fn register_internal_systems(&mut self) {
let (sender, receiver) = std::sync::mpsc::channel::<AppEvent>();
self.world.insert(sender);
let runtime = self
.dispatcher_builders
.get_mut(&Stage::Runtime)
.expect("runtime dispatcher builder missing");
runtime.add(SceneSystem, "furmint.scene_system", &[]);
let pre_update = self
.dispatcher_builders
.get_mut(&Stage::PreUpdate)
.expect("pre-update dispatcher builder missing");
pre_update.add(
AppEventReceivingSystem::new(receiver),
"furmint.app_event_receiving",
&[],
);
}
fn build_dispatcher_configs(&mut self) {
for stage in Stage::ORDER {
let builder = self
.dispatcher_builders
.get_mut(&stage)
.expect("dispatcher builder missing");
let regs = self
.dispatcher_config
.get_mut(&stage)
.expect("dispatcher config missing");
for reg in regs.drain(..) {
reg(builder);
}
#[cfg(debug_assertions)]
{
debug!("system graph for stage {:?}:", stage);
builder.print_par_seq();
}
}
}
fn build_dispatchers(&mut self) -> HashMap<Stage, Dispatcher<'static, 'static>> {
let mut dispatchers = HashMap::new();
for stage in Stage::ORDER {
let builder = self
.dispatcher_builders
.remove(&stage)
.expect("dispatcher builder missing");
dispatchers.insert(stage, builder.build());
}
dispatchers
}
}
pub(crate) fn push_system_registration<S>(
config: &mut Vec<SystemRegistration>,
sys: S,
deps: &'static [&'static str],
) where
for<'a> S: specs::System<'a> + Send + 'static,
{
config.push(Box::new(move |builder| {
let name = std::any::type_name::<S>();
builder.add(sys, name, deps);
}));
}