use crate::ecs::World;
pub(crate) fn overlay(world: &World) -> Option<crate::gfx::overlay::OverlaySystem> {
world
.query::<crate::components::GraphicsConfig>()
.next()
.map(|_| crate::gfx::overlay::OverlaySystem::new())
}
pub(crate) fn behavior(world: &World) -> Option<concinnity_core::behavior::BehaviorSystem> {
world
.query::<crate::components::Behavior>()
.next()
.map(|_| crate::behavior::build(crate::ecs::state_tree(world)))
}
pub(crate) fn spawn(world: &World) -> Option<crate::spawn::SpawnSystem> {
world
.query::<crate::components::GraphicsConfig>()
.next()
.map(|_| crate::spawn::SpawnSystem::new())
}
pub(crate) fn settings(world: &World) -> Option<crate::gfx::settings_system::SettingsSystem> {
world
.query::<crate::components::GraphicsConfig>()
.next()
.map(|_| crate::gfx::settings_system::SettingsSystem::new())
}
pub(crate) fn streaming(world: &World) -> Option<crate::gfx::streaming_system::StreamingSystem> {
world
.query::<crate::components::GraphicsConfig>()
.next()
.map(|_| crate::gfx::streaming_system::StreamingSystem::new())
}
pub(crate) fn graphics(world: &World) -> Option<crate::gfx::graphics_system::GraphicsSystem> {
world
.query::<crate::components::GraphicsConfig>()
.next()
.map(|_| crate::gfx::graphics_system::GraphicsSystem::new(crate::ecs::state_tree(world)))
}
pub(crate) fn input(world: &World) -> Option<crate::gfx::input_system::InputSystem> {
world
.query::<crate::components::GraphicsConfig>()
.next()
.map(|_| crate::gfx::input_system::InputSystem::new())
}
pub(crate) fn stat_hud(world: &World) -> Option<crate::hud::stat_hud::StatHudSystem> {
world
.query::<crate::components::StatHud>()
.next()
.cloned()
.map(crate::hud::stat_hud::StatHudSystem::new)
}
pub(crate) fn debug_hud(world: &World) -> Option<crate::hud::debug_hud::DebugHudSystem> {
if !(cfg!(debug_assertions) || crate::app::dev_flags::enabled()) {
return None;
}
world
.query::<crate::components::DebugHud>()
.next()
.cloned()
.map(crate::hud::debug_hud::DebugHudSystem::new)
}
pub(crate) fn loading_overlay(
world: &World,
) -> Option<crate::hud::loading_overlay::LoadingOverlaySystem> {
world
.query::<crate::components::LoadingOverlay>()
.next()
.cloned()
.map(crate::hud::loading_overlay::LoadingOverlaySystem::new)
}
pub(crate) fn physics(world: &World) -> Option<concinnity_core::physics::PhysicsSystem> {
let needs = world
.query::<crate::components::PhysicsConfig>()
.next()
.is_some()
|| world.query::<crate::components::RigidBody>().next().is_some()
|| world.query::<crate::components::PropBody>().next().is_some()
|| world
.query::<crate::components::TriggerVolume>()
.next()
.is_some()
|| world
.resource::<crate::resource::SkinnedMeshTable>()
.is_some_and(|t| t.has_capsule());
if !needs {
return None;
}
let config = world
.query::<crate::components::PhysicsConfig>()
.next()
.cloned()
.unwrap_or_default();
Some(crate::physics::build(config))
}
pub(crate) fn camera3d(world: &World) -> Option<crate::gfx::camera_controller::Camera3DSystem> {
let ctrl = controlled_camera(world)?;
ctrl.follow
.is_none()
.then(|| crate::gfx::camera_controller::Camera3DSystem::new(ctrl))
}
pub(crate) fn third_person(world: &World) -> Option<crate::gfx::third_person::ThirdPersonSystem> {
let ctrl = controlled_camera(world)?;
ctrl.follow
.is_some()
.then(|| crate::gfx::third_person::ThirdPersonSystem::new(&ctrl))
}
fn controlled_camera(world: &World) -> Option<crate::components::CameraController> {
world
.query::<crate::components::Camera3D>()
.find_map(|c| c.controller.clone())
}
pub(crate) fn fps_counter(world: &World) -> Option<crate::hud::fps_counter::FpsCounterSystem> {
world
.query::<crate::components::FpsCounter>()
.next()
.cloned()
.map(crate::hud::fps_counter::FpsCounterSystem::new)
}
pub(crate) fn animation(world: &World) -> Option<crate::gfx::animation::AnimationSystem> {
let declared = world
.query::<crate::components::Animation>()
.next()
.is_some()
|| world
.query::<crate::components::AnimationGraph>()
.next()
.is_some();
declared.then(crate::gfx::animation::AnimationSystem::new)
}
pub(crate) fn story(world: &World) -> Option<crate::story::StorySystem> {
world
.query::<crate::components::Story>()
.next()
.cloned()
.map(|story| crate::story::StorySystem::new(story, crate::ecs::state_tree(world)))
}
pub(crate) fn audio(world: &World) -> Option<crate::audio::AudioSystem> {
let needs = world
.query::<crate::components::AudioEmitter>()
.next()
.is_some()
|| world
.query::<crate::components::AudioCue>()
.next()
.is_some()
|| world
.query::<crate::components::Story>()
.next()
.is_some_and(|s| {
s.nodes.iter().any(|n| {
n.choice_music.is_some()
|| !n.choice_sounds.is_empty()
|| n.pages
.iter()
.any(|p| p.music.is_some() || !p.sounds.is_empty())
})
})
|| world
.query::<crate::components::Behavior>()
.any(crate::components::Behavior::plays_sound);
if !needs {
return None;
}
let audio = crate::config::Settings::load(crate::ecs::state_tree(world)).audio;
Some(crate::audio::AudioSystem::new(crate::audio::AudioVolumes {
master: audio.master_volume,
music: audio.music_volume,
sfx: audio.sfx_volume,
voice: audio.voice_volume,
}))
}
pub(crate) fn ui_input(world: &World) -> Option<crate::ui::UiInputSystem> {
let needs = world
.query::<crate::components::HitRegion>()
.next()
.is_some()
|| world.query::<crate::components::Screen>().next().is_some()
|| world
.query::<crate::components::KeyBinding>()
.next()
.is_some();
needs.then(crate::ui::UiInputSystem::new)
}
pub(crate) fn text_input(world: &World) -> Option<crate::text_input_system::TextInputSystem> {
world
.query::<crate::components::TextInput>()
.next()
.map(|_| crate::text_input_system::TextInputSystem::new())
}
#[cfg(test)]
mod tests {
use crate::components::{Camera3D, CameraController, PhysicsConfig, RigidBody};
use crate::ecs::{SYSTEMS, World};
fn controlled_camera() -> Camera3D {
Camera3D {
fov_y_degrees: 75.0,
near: 0.05,
far: 200.0,
view_matrix: [[0.0; 4]; 4],
position: [0.0, 1.0, 0.0],
yaw: 0.0,
pitch: 0.0,
desired_move: [0.0; 3],
jump_requested: false,
interact_requested: false,
controller: Some(CameraController::default()),
}
}
#[test]
fn physics_config_spawns_internal_system() {
let mut world = World::new();
world.add_component(PhysicsConfig::default());
world.start(SYSTEMS).unwrap();
let names: Vec<&str> = world.systems().iter().map(|s| s.name()).collect();
assert_eq!(names, ["PhysicsSystem"]);
}
#[test]
fn rigid_body_spawns_internal_system() {
let mut world = World::new();
world.add_component(RigidBody::default());
world.start(SYSTEMS).unwrap();
let names: Vec<&str> = world.systems().iter().map(|s| s.name()).collect();
assert_eq!(names, ["PhysicsSystem"]);
}
#[test]
fn no_physics_content_no_system() {
let mut world = World::new();
world.start(SYSTEMS).unwrap();
assert!(world.systems().is_empty());
}
#[test]
fn physics_runs_before_camera_controller() {
let mut world = World::new();
world.add_component(PhysicsConfig::default());
world.add_component(controlled_camera());
world.start(SYSTEMS).unwrap();
let names: Vec<&str> = world.systems().iter().map(|s| s.name()).collect();
assert_eq!(names, ["PhysicsSystem", "Camera3DSystem"]);
}
#[test]
fn audio_emitter_spawns_internal_system() {
let mut world = World::new();
world.add_component(crate::components::AudioEmitter::default());
world.start(SYSTEMS).unwrap();
let names: Vec<&str> = world.systems().iter().map(|s| s.name()).collect();
assert_eq!(names, ["AudioSystem"]);
}
#[test]
fn no_audio_emitter_means_no_system() {
let mut world = World::new();
world.start(SYSTEMS).unwrap();
assert!(world.systems().is_empty());
}
#[test]
fn audio_cue_spawns_internal_system() {
let mut world = World::new();
world.add_component(crate::components::AudioCue::default());
world.start(SYSTEMS).unwrap();
let names: Vec<&str> = world.systems().iter().map(|s| s.name()).collect();
assert!(names.contains(&"AudioSystem"), "{names:?}");
}
#[test]
fn initial_view_fires_its_cue() {
use crate::components::{AudioCue, Screen};
use crate::ecs::AudioClipHandle;
use crate::ecs::asset_id::AssetId;
let mut world = World::new();
let screen = AssetId(90);
world.add_component(Screen {
asset_id: screen,
initial: true,
fade_in_secs: 0.0,
..Default::default()
});
world.add_component(AudioCue {
screen: Some(screen),
clip: Some(AudioClipHandle(0)),
..Default::default()
});
world.start(SYSTEMS).unwrap();
world.step();
let matched = world
.systems()
.iter()
.find_map(|s| s.downcast_ref::<crate::audio::AudioSystem>())
.map(|a| a.cues_matched())
.expect("world has an AudioSystem");
assert_eq!(matched, 1, "the initial screen's cue should have matched");
}
}