1use std::collections::HashMap;
2
3use crate::{
4 app::SystemStage,
5 assets::{
6 deps::Dependencies,
7 storage::{Assets, RawAssetHandle},
8 upload::Asset,
9 },
10 ecs::{
11 plugin::Plugin,
12 resources::Resources,
13 system::{Local, Res, ResMut},
14 },
15};
16
17const STUCK_AFTER_TICKS: u32 = 300;
24
25fn should_warn_stuck(ticks: u32) -> bool {
26 ticks >= STUCK_AFTER_TICKS && ticks.is_multiple_of(STUCK_AFTER_TICKS)
27}
28
29pub struct AssetPlugin<B, T: Asset<B>> {
43 _marker: std::marker::PhantomData<(B, T)>,
44}
45
46impl<B, T: Asset<B>> AssetPlugin<B, T> {
47 pub fn new() -> Self {
48 Self { _marker: std::marker::PhantomData }
49 }
50}
51
52impl<B, T> Plugin for AssetPlugin<B, T>
53where
54 B: 'static + Send + Sync,
55 T: Asset<B>,
56{
57 fn build(&self, app: &mut crate::app::App) {
58 app.try_insert_resource(Assets::<T>::new());
59 app.add_system(SystemStage::AssetSync, sync_assets::<B, T>);
60 }
61}
62
63fn sync_assets<B, T>(
69 mut assets: ResMut<Assets<T>>,
70 backend: Option<Res<B>>,
71 mut blocked_ticks: Local<u32>,
72 mut pending_ticks: Local<HashMap<RawAssetHandle, u32>>,
73 world: &hecs::World,
74 resources: &Resources,
75) where
76 B: 'static + Send + Sync,
77 T: Asset<B>,
78{
79 let Some(backend) = backend else {
80 log_waiting::<B, T>(&assets, "backend", &mut blocked_ticks);
81 return;
82 };
83 let Some(deps) = T::Deps::try_gather(world, resources) else {
84 log_waiting::<B, T>(&assets, "dependencies", &mut blocked_ticks);
85 return;
86 };
87 *blocked_ticks = 0;
88
89 for handle in assets.take_removed() {
90 pending_ticks.remove(&handle);
91 }
92
93 let dirty = assets.take_dirty();
94 let mut still_pending = Vec::new();
95
96 let results: Vec<(RawAssetHandle, Option<String>, Option<Option<T::Processed>>)> = dirty
101 .iter()
102 .map(|&handle| match assets.get_source_quiet(handle) {
103 None => (handle, None, None),
104 Some(source) => {
105 let name = assets.name_for_handle(handle).map(String::from);
106 let outcome = source.upload(&backend, &deps);
107 (handle, name, Some(outcome))
108 }
109 })
110 .collect();
111
112 for (handle, name, outcome) in results {
114 let label = name.as_deref().map(|n| format!(" ({n})")).unwrap_or_default();
115 match outcome {
116 None => {
117 tracing::debug!(
118 "{}: handle {:?} was in the dirty queue but the source asset is already \
119 gone (inserted and removed in the same tick?)",
120 std::any::type_name::<T>(),
121 handle
122 );
123 pending_ticks.remove(&handle);
124 }
125 Some(None) => {
126 let ticks = pending_ticks.entry(handle).or_insert(0);
127 *ticks += 1;
128 if should_warn_stuck(*ticks) {
129 tracing::warn!(
130 "{}: {:?}{} has not uploaded after {} ticks — upload() may be \
131 unconditionally returning None, or a Deps resource it needs is never \
132 actually going to appear. Still retrying every tick.",
133 std::any::type_name::<T>(),
134 handle,
135 label,
136 *ticks
137 );
138 } else {
139 tracing::debug!(
140 "{}: {:?}{} upload returned None — a required dependency is not yet \
141 ready, requeued for next tick",
142 std::any::type_name::<T>(),
143 handle,
144 label,
145 );
146 }
147 still_pending.push(handle);
148 }
149 Some(Some(value)) => {
150 assets.set_processed(handle, value);
151 pending_ticks.remove(&handle);
152 tracing::debug!(
153 "{}: uploaded {:?}{}",
154 std::any::type_name::<T>(),
155 handle,
156 label
157 );
158 }
159 }
160 }
161
162 if !still_pending.is_empty() {
163 tracing::debug!(
164 "{}: {} handle(s) still pending upload (waiting on dependencies)",
165 std::any::type_name::<T>(),
166 still_pending.len()
167 );
168 }
169
170 assets.requeue(still_pending);
171}
172
173fn log_waiting<B, T>(assets: &Assets<T>, what: &str, blocked_ticks: &mut u32)
174where
175 B: 'static + Send + Sync,
176 T: Asset<B>,
177{
178 if assets.dirty_is_empty() {
179 *blocked_ticks = 0;
180 return;
181 }
182
183 *blocked_ticks += 1;
184 if should_warn_stuck(*blocked_ticks) {
185 tracing::warn!(
186 "{}: {} asset(s) have been queued for {} ticks, still waiting on {what} before \
187 upload can begin — if {what} is never going to appear, this pipeline will wait \
188 forever.",
189 std::any::type_name::<T>(),
190 assets.dirty_len(),
191 *blocked_ticks,
192 );
193 } else {
194 tracing::debug!(
195 "{}: {} asset(s) queued but waiting on {what} before upload can begin",
196 std::any::type_name::<T>(),
197 assets.dirty_len()
198 );
199 }
200}