diff --git a/crates/screens/src/data.rs b/crates/screens/src/data.rs index bb805a1..646c54f 100644 --- a/crates/screens/src/data.rs +++ b/crates/screens/src/data.rs @@ -1,17 +1,24 @@ use crate::prelude::*; -use bevy::{ - ecs::{ - component::ComponentId, - schedule::ScheduleLabel, - system::{ReadOnlySystemParam, SystemParam}, - }, - platform::collections::HashMap, +use bevy::ecs::{ + schedule::ScheduleLabel, + system::{ReadOnlySystemParam, SystemParam}, }; use std::{any::TypeId, marker::PhantomData}; mod general_api { + use thiserror::Error; + use super::*; + #[allow(missing_docs)] + #[derive(Error, Debug)] + pub enum ScreenError { + #[error("Could not find screen {0}! Did you register it?")] + NoSuchScreen(String), + #[error("Could not find screen with ID {0:?}!")] + NoSuchScreenId(ScreenId), + } + /// Call this when you want to switch screens. This will trigger a /// [SwitchToScreenMsg] with the screen's [ComponentId]. #[derive(Event, Debug, PartialEq, Eq, Clone, Deref, Default)] @@ -27,7 +34,16 @@ mod general_api { /// can buffer any [SwitchToScreenMsg]s to avoid conflicts. Only the last /// valid [SwitchToScreenMsg] will be read. #[derive(Message, Debug, PartialEq, Eq, Clone, Deref)] - pub struct SwitchToScreenMsg(pub ComponentId); + pub struct SwitchToScreenMsg(pub ScreenId); + + /// Signals that the current screen has changed. + #[derive(Event, Debug, PartialEq, Eq, Clone)] + pub struct ScreenChanged { + #[allow(missing_docs)] + pub from: Option, + #[allow(missing_docs)] + pub to: ScreenId, + } /// Will cause the given screen to finish loading. Has no effect if the /// screen is not currently loading. @@ -86,28 +102,91 @@ mod general_api { pub use general_api::*; mod screens { - use bevy::ecs::change_detection::Tick; + use bevy::{ecs::change_detection::Tick, utils::TypeIdMap}; use super::*; - /// Marker struct for a screen. - #[derive(Component, Reflect, PartialEq)] - pub struct ScreenMarker(pub ComponentId); + /// Generates [`ScreenId`]s. + #[derive(Resource, Debug, Default)] + pub(crate) struct ScreenIds { + next: bevy::platform::sync::atomic::AtomicUsize, + } - /// Stores a map from the system's name to its spawn function. - /// Used to dynamically load a screen. + impl ScreenIds { + pub fn next(&self) -> ScreenId { + ScreenId( + self.next + .fetch_add(1, bevy::platform::sync::atomic::Ordering::Relaxed), + ) + } + } + /// The screen's ID. + #[derive(Clone, Debug, PartialEq, Eq, Deref, Copy, Reflect)] + pub struct ScreenId(pub(crate) usize); + + /// The screen registry holds a map between the screen's type id and it's [ScreenId]. #[derive(Resource, Debug, Deref, DerefMut, Default)] - pub struct ScreenRegistry(HashMap); + pub struct ScreenRegistry(TypeIdMap); + impl ScreenRegistry { + #[allow(missing_docs)] + pub fn get(&self, id: &TypeId) -> Result { + self.0 + .get(id) + .copied() + .ok_or(ScreenError::NoSuchScreen(format!("{:?}", id))) + } + } + + /// Efficiently accessible vec of screen data. + /// Do not use this directly. Instead prefer to use [ScreenDataRef] or [ScreenDataMut] + #[derive(Resource, Debug, Deref, DerefMut, Default)] + pub struct ScreenData(Vec>); + impl ScreenData { + #[allow(missing_docs)] + pub fn get(&self, id: ScreenId) -> Result<&ScreenInfo, ScreenError> { + self.0 + .get(*id) + .and_then(|v| v.as_ref()) + .ok_or(ScreenError::NoSuchScreenId(id)) + } + #[allow(missing_docs)] + pub fn get_mut(&mut self, id: ScreenId) -> Result<&mut ScreenInfo, ScreenError> { + self.0 + .get_mut(*id) + .and_then(|v| v.as_mut()) + .ok_or(ScreenError::NoSuchScreenId(id)) + } + #[allow(missing_docs)] + pub fn iter_some(&self) -> impl Iterator { + self.0.iter().filter_map(|v| v.as_ref()) + } + #[allow(missing_docs)] + pub fn iter_some_mut(&mut self) -> impl Iterator { + self.0.iter_mut().filter_map(|v| v.as_mut()) + } + } + + /// The current screen's ID. + #[derive(Resource, Debug, Deref, DerefMut, Default)] + pub struct CurrentScreen(Option); + impl CurrentScreen { + /// Gets the [ScreenId] for the given [Screen]. + /// This will usually be populated, unless you have yet to switch to any screen. + pub fn get_id(&self) -> Option { + self.0 + } + } /// Data about a given screen. This is where all the screen's identifying information lives, including it's [ScreenState]. #[derive(Debug)] - pub struct ScreenData { + pub struct ScreenInfo { /// Serialized name of the [Screen] name: String, - id: ComponentId, state: ScreenState, - /// TypeId of the underlying [Screen] component + /// [TypeId] of the underlying [Screen] component type_id: TypeId, + /// [ScreenId] of the underlying [Screen] component + screen_id: ScreenId, /// Indicates that the state has changed and needs to run the corresponding state schedule. pub(crate) needs_update: bool, pub(crate) changed_at: Tick, @@ -119,12 +198,11 @@ mod screens { /// Deinitialize immediately skip_unload: bool, } - impl ScreenData { + impl ScreenInfo { #[allow(missing_docs)] - pub fn new(id: ComponentId, tick: Tick) -> Self { + pub fn new(screen_id: ScreenId, tick: Tick) -> Self { Self { name: S::name(), - id, state: ScreenState::Unloaded, type_id: TypeId::of::(), needs_update: true, @@ -133,6 +211,7 @@ mod screens { load_strategy: LoadStrategy::Blocking, changed_at: tick, initialized: false, + screen_id, } } @@ -238,13 +317,13 @@ mod screens { } #[allow(missing_docs)] - pub fn id(&self) -> ComponentId { - self.id + pub fn name(&self) -> &str { + &self.name } #[allow(missing_docs)] - pub fn name(&self) -> &str { - &self.name + pub fn screen_id(&self) -> ScreenId { + self.screen_id } } } @@ -316,21 +395,21 @@ mod system_params { use super::*; - /// Read-only [SystemParam] for easy access to a screen's [ScreenData] - pub struct ScreenDataRef<'w, S: Screen> { - data: &'w ScreenData, + /// Read-only [SystemParam] for easy access to a screen's [ScreenInfo] + pub struct ScreenInfoRef<'w, S: Screen> { + data: &'w ScreenInfo, _ghost: PhantomData, } - impl<'w, S: Screen> ScreenDataRef<'w, S> { + impl<'w, S: Screen> ScreenInfoRef<'w, S> { #[allow(missing_docs)] - pub fn data(&self) -> &'w ScreenData { + pub fn data(&self) -> &'w ScreenInfo { self.data } } - unsafe impl<'w, S: Screen> SystemParam for ScreenDataRef<'w, S> { + unsafe impl<'w, S: Screen> SystemParam for ScreenInfoRef<'w, S> { type State = (); - type Item<'world, 'state> = ScreenDataRef<'world, S>; + type Item<'world, 'state> = ScreenInfoRef<'world, S>; fn init_state(_world: &mut World) -> Self::State {} @@ -350,57 +429,57 @@ mod system_params { world: bevy::ecs::world::unsafe_world_cell::UnsafeWorldCell<'world>, _change_tick: bevy::ecs::change_detection::Tick, ) -> Self::Item<'world, 'state> { - let cid = world.components().get_id(TypeId::of::()).unwrap(); let registry = unsafe { world.get_resource::().unwrap() }; - let data = registry.get(&cid).unwrap(); - ScreenDataRef { + let idx = registry.get(&TypeId::of::()).unwrap(); + let data_res = unsafe { world.get_resource::().unwrap() }; + let data = data_res + .get(idx) + .map_err(|_| ScreenError::NoSuchScreen(S::name())) + .unwrap(); + ScreenInfoRef { _ghost: PhantomData, data, } } } - unsafe impl<'w, S: Screen> ReadOnlySystemParam for ScreenDataRef<'w, S> {} + unsafe impl<'w, S: Screen> ReadOnlySystemParam for ScreenInfoRef<'w, S> {} /// [SystemParam] for easy mutable access to the given screen's data. /// All functionality happens through helper functions for API sanity. - pub struct ScreenDataMut<'w, S: Screen> { + pub struct ScreenInfoMut<'w, S: Screen> { _ghost: PhantomData, - registry: Mut<'w, ScreenRegistry>, - cid: ComponentId, + data: Mut<'w, ScreenInfo>, change_tick: Tick, } - impl<'w, S: Screen> ScreenDataMut<'w, S> { + impl<'w, S: Screen> ScreenInfoMut<'w, S> { /// Loads the screen. Has no effect if the screen is already Loaded or Ready. pub fn load(&mut self) { let tick = self.change_tick; - self.data_mut().load(tick); + self.data.load(tick); } /// Unloads the screen. Has no effect if the screen is already Loaded or Ready. pub fn unload(&mut self) { let tick = self.change_tick; - self.data_mut().unload(tick); + self.data.unload(tick); } /// Loads the screen. Has no effect if the screen is not Loading. pub fn finish_loading(&mut self) { let tick = self.change_tick; - self.data_mut().finish_loading(tick); + self.data.finish_loading(tick); } /// Loads the screen. Has no effect if the screen is not Loading. pub fn finish_unloading(&mut self) { let tick = self.change_tick; - self.data_mut().finish_unloading(tick); + self.data.finish_unloading(tick); } #[allow(missing_docs)] - pub fn data(&self) -> &ScreenData { - self.registry.get(&self.cid).unwrap() - } - fn data_mut(&mut self) -> &mut ScreenData { - self.registry.get_mut(&self.cid).unwrap() + pub fn data(&self) -> &ScreenInfo { + self.data.as_ref() } } - unsafe impl<'w, S: Screen> SystemParam for ScreenDataMut<'w, S> { + unsafe impl<'w, S: Screen> SystemParam for ScreenInfoMut<'w, S> { type State = (); - type Item<'world, 'state> = ScreenDataMut<'world, S>; + type Item<'world, 'state> = ScreenInfoMut<'world, S>; fn init_state(_world: &mut World) -> Self::State {} @@ -421,16 +500,141 @@ mod system_params { change_tick: bevy::ecs::change_detection::Tick, ) -> Self::Item<'world, 'state> { let registry = unsafe { world.get_resource_mut::().unwrap() }; - let cid = world.components().get_id(TypeId::of::()).unwrap(); + let data_res = unsafe { world.get_resource_mut::().unwrap() }; + let screen_id = registry + .get(&TypeId::of::()) + .map_err(|_| ScreenError::NoSuchScreen(S::name())) + .unwrap(); + let data = data_res.map_unchanged(|res| res.get_mut(screen_id).unwrap()); Self::Item { - registry, - cid, + data, _ghost: PhantomData, change_tick, } } } + + /// Gets the [ScreenId] for the given [Screen] + #[derive(Debug, Copy, Clone, Deref)] + pub struct ScreenIdFor { + #[deref] + id: ScreenId, + _ghost: PhantomData, + } + unsafe impl SystemParam for ScreenIdFor { + type Item<'world, 'state> = ScreenIdFor; + type State = (); + + fn init_state(_: &mut World) -> Self::State {} + + fn init_access( + _state: &Self::State, + _system_meta: &mut bevy::ecs::system::SystemMeta, + _component_access_set: &mut bevy::ecs::query::FilteredAccessSet, + _world: &mut World, + ) { + } + + unsafe fn get_param<'world, 'state>( + _state: &'state mut Self::State, + _system_meta: &bevy::ecs::system::SystemMeta, + world: bevy::ecs::world::unsafe_world_cell::UnsafeWorldCell<'world>, + _change_tick: Tick, + ) -> Self::Item<'world, 'state> { + let registry = unsafe { world.get_resource::().unwrap() }; + let id = registry.get(&TypeId::of::()).unwrap(); + Self { + id, + _ghost: PhantomData, + } + } + } + unsafe impl ReadOnlySystemParam for ScreenIdFor {} + + /// [SystemParam] for easy access to all screen info. + #[derive(SystemParam)] + pub struct Screens<'w> { + registry: Res<'w, ScreenRegistry>, + data: Res<'w, ScreenData>, + } + impl<'w> Screens<'w> { + /// Gets the [ScreenInfo] for the first [Screen] with a matching name. + /// Note that screen name uniqueness is not enforced. + pub fn get_by_name(&self, name: &str) -> Result<&ScreenInfo, ScreenError> { + self.data + .iter_some() + .find(|v| v.name() == name) + .ok_or(ScreenError::NoSuchScreen(name.into())) + } + /// Gets the [ScreenInfo] for the [Screen] with the corresponding [TypeId] + pub fn get_by_type_id(&self, id: &TypeId) -> Result<&ScreenInfo, ScreenError> { + self.registry.get(id).and_then(|id| self.data.get(id)) + } + /// Gets the [ScreenInfo] for the [Screen] with the corresponding [TypeId] + pub fn get_by_id(&self, id: ScreenId) -> Result<&ScreenInfo, ScreenError> { + self.data.get(id) + } + /// Gets the [ScreenInfo] for this [Screen]. Alternative to calling [ScreenInfoRef] + pub fn get(&self) -> Result<&ScreenInfo, ScreenError> { + self.registry + .get(&TypeId::of::()) + .and_then(|id| self.data.get(id)) + } + } + /// [SystemParam] for easy mutable access to all screen info. + #[derive(SystemParam)] + pub struct ScreensMut<'w> { + registry: ResMut<'w, ScreenRegistry>, + data: ResMut<'w, ScreenData>, + } + impl<'w> ScreensMut<'w> { + /// Gets the [ScreenInfo] for the first [Screen] with a matching name. + /// Note that screen name uniqueness is not enforced. + pub fn get_by_name(&self, name: &str) -> Result<&ScreenInfo, ScreenError> { + self.data + .iter_some() + .find(|v| v.name() == name) + .ok_or(ScreenError::NoSuchScreen(name.into())) + } + /// Gets the [ScreenInfo] for the [Screen] with the corresponding [TypeId] + pub fn get_by_type_id(&self, id: &TypeId) -> Result<&ScreenInfo, ScreenError> { + self.registry.get(id).and_then(|id| self.data.get(id)) + } + /// Gets the [ScreenInfo] for the [Screen] with the corresponding [TypeId] + pub fn get_by_id(&self, id: ScreenId) -> Result<&ScreenInfo, ScreenError> { + self.data.get(id) + } + /// Gets the [ScreenInfo] for this [Screen]. Alternative to calling [ScreenInfoRef] + pub fn get(&self) -> Result<&ScreenInfo, ScreenError> { + self.registry + .get(&TypeId::of::()) + .and_then(|id| self.data.get(id)) + } + /// Mutably gets the [ScreenInfo] for the first [Screen] with a matching name. + /// Note that screen name uniqueness is not enforced. + pub fn get_by_name_mut(&mut self, name: &str) -> Result<&mut ScreenInfo, ScreenError> { + self.data + .iter_some_mut() + .find(|v| v.name() == name) + .ok_or(ScreenError::NoSuchScreen(name.into())) + } + /// Mutably gets the [ScreenInfo] for the [Screen] with the corresponding [TypeId]. + pub fn get_by_type_id_mut(&mut self, id: &TypeId) -> Result<&mut ScreenInfo, ScreenError> { + self.registry.get(id).and_then(|id| self.data.get_mut(id)) + } + /// Mutably gets the [ScreenInfo] for this [Screen]. Alternative to calling [ScreenInfoRef] + pub fn get_mut(&mut self) -> Result<&mut ScreenInfo, ScreenError> { + self.registry + .get(&TypeId::of::()) + .and_then(|id| self.data.get_mut(id)) + } + /// Gets the [ScreenInfo] for the [Screen] with the corresponding [TypeId] + pub fn get_by_id_mut(&mut self, id: ScreenId) -> Result<&mut ScreenInfo, ScreenError> { + self.data.get_mut(id) + } + } } + pub use system_params::*; mod helpers { @@ -439,24 +643,24 @@ mod helpers { /// Condition, like [in_state], but for screens. pub fn screen_has_state( state: ScreenState, - ) -> impl FnMut(ScreenDataRef) -> bool + Clone { - move |data: ScreenDataRef| data.data().state() == state + ) -> impl FnMut(ScreenInfoRef) -> bool + Clone { + move |data: ScreenInfoRef| data.data().state() == state } /// Is the screen still loading? - pub fn screen_loading() -> impl FnMut(ScreenDataRef) -> bool + Clone { - |data: ScreenDataRef| matches!(data.data().state(), ScreenState::Loading) + pub fn screen_loading() -> impl FnMut(ScreenInfoRef) -> bool + Clone { + |data: ScreenInfoRef| matches!(data.data().state(), ScreenState::Loading) } /// Has the screen finished loading? - pub fn screen_ready() -> impl FnMut(ScreenDataRef) -> bool + Clone { - |data: ScreenDataRef| matches!(data.data().state(), ScreenState::Ready) + pub fn screen_ready() -> impl FnMut(ScreenInfoRef) -> bool + Clone { + |data: ScreenInfoRef| matches!(data.data().state(), ScreenState::Ready) } /// Is the screen currently unloading? - pub fn screen_unloading() -> impl FnMut(ScreenDataRef) -> bool + Clone { - |data: ScreenDataRef| matches!(data.data().state(), ScreenState::Unloading) + pub fn screen_unloading() -> impl FnMut(ScreenInfoRef) -> bool + Clone { + |data: ScreenInfoRef| matches!(data.data().state(), ScreenState::Unloading) } /// Has the screen finished unloading? - pub fn screen_unloaded() -> impl FnMut(ScreenDataRef) -> bool + Clone { - |data: ScreenDataRef| matches!(data.data().state(), ScreenState::Unloaded) + pub fn screen_unloaded() -> impl FnMut(ScreenInfoRef) -> bool + Clone { + |data: ScreenInfoRef| matches!(data.data().state(), ScreenState::Unloaded) } /// Label of a schedule which fires when the screen has begun to load. diff --git a/crates/screens/src/plugin.rs b/crates/screens/src/plugin.rs index b58e3a1..a4aa460 100644 --- a/crates/screens/src/plugin.rs +++ b/crates/screens/src/plugin.rs @@ -9,7 +9,9 @@ pub struct ScreenPlugin; impl Plugin for ScreenPlugin { fn build(&self, app: &mut App) { app.init_resource::(); + app.init_resource::(); app.init_resource::(); + app.init_resource::(); app.add_message::(); app.add_plugins(( HierarchyPropagatePlugin::::new(PostUpdate), diff --git a/crates/screens/src/scope.rs b/crates/screens/src/scope.rs index 6458c12..03c230e 100644 --- a/crates/screens/src/scope.rs +++ b/crates/screens/src/scope.rs @@ -1,3 +1,5 @@ +use std::any::TypeId; + pub use crate::prelude::*; use bevy::{ecs::system::ScheduleSystem, platform::collections::HashMap}; use strum::IntoEnumIterator; @@ -95,13 +97,20 @@ where } fn build(self, app: &mut App) { - // init - let id = app.world_mut().register_component::(); - let tick = app.world_mut().change_tick(); - let mut registry = app.world_mut().get_resource_or_init::(); + if let Some(registry) = app.world().get_resource::() + && registry.get(&TypeId::of::()).is_ok() + { + warn!("Already registered {}, not registering again", S::name()); + return; + } + + let id = { + let ids = app.world_mut().get_resource_or_init::(); + ids.next() + }; - // insert data - let mut data = ScreenData::new::(id, tick); + let tick = app.world_mut().change_tick(); + let mut data = ScreenInfo::new::(id, tick); let skip_load = self .schedules .get(&ScreenSchedule::Loading) @@ -115,8 +124,17 @@ where data.set_skip_load(self.skip_load.unwrap_or(skip_load)); data.set_skip_unload(self.skip_unload.unwrap_or(skip_unload)); data.set_load_strategy(self.load_strategy); - debug!("Built screen {data:#?}"); - registry.insert(id, data); + + { + let mut data_res = app.world_mut().get_resource_or_init::(); + let min_size = id.min(data_res.len()); + data_res.resize_with(min_size, || None); + data_res.insert(*id, Some(data)); + }; + { + let mut registry = app.world_mut().get_resource_or_init::(); + registry.insert(TypeId::of::(), id); + }; // watch screen switcher app.add_observer(on_switch_screen::); @@ -170,6 +188,7 @@ where }); } app.add_systems(on_screen_unloaded::(), clean_up_scoped_entities::); + debug!("Built {} (id={:?})", S::name(), id); } } @@ -193,7 +212,7 @@ where fn clean_up_scoped_entities( mut commands: Commands, - mut screen_data: ScreenDataMut, + mut screen_data: ScreenInfoMut, // Any entity which is (explicitly marked as ScreenScoped, or is _not_ marked // as persistent) _and_ is not a top-level observer screen_scoped: Query< diff --git a/crates/screens/src/systems.rs b/crates/screens/src/systems.rs index 2dab051..11061b0 100644 --- a/crates/screens/src/systems.rs +++ b/crates/screens/src/systems.rs @@ -1,120 +1,124 @@ //! This module contains all systems, including observers. +use std::any::TypeId; + use crate::prelude::*; -use bevy::ecs::{component::ComponentIdFor, system::SystemChangeTick}; +use bevy::ecs::system::SystemChangeTick; fn handle_switch_msg( mut reader: MessageReader, - mut registry: ResMut, + mut registry: ResMut, tick: SystemChangeTick, + mut commands: Commands, + mut current_screen: ResMut, ) { // get the most recent valid message, then load it and unload all others if reader.is_empty() { return; } let vec = reader.read().collect::>(); - let msg_key = vec.iter().rev().find(|msg| registry.get(&msg.0).is_some()); + let msg_key = vec.iter().rev().find(|msg| registry.get(msg.0).is_ok()); let msg_key = rq!(msg_key); - for (key, data) in registry.iter_mut() { - if *key == ***msg_key { - data.load(tick.this_run()); - } else { - data.unload(tick.this_run()); + for (key, value) in registry.iter_mut().enumerate() { + if let Some(data) = value { + if key == *msg_key.0 { + data.load(tick.this_run()); + } else { + data.unload(tick.this_run()); + } } } + commands.trigger(ScreenChanged { + from: **current_screen, + to: msg_key.0, + }); + **current_screen = Some(msg_key.0) } /// NOTE: This is registered in scope.rs pub(crate) fn on_switch_screen( _trigger: On>, - id: ComponentIdFor, + registry: Res, mut commands: Commands, ) { - commands.write_message(SwitchToScreenMsg(id.get())); + let id = registry + .get(&TypeId::of::()) + .map_err(|_| ScreenError::NoSuchScreen(S::name())); + commands.write_message(SwitchToScreenMsg(id.unwrap())); } pub(crate) fn on_finish_loading( _trigger: On>, - mut data: ScreenDataMut, + mut data: ScreenInfoMut, ) { data.finish_loading(); } pub(crate) fn on_finish_unloading( _trigger: On>, - mut data: ScreenDataMut, + mut data: ScreenInfoMut, ) { data.finish_unloading(); } -fn run_schedules( - mut registry: ResMut, - mut commands: Commands, - tick: SystemChangeTick, - screens: Query<&ScreenMarker>, -) { - for data in registry.values_mut() { - if matches!(data.state(), ScreenState::Loading | ScreenState::Ready) - && !screens.iter().contains(&ScreenMarker(data.id())) - { - commands.spawn((ScreenMarker(data.id()), Name::new(data.name().to_owned()))); - } - match data.state() { +fn run_schedules(mut data: ResMut, mut commands: Commands, tick: SystemChangeTick) { + for info in data.iter_mut().filter_map(|info| info.as_mut()) { + match info.state() { ScreenState::Unloaded => { - if !data.initialized { - data.initialized = true; - data.needs_update = false; - data.changed_at = tick.this_run(); + if !info.initialized { + info.initialized = true; + info.needs_update = false; + info.changed_at = tick.this_run(); } - if data.needs_update { - commands.run_schedule(OnScreenUnloaded(data.type_id())); - data.needs_update = false; - data.changed_at = tick.this_run(); + if info.needs_update { + commands.run_schedule(OnScreenUnloaded(info.type_id())); + info.needs_update = false; + info.changed_at = tick.this_run(); } } ScreenState::Loading => { commands.run_schedule(ScreenScheduleLabel::from_id( ScreenSchedule::Loading, - data.type_id(), + info.type_id(), )); - if data.needs_update { - commands.run_schedule(OnScreenLoad(data.type_id())); - data.needs_update = false; - data.changed_at = tick.this_run(); + if info.needs_update { + commands.run_schedule(OnScreenLoad(info.type_id())); + info.needs_update = false; + info.changed_at = tick.this_run(); } - if matches!(data.load_strategy(), LoadStrategy::Nonblocking) { + if matches!(info.load_strategy(), LoadStrategy::Nonblocking) { commands.run_schedule(ScreenScheduleLabel::from_id( ScreenSchedule::Update, - data.type_id(), + info.type_id(), )); } } ScreenState::Ready => { commands.run_schedule(ScreenScheduleLabel::from_id( ScreenSchedule::Update, - data.type_id(), + info.type_id(), )); - if data.needs_update { - commands.run_schedule(OnScreenReady(data.type_id())); - data.needs_update = false; - data.changed_at = tick.this_run(); + if info.needs_update { + commands.run_schedule(OnScreenReady(info.type_id())); + info.needs_update = false; + info.changed_at = tick.this_run(); } } ScreenState::Unloading => { commands.run_schedule(ScreenScheduleLabel::from_id( ScreenSchedule::Unloading, - data.type_id(), + info.type_id(), )); - if data.needs_update { - commands.run_schedule(OnScreenUnload(data.type_id())); - data.needs_update = false; - data.changed_at = tick.this_run(); + if info.needs_update { + commands.run_schedule(OnScreenUnload(info.type_id())); + info.needs_update = false; + info.changed_at = tick.this_run(); } } } } } -fn run_fixed_schedules(registry: ResMut, mut commands: Commands) { - for data in registry.values() { +fn run_fixed_schedules(mut registry: ResMut, mut commands: Commands) { + for data in registry.iter_mut().filter_map(|d| d.as_mut()) { match data.state() { ScreenState::Loading => { if matches!(data.load_strategy(), LoadStrategy::Nonblocking) { @@ -138,18 +142,11 @@ fn run_fixed_schedules(registry: ResMut, mut commands: Commands) pub(crate) fn initial_screen( mut commands: Commands, initial_screen: Res, - registry: Res, + screens: Screens, ) { if let Some(initial_screen) = (*initial_screen).as_ref() { - if let Some(cid) = registry - .values() - .find_map(|v| (v.name() == initial_screen).then_some(v.id())) - { - info!("Switching to initial screen {}", *initial_screen); - commands.write_message(SwitchToScreenMsg(cid)); - } else { - warn!("Could not find screen with name {initial_screen}"); - } + let info = screens.get_by_name(initial_screen).unwrap(); + commands.write_message(SwitchToScreenMsg(info.screen_id())); } } diff --git a/crates/screens/src/trait_impl.rs b/crates/screens/src/trait_impl.rs index 0c9b5f8..1dc8d76 100644 --- a/crates/screens/src/trait_impl.rs +++ b/crates/screens/src/trait_impl.rs @@ -1,9 +1,5 @@ pub use crate::prelude::*; -/// An empty settings parameter. -#[derive(Resource, Default)] -pub struct NoSettings; - /// How should the screen load its assets? /// If `LoadingStrategy` is Blocking, the screen's systems will not run until /// loading is complete. If it is Nonblocking, the screen's systems will run diff --git a/crates/screens/tests/screens/entity_scope.rs b/crates/screens/tests/screens/entity_scope.rs index efd04b6..73666c5 100644 --- a/crates/screens/tests/screens/entity_scope.rs +++ b/crates/screens/tests/screens/entity_scope.rs @@ -33,7 +33,7 @@ impl Screen for ScopedEntitiesScreen { fn builder(mut builder: ScreenScopeBuilder) -> ScreenScopeBuilder { builder.add_systems( ScreenSchedule::Loading, - |mut commands: Commands, mut data: ScreenDataMut| { + |mut commands: Commands, mut data: ScreenInfoMut| { commands.spawn(( Target::PersistentParent, Propagate(Persistent), diff --git a/crates/screens/tests/screens/lifecycle.rs b/crates/screens/tests/screens/lifecycle.rs index a367129..3cc05a6 100644 --- a/crates/screens/tests/screens/lifecycle.rs +++ b/crates/screens/tests/screens/lifecycle.rs @@ -1,6 +1,3 @@ -use bevy::ecs::component::ComponentIdFor; -use itertools::Itertools; - use crate::prelude::*; macro_rules! gen_fns { @@ -60,7 +57,7 @@ gen_test_fns!(on_screen_load, on_screen_ready, on_screen_unloaded); macro_rules! progress_by { ($name:ident) => { - |mut data: ScreenDataMut| { + |mut data: ScreenInfoMut| { data.$name(); } }; @@ -77,11 +74,13 @@ impl Screen for LifecycleScreen { ( loading, progress_by!(finish_loading), - |query: Query<&ScreenMarker>, - id: ComponentIdFor, + |current_screen: Res, + id: ScreenIdFor, + screens: Screens, mut commands: Commands| { - if !query.iter().contains(&ScreenMarker(id.get())) { - error!("Failed to find LifecycleScreen in component hierarchy!"); + if current_screen.get_id() != Some(*id) { + let s = screens.get_by_id(*id).unwrap(); + error!("while loading, current screen was {}", s.name()); commands.write_message(AppExit::error()); } }, @@ -108,10 +107,10 @@ impl Screen for LifecycleScreen { ScreenSchedule::OnUnloaded, ( unloaded, - |query: Query<&ScreenMarker>, - id: ComponentIdFor, + |current_screen: Res, + id: ScreenIdFor, mut commands: Commands| { - if query.iter().contains(&ScreenMarker(id.get())) { + if current_screen.get_id() == Some(*id) { error!("Found LifecycleScreen in component hierarchy after unload!"); commands.write_message(AppExit::error()); } diff --git a/crates/screens/tests/screens/load_strategy.rs b/crates/screens/tests/screens/load_strategy.rs index 2255755..dfd8051 100644 --- a/crates/screens/tests/screens/load_strategy.rs +++ b/crates/screens/tests/screens/load_strategy.rs @@ -22,7 +22,7 @@ impl Screen for LoadStrategyScreen { } impl LoadStrategyScreen { - fn load(mut count: Local, mut data: ScreenDataMut) { + fn load(mut count: Local, mut data: ScreenInfoMut) { *count += 1; if *count == 100 { data.finish_loading(); @@ -32,7 +32,7 @@ impl LoadStrategyScreen { fn update( mut count: Local, - data: ScreenDataRef, + data: ScreenInfoRef, mut commands: Commands, mut value: ResMut, ) { @@ -43,7 +43,7 @@ impl LoadStrategyScreen { } } - fn unloaded(data: ScreenDataRef, value: Res, mut commands: Commands) { + fn unloaded(data: ScreenInfoRef, value: Res, mut commands: Commands) { let expected_value = match data.data().load_strategy() { LoadStrategy::Nonblocking => 100, LoadStrategy::Blocking => 1, diff --git a/crates/template/src/screen/dev/camera_test/screen.rs b/crates/template/src/screen/dev/camera_test/screen.rs index cec0e13..a9592c6 100644 --- a/crates/template/src/screen/dev/camera_test/screen.rs +++ b/crates/template/src/screen/dev/camera_test/screen.rs @@ -17,7 +17,7 @@ fn init( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, - mut screen: ScreenDataMut, + mut screen: ScreenInfoMut, ) { // spawn everything let cube = meshes.add(Cuboid::default()); diff --git a/crates/template/src/screen/util.rs b/crates/template/src/screen/util.rs index 65fb072..a43b95b 100644 --- a/crates/template/src/screen/util.rs +++ b/crates/template/src/screen/util.rs @@ -9,7 +9,7 @@ pub enum ScreenLoadingState { Unloaded(TypeId), } impl ScreenLoadingState { - fn finish_loading(mut data: ScreenDataMut) { + fn finish_loading(mut data: ScreenInfoMut) { data.finish_loading(); } fn register(app: &mut App) {