1use crate::{ExtractSchedule, MainWorld, Render, RenderApp, RenderSet};
2use bevy_app::{App, Plugin, SubApp};
3use bevy_asset::{Asset, AssetEvent, AssetId, Assets};
4use bevy_ecs::{
5 prelude::{Commands, EventReader, IntoSystemConfigs, ResMut, Resource},
6 schedule::SystemConfigs,
7 system::{StaticSystemParam, SystemParam, SystemParamItem, SystemState},
8 world::{FromWorld, Mut},
9};
10use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
11use bevy_render_macros::ExtractResource;
12use bevy_utils::{tracing::debug, HashMap, HashSet};
13use serde::{Deserialize, Serialize};
14use std::marker::PhantomData;
15use thiserror::Error;
16
17#[derive(Debug, Error)]
18pub enum PrepareAssetError<E: Send + Sync + 'static> {
19 #[error("Failed to prepare asset")]
20 RetryNextUpdate(E),
21}
22
23pub trait RenderAsset: Send + Sync + 'static + Sized {
31 type SourceAsset: Asset + Clone;
33
34 type Param: SystemParam;
38
39 #[inline]
41 fn asset_usage(_source_asset: &Self::SourceAsset) -> RenderAssetUsages {
42 RenderAssetUsages::default()
43 }
44
45 #[inline]
48 #[allow(unused_variables)]
49 fn byte_len(source_asset: &Self::SourceAsset) -> Option<usize> {
50 None
51 }
52
53 fn prepare_asset(
57 source_asset: Self::SourceAsset,
58 param: &mut SystemParamItem<Self::Param>,
59 ) -> Result<Self, PrepareAssetError<Self::SourceAsset>>;
60}
61
62bitflags::bitflags! {
63 #[repr(transparent)]
86 #[derive(Serialize, Deserialize, Hash, Clone, Copy, PartialEq, Eq, Debug, Reflect)]
87 #[reflect_value(Serialize, Deserialize, Hash, PartialEq, Debug)]
88 pub struct RenderAssetUsages: u8 {
89 const MAIN_WORLD = 1 << 0;
90 const RENDER_WORLD = 1 << 1;
91 }
92}
93
94impl Default for RenderAssetUsages {
95 fn default() -> Self {
104 RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD
105 }
106}
107
108pub struct RenderAssetPlugin<A: RenderAsset, AFTER: RenderAssetDependency + 'static = ()> {
119 phantom: PhantomData<fn() -> (A, AFTER)>,
120}
121
122impl<A: RenderAsset, AFTER: RenderAssetDependency + 'static> Default
123 for RenderAssetPlugin<A, AFTER>
124{
125 fn default() -> Self {
126 Self {
127 phantom: Default::default(),
128 }
129 }
130}
131
132impl<A: RenderAsset, AFTER: RenderAssetDependency + 'static> Plugin
133 for RenderAssetPlugin<A, AFTER>
134{
135 fn build(&self, app: &mut App) {
136 app.init_resource::<CachedExtractRenderAssetSystemState<A>>();
137 if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
138 render_app
139 .init_resource::<ExtractedAssets<A>>()
140 .init_resource::<RenderAssets<A>>()
141 .init_resource::<PrepareNextFrameAssets<A>>()
142 .add_systems(ExtractSchedule, extract_render_asset::<A>);
143 AFTER::register_system(
144 render_app,
145 prepare_assets::<A>.in_set(RenderSet::PrepareAssets),
146 );
147 }
148 }
149}
150
151pub trait RenderAssetDependency {
153 fn register_system(render_app: &mut SubApp, system: SystemConfigs);
154}
155
156impl RenderAssetDependency for () {
157 fn register_system(render_app: &mut SubApp, system: SystemConfigs) {
158 render_app.add_systems(Render, system);
159 }
160}
161
162impl<A: RenderAsset> RenderAssetDependency for A {
163 fn register_system(render_app: &mut SubApp, system: SystemConfigs) {
164 render_app.add_systems(Render, system.after(prepare_assets::<A>));
165 }
166}
167
168#[derive(Resource)]
170pub struct ExtractedAssets<A: RenderAsset> {
171 extracted: Vec<(AssetId<A::SourceAsset>, A::SourceAsset)>,
172 removed: HashSet<AssetId<A::SourceAsset>>,
173 added: HashSet<AssetId<A::SourceAsset>>,
174}
175
176impl<A: RenderAsset> Default for ExtractedAssets<A> {
177 fn default() -> Self {
178 Self {
179 extracted: Default::default(),
180 removed: Default::default(),
181 added: Default::default(),
182 }
183 }
184}
185
186#[derive(Resource)]
189pub struct RenderAssets<A: RenderAsset>(HashMap<AssetId<A::SourceAsset>, A>);
190
191impl<A: RenderAsset> Default for RenderAssets<A> {
192 fn default() -> Self {
193 Self(Default::default())
194 }
195}
196
197impl<A: RenderAsset> RenderAssets<A> {
198 pub fn get(&self, id: impl Into<AssetId<A::SourceAsset>>) -> Option<&A> {
199 self.0.get(&id.into())
200 }
201
202 pub fn get_mut(&mut self, id: impl Into<AssetId<A::SourceAsset>>) -> Option<&mut A> {
203 self.0.get_mut(&id.into())
204 }
205
206 pub fn insert(&mut self, id: impl Into<AssetId<A::SourceAsset>>, value: A) -> Option<A> {
207 self.0.insert(id.into(), value)
208 }
209
210 pub fn remove(&mut self, id: impl Into<AssetId<A::SourceAsset>>) -> Option<A> {
211 self.0.remove(&id.into())
212 }
213
214 pub fn iter(&self) -> impl Iterator<Item = (AssetId<A::SourceAsset>, &A)> {
215 self.0.iter().map(|(k, v)| (*k, v))
216 }
217
218 pub fn iter_mut(&mut self) -> impl Iterator<Item = (AssetId<A::SourceAsset>, &mut A)> {
219 self.0.iter_mut().map(|(k, v)| (*k, v))
220 }
221}
222
223#[derive(Resource)]
224struct CachedExtractRenderAssetSystemState<A: RenderAsset> {
225 state: SystemState<(
226 EventReader<'static, 'static, AssetEvent<A::SourceAsset>>,
227 ResMut<'static, Assets<A::SourceAsset>>,
228 )>,
229}
230
231impl<A: RenderAsset> FromWorld for CachedExtractRenderAssetSystemState<A> {
232 fn from_world(world: &mut bevy_ecs::world::World) -> Self {
233 Self {
234 state: SystemState::new(world),
235 }
236 }
237}
238
239fn extract_render_asset<A: RenderAsset>(mut commands: Commands, mut main_world: ResMut<MainWorld>) {
242 main_world.resource_scope(
243 |world, mut cached_state: Mut<CachedExtractRenderAssetSystemState<A>>| {
244 let (mut events, mut assets) = cached_state.state.get_mut(world);
245
246 let mut changed_assets = HashSet::default();
247 let mut removed = HashSet::default();
248
249 for event in events.read() {
250 #[allow(clippy::match_same_arms)]
251 match event {
252 AssetEvent::Added { id } | AssetEvent::Modified { id } => {
253 changed_assets.insert(*id);
254 }
255 AssetEvent::Removed { .. } => {}
256 AssetEvent::Unused { id } => {
257 changed_assets.remove(id);
258 removed.insert(*id);
259 }
260 AssetEvent::LoadedWithDependencies { .. } => {
261 }
263 }
264 }
265
266 let mut extracted_assets = Vec::new();
267 let mut added = HashSet::new();
268 for id in changed_assets.drain() {
269 if let Some(asset) = assets.get(id) {
270 let asset_usage = A::asset_usage(asset);
271 if asset_usage.contains(RenderAssetUsages::RENDER_WORLD) {
272 if asset_usage == RenderAssetUsages::RENDER_WORLD {
273 if let Some(asset) = assets.remove(id) {
274 extracted_assets.push((id, asset));
275 added.insert(id);
276 }
277 } else {
278 extracted_assets.push((id, asset.clone()));
279 added.insert(id);
280 }
281 }
282 }
283 }
284
285 commands.insert_resource(ExtractedAssets::<A> {
286 extracted: extracted_assets,
287 removed,
288 added,
289 });
290 cached_state.state.apply(world);
291 },
292 );
293}
294
295#[derive(Resource)]
298pub struct PrepareNextFrameAssets<A: RenderAsset> {
299 assets: Vec<(AssetId<A::SourceAsset>, A::SourceAsset)>,
300}
301
302impl<A: RenderAsset> Default for PrepareNextFrameAssets<A> {
303 fn default() -> Self {
304 Self {
305 assets: Default::default(),
306 }
307 }
308}
309
310pub fn prepare_assets<A: RenderAsset>(
313 mut extracted_assets: ResMut<ExtractedAssets<A>>,
314 mut render_assets: ResMut<RenderAssets<A>>,
315 mut prepare_next_frame: ResMut<PrepareNextFrameAssets<A>>,
316 param: StaticSystemParam<<A as RenderAsset>::Param>,
317 mut bpf: ResMut<RenderAssetBytesPerFrame>,
318) {
319 let mut wrote_asset_count = 0;
320
321 let mut param = param.into_inner();
322 let queued_assets = std::mem::take(&mut prepare_next_frame.assets);
323 for (id, extracted_asset) in queued_assets {
324 if extracted_assets.removed.contains(&id) || extracted_assets.added.contains(&id) {
325 continue;
327 }
328
329 let write_bytes = if let Some(size) = A::byte_len(&extracted_asset) {
330 if bpf.exhausted() {
335 prepare_next_frame.assets.push((id, extracted_asset));
336 continue;
337 }
338 size
339 } else {
340 0
341 };
342
343 match A::prepare_asset(extracted_asset, &mut param) {
344 Ok(prepared_asset) => {
345 render_assets.insert(id, prepared_asset);
346 bpf.write_bytes(write_bytes);
347 wrote_asset_count += 1;
348 }
349 Err(PrepareAssetError::RetryNextUpdate(extracted_asset)) => {
350 prepare_next_frame.assets.push((id, extracted_asset));
351 }
352 }
353 }
354
355 for removed in extracted_assets.removed.drain() {
356 render_assets.remove(removed);
357 }
358
359 for (id, extracted_asset) in extracted_assets.extracted.drain(..) {
360 render_assets.remove(id);
364
365 let write_bytes = if let Some(size) = A::byte_len(&extracted_asset) {
366 if bpf.exhausted() {
367 prepare_next_frame.assets.push((id, extracted_asset));
368 continue;
369 }
370 size
371 } else {
372 0
373 };
374
375 match A::prepare_asset(extracted_asset, &mut param) {
376 Ok(prepared_asset) => {
377 render_assets.insert(id, prepared_asset);
378 bpf.write_bytes(write_bytes);
379 wrote_asset_count += 1;
380 }
381 Err(PrepareAssetError::RetryNextUpdate(extracted_asset)) => {
382 prepare_next_frame.assets.push((id, extracted_asset));
383 }
384 }
385 }
386
387 if bpf.exhausted() && !prepare_next_frame.assets.is_empty() {
388 debug!(
389 "{} write budget exhausted with {} assets remaining (wrote {})",
390 std::any::type_name::<A>(),
391 prepare_next_frame.assets.len(),
392 wrote_asset_count
393 );
394 }
395}
396
397#[derive(Resource, Default, Debug, Clone, Copy, ExtractResource)]
401pub struct RenderAssetBytesPerFrame {
402 pub max_bytes: Option<usize>,
403 pub available: usize,
404}
405
406impl RenderAssetBytesPerFrame {
407 pub fn new(max_bytes: usize) -> Self {
413 Self {
414 max_bytes: Some(max_bytes),
415 available: 0,
416 }
417 }
418
419 pub fn reset(&mut self) {
421 self.available = self.max_bytes.unwrap_or(usize::MAX);
422 }
423
424 pub fn available_bytes(&self, required_bytes: usize) -> usize {
426 if self.max_bytes.is_none() {
427 return required_bytes;
428 }
429
430 required_bytes.min(self.available)
431 }
432
433 fn write_bytes(&mut self, bytes: usize) {
435 if self.max_bytes.is_none() {
436 return;
437 }
438
439 let write_bytes = bytes.min(self.available);
440 self.available -= write_bytes;
441 }
442
443 fn exhausted(&self) -> bool {
445 self.max_bytes.is_some() && self.available == 0
446 }
447}