use crate::{
app::SystemStage,
assets::deps::Dependencies,
ecs::plugin::Plugin,
ecs::resources::Resources,
ecs::system::{Commands, Local, Res},
};
pub trait LazyResource<B>: 'static + Send + Sync + Sized {
type Deps<'a>: Dependencies<'a>;
fn construct<'a>(backend: &B, deps: &Self::Deps<'a>) -> Option<Self>;
}
pub struct LazyResourcePlugin<B, T: LazyResource<B>> {
_marker: std::marker::PhantomData<(B, T)>,
}
impl<B, T: LazyResource<B>> LazyResourcePlugin<B, T> {
pub fn new() -> Self {
Self {
_marker: std::marker::PhantomData,
}
}
}
impl<B, T> Plugin for LazyResourcePlugin<B, T>
where
B: 'static + Send + Sync,
T: LazyResource<B>,
{
fn build(&self, app: &mut crate::prelude::App) {
app.provides::<T>();
app.add_system(SystemStage::AssetSyncDeps, construct_resource::<B, T>);
}
}
const STUCK_AFTER_TICKS: u32 = 300;
fn construct_resource<B, T>(
mut commands: Commands,
backend: Option<Res<B>>,
existing: Option<Res<T>>,
mut waiting_ticks: Local<u32>,
world: &hecs::World,
resources: &Resources,
) where
B: 'static + Send + Sync,
T: LazyResource<B>,
{
if existing.is_some() {
return;
}
let Some(backend) = backend else {
warn_if_stuck::<T>("waiting on backend to construct resource", &mut waiting_ticks);
return;
};
let Some(deps) = T::Deps::try_gather(world, resources) else {
warn_if_stuck::<T>("waiting on dependencies to construct resource", &mut waiting_ticks);
return;
};
match T::construct(&backend, &deps) {
Some(value) => {
commands.insert_resource(value);
}
None => {
warn_if_stuck::<T>("construct() returned None, will retry next tick", &mut waiting_ticks);
}
}
}
fn warn_if_stuck<T: 'static>(reason: &str, waiting_ticks: &mut u32) {
*waiting_ticks += 1;
if *waiting_ticks >= STUCK_AFTER_TICKS && waiting_ticks.is_multiple_of(STUCK_AFTER_TICKS) {
tracing::warn!(
"{}: still not constructed after {} ticks ({reason}) — if this is waiting on a \
resource that's never actually going to appear, it will wait forever.",
std::any::type_name::<T>(),
*waiting_ticks
);
} else {
tracing::trace!("{}: {reason}", std::any::type_name::<T>());
}
}