use crate::{Extract, ExtractSchedule, Render, RenderApp, RenderSet}; use bevy_app::{App, Plugin}; use bevy_asset::{Asset, AssetEvent, AssetId, Assets}; use bevy_ecs::{ prelude::*, schedule::SystemConfigs, system::{StaticSystemParam, SystemParam, SystemParamItem}, }; use bevy_utils::{thiserror::Error, HashMap, HashSet}; use std::marker::PhantomData; #[derive(Debug, Error)] pub enum PrepareAssetError { #[error("Failed to prepare asset")] RetryNextUpdate(E), } /// Describes how an asset gets extracted and prepared for rendering. /// /// In the [`ExtractSchedule`] step the asset is transferred /// from the "main world" into the "render world". /// Therefore it is converted into a [`RenderAsset::ExtractedAsset`], which may be the same type /// as the render asset itself. /// /// After that in the [`RenderSet::PrepareAssets`] step the extracted asset /// is transformed into its GPU-representation of type [`RenderAsset::PreparedAsset`]. pub trait RenderAsset: Asset { /// The representation of the asset in the "render world". type ExtractedAsset: Send + Sync + 'static; /// The GPU-representation of the asset. type PreparedAsset: Send + Sync + 'static; /// Specifies all ECS data required by [`RenderAsset::prepare_asset`]. /// For convenience use the [`lifetimeless`](bevy_ecs::system::lifetimeless) [`SystemParam`]. type Param: SystemParam; /// Converts the asset into a [`RenderAsset::ExtractedAsset`]. fn extract_asset(&self) -> Self::ExtractedAsset; /// Prepares the `extracted asset` for the GPU by transforming it into /// a [`RenderAsset::PreparedAsset`]. Therefore ECS data may be accessed via the `param`. fn prepare_asset( extracted_asset: Self::ExtractedAsset, param: &mut SystemParamItem, ) -> Result>; } /// This plugin extracts the changed assets from the "app world" into the "render world" /// and prepares them for the GPU. They can then be accessed from the [`RenderAssets`] resource. /// /// Therefore it sets up the [`ExtractSchedule`] and /// [`RenderSet::PrepareAssets`] steps for the specified [`RenderAsset`]. /// /// The `AFTER` generic parameter can be used to specify that `A::prepare_asset` should not be run until /// `prepare_assets::` has completed. This allows the `prepare_asset` function to depend on another /// prepared [`RenderAsset`], for example `Mesh::prepare_asset` relies on `RenderAssets::` for morph /// targets, so the plugin is created as `RenderAssetPlugin::::default()`. pub struct RenderAssetPlugin { phantom: PhantomData (A, AFTER)>, } impl Default for RenderAssetPlugin { fn default() -> Self { Self { phantom: Default::default(), } } } impl Plugin for RenderAssetPlugin { fn build(&self, app: &mut App) { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::>() .init_resource::>() .init_resource::>() .add_systems(ExtractSchedule, extract_render_asset::); AFTER::register_system( render_app, prepare_assets::.in_set(RenderSet::PrepareAssets), ); } } } // helper to allow specifying dependencies between render assets pub trait RenderAssetDependency { fn register_system(render_app: &mut App, system: SystemConfigs); } impl RenderAssetDependency for () { fn register_system(render_app: &mut App, system: SystemConfigs) { render_app.add_systems(Render, system); } } impl RenderAssetDependency for A { fn register_system(render_app: &mut App, system: SystemConfigs) { render_app.add_systems(Render, system.after(prepare_assets::)); } } /// Temporarily stores the extracted and removed assets of the current frame. #[derive(Resource)] pub struct ExtractedAssets { extracted: Vec<(AssetId, A::ExtractedAsset)>, removed: Vec>, } impl Default for ExtractedAssets { fn default() -> Self { Self { extracted: Default::default(), removed: Default::default(), } } } /// Stores all GPU representations ([`RenderAsset::PreparedAssets`](RenderAsset::PreparedAsset)) /// of [`RenderAssets`](RenderAsset) as long as they exist. #[derive(Resource)] pub struct RenderAssets(HashMap, A::PreparedAsset>); impl Default for RenderAssets { fn default() -> Self { Self(Default::default()) } } impl RenderAssets { pub fn get(&self, id: impl Into>) -> Option<&A::PreparedAsset> { self.0.get(&id.into()) } pub fn get_mut(&mut self, id: impl Into>) -> Option<&mut A::PreparedAsset> { self.0.get_mut(&id.into()) } pub fn insert( &mut self, id: impl Into>, value: A::PreparedAsset, ) -> Option { self.0.insert(id.into(), value) } pub fn remove(&mut self, id: impl Into>) -> Option { self.0.remove(&id.into()) } pub fn iter(&self) -> impl Iterator, &A::PreparedAsset)> { self.0.iter().map(|(k, v)| (*k, v)) } pub fn iter_mut(&mut self) -> impl Iterator, &mut A::PreparedAsset)> { self.0.iter_mut().map(|(k, v)| (*k, v)) } } /// This system extracts all created or modified assets of the corresponding [`RenderAsset`] type /// into the "render world". fn extract_render_asset( mut commands: Commands, mut events: Extract>>, assets: Extract>>, ) { let mut changed_assets = HashSet::default(); let mut removed = Vec::new(); for event in events.read() { match event { AssetEvent::Added { id } | AssetEvent::Modified { id } => { changed_assets.insert(*id); } AssetEvent::Removed { id } => { changed_assets.remove(id); removed.push(*id); } AssetEvent::LoadedWithDependencies { .. } => { // TODO: handle this } } } let mut extracted_assets = Vec::new(); for id in changed_assets.drain() { if let Some(asset) = assets.get(id) { extracted_assets.push((id, asset.extract_asset())); } } commands.insert_resource(ExtractedAssets { extracted: extracted_assets, removed, }); } // TODO: consider storing inside system? /// All assets that should be prepared next frame. #[derive(Resource)] pub struct PrepareNextFrameAssets { assets: Vec<(AssetId, A::ExtractedAsset)>, } impl Default for PrepareNextFrameAssets { fn default() -> Self { Self { assets: Default::default(), } } } /// This system prepares all assets of the corresponding [`RenderAsset`] type /// which where extracted this frame for the GPU. pub fn prepare_assets( mut extracted_assets: ResMut>, mut render_assets: ResMut>, mut prepare_next_frame: ResMut>, param: StaticSystemParam<::Param>, ) { let mut param = param.into_inner(); let queued_assets = std::mem::take(&mut prepare_next_frame.assets); for (id, extracted_asset) in queued_assets { match R::prepare_asset(extracted_asset, &mut param) { Ok(prepared_asset) => { render_assets.insert(id, prepared_asset); } Err(PrepareAssetError::RetryNextUpdate(extracted_asset)) => { prepare_next_frame.assets.push((id, extracted_asset)); } } } for removed in std::mem::take(&mut extracted_assets.removed) { render_assets.remove(removed); } for (id, extracted_asset) in std::mem::take(&mut extracted_assets.extracted) { match R::prepare_asset(extracted_asset, &mut param) { Ok(prepared_asset) => { render_assets.insert(id, prepared_asset); } Err(PrepareAssetError::RetryNextUpdate(extracted_asset)) => { prepare_next_frame.assets.push((id, extracted_asset)); } } } }