use std::{any::type_name, marker::PhantomData, sync::Arc, sync::RwLock, sync::TryLockError};
use anyhow::Result;
use bevy::app::{App, Plugin as BevyPlugin, PostUpdate};
use bevy::asset::{prelude::*, AssetLoader, AsyncReadExt, LoadContext};
use bevy::ecs::{prelude::*, schedule::ScheduleLabel, system::EntityCommands};
use bevy::log::{error, info};
use bevy::reflect::{TypeRegistry, TypeRegistryArc};
use bevy::scene::scene_spawner_system;
use bevy::transform::TransformSystem;
use bevy::utils::get_short_name;
use thiserror::Error;
use crate::{Handles, ParseDsl};
pub use spawn::{Chirp, ChirpState};
mod internal;
mod scene;
pub(super) mod spawn;
#[derive(Debug, Error)]
#[allow(missing_docs)] pub enum AddError {
#[error("Failed to set function '{0}' in chirp handle registry: Lock poisoned")]
Poisoned(String),
#[error("Failed to set function '{0}' in chirp handle registry: Lock already taken")]
WouldBlock(String),
}
#[derive(Bundle)]
pub struct ChirpBundle {
pub state: ChirpState,
pub scene: Handle<Chirp>,
}
impl ChirpBundle {
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn new(scene: Handle<Chirp>) -> Self {
Self { state: ChirpState::Loading, scene }
}
}
impl From<Handle<Chirp>> for ChirpBundle {
fn from(value: Handle<Chirp>) -> Self {
Self::new(value)
}
}
#[derive(Resource)]
pub struct WorldHandles<D>(pub(crate) HandlesArc, PhantomData<fn(D)>);
type HandlesArc = Arc<RwLock<Handles>>;
impl<D> WorldHandles<D> {
pub fn add_function(
&mut self,
name: String,
function: impl Fn(&TypeRegistry, Option<&LoadContext>, &mut EntityCommands)
+ Send
+ Sync
+ 'static,
) -> Result<(), AddError> {
let mut handles = self.0.try_write().map_err(|err| match err {
TryLockError::Poisoned(_) => AddError::Poisoned(name.clone()),
TryLockError::WouldBlock => AddError::WouldBlock(name.clone()),
})?;
handles.add_function(name, function);
drop(handles);
Ok(())
}
}
pub struct ChirpLoader<D> {
registry: TypeRegistryArc,
handles: HandlesArc,
_dsl: PhantomData<fn(D)>,
}
impl<D: 'static> FromWorld for ChirpLoader<D> {
fn from_world(world: &mut World) -> Self {
let registry = world.resource::<AppTypeRegistry>().0.clone();
let handles = HandlesArc::default();
world.insert_resource(WorldHandles::<D>(Arc::clone(&handles), PhantomData));
Self { registry, handles, _dsl: PhantomData }
}
}
impl<D: ParseDsl + 'static> AssetLoader for ChirpLoader<D> {
type Asset = Chirp;
type Settings = ();
type Error = std::io::Error;
fn load<'a>(
&'a self,
reader: &'a mut bevy::asset::io::Reader,
_: &'a Self::Settings,
load_context: &'a mut LoadContext,
) -> bevy::utils::BoxedFuture<'a, Result<Self::Asset, std::io::Error>> {
Box::pin(async move {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let registry = self.registry.internal.read().unwrap();
let Ok(handles) = self.handles.as_ref().read() else {
let name = get_short_name(type_name::<D>());
error!("Can't read handles in ChirpLoader<{name}>");
return Ok(Chirp(spawn::Chirp_::LoadError));
};
let chirp = internal::Loader::<D>::new(load_context, ®istry, &handles).load(&bytes);
drop(registry);
let path = load_context.path().to_string_lossy();
info!("Complete loading of chirp: {path}");
Ok(Chirp(chirp))
})
}
fn extensions(&self) -> &[&str] {
&["chirp"]
}
}
pub struct Plugin<D>(PhantomData<fn(D)>);
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct SpawnChirp;
impl Plugin<()> {
#[must_use]
pub fn new<D: ParseDsl + 'static>() -> Plugin<D> {
Plugin(PhantomData)
}
}
impl<D: ParseDsl + 'static> BevyPlugin for Plugin<D> {
fn build(&self, app: &mut App) {
let chirp_asset_systems = (
spawn::update_asset_changed,
spawn::manage_chirp_state,
spawn::spawn_chirps::<D>,
)
.chain()
.after(scene_spawner_system);
let chirp_asset_systems = chirp_asset_systems.before(TransformSystem::TransformPropagate);
#[cfg(feature = "bevy/bevy_ui")]
let chirp_asset_systems = chirp_asset_systems
.before(bevy::ui::UiSystem::Layout)
.before(bevy::ui::UiSystem::Focus)
.before(bevy::ui::UiSystem::Stack);
app.add_systems(PostUpdate, chirp_asset_systems);
app.init_asset::<Chirp>()
.register_type::<ChirpState>()
.init_asset_loader::<ChirpLoader<D>>();
}
}