Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions crates/bevy_asset/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ pub(crate) fn bevy_asset_path() -> Path {
}

const DEPENDENCY_ATTRIBUTE: &str = "dependency";
const ASSET_STORAGE_ATTRIBUTE: &str = "asset_storage";

/// Implement the `Asset` trait.
#[proc_macro_derive(Asset, attributes(dependency))]
#[proc_macro_derive(Asset, attributes(dependency, asset_storage))]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd personally prefer to hold off on asset storage changes until we've sorted out "assets as entities". I think we should try to reconcile that design with the "arc-ed assets" needs.

Copy link
Contributor Author

@brianreavis brianreavis Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see this as a potential stopgap before Assets as Entities - which I’m excited for, but seems quite a ways out. The status quo of cloning heavy images and meshes into the render world is a bit rough.

The goal here is to provide a stepping stone that doesn't rock the boat much. At best, it could inform assets as entities, and at worst, it could be yeeted w/o complication when building Assets as Entities1.

Regarding the stepping stone comment, the storage abstractions here could potentially be used if assets end up as entities:

#[derive(Component)]
AssetComponent<A: Asset>(StoredAsset<A>) 

Footnotes

  1. If it’d be helpful to delete HybridStorageStrategy that provides new async-specific APIs like get_arc and get_arc_rwlock to Res<Assets<A>>, I think that’d be fine. Assets could be arc-ed with ArcedStorageStrategy without the user knowing anything about the underlying implementation.

pub fn derive_asset(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let bevy_asset_path: Path = bevy_asset_path();
Expand All @@ -26,8 +27,22 @@ pub fn derive_asset(input: TokenStream) -> TokenStream {
Err(err) => return err.into_compile_error().into(),
};

// Check for custom asset_storage attribute
let storage_type = ast
.attrs
.iter()
.find(|attr| attr.path().is_ident(ASSET_STORAGE_ATTRIBUTE))
.and_then(|attr| attr.parse_args::<syn::Type>().ok())
.unwrap_or_else(|| {
// Default to StackAssetStorage if no custom storage is specified
let raw_storage = format_ident!("StackAssetStorage");
syn::parse_quote!(#bevy_asset_path::#raw_storage)
});

TokenStream::from(quote! {
impl #impl_generics #bevy_asset_path::Asset for #struct_name #type_generics #where_clause { }
impl #impl_generics #bevy_asset_path::Asset for #struct_name #type_generics #where_clause {
type AssetStorage = #storage_type;
}
#dependency_visitor
})
}
Expand Down
197 changes: 146 additions & 51 deletions crates/bevy_asset/src/assets.rs

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions crates/bevy_asset/src/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -646,15 +646,17 @@ mod tests {
/// `PartialReflect::reflect_clone`/`PartialReflect::to_dynamic` should increase the strong count of a strong handle
#[test]
fn strong_handle_reflect_clone() {
use crate::{AssetApp, AssetPlugin, Assets, VisitAssetDependencies};
use crate::{AssetApp, AssetPlugin, Assets, StackAssetStorage, VisitAssetDependencies};
use bevy_app::App;
use bevy_reflect::FromReflect;

#[derive(Reflect)]
struct MyAsset {
value: u32,
}
impl Asset for MyAsset {}
impl Asset for MyAsset {
type AssetStorage = StackAssetStorage;
}
impl VisitAssetDependencies for MyAsset {
fn visit_dependencies(&self, _visit: &mut impl FnMut(UntypedAssetId)) {}
}
Expand Down
20 changes: 13 additions & 7 deletions crates/bevy_asset/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ pub mod prelude {

#[doc(hidden)]
pub use crate::{
Asset, AssetApp, AssetEvent, AssetId, AssetMode, AssetPlugin, AssetServer, Assets,
DirectAssetAccessExt, Handle, UntypedHandle,
Asset, AssetApp, AssetEvent, AssetId, AssetMode, AssetMut, AssetPlugin, AssetRef,
AssetServer, AssetSnapshot, Assets, DirectAssetAccessExt, Handle, UntypedHandle,
};
}

Expand All @@ -185,6 +185,7 @@ mod path;
mod reflect;
mod render_asset;
mod server;
mod storage;

pub use assets::*;
pub use bevy_asset_macros::Asset;
Expand All @@ -203,6 +204,7 @@ pub use path::*;
pub use reflect::*;
pub use render_asset::*;
pub use server::*;
pub use storage::*;

pub use uuid;

Expand Down Expand Up @@ -453,7 +455,9 @@ impl Plugin for AssetPlugin {
label = "invalid `Asset`",
note = "consider annotating `{Self}` with `#[derive(Asset)]`"
)]
pub trait Asset: VisitAssetDependencies + TypePath + Send + Sync + 'static {}
pub trait Asset: VisitAssetDependencies + TypePath + Send + Sync + Sized + 'static {
type AssetStorage: AssetStorageStrategy<Self>;
}

