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
11pub 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}