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::Startup | Self::AssetSync | Self::AssetSyncDeps)
}
}
const TICK_STAGES: [SystemStage; 6] = [
SystemStage::PreUpdate,
SystemStage::Update,
SystemStage::PostUpdate,
SystemStage::PreRender,
SystemStage::Render,
SystemStage::PostRender,
];
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,
startup_done: Vec<bool>,
}
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(),
startup_done: 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() {
let _guard = crate::ecs::resources::set_current_system(system.name());
system.run(&self.world, &self.resources);
}
}
self.resources.get_command_buffer().run_on(&mut self.world);
self.resources.generation() != gen_before
}
fn run_startup_ready(&mut self) -> bool {
let Some(systems) = self.systems.get_mut(&SystemStage::Startup) else {
return false;
};
if self.startup_done.len() != systems.len() {
self.startup_done.resize(systems.len(), false);
}
let mut any_ran = false;
for (idx, system) in systems.iter_mut().enumerate() {
if self.startup_done[idx] {
continue;
}
let ready = system
.requires()
.iter()
.all(|req| (req.present)(&self.world, &self.resources));
if !ready {
continue;
}
let _guard = crate::ecs::resources::set_current_system(system.name());
system.run(&self.world, &self.resources);
self.startup_done[idx] = true;
any_ran = true;
}
if any_ran {
self.resources.get_command_buffer().run_on(&mut self.world);
}
any_ran
}
fn validate_stage_resources(&self, stage: SystemStage) {
if stage.is_convergent() {
return;
}
let Some(systems) = self.systems.get(&stage) else {
return;
};
let mut missing: Vec<(&'static str, &'static str)> = Vec::new();
for system in systems {
for req in system.requires() {
if !(req.present)(&self.world, &self.resources) {
missing.push((system.name(), req.name));
tracing::error!(
stage = ?stage,
system = system.name(),
resource = req.name,
"system requires a resource that is not yet available"
);
}
}
}
if !missing.is_empty() {
missing.sort_unstable();
missing.dedup();
panic!(
"{stage:?}: system(s) require resource(s) that are not yet available:\n{}\n\n\
Insert these via App::add_resource, or a Startup/AssetSync system, before \
this stage runs.",
missing
.iter()
.map(|(system, resource)| format!(" - system `{system}` requires `{resource}`"))
.collect::<Vec<_>>()
.join("\n")
);
}
}
fn reconverge(&mut self, max_passes: u32) {
for pass in 0..max_passes {
let gen_before = self.resources.generation();
self.run_startup_ready();
self.run_stage_once(SystemStage::AssetSync);
self.run_stage_once(SystemStage::AssetSyncDeps);
if self.resources.generation() == gen_before {
return;
}
if pass == max_passes - 1 {
tracing::warn!(
"Startup/AssetSync/AssetSyncDeps did not settle after {max_passes} passes — \
a dependency may be permanently unsatisfiable. Check for a Startup system \
whose Res/ResMut requirement is never met, a LazyResource whose construct() \
always returns None, 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();
let startup_len = self
.systems
.get(&SystemStage::Startup)
.map(Vec::len)
.unwrap_or(0);
self.startup_done = vec![false; startup_len];
self.reconverge(64);
self
}
pub fn update(&mut self) {
self.reconverge(64);
for stage in TICK_STAGES {
self.validate_stage_resources(stage);
self.run_stage_once(stage);
self.reconverge(64);
}
}
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);
}
}