Skip to main content

pebble/assets/
plugin.rs

1use crate::{
2    app::App,
3    assets::{deps::Dependencies, handle::Handle, storage::Assets, upload::Asset},
4    ecs::{
5        plugin::Plugin,
6        resources::{Read, Resources, Write},
7        system::SystemStage,
8    },
9};
10
11/// Registers `Assets<T>` and the system that retries `T::upload` each
12/// tick on `SystemStage::AssetSync` until it succeeds. Every built-in asset
13/// type is registered this way; do the same for your own.
14pub struct AssetPlugin<B, T: Asset<B>> {
15    _marker: std::marker::PhantomData<(B, T)>,
16}
17
18impl<B, T: Asset<B>> AssetPlugin<B, T> {
19    pub fn new() -> Self {
20        Self {
21            _marker: std::marker::PhantomData,
22        }
23    }
24}
25
26impl<B, T> Plugin for AssetPlugin<B, T>
27where
28    B: 'static + Send + Sync,
29    T: Asset<B> + 'static,
30{
31    fn build(self, app: App) -> App {
32        app.insert_resource(Assets::<T>::new())
33            .add_system(SystemStage::AssetSync, sync_assets::<B, T>)
34    }
35}
36
37pub(crate) fn sync_assets<B, T>(mut assets: Write<Assets<T>>, backend: Option<Read<B>>, resources: &Resources)
38where
39    B: 'static + Send + Sync,
40    T: Asset<B>,
41{
42    let Some(backend) = backend else {
43        return;
44    };
45    let Some(deps) = T::Deps::try_gather(resources) else {
46        return;
47    };
48
49    let dirty = assets.take_dirty();
50    let mut still_pending = Vec::new();
51
52    for raw in dirty {
53        let handle = Handle::<T>::new(raw);
54        let Some(source) = assets.get_source(handle) else {
55            continue;
56        };
57
58        match source.upload(&backend, &deps) {
59            None => still_pending.push(raw),
60            Some(processed) => assets.set_processed(raw, processed),
61        }
62    }
63
64    assets.requeue(still_pending);
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    use crate::{assets::upload::AssetSource, ecs::schedule::Schedule};
71
72    struct TestBackend(i32);
73
74    struct DepMarker(i32);
75
76    struct TestAsset(i32);
77
78    impl AssetSource for TestAsset {
79        type Processed = i32;
80    }
81
82    impl Asset<TestBackend> for TestAsset {
83        type Deps<'a> = Read<'a, DepMarker>;
84
85        fn upload<'a>(&self, backend: &TestBackend, deps: &Self::Deps<'a>) -> Option<i32> {
86            Some(self.0 * backend.0 * deps.0)
87        }
88    }
89
90    #[test]
91    fn upload_waits_for_backend_and_deps_then_processes() {
92        let mut world = hecs::World::default();
93        let mut resources = Resources::default();
94        resources.insert(hecs::CommandBuffer::default());
95        resources.insert(Assets::<TestAsset>::new());
96
97        let handle = resources.get_mut::<Assets<TestAsset>>().insert("test", TestAsset(3));
98
99        let mut schedule = Schedule::default();
100        schedule.add_system(sync_assets::<TestBackend, TestAsset>);
101
102        schedule.run(&mut world, &mut resources);
103        assert!(!resources.get::<Assets<TestAsset>>().is_ready(handle));
104
105        resources.insert(TestBackend(2));
106        schedule.run(&mut world, &mut resources);
107        assert!(!resources.get::<Assets<TestAsset>>().is_ready(handle), "should still wait on the missing dependency");
108
109        resources.insert(DepMarker(5));
110        schedule.run(&mut world, &mut resources);
111        assert!(resources.get::<Assets<TestAsset>>().is_ready(handle));
112        assert_eq!(*resources.get::<Assets<TestAsset>>().get(handle).unwrap(), 3 * 2 * 5);
113    }
114}