use crate::{RenderApp, RenderStage}; use bevy_app::{App, Plugin}; use bevy_asset::{Asset, AssetEvent, Assets, Handle}; use bevy_ecs::{ prelude::*, system::{StaticSystemParam, SystemParam, SystemParamItem}, }; use bevy_utils::{HashMap, HashSet}; use std::marker::PhantomData; pub enum PrepareAssetError { RetryNextUpdate(E), } /// Describes how an asset gets extracted and prepared for rendering. /// /// In the [`RenderStage::Extract`](crate::RenderStage::Extract) step the asset is transferred /// from the "app 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 [`RenderStage::Prepare`](crate::RenderStage::Prepare) step the extracted asset /// is transformed into its GPU-representation of type [`RenderAsset::PreparedAsset`]. pub trait RenderAsset: Asset { /// The representation of the the asset in the "render world". type ExtractedAsset: Send + Sync + 'static; /// The GPU-representation of the 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>; } #[derive(Clone, Hash, Debug, PartialEq, Eq, SystemLabel)] pub enum PrepareAssetLabel { PreAssetPrepare, AssetPrepare, PostAssetPrepare, } impl Default for PrepareAssetLabel { fn default() -> Self { Self::AssetPrepare } } /// 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 [`RenderStage::Extract`](crate::RenderStage::Extract) and /// [`RenderStage::Prepare`](crate::RenderStage::Prepare) steps for the specified [`RenderAsset`]. pub struct RenderAssetPlugin { prepare_asset_label: PrepareAssetLabel, phantom: PhantomData A>, } impl RenderAssetPlugin { pub fn with_prepare_asset_label(prepare_asset_label: PrepareAssetLabel) -> Self { Self { prepare_asset_label, phantom: PhantomData, } } } impl Default for RenderAssetPlugin { fn default() -> Self { Self { prepare_asset_label: Default::default(), phantom: PhantomData, } } } impl Plugin for RenderAssetPlugin { fn build(&self, app: &mut App) { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { let prepare_asset_system = prepare_assets::.label(self.prepare_asset_label.clone()); let prepare_asset_system = match self.prepare_asset_label { PrepareAssetLabel::PreAssetPrepare => prepare_asset_system, PrepareAssetLabel::AssetPrepare => { prepare_asset_system.after(PrepareAssetLabel::PreAssetPrepare) } PrepareAssetLabel::PostAssetPrepare => { prepare_asset_system.after(PrepareAssetLabel::AssetPrepare) } }; render_app .init_resource::>() .init_resource::>() .init_resource::>() .add_system_to_stage(RenderStage::Extract, extract_render_asset::) .add_system_to_stage(RenderStage::Prepare, prepare_asset_system); } } } /// Temporarily stores the extracted and removed assets of the current frame. pub struct ExtractedAssets { extracted: Vec<(Handle, 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. pub type RenderAssets = HashMap, ::PreparedAsset>; /// This system extracts all crated or modified assets of the corresponding [`RenderAsset`] type /// into the "render world". fn extract_render_asset( mut commands: Commands, mut events: EventReader>, assets: Res>, ) { let mut changed_assets = HashSet::default(); let mut removed = Vec::new(); for event in events.iter() { match event { AssetEvent::Created { handle } | AssetEvent::Modified { handle } => { changed_assets.insert(handle); } AssetEvent::Removed { handle } => { changed_assets.remove(handle); removed.push(handle.clone_weak()); } } } let mut extracted_assets = Vec::new(); for handle in changed_assets.drain() { if let Some(asset) = assets.get(handle) { extracted_assets.push((handle.clone_weak(), asset.extract_asset())); } } commands.insert_resource(ExtractedAssets { extracted: extracted_assets, removed, }); } // TODO: consider storing inside system? /// All assets that should be prepared next frame. pub struct PrepareNextFrameAssets { assets: Vec<(Handle, 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. 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 mut queued_assets = std::mem::take(&mut prepare_next_frame.assets); for (handle, extracted_asset) in queued_assets.drain(..) { match R::prepare_asset(extracted_asset, &mut param) { Ok(prepared_asset) => { render_assets.insert(handle, prepared_asset); } Err(PrepareAssetError::RetryNextUpdate(extracted_asset)) => { prepare_next_frame.assets.push((handle, extracted_asset)); } } } for removed in std::mem::take(&mut extracted_assets.removed) { render_assets.remove(&removed); } for (handle, extracted_asset) in std::mem::take(&mut extracted_assets.extracted) { match R::prepare_asset(extracted_asset, &mut param) { Ok(prepared_asset) => { render_assets.insert(handle, prepared_asset); } Err(PrepareAssetError::RetryNextUpdate(extracted_asset)) => { prepare_next_frame.assets.push((handle, extracted_asset)); } } } }