use crate::prelude::*;
use bevy::{
ecs::{
component::ComponentId,
schedule::ScheduleLabel,
system::{ReadOnlySystemParam, SystemParam},
},
platform::collections::HashMap,
};
use std::{any::TypeId, marker::PhantomData};
mod general_api {
use super::*;
#[derive(Event, Debug, PartialEq, Eq, Clone, Deref, Default)]
pub struct SwitchToScreen<S: Screen>(PhantomData<S>);
pub fn switch_to_screen<S: Screen>() -> SwitchToScreen<S> {
SwitchToScreen::<S>::default()
}
#[derive(Message, Debug, PartialEq, Eq, Clone, Deref)]
pub struct SwitchToScreenMsg(pub ComponentId);
#[derive(Event, Debug, PartialEq, Eq, Clone, Deref, Default)]
pub struct FinishLoading<S: Screen>(PhantomData<S>);
pub fn finish_loading<S: Screen>() -> FinishLoading<S> {
FinishLoading::<S>::default()
}
#[derive(Event, Debug, PartialEq, Eq, Clone, Deref, Default)]
pub struct FinishUnloading<S: Screen>(PhantomData<S>);
pub fn finish_unloading<S: Screen>() -> FinishUnloading<S> {
FinishUnloading::<S>::default()
}
#[derive(Component, Debug, Reflect, Clone, Copy, Default, PartialEq)]
pub struct ScreenScoped;
#[derive(Component, Debug, Reflect, Clone, Copy, Default, PartialEq)]
pub struct Persistent;
#[derive(Resource, Default, Debug, Deref)]
pub struct InitialScreen(Option<String>);
impl InitialScreen {
#[allow(missing_docs)]
pub fn new<S: Screen>() -> Self {
Self(Some(S::name()))
}
#[allow(missing_docs)]
pub fn from_name(name: String) -> Self {
Self(Some(name))
}
}
}
pub use general_api::*;
mod screens {
use bevy::ecs::change_detection::Tick;
use super::*;
#[derive(Component, Reflect, PartialEq)]
pub struct ScreenMarker(pub ComponentId);
#[derive(Resource, Debug, Deref, DerefMut, Default)]
pub struct ScreenRegistry(HashMap<ComponentId, ScreenData>);
#[derive(Debug)]
pub struct ScreenData {
name: String,
id: ComponentId,
state: ScreenState,
type_id: TypeId,
pub(crate) needs_update: bool,
pub(crate) changed_at: Tick,
pub(crate) initialized: bool,
load_strategy: LoadStrategy,
skip_load: bool,
skip_unload: bool,
}
impl ScreenData {
#[allow(missing_docs)]
pub fn new<S: Screen>(id: ComponentId, tick: Tick) -> Self {
Self {
name: S::name(),
id,
state: ScreenState::Unloaded,
type_id: TypeId::of::<S>(),
needs_update: true,
skip_load: true,
skip_unload: true,
load_strategy: LoadStrategy::Blocking,
changed_at: tick,
initialized: false,
}
}
pub fn load(&mut self, tick: Tick) {
if matches!(self.state, ScreenState::Unloaded | ScreenState::Unloading) {
if self.skip_load {
self.state = ScreenState::Ready
} else {
self.state = ScreenState::Loading;
}
self.needs_update = true;
self.changed_at = tick;
}
}
pub fn unload(&mut self, tick: Tick) {
if matches!(self.state, ScreenState::Loading | ScreenState::Ready) {
if self.skip_unload {
self.state = ScreenState::Unloaded
} else {
self.state = ScreenState::Unloading;
}
self.needs_update = true;
self.changed_at = tick;
}
}
pub fn finish_loading(&mut self, tick: Tick) {
if matches!(self.state, ScreenState::Loading) {
self.state = ScreenState::Ready;
self.needs_update = true;
self.changed_at = tick;
}
}
pub fn finish_unloading(&mut self, tick: Tick) {
if matches!(self.state, ScreenState::Unloading) {
self.state = ScreenState::Unloaded;
self.needs_update = true;
self.changed_at = tick;
}
}
#[allow(missing_docs)]
pub fn load_strategy(&self) -> LoadStrategy {
self.load_strategy
}
#[allow(missing_docs)]
pub fn skip_load(&self) -> bool {
self.skip_load
}
#[allow(missing_docs)]
pub fn skip_unload(&self) -> bool {
self.skip_unload
}
#[allow(missing_docs)]
pub fn set_skip_unload(&mut self, skip_unload: bool) {
self.skip_unload = skip_unload;
}
#[allow(missing_docs)]
pub fn set_skip_load(&mut self, skip_load: bool) {
self.skip_load = skip_load;
}
#[allow(missing_docs)]
pub fn set_load_strategy(&mut self, load_strategy: LoadStrategy) {
self.load_strategy = load_strategy;
}
#[allow(missing_docs)]
pub fn initialized(&self) -> bool {
self.initialized
}
#[allow(missing_docs)]
pub fn changed_at(&self) -> Tick {
self.changed_at
}
#[allow(missing_docs)]
pub fn needs_update(&self) -> bool {
self.needs_update
}
#[allow(missing_docs)]
pub fn type_id(&self) -> TypeId {
self.type_id
}
#[allow(missing_docs)]
pub fn state(&self) -> ScreenState {
self.state
}
#[allow(missing_docs)]
pub fn id(&self) -> ComponentId {
self.id
}
#[allow(missing_docs)]
pub fn name(&self) -> &str {
&self.name
}
}
}
pub use screens::*;
mod schedules {
use super::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::EnumIter)]
pub enum ScreenSchedule {
Update,
FixedUpdate,
Loading,
Unloading,
OnLoad,
OnReady,
OnUnload,
OnUnloaded,
}
#[derive(ScheduleLabel, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ScreenScheduleLabel {
id: TypeId,
kind: ScreenSchedule,
}
impl ScreenScheduleLabel {
#[allow(missing_docs)]
pub fn new<S: Screen>(kind: ScreenSchedule) -> Self {
Self {
id: TypeId::of::<S>(),
kind,
}
}
#[allow(missing_docs)]
pub fn from_id(kind: ScreenSchedule, id: TypeId) -> Self {
Self { id, kind }
}
}
}
pub use schedules::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum ScreenState {
#[default]
#[allow(missing_docs)]
Unloaded,
#[allow(missing_docs)]
Loading,
#[allow(missing_docs)]
Ready,
#[allow(missing_docs)]
Unloading,
}
mod system_params {
use bevy::ecs::change_detection::Tick;
use super::*;
pub struct ScreenDataRef<'w, S: Screen> {
data: &'w ScreenData,
_ghost: PhantomData<S>,
}
impl<'w, S: Screen> ScreenDataRef<'w, S> {
#[allow(missing_docs)]
pub fn data(&self) -> &'w ScreenData {
self.data
}
}
unsafe impl<'w, S: Screen> SystemParam for ScreenDataRef<'w, S> {
type State = ();
type Item<'world, 'state> = ScreenDataRef<'world, S>;
fn init_state(_world: &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,
) {
component_access_set
.add_unfiltered_resource_read(world.resource_id::<ScreenRegistry>().unwrap());
}
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: bevy::ecs::change_detection::Tick,
) -> Self::Item<'world, 'state> {
let cid = world.components().get_id(TypeId::of::<S>()).unwrap();
let registry = unsafe { world.get_resource::<ScreenRegistry>().unwrap() };
let data = registry.get(&cid).unwrap();
ScreenDataRef {
_ghost: PhantomData,
data,
}
}
}
unsafe impl<'w, S: Screen> ReadOnlySystemParam for ScreenDataRef<'w, S> {}
pub struct ScreenDataMut<'w, S: Screen> {
_ghost: PhantomData<S>,
registry: Mut<'w, ScreenRegistry>,
cid: ComponentId,
change_tick: Tick,
}
impl<'w, S: Screen> ScreenDataMut<'w, S> {
pub fn load(&mut self) {
let tick = self.change_tick;
self.data_mut().load(tick);
}
pub fn unload(&mut self) {
let tick = self.change_tick;
self.data_mut().unload(tick);
}
pub fn finish_loading(&mut self) {
let tick = self.change_tick;
self.data_mut().finish_loading(tick);
}
pub fn finish_unloading(&mut self) {
let tick = self.change_tick;
self.data_mut().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()
}
}
unsafe impl<'w, S: Screen> SystemParam for ScreenDataMut<'w, S> {
type State = ();
type Item<'world, 'state> = ScreenDataMut<'world, S>;
fn init_state(_world: &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,
) {
component_access_set
.add_unfiltered_resource_write(world.resource_id::<ScreenRegistry>().unwrap());
}
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: bevy::ecs::change_detection::Tick,
) -> Self::Item<'world, 'state> {
let registry = unsafe { world.get_resource_mut::<ScreenRegistry>().unwrap() };
let cid = world.components().get_id(TypeId::of::<S>()).unwrap();
Self::Item {
registry,
cid,
_ghost: PhantomData,
change_tick,
}
}
}
}
pub use system_params::*;
mod helpers {
pub use super::*;
pub fn screen_has_state<S: Screen>(
state: ScreenState,
) -> impl FnMut(ScreenDataRef<S>) -> bool + Clone {
move |data: ScreenDataRef<S>| data.data().state() == state
}
pub fn screen_loading<S: Screen>() -> impl FnMut(ScreenDataRef<S>) -> bool + Clone {
|data: ScreenDataRef<S>| matches!(data.data().state(), ScreenState::Loading)
}
pub fn screen_ready<S: Screen>() -> impl FnMut(ScreenDataRef<S>) -> bool + Clone {
|data: ScreenDataRef<S>| matches!(data.data().state(), ScreenState::Ready)
}
pub fn screen_unloading<S: Screen>() -> impl FnMut(ScreenDataRef<S>) -> bool + Clone {
|data: ScreenDataRef<S>| matches!(data.data().state(), ScreenState::Unloading)
}
pub fn screen_unloaded<S: Screen>() -> impl FnMut(ScreenDataRef<S>) -> bool + Clone {
|data: ScreenDataRef<S>| matches!(data.data().state(), ScreenState::Unloaded)
}
#[derive(ScheduleLabel, Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct OnScreenLoad(pub TypeId);
pub fn on_screen_load<S: Screen>() -> impl ScheduleLabel {
OnScreenLoad(TypeId::of::<S>())
}
#[derive(ScheduleLabel, Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct OnScreenReady(pub TypeId);
pub fn on_screen_ready<S: Screen>() -> impl ScheduleLabel {
OnScreenReady(TypeId::of::<S>())
}
#[derive(ScheduleLabel, Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct OnScreenUnload(pub TypeId);
pub fn on_screen_unload<S: Screen>() -> impl ScheduleLabel {
OnScreenUnload(TypeId::of::<S>())
}
#[derive(ScheduleLabel, Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct OnScreenUnloaded(pub TypeId);
pub fn on_screen_unloaded<S: Screen>() -> impl ScheduleLabel {
OnScreenUnloaded(TypeId::of::<S>())
}
}
pub use helpers::*;