/// A trait for components that can be used as asset identifiers, e.g. handle wrappers.
pub trait AsAssetId: Component {
Expand Down Expand Up @@ -581,7 +585,8 @@ pub trait AssetApp {
/// This enables reflection code to access assets. For detailed information, see the docs on [`ReflectAsset`] and [`ReflectHandle`].
fn register_asset_reflect<A>(&mut self) -> &mut Self
where
A: Asset + Reflect + FromReflect + GetTypeRegistration;
A: Asset + Reflect + FromReflect + GetTypeRegistration,
A::AssetStorage: AssetWriteStrategy<A>;
/// Preregisters a loader for the given extensions, that will block asset loads until a real loader
/// is registered.
fn preregister_asset_loader<L: AssetLoader>(&mut self, extensions: &[&str]) -> &mut Self;
Expand Down Expand Up @@ -671,6 +676,7 @@ impl AssetApp for App {
fn register_asset_reflect<A>(&mut self) -> &mut Self
where
A: Asset + Reflect + FromReflect + GetTypeRegistration,
A::AssetStorage: AssetWriteStrategy<A>,
{
let type_registry = self.world().resource::<AppTypeRegistry>();
{
Expand Down Expand Up @@ -716,8 +722,8 @@ mod tests {
},
loader::{AssetLoader, LoadContext},
Asset, AssetApp, AssetEvent, AssetId, AssetLoadError, AssetLoadFailedEvent, AssetPath,
AssetPlugin, AssetServer, Assets, InvalidGenerationError, LoadState, UnapprovedPathMode,
UntypedHandle,
AssetPlugin, AssetRef, AssetServer, Assets, InvalidGenerationError, LoadState,
UnapprovedPathMode, UntypedHandle,
};
use alloc::{
boxed::Box,
Expand Down Expand Up @@ -927,7 +933,7 @@ mod tests {

const LARGE_ITERATION_COUNT: usize = 10000;

fn get<A: Asset>(world: &World, id: AssetId<A>) -> Option<&A> {
fn get<'w, A: Asset>(world: &'w World, id: AssetId<A>) -> Option<AssetRef<'w, A>> {
world.resource::<Assets<A>>().get(id)
}

Expand Down
6 changes: 4 additions & 2 deletions crates/bevy_asset/src/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use alloc::{

use crate::{
loader::AssetLoader, processor::Process, Asset, AssetPath, DeserializeMetaError,
VisitAssetDependencies,
StackAssetStorage, VisitAssetDependencies,
};
use downcast_rs::{impl_downcast, Downcast};
use ron::ser::PrettyConfig;
Expand Down Expand Up @@ -189,7 +189,9 @@ impl Process for () {
}
}

impl Asset for () {}
impl Asset for () {
type AssetStorage = StackAssetStorage;
}

impl VisitAssetDependencies for () {
fn visit_dependencies(&self, _visit: &mut impl FnMut(bevy_asset::UntypedAssetId)) {
Expand Down
Loading