use crate::{ExtractSchedule, MainWorld, Render, RenderApp, RenderSet};
use bevy_app::{App, Plugin};
use bevy_asset::{Asset, AssetEvent, AssetId, Assets};
use bevy_ecs::{
prelude::{Commands, EventReader, IntoSystemConfigs, ResMut, Resource},
schedule::SystemConfigs,
system::{StaticSystemParam, SystemParam, SystemParamItem, SystemState},
world::{FromWorld, Mut},
};
use bevy_reflect::{
utility::{reflect_hasher, NonGenericTypeInfoCell},
FromReflect, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypePath,
Typed, ValueInfo,
};
use bevy_utils::{thiserror::Error, HashMap, HashSet};
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
#[derive(Debug, Error)]
pub enum PrepareAssetError<E: Send + Sync + 'static> {
#[error("Failed to prepare asset")]
RetryNextUpdate(E),
}
pub trait RenderAsset: Asset + Clone {
type PreparedAsset: Send + Sync + 'static;
type Param: SystemParam;
fn asset_usage(&self) -> RenderAssetUsages;
fn prepare_asset(
self,
param: &mut SystemParamItem<Self::Param>,
) -> Result<Self::PreparedAsset, PrepareAssetError<Self>>;
}
bitflags::bitflags! {
#[repr(transparent)]
#[derive(Serialize, TypePath, Deserialize, Hash, Clone, Copy, PartialEq, Eq, Debug)]
pub struct RenderAssetUsages: u8 {
const MAIN_WORLD = 1 << 0;
const RENDER_WORLD = 1 << 1;
}
}
impl Default for RenderAssetUsages {
fn default() -> Self {
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD
}
}
impl Reflect for RenderAssetUsages {
fn get_represented_type_info(&self) -> Option<&'static bevy_reflect::TypeInfo> {
Some(<Self as Typed>::type_info())
}
fn into_any(self: Box<Self>) -> Box<dyn std::any::Any> {
self
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn into_reflect(self: Box<Self>) -> Box<dyn Reflect> {
self
}
fn as_reflect(&self) -> &dyn Reflect {
self
}
fn as_reflect_mut(&mut self) -> &mut dyn Reflect {
self
}
fn apply(&mut self, value: &dyn Reflect) {
let value = value.as_any();
if let Some(&value) = value.downcast_ref::<Self>() {
*self = value;
} else {
panic!("Value is not a {}.", Self::type_path());
}
}
fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
*self = value.take()?;
Ok(())
}
fn reflect_kind(&self) -> bevy_reflect::ReflectKind {
ReflectKind::Value
}
fn reflect_ref(&self) -> bevy_reflect::ReflectRef {
ReflectRef::Value(self)
}
fn reflect_mut(&mut self) -> bevy_reflect::ReflectMut {
ReflectMut::Value(self)
}
fn reflect_owned(self: Box<Self>) -> bevy_reflect::ReflectOwned {
ReflectOwned::Value(self)
}
fn clone_value(&self) -> Box<dyn Reflect> {
Box::new(*self)
}
fn reflect_hash(&self) -> Option<u64> {
use std::hash::Hash;
use std::hash::Hasher;
let mut hasher = reflect_hasher();
Hash::hash(&std::any::Any::type_id(self), &mut hasher);
Hash::hash(self, &mut hasher);
Some(hasher.finish())
}
fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option<bool> {
let value = value.as_any();
if let Some(value) = value.downcast_ref::<Self>() {
Some(std::cmp::PartialEq::eq(self, value))
} else {
Some(false)
}
}
}
impl FromReflect for RenderAssetUsages {
fn from_reflect(reflect: &dyn Reflect) -> Option<Self> {
let raw_value = *reflect.as_any().downcast_ref::<u8>()?;
Self::from_bits(raw_value)
}
}
impl Typed for RenderAssetUsages {
fn type_info() -> &'static TypeInfo {
static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new();
CELL.get_or_set(|| TypeInfo::Value(ValueInfo::new::<Self>()))
}
}
pub struct RenderAssetPlugin<A: RenderAsset, AFTER: RenderAssetDependency + 'static = ()> {
phantom: PhantomData<fn() -> (A, AFTER)>,
}
impl<A: RenderAsset, AFTER: RenderAssetDependency + 'static> Default
for RenderAssetPlugin<A, AFTER>
{
fn default() -> Self {
Self {
phantom: Default::default(),
}
}
}
impl<A: RenderAsset, AFTER: RenderAssetDependency + 'static> Plugin
for RenderAssetPlugin<A, AFTER>
{
fn build(&self, app: &mut App) {
app.init_resource::<CachedExtractRenderAssetSystemState<A>>();
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<ExtractedAssets<A>>()
.init_resource::<RenderAssets<A>>()
.init_resource::<PrepareNextFrameAssets<A>>()
.add_systems(ExtractSchedule, extract_render_asset::<A>);
AFTER::register_system(
render_app,
prepare_assets::<A>.in_set(RenderSet::PrepareAssets),
);
}
}
}
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<A: RenderAsset> RenderAssetDependency for A {
fn register_system(render_app: &mut App, system: SystemConfigs) {
render_app.add_systems(Render, system.after(prepare_assets::<A>));
}
}
#[derive(Resource)]
pub struct ExtractedAssets<A: RenderAsset> {
extracted: Vec<(AssetId<A>, A)>,
removed: Vec<AssetId<A>>,
}
impl<A: RenderAsset> Default for ExtractedAssets<A> {
fn default() -> Self {
Self {
extracted: Default::default(),
removed: Default::default(),
}
}
}
#[derive(Resource)]
pub struct RenderAssets<A: RenderAsset>(HashMap<AssetId<A>, A::PreparedAsset>);
impl<A: RenderAsset> Default for RenderAssets<A> {
fn default() -> Self {
Self(Default::default())
}
}
impl<A: RenderAsset> RenderAssets<A> {
pub fn get(&self, id: impl Into<AssetId<A>>) -> Option<&A::PreparedAsset> {
self.0.get(&id.into())
}
pub fn get_mut(&mut self, id: impl Into<AssetId<A>>) -> Option<&mut A::PreparedAsset> {
self.0.get_mut(&id.into())
}
pub fn insert(
&mut self,
id: impl Into<AssetId<A>>,
value: A::PreparedAsset,
) -> Option<A::PreparedAsset> {
self.0.insert(id.into(), value)
}
pub fn remove(&mut self, id: impl Into<AssetId<A>>) -> Option<A::PreparedAsset> {
self.0.remove(&id.into())
}
pub fn iter(&self) -> impl Iterator<Item = (AssetId<A>, &A::PreparedAsset)> {
self.0.iter().map(|(k, v)| (*k, v))
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = (AssetId<A>, &mut A::PreparedAsset)> {
self.0.iter_mut().map(|(k, v)| (*k, v))
}
}
#[derive(Resource)]
struct CachedExtractRenderAssetSystemState<A: RenderAsset> {
state: SystemState<(
EventReader<'static, 'static, AssetEvent<A>>,
ResMut<'static, Assets<A>>,
)>,
}
impl<A: RenderAsset> FromWorld for CachedExtractRenderAssetSystemState<A> {
fn from_world(world: &mut bevy_ecs::world::World) -> Self {
Self {
state: SystemState::new(world),
}
}
}
fn extract_render_asset<A: RenderAsset>(mut commands: Commands, mut main_world: ResMut<MainWorld>) {
main_world.resource_scope(
|world, mut cached_state: Mut<CachedExtractRenderAssetSystemState<A>>| {
let (mut events, mut assets) = cached_state.state.get_mut(world);
let mut changed_assets = HashSet::default();
let mut removed = Vec::new();
for event in events.read() {
#[allow(clippy::match_same_arms)]
match event {
AssetEvent::Added { id } | AssetEvent::Modified { id } => {
changed_assets.insert(*id);
}
AssetEvent::Removed { .. } => {}
AssetEvent::Unused { id } => {
changed_assets.remove(id);
removed.push(*id);
}
AssetEvent::LoadedWithDependencies { .. } => {
}
}
}
let mut extracted_assets = Vec::new();
for id in changed_assets.drain() {
if let Some(asset) = assets.get(id) {
let asset_usage = asset.asset_usage();
if asset_usage.contains(RenderAssetUsages::RENDER_WORLD) {
if asset_usage == RenderAssetUsages::RENDER_WORLD {
if let Some(asset) = assets.remove(id) {
extracted_assets.push((id, asset));
}
} else {
extracted_assets.push((id, asset.clone()));
}
}
}
}
commands.insert_resource(ExtractedAssets {
extracted: extracted_assets,
removed,
});
cached_state.state.apply(world);
},
);
}
#[derive(Resource)]
pub struct PrepareNextFrameAssets<A: RenderAsset> {
assets: Vec<(AssetId<A>, A)>,
}
impl<A: RenderAsset> Default for PrepareNextFrameAssets<A> {
fn default() -> Self {
Self {
assets: Default::default(),
}
}
}
pub fn prepare_assets<A: RenderAsset>(
mut extracted_assets: ResMut<ExtractedAssets<A>>,
mut render_assets: ResMut<RenderAssets<A>>,
mut prepare_next_frame: ResMut<PrepareNextFrameAssets<A>>,
param: StaticSystemParam<<A as RenderAsset>::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 extracted_asset.prepare_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 extracted_assets.removed.drain(..) {
render_assets.remove(removed);
}
for (id, extracted_asset) in extracted_assets.extracted.drain(..) {
match extracted_asset.prepare_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));
}
}
}
}