use crate::{
assets::required::RequiredResources,
ecs::{
plugin::Plugin,
resources::Resources,
system::{IntoSystem, System},
system_set::IntoSystemSet,
},
};
use std::collections::BTreeMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum SystemStage {
Startup,
PreUpdate,
Update,
PostUpdate,
PreRender,
AssetSync,
AssetSyncDeps,
Render,
PostRender,
}
impl SystemStage {
pub fn is_convergent(self) -> bool {
matches!(self, Self::AssetSync | Self::AssetSyncDeps)
}
}
pub type AppRunner = Box<dyn FnOnce(App)>;
pub struct App {
pub(crate) world: hecs::World,
pub(crate) resources: Resources,
plugins: Vec<Box<dyn Plugin>>,
systems: BTreeMap<SystemStage, Vec<Box<dyn System>>>,
runner: Option<AppRunner>,
pub(crate) required: RequiredResources,
update_stages: Vec<SystemStage>,
}
impl Default for App {
fn default() -> Self {
Self::new()
}
}
impl App {
pub fn new() -> Self {
let mut world = hecs::World::default();
let mut resources = Resources::new(&mut world);
resources.insert_resource(&mut world, ());
Self {
world: world,
resources: resources,
plugins: Vec::new(),
systems: BTreeMap::new(),
runner: Some(Box::new(|mut app| {
loop {
app.update();
}
})),
required: RequiredResources::new(),
update_stages: Vec::new(),
}
}
fn run_stage_once(&mut self, stage: SystemStage) -> bool {
let gen_before = self.resources.generation();
if let Some(systems) = self.systems.get_mut(&stage) {
for system in systems.iter_mut() {
system.run(&self.world, &self.resources);
}
}
self.resources.get_command_buffer().run_on(&mut self.world);
self.resources.generation() != gen_before
}
fn run_stage_to_convergence(&mut self, stage: SystemStage, max_passes: u32) {
for pass in 0..max_passes {
if !self.run_stage_once(stage) {
return;
}
if pass == max_passes - 1 {
tracing::warn!(
"{stage:?}: convergence did not settle after {max_passes} passes — \
a dependency may be permanently unsatisfiable. Check for a \
LazyResource whose construct() or an Asset whose upload() \
always returns None."
);
}
}
}
pub fn add_plugin(&mut self, plugin: impl Plugin) -> &mut Self {
self.plugins.push(Box::new(plugin));
self
}
pub fn add_resource(&mut self, res: impl hecs::Component) -> &mut Self {
self.resources.insert_resource(&mut self.world, res);
self
}
pub fn get_resource<'a, T: hecs::Component>(&'a self) -> hecs::Ref<'a, T> {
self.resources.get_resource(&self.world)
}
pub fn get_resource_mut<'a, T: hecs::Component>(&'a self) -> hecs::RefMut<'a, T> {
self.resources.get_resource_mut(&self.world)
}
pub fn try_insert_resource<T: hecs::Component>(&mut self, res: T) -> bool {
self.resources.try_insert(&mut self.world, res)
}
pub fn add_system<Marker>(
&mut self,
stage: SystemStage,
system: impl IntoSystem<Marker> + 'static,
) -> &mut Self {
self.systems
.entry(stage)
.or_default()
.push(Box::new(system.into_system()));
self
}
pub fn add_systems<Marker>(
&mut self,
stage: SystemStage,
systems: impl IntoSystemSet<Marker>,
) -> &mut Self {
let entry = self.systems.entry(stage).or_default();
entry.extend(systems.into_system_set());
self
}
pub fn build(&mut self) -> &mut Self {
let mut iterations = 0;
const MAX_PLUGIN_BUILD_ITERATIONS: u32 = 64;
while !self.plugins.is_empty() {
iterations += 1;
if iterations > MAX_PLUGIN_BUILD_ITERATIONS {
panic!(
"App::build() exceeded {MAX_PLUGIN_BUILD_ITERATIONS} plugin-registration passes — \
likely a cycle where plugins keep registering each other. Check for a plugin whose \
build() unconditionally re-adds itself or another plugin that re-adds it."
);
}
let plugins: Vec<_> = self.plugins.drain(..).collect();
for plugin in plugins {
plugin.build(self);
}
}
self.required.validate();
self.run_stage_once(SystemStage::Startup);
self.systems.remove(&SystemStage::Startup);
self.run_stage_to_convergence(SystemStage::AssetSync, 64);
self.run_stage_to_convergence(SystemStage::AssetSyncDeps, 64);
self.update_stages = self.systems.keys().copied().collect();
self
}
pub fn update(&mut self) {
let stages = self.update_stages.clone();
for stage in stages {
if stage.is_convergent() {
self.run_stage_to_convergence(stage, 64);
} else {
self.run_stage_once(stage);
}
}
}
pub fn set_runner<F>(&mut self, runner: F) -> &mut Self
where
F: FnOnce(App) + 'static,
{
self.runner = Some(Box::new(runner));
self
}
pub fn run(&mut self) {
let mut owned_app = std::mem::take(self);
let runner = owned_app.runner.take().expect("No runner found!");
runner(owned_app);
}
}