use crate::ecs::{PipelineContext, access_ids, decompose, schedule};
fn before_init(ctx: &mut PipelineContext) {
#[cfg(debug_assertions)]
access_ids::install_hook();
decompose::run(ctx);
}
crate::define_systems! {
complete_world: concinnity_core::defaults::run,
before_init: before_init,
prepare_events: access_ids::ensure_event_queues,
OverlaySystem => crate::gfx::overlay::OverlaySystem {
gate: schedule::overlay,
present_when: "the world declares a GraphicsConfig",
after: [],
before: [BehaviorSystem, SpawnSystem, GraphicsSystem, InputSystem, PhysicsSystem, AnimationSystem],
},
BehaviorSystem => concinnity_core::behavior::BehaviorSystem {
gate: schedule::behavior,
present_when: "the world declares any Behavior",
after: [OverlaySystem],
before: [SpawnSystem, SettingsSystem, StorySystem, AudioSystem],
},
SpawnSystem => crate::spawn::SpawnSystem {
gate: schedule::spawn,
present_when: "the world declares a GraphicsConfig",
after: [BehaviorSystem],
before: [GraphicsSystem],
},
SettingsSystem => crate::gfx::settings_system::SettingsSystem {
gate: schedule::settings,
present_when: "the world declares a GraphicsConfig",
after: [],
before: [GraphicsSystem],
},
StreamingSystem => crate::gfx::streaming_system::StreamingSystem {
gate: schedule::streaming,
present_when: "the world declares a GraphicsConfig",
after: [],
before: [GraphicsSystem],
},
GraphicsSystem => crate::gfx::graphics_system::GraphicsSystem {
gate: schedule::graphics,
present_when: "the world declares a GraphicsConfig",
after: [SpawnSystem, SettingsSystem, StreamingSystem],
before: [InputSystem],
},
InputSystem => crate::gfx::input_system::InputSystem {
gate: schedule::input,
present_when: "the world declares a GraphicsConfig",
after: [GraphicsSystem],
before: [],
},
StatHud => crate::hud::stat_hud::StatHudSystem {
gate: schedule::stat_hud,
present_when: "the world declares a StatHud",
after: [],
before: [],
},
DebugHud => crate::hud::debug_hud::DebugHudSystem {
gate: schedule::debug_hud,
present_when: "the world declares a DebugHud AND the binary is a debug build or a `cn debug` session",
after: [],
before: [],
},
LoadingOverlaySystem => crate::hud::loading_overlay::LoadingOverlaySystem {
gate: schedule::loading_overlay,
present_when: "the world declares a LoadingOverlay",
after: [StreamingSystem],
before: [UiInputSystem],
},
PhysicsSystem => concinnity_core::physics::PhysicsSystem {
gate: schedule::physics,
present_when: "the world declares a PhysicsConfig, RigidBody, PropBody, or TriggerVolume, or a skinned mesh bakes a character capsule",
after: [OverlaySystem],
before: [Camera3DSystem, ThirdPersonSystem],
},
Camera3DSystem => crate::gfx::camera_controller::Camera3DSystem {
gate: schedule::camera3d,
present_when: "the first controlled Camera3D has no follow block",
after: [PhysicsSystem],
before: [AudioSystem],
},
ThirdPersonSystem => crate::gfx::third_person::ThirdPersonSystem {
gate: schedule::third_person,
present_when: "the first controlled Camera3D has a follow block",
after: [PhysicsSystem],
before: [AudioSystem],
},
FpsCounter => crate::hud::fps_counter::FpsCounterSystem {
gate: schedule::fps_counter,
present_when: "the world declares an FpsCounter",
after: [],
before: [],
},
AnimationSystem => crate::gfx::animation::AnimationSystem {
gate: schedule::animation,
present_when: "the world declares any Animation or AnimationGraph",
after: [OverlaySystem],
before: [],
},
StorySystem => crate::story::StorySystem {
gate: schedule::story,
present_when: "the world declares a Story",
after: [BehaviorSystem],
before: [AudioSystem],
},
AudioSystem => crate::audio::AudioSystem {
gate: schedule::audio,
present_when: "the world declares any AudioEmitter, AudioCue, a Story page/choice with audio, or a Behavior with a sound node",
after: [BehaviorSystem, Camera3DSystem, ThirdPersonSystem, StorySystem],
before: [],
},
UiInputSystem => crate::ui::UiInputSystem {
gate: schedule::ui_input,
present_when: "the world declares any HitRegion, Screen, or KeyBinding",
after: [LoadingOverlaySystem],
before: [],
},
TextInputSystem => crate::text_input_system::TextInputSystem {
gate: schedule::text_input,
present_when: "the world declares any TextInput",
after: [],
before: [],
},
}
#[cfg(test)]
mod tests {
use super::SYSTEMS;
use crate::ecs::{ComponentAsset, World};
const ENTRIES: &[crate::ecs::SystemEntry] = SYSTEMS.entries;
#[test]
fn hud_components_spawn_in_schedule_order() {
use crate::components::{DebugHud, FpsCounter, StatHud};
let mut world = World::new();
world.add_component(FpsCounter::default());
world.add_component(StatHud::default());
world.add_component(DebugHud::default());
world.start(SYSTEMS).unwrap();
let names: Vec<&str> = world.systems().iter().map(|s| s.name()).collect();
assert_eq!(names, ["StatHud", "DebugHud", "FpsCounter"]);
}
#[test]
fn system_manifest_matches_started_systems() {
use crate::components::{DebugHud, FpsCounter, StatHud, Story, TextInput};
let mut world = World::new();
world.add_component(StatHud::default());
world.add_component(DebugHud::default());
world.add_component(FpsCounter::default());
world.add_component(Story::default());
world.add_component(TextInput::default());
let manifest = world.system_manifest(SYSTEMS);
world.start(SYSTEMS).unwrap();
let built: Vec<&str> = world.systems().iter().map(|s| s.name()).collect();
assert_eq!(manifest, built);
}
#[test]
fn start_completes_the_world_with_its_engine_defaults() {
use crate::components::{EngineDefaults, PhysicsConfig, PropBody};
let mut world = World::new();
world.add_component(PropBody::default());
world.start(SYSTEMS).unwrap();
assert_eq!(world.query::<PhysicsConfig>().count(), 1);
assert_eq!(world.query::<EngineDefaults>().count(), 0);
let built: Vec<&str> = world.systems().iter().map(|s| s.name()).collect();
assert_eq!(built, ["PhysicsSystem"]);
}
#[test]
fn system_manifest_is_a_table_order_subset() {
use crate::components::{FpsCounter, StatHud};
let mut world = World::new();
world.add_component(FpsCounter::default());
world.add_component(StatHud::default());
let table: Vec<&str> = ENTRIES.iter().map(|e| e.name).collect();
let manifest = world.system_manifest(SYSTEMS);
let mut cursor = table.iter();
for name in &manifest {
assert!(
cursor.any(|t| t == name),
"'{name}' out of table order or unknown: {manifest:?}"
);
}
}
#[test]
fn streaming_runs_immediately_before_graphics() {
let mut world = World::new();
world.add_component(crate::components::GraphicsConfig::default());
let manifest = world.system_manifest(SYSTEMS);
let s = manifest
.iter()
.position(|n| *n == "StreamingSystem")
.expect("StreamingSystem present for a GraphicsConfig world");
let g = manifest
.iter()
.position(|n| *n == "GraphicsSystem")
.expect("GraphicsSystem present for a GraphicsConfig world");
assert_eq!(
g,
s + 1,
"StreamingSystem is directly before GraphicsSystem: {manifest:?}"
);
}
#[test]
fn camera_controller_gates_are_exclusive() {
use crate::components::{Camera3D, CameraController, FollowController};
let mut fly_cam = Camera3D::bake(Default::default());
fly_cam.controller = Some(CameraController::default());
let mut fly = World::new();
fly.add_component(fly_cam);
assert_eq!(fly.system_manifest(SYSTEMS), ["Camera3DSystem"]);
let mut follow_cam = Camera3D::bake(Default::default());
follow_cam.controller = Some(CameraController {
follow: Some(FollowController::default()),
..Default::default()
});
let mut follow = World::new();
follow.add_component(follow_cam);
assert_eq!(follow.system_manifest(SYSTEMS), ["ThirdPersonSystem"]);
}
#[test]
fn audio_gate_probes_without_a_device() {
let mut world = World::new();
world.add_component(crate::components::AudioEmitter::default());
assert_eq!(world.system_manifest(SYSTEMS), ["AudioSystem"]);
}
#[test]
fn story_component_spawns_story_system() {
let mut world = World::new();
world.add_component(crate::components::Story::default());
world.start(SYSTEMS).unwrap();
let names: Vec<&str> = world.systems().iter().map(|s| s.name()).collect();
assert_eq!(names, ["StorySystem"]);
}
#[test]
fn empty_world_fills_from_components_then_systems() {
use crate::components::{FpsCounter, TextLabel};
let mut world = World::new();
assert!(world.is_empty());
assert_eq!(world.component_count(), 0);
assert_eq!(world.system_count(), 0);
world.add(ComponentAsset::from(TextLabel::default()));
assert!(!world.is_empty());
assert_eq!(world.component_count(), 1);
world.add_component(FpsCounter::default());
world.start(SYSTEMS).unwrap();
assert_eq!(world.system_count(), 1, "the FpsCounter gate built one");
}
#[test]
fn declared_edges_respect_table_order() {
let position = |name: &str| {
ENTRIES
.iter()
.position(|e| e.name == name)
.expect("a known entry")
};
for (i, entry) in ENTRIES.iter().enumerate() {
for after in entry.after {
assert!(
position(after) < i,
"{} runs after {after}, but the table runs {after} later",
entry.name,
);
}
for before in entry.before {
assert!(
i < position(before),
"{} runs before {before}, but the table runs {before} earlier",
entry.name,
);
}
}
}
#[test]
fn edge_names_exist() {
for entry in ENTRIES {
for name in entry.after.iter().chain(entry.before) {
assert!(
ENTRIES.iter().any(|e| e.name == *name),
"{} names unknown system {name}",
entry.name
);
}
}
}
#[test]
fn every_entry_documents_its_gate() {
for entry in ENTRIES {
assert!(
!entry.present_when.is_empty(),
"{} has no present_when",
entry.name
);
}
}
}