use alloc::boxed::Box;
use crate::behavior::BehaviorSystem;
use crate::components::{Behavior, PhysicsConfig, PropBody, RigidBody, TriggerVolume};
use crate::ecs::{System, SystemEntry, SystemTable, World};
use crate::physics::PhysicsSystem;
use crate::resource::SkinnedMeshTable;
fn behavior(world: &World) -> Option<Box<dyn System>> {
world.query::<Behavior>().next()?;
Some(Box::new(BehaviorSystem::new()))
}
fn physics(world: &World) -> Option<Box<dyn System>> {
let needs = world.query::<PhysicsConfig>().next().is_some()
|| world.query::<RigidBody>().next().is_some()
|| world.query::<PropBody>().next().is_some()
|| world.query::<TriggerVolume>().next().is_some()
|| world
.resource::<SkinnedMeshTable>()
.is_some_and(|t| t.has_capsule());
if !needs {
return None;
}
let config = world
.query::<PhysicsConfig>()
.next()
.cloned()
.unwrap_or_default();
Some(Box::new(PhysicsSystem::new(config)))
}
pub const HEADLESS_SYSTEMS: &SystemTable = &SystemTable {
entries: &[
SystemEntry {
name: "BehaviorSystem",
present_when: "the world declares any Behavior",
gate: behavior,
after: &[],
before: &[],
},
SystemEntry {
name: "PhysicsSystem",
present_when: "the world declares a PhysicsConfig, RigidBody, PropBody, or TriggerVolume, or a skinned mesh bakes a character capsule",
gate: physics,
after: &[],
before: &[],
},
],
before_init: None,
prepare_events: None,
};
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
#[test]
fn declared_edges_respect_table_order_and_name_real_entries() {
let entries = HEADLESS_SYSTEMS.entries;
let position = |name: &str| entries.iter().position(|e| e.name == name);
for (i, entry) in entries.iter().enumerate() {
for after in entry.after {
let at = position(after).unwrap_or_else(|| panic!("{} names {after}", entry.name));
assert!(
at < i,
"{} runs after {after}, but the table runs {after} later",
entry.name,
);
}
for before in entry.before {
let at =
position(before).unwrap_or_else(|| panic!("{} names {before}", entry.name));
assert!(
i < at,
"{} runs before {before}, but the table runs {before} earlier",
entry.name,
);
}
assert!(
!entry.present_when.is_empty(),
"{} has no present_when",
entry.name
);
}
}
#[test]
fn an_empty_world_gates_no_systems() {
let world = World::new();
assert!(world.system_manifest(HEADLESS_SYSTEMS).is_empty());
}
#[test]
fn a_behavior_gates_the_behavior_system() {
let mut world = World::new();
world.add_component(Behavior::default());
let manifest: Vec<&str> = world.system_manifest(HEADLESS_SYSTEMS);
assert_eq!(manifest, ["BehaviorSystem"]);
}
#[test]
fn physics_content_gates_the_physics_system() {
let mut world = World::new();
world.add_component(PhysicsConfig::default());
assert_eq!(world.system_manifest(HEADLESS_SYSTEMS), ["PhysicsSystem"]);
let mut world = World::new();
world.add_component(RigidBody::default());
assert_eq!(world.system_manifest(HEADLESS_SYSTEMS), ["PhysicsSystem"]);
let mut world = World::new();
world.add_component(Behavior::default());
world.add_component(PhysicsConfig::default());
assert_eq!(
world.system_manifest(HEADLESS_SYSTEMS),
["BehaviorSystem", "PhysicsSystem"]
);
}
}