use crate::ecs::world::World;
pub type SystemFn = fn(&mut World);
#[derive(Clone)]
pub struct FrameScheduleEntry {
pub name: &'static str,
pub system: SystemFn,
}
#[derive(Clone, Default)]
pub struct FrameSchedule {
pub entries: Vec<FrameScheduleEntry>,
}
#[derive(Clone, Default)]
pub struct Schedules {
pub frame: FrameSchedule,
pub retained_ui: FrameSchedule,
}
pub fn schedule_push(schedule: &mut FrameSchedule, name: &'static str, system: SystemFn) {
schedule.entries.push(FrameScheduleEntry { name, system });
}
pub fn schedule_insert_before(
schedule: &mut FrameSchedule,
target: &str,
name: &'static str,
system: SystemFn,
) {
let position = schedule
.entries
.iter()
.position(|entry| entry.name == target)
.unwrap_or_else(|| panic!("schedule_insert_before: no system named \"{target}\""));
schedule
.entries
.insert(position, FrameScheduleEntry { name, system });
}
pub fn schedule_insert_after(
schedule: &mut FrameSchedule,
target: &str,
name: &'static str,
system: SystemFn,
) {
let position = schedule
.entries
.iter()
.position(|entry| entry.name == target)
.unwrap_or_else(|| panic!("schedule_insert_after: no system named \"{target}\""))
+ 1;
schedule
.entries
.insert(position, FrameScheduleEntry { name, system });
}
pub fn schedule_remove(schedule: &mut FrameSchedule, name: &str) {
schedule.entries.retain(|entry| entry.name != name);
}
pub fn schedule_contains(schedule: &FrameSchedule, name: &str) -> bool {
schedule.entries.iter().any(|entry| entry.name == name)
}
pub fn schedule_run(schedule: &FrameSchedule, world: &mut World) {
let _span = tracing::info_span!("systems").entered();
for entry in &schedule.entries {
(entry.system)(world);
}
}
pub mod system_names {
pub const INITIALIZE_AUDIO: &str = "initialize_audio";
pub const BUILD_AUDIO_BUSES: &str = "build_audio_buses";
pub const UPDATE_AUDIO: &str = "update_audio";
#[cfg(target_arch = "wasm32")]
pub const SYNC_WASM_CANVAS_SIZE: &str = "sync_wasm_canvas_size";
pub const ENSURE_BOUNDING_VOLUMES: &str = "ensure_bounding_volumes";
pub const RUN_PHYSICS: &str = "run_physics";
pub const UPDATE_ANIMATION_PLAYERS: &str = "update_animation_players";
pub const APPLY_ANIMATIONS: &str = "apply_animations";
pub const TRANSFORM_SYSTEMS: &str = "transform_systems";
pub const UPDATE_INSTANCED_MESH_CACHES: &str = "update_instanced_mesh_caches";
pub const RETAINED_UI: &str = "retained_ui";
#[cfg(feature = "gizmos")]
pub const GIZMO_OVERLAY: &str = "gizmo_overlay";
#[cfg(feature = "gizmos")]
pub const NAV_GIZMO_OVERLAY: &str = "nav_gizmo_overlay";
pub const APPLY_IME_ALLOWED: &str = "apply_ime_allowed";
pub const RESET_MOUSE: &str = "reset_mouse";
pub const RESET_KEYBOARD: &str = "reset_keyboard";
pub const RESET_TOUCH: &str = "reset_touch";
pub const PROCESS_COMMANDS: &str = "process_commands";
pub const CLEANUP_UNUSED_RESOURCES: &str = "cleanup_unused_resources";
pub const RUN_NAVMESH: &str = "run_navmesh";
pub const SYNC_TEXT_MESHES: &str = "sync_text_meshes";
pub const POLL_FILE_WATCHER: &str = "poll_file_watcher";
pub const POLL_ASSET_WATCHER: &str = "poll_asset_watcher";
pub const UPDATE_PARTICLE_EMITTERS: &str = "update_particle_emitters";
}
pub mod retained_ui_system_names {
pub const INPUT_SYNC: &str = "ui_retained_input_sync";
pub const GAMEPAD_NAVIGATION: &str = "ui_gamepad_navigation";
pub const LAYOUT_PICKING: &str = "ui_layout_picking";
pub const WIDGET_INTERACTION: &str = "ui_widget_interaction";
pub const EVENT_BUBBLE: &str = "ui_event_bubble";
pub const EMIT_CLICK_EVENTS: &str = "ui_emit_click_events";
pub const LAYOUT_STATE_UPDATE: &str = "ui_layout_state_update";
pub const DOCKED_PANEL_LAYOUT: &str = "ui_docked_panel_layout";
pub const RESPONSIVE_APPLY: &str = "ui_responsive_apply";
pub const LAYOUT_COMPUTE: &str = "ui_layout_compute";
pub const THEME_TRANSITION_TICK: &str = "ui_theme_transition_tick";
pub const THEME_APPLY: &str = "ui_theme_apply";
pub const LAYOUT_COLOR_BLEND: &str = "ui_layout_color_blend";
pub const LAYOUT_TRANSFORM_BLEND: &str = "ui_layout_transform_blend";
pub const LAYOUT_RENDER_SYNC: &str = "ui_layout_render_sync";
}
pub fn build_default_frame_schedule() -> FrameSchedule {
let mut schedule = FrameSchedule::default();
#[cfg(feature = "audio")]
{
schedule_push(
&mut schedule,
system_names::INITIALIZE_AUDIO,
crate::ecs::audio::systems::initialize_audio_system,
);
schedule_push(
&mut schedule,
system_names::BUILD_AUDIO_BUSES,
crate::ecs::audio::systems::build_audio_buses_system,
);
schedule_push(
&mut schedule,
system_names::UPDATE_AUDIO,
crate::ecs::audio::systems::update_audio_system,
);
}
#[cfg(target_arch = "wasm32")]
schedule_push(
&mut schedule,
system_names::SYNC_WASM_CANVAS_SIZE,
crate::ecs::camera::systems::sync_wasm_canvas_size,
);
schedule_push(
&mut schedule,
system_names::ENSURE_BOUNDING_VOLUMES,
crate::ecs::bounding_volume::systems::ensure_bounding_volumes,
);
#[cfg(feature = "physics")]
schedule_push(
&mut schedule,
system_names::RUN_PHYSICS,
crate::ecs::physics::systems::run_physics_systems,
);
schedule_push(
&mut schedule,
system_names::UPDATE_ANIMATION_PLAYERS,
crate::ecs::animation::systems::update_animation_players,
);
schedule_push(
&mut schedule,
system_names::APPLY_ANIMATIONS,
crate::ecs::animation::systems::apply_animations,
);
#[cfg(feature = "navmesh")]
schedule_push(
&mut schedule,
system_names::RUN_NAVMESH,
crate::ecs::navmesh::systems::run_navmesh_systems,
);
schedule_push(
&mut schedule,
system_names::SYNC_TEXT_MESHES,
crate::ecs::text::systems::sync_text_meshes_system,
);
schedule_push(
&mut schedule,
system_names::UPDATE_PARTICLE_EMITTERS,
crate::ecs::particles::systems::update_particle_emitters,
);
schedule_push(
&mut schedule,
system_names::TRANSFORM_SYSTEMS,
crate::ecs::transform::systems::run_systems,
);
schedule_push(
&mut schedule,
system_names::UPDATE_INSTANCED_MESH_CACHES,
crate::ecs::mesh::systems::update_instanced_mesh_caches_system,
);
schedule_push(
&mut schedule,
system_names::RETAINED_UI,
run_retained_ui_schedule,
);
#[cfg(feature = "gizmos")]
{
schedule_push(
&mut schedule,
system_names::GIZMO_OVERLAY,
crate::ecs::gizmos::gizmo_overlay_system,
);
schedule_push(
&mut schedule,
system_names::NAV_GIZMO_OVERLAY,
crate::ecs::gizmos::nav_gizmo_overlay_system,
);
}
schedule_push(
&mut schedule,
system_names::APPLY_IME_ALLOWED,
crate::ecs::input::systems::apply_ime_allowed_system,
);
schedule_push(
&mut schedule,
system_names::RESET_MOUSE,
crate::ecs::input::systems::reset_mouse_system,
);
schedule_push(
&mut schedule,
system_names::RESET_KEYBOARD,
crate::ecs::input::systems::reset_keyboard_system,
);
schedule_push(
&mut schedule,
system_names::RESET_TOUCH,
crate::ecs::input::systems::reset_touch_system,
);
#[cfg(all(feature = "file_watcher", not(target_arch = "wasm32")))]
schedule_push(
&mut schedule,
system_names::POLL_FILE_WATCHER,
crate::ecs::file_watcher::poll_file_watcher_system,
);
#[cfg(all(
feature = "assets",
feature = "file_watcher",
not(target_arch = "wasm32")
))]
schedule_push(
&mut schedule,
system_names::POLL_ASSET_WATCHER,
crate::ecs::asset_watcher::poll_asset_watcher_system,
);
schedule_push(
&mut schedule,
system_names::PROCESS_COMMANDS,
crate::ecs::world::process_commands_system,
);
schedule_push(
&mut schedule,
system_names::CLEANUP_UNUSED_RESOURCES,
crate::ecs::world::cleanup_unused_resources_system,
);
schedule
}
pub fn build_default_retained_ui_schedule() -> FrameSchedule {
let mut schedule = FrameSchedule::default();
schedule_push(
&mut schedule,
retained_ui_system_names::INPUT_SYNC,
crate::ecs::ui::widget_systems::ui_retained_input_sync_system,
);
schedule_push(
&mut schedule,
retained_ui_system_names::LAYOUT_PICKING,
crate::ecs::ui::picking::ui_layout_picking_system,
);
#[cfg(feature = "gamepad")]
schedule_push(
&mut schedule,
retained_ui_system_names::GAMEPAD_NAVIGATION,
crate::ecs::ui::gamepad_navigation::ui_gamepad_navigation_system,
);
schedule_push(
&mut schedule,
retained_ui_system_names::WIDGET_INTERACTION,
crate::ecs::ui::widget_systems::ui_widget_interaction_system,
);
schedule_push(
&mut schedule,
retained_ui_system_names::EVENT_BUBBLE,
crate::ecs::ui::widget_systems::ui_event_bubble_system,
);
schedule_push(
&mut schedule,
retained_ui_system_names::EMIT_CLICK_EVENTS,
crate::ecs::ui::widget_systems::ui_emit_click_events_system,
);
schedule_push(
&mut schedule,
retained_ui_system_names::LAYOUT_STATE_UPDATE,
crate::ecs::ui::systems::ui_layout_state_update_system,
);
schedule_push(
&mut schedule,
retained_ui_system_names::DOCKED_PANEL_LAYOUT,
crate::ecs::ui::systems::ui_docked_panel_layout_system,
);
schedule_push(
&mut schedule,
retained_ui_system_names::RESPONSIVE_APPLY,
crate::ecs::ui::systems::ui_responsive_apply_system,
);
schedule_push(
&mut schedule,
retained_ui_system_names::LAYOUT_COMPUTE,
crate::ecs::ui::systems::ui_layout_compute_system,
);
schedule_push(
&mut schedule,
retained_ui_system_names::THEME_TRANSITION_TICK,
crate::ecs::ui::systems::ui_theme_transition_tick_system,
);
schedule_push(
&mut schedule,
retained_ui_system_names::THEME_APPLY,
crate::ecs::ui::systems::ui_theme_apply_system,
);
schedule_push(
&mut schedule,
retained_ui_system_names::LAYOUT_COLOR_BLEND,
crate::ecs::ui::systems::ui_layout_color_blend_system,
);
schedule_push(
&mut schedule,
retained_ui_system_names::LAYOUT_TRANSFORM_BLEND,
crate::ecs::ui::systems::ui_layout_transform_blend_system,
);
schedule_push(
&mut schedule,
retained_ui_system_names::LAYOUT_RENDER_SYNC,
crate::ecs::ui::render_sync::ui_layout_render_sync_system,
);
schedule
}
pub fn run_retained_ui_schedule(world: &mut World) {
let schedule = std::mem::take(&mut world.resources.schedules.retained_ui);
schedule_run(&schedule, world);
world.resources.schedules.retained_ui = schedule;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn frame_schedule_contains_one_retained_ui_entry() {
let schedule = build_default_frame_schedule();
let count = schedule
.entries
.iter()
.filter(|entry| entry.name == system_names::RETAINED_UI)
.count();
assert_eq!(
count, 1,
"frame schedule must collapse retained UI into a single entry"
);
}
#[test]
fn retained_ui_schedule_orders_pipeline_correctly() {
let schedule = build_default_retained_ui_schedule();
let names: Vec<&str> = schedule.entries.iter().map(|entry| entry.name).collect();
let expected_prefix = [
retained_ui_system_names::INPUT_SYNC,
retained_ui_system_names::LAYOUT_PICKING,
#[cfg(feature = "gamepad")]
retained_ui_system_names::GAMEPAD_NAVIGATION,
retained_ui_system_names::WIDGET_INTERACTION,
retained_ui_system_names::EVENT_BUBBLE,
retained_ui_system_names::EMIT_CLICK_EVENTS,
retained_ui_system_names::LAYOUT_STATE_UPDATE,
retained_ui_system_names::DOCKED_PANEL_LAYOUT,
retained_ui_system_names::RESPONSIVE_APPLY,
retained_ui_system_names::LAYOUT_COMPUTE,
retained_ui_system_names::THEME_TRANSITION_TICK,
retained_ui_system_names::THEME_APPLY,
retained_ui_system_names::LAYOUT_COLOR_BLEND,
retained_ui_system_names::LAYOUT_TRANSFORM_BLEND,
retained_ui_system_names::LAYOUT_RENDER_SYNC,
];
assert_eq!(names, expected_prefix);
}
}