use std::any::TypeId;
use std::collections::HashMap;
use bevy::ecs::intern::Interned;
use bevy::ecs::schedule::{IntoScheduleConfigs, ScheduleLabel, Schedules, SystemSet};
use bevy::ecs::system::ScheduleSystem;
use bevy::ecs::world::World;
use bevy::prelude::{AppTypeRegistry, Component};
use bevy::reflect::GetTypeRegistration;
#[derive(SystemSet, Clone, Eq, PartialEq, Hash, Debug)]
pub struct GameSystems(pub &'static str);
#[derive(Component, Clone, Copy, Debug)]
pub struct GameRegistered(pub &'static str);
#[derive(Default, Debug)]
pub struct GameBookkeeping {
pub schedules: Vec<Interned<dyn ScheduleLabel>>,
pub resources: Vec<TypeId>,
pub reflect_types: Vec<TypeId>,
}
#[derive(Default, Debug, bevy::prelude::Resource)]
pub struct GameRegistry {
games: HashMap<String, GameBookkeeping>,
}
impl GameRegistry {
pub fn entry(&mut self, name: &str) -> &mut GameBookkeeping {
self.games.entry(name.to_owned()).or_default()
}
pub fn take(&mut self, name: &str) -> Option<GameBookkeeping> {
self.games.remove(name)
}
pub fn contains(&self, name: &str) -> bool {
self.games.contains_key(name)
}
}
pub trait GamePlugin: Send + Sync + 'static {
fn build(&self, ctx: &mut GameApp<'_>);
fn teardown(&self, ctx: &mut GameApp<'_>) {
ctx.teardown_tracked();
}
}
pub struct GameApp<'w> {
world: &'w mut World,
name: &'static str,
}
impl<'w> GameApp<'w> {
pub fn new(world: &'w mut World, name: &'static str) -> Self {
Self { world, name }
}
pub fn world_mut(&mut self) -> &mut World {
self.world
}
pub fn add_systems<M>(
&mut self,
schedule: impl ScheduleLabel + Clone,
systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
) -> &mut Self {
let set = GameSystems(self.name);
let configured = systems.in_set(set);
let interned = schedule.clone().intern();
bevy::log::debug!(
"GameApp::add_systems called by `{}` into schedule {:?}",
self.name,
interned
);
self.world
.resource_mut::<Schedules>()
.add_systems(schedule, configured);
let entry = self
.world
.get_resource_or_insert_with::<GameRegistry>(GameRegistry::default)
.into_inner()
.entry(self.name);
if !entry.schedules.contains(&interned) {
entry.schedules.push(interned);
}
self
}
pub fn insert_resource<R: bevy::prelude::Resource>(&mut self, res: R) -> &mut Self {
let id = TypeId::of::<R>();
self.world.insert_resource(res);
let entry = self
.world
.get_resource_or_insert_with::<GameRegistry>(GameRegistry::default)
.into_inner()
.entry(self.name);
if !entry.resources.contains(&id) {
entry.resources.push(id);
}
self
}
pub fn init_resource<R: bevy::prelude::Resource + Default>(&mut self) -> &mut Self {
let id = TypeId::of::<R>();
self.world.init_resource::<R>();
let entry = self
.world
.get_resource_or_insert_with::<GameRegistry>(GameRegistry::default)
.into_inner()
.entry(self.name);
if !entry.resources.contains(&id) {
entry.resources.push(id);
}
self
}
pub fn register_type<T: GetTypeRegistration>(&mut self) -> &mut Self {
let id = TypeId::of::<T>();
if let Some(registry) = self.world.get_resource::<AppTypeRegistry>() {
registry.write().register::<T>();
}
let entry = self
.world
.get_resource_or_insert_with::<GameRegistry>(GameRegistry::default)
.into_inner()
.entry(self.name);
if !entry.reflect_types.contains(&id) {
entry.reflect_types.push(id);
}
self
}
pub fn teardown_tracked(&mut self) {
let bookkeeping = self
.world
.get_resource_mut::<GameRegistry>()
.and_then(|mut r| r.take(self.name));
let Some(book) = bookkeeping else {
return;
};
use bevy::ecs::schedule::{ScheduleCleanupPolicy, Schedules};
let name = self.name;
let schedule_labels: Vec<_> = book.schedules.clone();
self.world
.resource_scope::<Schedules, _>(|world, mut schedules| {
for label in &schedule_labels {
match schedules.remove_systems_in_set(
*label,
GameSystems(name),
world,
ScheduleCleanupPolicy::RemoveSystemsOnly,
) {
Ok(count) => bevy::log::debug!(
"teardown: removed {count} systems from GameSystems({name}) \
in schedule {:?}",
label
),
Err(e) => bevy::log::warn!(
"teardown: remove_systems_in_set for GameSystems({name}) \
in schedule {:?} failed: {e:?}",
label
),
}
}
});
let mut to_despawn = Vec::new();
let mut q = self
.world
.query::<(bevy::prelude::Entity, &GameRegistered)>();
for (entity, tag) in q.iter(self.world) {
if tag.0 == name {
to_despawn.push(entity);
}
}
for e in to_despawn {
if let Ok(ec) = self.world.get_entity_mut(e) {
ec.despawn();
}
}
for id in &book.resources {
if let Some(component_id) = self.world.components().get_resource_id(*id) {
self.world.remove_resource_by_id(component_id);
}
}
let _ = &book.reflect_types;
}
pub fn name(&self) -> &'static str {
self.name
}
}
impl<'w> GameApp<'w> {
pub fn spawn_observer<E, B, M>(
&mut self,
observer: impl IntoObserverSystemBoxed<E, B, M>,
) -> &mut Self
where
E: bevy::prelude::Event,
B: bevy::prelude::Bundle,
{
let tag = GameRegistered(self.name);
let observer = observer.into_boxed_observer();
let id = self.world.spawn_empty().id();
self.world.entity_mut(id).insert(observer);
self.world.entity_mut(id).insert(tag);
self
}
}
pub trait IntoObserverSystemBoxed<E, B, M>: 'static {
fn into_boxed_observer(self) -> bevy::prelude::Observer;
}
impl<E, B, M, S> IntoObserverSystemBoxed<E, B, M> for S
where
E: bevy::prelude::Event,
B: bevy::prelude::Bundle,
S: bevy::ecs::system::IntoObserverSystem<E, B, M> + 'static,
{
fn into_boxed_observer(self) -> bevy::prelude::Observer {
bevy::prelude::Observer::new(self)
}
}