1use crate::{
2 app::SystemStage,
3 assets::{
4 deps::Dependencies,
5 storage::{Assets, ProcessedAssets},
6 upload::Asset,
7 },
8 ecs::{
9 plugin::Plugin,
10 resources::Resources,
11 system::{Res, ResMut},
12 },
13};
14
15pub struct AssetPlugin<B, T: Asset<B>> {
32 _marker: std::marker::PhantomData<(B, T)>,
33}
34
35impl<B, T: Asset<B>> AssetPlugin<B, T> {
36 pub fn new() -> Self {
37 Self {
38 _marker: std::marker::PhantomData,
39 }
40 }
41}
42
43impl<B, T> Plugin for AssetPlugin<B, T>
44where
45 B: 'static + Send + Sync,
46 T: Asset<B>,
47{
48 fn build(&self, app: &mut crate::app::App) {
49 app.try_insert_resource(Assets::<T::Source>::new());
50 app.try_insert_resource(ProcessedAssets::<T>::new());
51 app.add_system(SystemStage::AssetSync, sync_assets::<B, T>);
52 app.required.provides::<ProcessedAssets<T>>();
53 }
54}
55
56fn sync_assets<B, T>(
62 mut cpu: ResMut<Assets<T::Source>>,
63 mut processed: ResMut<ProcessedAssets<T>>,
64 backend: Option<Res<B>>,
65 world: &hecs::World,
66 resources: &Resources,
67) where
68 B: 'static + Send + Sync,
69 T: Asset<B>,
70{
71 let Some(backend) = backend else {
72 log_waiting::<B, T>(&cpu, "backend");
73 return;
74 };
75 let Some(deps) = T::Deps::try_gather(world, resources) else {
76 log_waiting::<B, T>(&cpu, "dependencies");
77 return;
78 };
79
80 for handle in cpu.take_removed() {
81 processed.remove(handle);
82 }
83
84 let mut still_pending = Vec::new();
85
86 for handle in cpu.take_dirty() {
87 let Some(source) = cpu.get(handle) else {
88 tracing::debug!(
90 "{}: handle {:?} was in the dirty queue but the source asset is already gone \
91 (inserted and removed in the same tick?)",
92 std::any::type_name::<T>(),
93 handle
94 );
95 continue;
96 };
97 match T::upload(source, &backend, &deps) {
98 Some(value) => {
99 if let Some(name) = cpu.name_for_handle(handle) {
100 processed.names.insert(name.to_string(), handle);
101 }
102 tracing::debug!(
103 "{}: uploaded {:?}{}",
104 std::any::type_name::<T>(),
105 handle,
106 cpu.name_for_handle(handle)
107 .map(|n| format!(" ({n})"))
108 .unwrap_or_default()
109 );
110 processed.insert(handle, value);
111 }
112 None => {
113 tracing::debug!(
114 "{}: {:?} upload returned None — a required dependency is not yet ready, \
115 requeued for next tick",
116 std::any::type_name::<T>(),
117 handle
118 );
119 still_pending.push(handle);
120 }
121 }
122 }
123
124 if !still_pending.is_empty() {
125 tracing::debug!(
126 "{}: {} handle(s) still pending upload (waiting on dependencies)",
127 std::any::type_name::<T>(),
128 still_pending.len()
129 );
130 }
131
132 cpu.requeue(still_pending);
133}
134
135fn log_waiting<D, T>(cpu: &Assets<T::Source>, what: &str)
136where
137 D: 'static + Send + Sync,
138 T: Asset<D>,
139{
140 if !cpu.dirty_is_empty() {
141 tracing::debug!(
142 "{}: {} asset(s) queued but waiting on {what} before upload can begin",
143 std::any::type_name::<T>(),
144 cpu.dirty_len()
145 );
146 }
147}