use std::any::type_name;
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::Arc;
use crate::access::AsyncWorld;
use crate::executor::{with_world_mut, ASSET_SERVER};
use crate::sync::oneshot::MaybeChannelOut;
use crate::{AccessError, AccessResult};
use bevy::asset::meta::Settings;
use bevy::asset::{Asset, AssetId, AssetPath, AssetServer, Assets, Handle, LoadState};
use bevy::ecs::world::World;
use event_listener::Event;
use futures::future::{ready, Either};
#[derive(Debug, Default)]
pub struct AssetBarrierInner {
pub count: AtomicI32,
pub notify: Event,
}
#[derive(Debug, Default)]
pub struct AssetBarrierGuard(Arc<AssetBarrierInner>);
impl Clone for AssetBarrierGuard {
fn clone(&self) -> Self {
self.0.count.fetch_add(1, Ordering::AcqRel);
Self(self.0.clone())
}
}
impl Drop for AssetBarrierGuard {
fn drop(&mut self) {
let prev = self.0.count.fetch_sub(1, Ordering::AcqRel);
if prev <= 1 {
self.0.notify.notify(usize::MAX);
}
}
}
#[derive(Debug, Default)]
pub struct AssetSet(Arc<AssetBarrierInner>);
impl AssetSet {
pub fn new(&self) -> AssetSet {
AssetSet::default()
}
pub fn load<A: Asset>(&self, path: impl Into<AssetPath<'static>>) -> Handle<A> {
if !ASSET_SERVER.is_set() {
panic!("AssetServer does not exist.")
}
self.0.count.fetch_add(1, Ordering::AcqRel);
ASSET_SERVER.with(|s| s.load_acquire::<A, _>(path, AssetBarrierGuard(self.0.clone())))
}
pub async fn wait(&self) {
loop {
if self.0.count.load(Ordering::Acquire) == 0 {
return;
}
self.0.notify.listen().await;
}
}
}
#[derive(Debug)]
pub enum AsyncAsset<A: Asset> {
Strong(Handle<A>),
Weak(AssetId<A>),
}
impl<A: Asset> Clone for AsyncAsset<A> {
fn clone(&self) -> Self {
match self {
AsyncAsset::Strong(handle) => AsyncAsset::Strong(handle.clone()),
AsyncAsset::Weak(asset_id) => AsyncAsset::Weak(*asset_id),
}
}
}
impl<A: Asset> From<Handle<A>> for AsyncAsset<A> {
fn from(value: Handle<A>) -> Self {
AsyncAsset::Strong(value)
}
}
impl<A: Asset> From<AssetId<A>> for AsyncAsset<A> {
fn from(value: AssetId<A>) -> Self {
AsyncAsset::Weak(value)
}
}
impl<A: Asset> From<&AsyncAsset<A>> for AssetId<A> {
fn from(val: &AsyncAsset<A>) -> Self {
val.id()
}
}
impl AsyncWorld {
pub fn load_asset<A: Asset>(
&self,
path: impl Into<AssetPath<'static>> + Send + 'static,
) -> AsyncAsset<A> {
if !ASSET_SERVER.is_set() {
panic!("AssetServer does not exist.")
}
AsyncAsset::Strong(ASSET_SERVER.with(|s| s.load::<A>(path)))
}
pub fn load_asset_with_settings<A: Asset, S: Settings>(
&self,
path: impl Into<AssetPath<'static>> + Send + 'static,
f: impl Fn(&mut S) + Send + Sync + 'static,
) -> AsyncAsset<A> {
if !ASSET_SERVER.is_set() {
panic!("AssetServer does not exist.")
}
AsyncAsset::Strong(ASSET_SERVER.with(|s| s.load_with_settings::<A, S>(path, f)))
}
pub fn add_asset<A: Asset + 'static>(&self, item: A) -> AccessResult<Handle<A>> {
with_world_mut(|w| {
Ok(w.get_resource_mut::<Assets<A>>()
.ok_or(AccessError::ResourceNotFound {
name: type_name::<Assets<A>>(),
})?
.add(item))
})
}
}
impl<A: Asset> AsyncAsset<A> {
pub fn new_weak(handle: impl Into<AssetId<A>>) -> AsyncAsset<A> {
AsyncAsset::Weak(handle.into())
}
pub fn id(&self) -> AssetId<A> {
match self {
AsyncAsset::Strong(handle) => handle.id(),
AsyncAsset::Weak(id) => *id,
}
}
pub fn clone_weak(&self) -> Self {
Self::Weak(self.id())
}
pub fn try_into_handle(self) -> Option<Handle<A>> {
match self {
AsyncAsset::Strong(handle) => Some(handle),
AsyncAsset::Weak(_) => None,
}
}
pub fn loaded(&self) -> MaybeChannelOut<bool> {
if !ASSET_SERVER.is_set() {
panic!("AssetServer does not exist.")
}
let id = self.id();
match ASSET_SERVER.with(|server| server.load_state(id)) {
LoadState::Loaded => return Either::Right(ready(true)),
LoadState::Failed(..) => return Either::Right(ready(false)),
_ => (),
};
AsyncWorld.watch_left(move |world: &mut World| {
match world.resource::<AssetServer>().load_state(id) {
LoadState::Loaded => Some(true),
LoadState::Failed(..) => Some(false),
_ => None,
}
})
}
}