Vendor dependencies for 0.3.0 release

This commit is contained in:
2025-09-27 10:29:08 -05:00
parent 0c8d39d483
commit 82ab7f317b
26803 changed files with 16134934 additions and 0 deletions

472
vendor/bevy_asset/src/asset_changed.rs vendored Normal file
View File

@@ -0,0 +1,472 @@
//! Defines the [`AssetChanged`] query filter.
//!
//! Like [`Changed`](bevy_ecs::prelude::Changed), but for [`Asset`]s,
//! and triggers whenever the handle or the underlying asset changes.
use crate::{AsAssetId, Asset, AssetId};
use bevy_ecs::component::Components;
use bevy_ecs::{
archetype::Archetype,
component::{ComponentId, Tick},
prelude::{Entity, Resource, World},
query::{FilteredAccess, QueryData, QueryFilter, ReadFetch, WorldQuery},
storage::{Table, TableRow},
world::unsafe_world_cell::UnsafeWorldCell,
};
use bevy_platform::collections::HashMap;
use core::marker::PhantomData;
use disqualified::ShortName;
use tracing::error;
/// A resource that stores the last tick an asset was changed. This is used by
/// the [`AssetChanged`] filter to determine if an asset has changed since the last time
/// a query ran.
///
/// This resource is automatically managed by the [`AssetEvents`](crate::AssetEvents) schedule and
/// should not be exposed to the user in order to maintain safety guarantees. Any additional uses of
/// this resource should be carefully audited to ensure that they do not introduce any safety
/// issues.
#[derive(Resource)]
pub(crate) struct AssetChanges<A: Asset> {
change_ticks: HashMap<AssetId<A>, Tick>,
last_change_tick: Tick,
}
impl<A: Asset> AssetChanges<A> {
pub(crate) fn insert(&mut self, asset_id: AssetId<A>, tick: Tick) {
self.last_change_tick = tick;
self.change_ticks.insert(asset_id, tick);
}
pub(crate) fn remove(&mut self, asset_id: &AssetId<A>) {
self.change_ticks.remove(asset_id);
}
}
impl<A: Asset> Default for AssetChanges<A> {
fn default() -> Self {
Self {
change_ticks: Default::default(),
last_change_tick: Tick::new(0),
}
}
}
struct AssetChangeCheck<'w, A: AsAssetId> {
// This should never be `None` in practice, but we need to handle the case
// where the `AssetChanges` resource was removed.
change_ticks: Option<&'w HashMap<AssetId<A::Asset>, Tick>>,
last_run: Tick,
this_run: Tick,
}
impl<A: AsAssetId> Clone for AssetChangeCheck<'_, A> {
fn clone(&self) -> Self {
*self
}
}
impl<A: AsAssetId> Copy for AssetChangeCheck<'_, A> {}
impl<'w, A: AsAssetId> AssetChangeCheck<'w, A> {
fn new(changes: &'w AssetChanges<A::Asset>, last_run: Tick, this_run: Tick) -> Self {
Self {
change_ticks: Some(&changes.change_ticks),
last_run,
this_run,
}
}
// TODO(perf): some sort of caching? Each check has two levels of indirection,
// which is not optimal.
fn has_changed(&self, handle: &A) -> bool {
let is_newer = |tick: &Tick| tick.is_newer_than(self.last_run, self.this_run);
let id = handle.as_asset_id();
self.change_ticks
.is_some_and(|change_ticks| change_ticks.get(&id).is_some_and(is_newer))
}
}
/// Filter that selects entities with an `A` for an asset that changed
/// after the system last ran, where `A` is a component that implements
/// [`AsAssetId`].
///
/// Unlike `Changed<A>`, this is true whenever the asset for the `A`
/// in `ResMut<Assets<A>>` changed. For example, when a mesh changed through the
/// [`Assets<Mesh>::get_mut`] method, `AssetChanged<Mesh>` will iterate over all
/// entities with the `Handle<Mesh>` for that mesh. Meanwhile, `Changed<Handle<Mesh>>`
/// will iterate over no entities.
///
/// Swapping the actual `A` component is a common pattern. So you
/// should check for _both_ `AssetChanged<A>` and `Changed<A>` with
/// `Or<(Changed<A>, AssetChanged<A>)>`.
///
/// # Quirks
///
/// - Asset changes are registered in the [`AssetEvents`] schedule.
/// - Removed assets are not detected.
///
/// The list of changed assets only gets updated in the
/// [`AssetEvents`] schedule which runs in `Last`. Therefore, `AssetChanged`
/// will only pick up asset changes in schedules following `AssetEvents` or the
/// next frame. Consider adding the system in the `Last` schedule after [`AssetEvents`] if you need
/// to react without frame delay to asset changes.
///
/// # Performance
///
/// When at least one `A` is updated, this will
/// read a hashmap once per entity with an `A` component. The
/// runtime of the query is proportional to how many entities with an `A`
/// it matches.
///
/// If no `A` asset updated since the last time the system ran, then no lookups occur.
///
/// [`AssetEvents`]: crate::AssetEvents
/// [`Assets<Mesh>::get_mut`]: crate::Assets::get_mut
pub struct AssetChanged<A: AsAssetId>(PhantomData<A>);
/// [`WorldQuery`] fetch for [`AssetChanged`].
#[doc(hidden)]
pub struct AssetChangedFetch<'w, A: AsAssetId> {
inner: Option<ReadFetch<'w, A>>,
check: AssetChangeCheck<'w, A>,
}
impl<'w, A: AsAssetId> Clone for AssetChangedFetch<'w, A> {
fn clone(&self) -> Self {
Self {
inner: self.inner,
check: self.check,
}
}
}
/// [`WorldQuery`] state for [`AssetChanged`].
#[doc(hidden)]
pub struct AssetChangedState<A: AsAssetId> {
asset_id: ComponentId,
resource_id: ComponentId,
_asset: PhantomData<fn(A)>,
}
#[expect(unsafe_code, reason = "WorldQuery is an unsafe trait.")]
/// SAFETY: `ROQueryFetch<Self>` is the same as `QueryFetch<Self>`
unsafe impl<A: AsAssetId> WorldQuery for AssetChanged<A> {
type Fetch<'w> = AssetChangedFetch<'w, A>;
type State = AssetChangedState<A>;
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
fetch
}
unsafe fn init_fetch<'w>(
world: UnsafeWorldCell<'w>,
state: &Self::State,
last_run: Tick,
this_run: Tick,
) -> Self::Fetch<'w> {
// SAFETY:
// - `AssetChanges` is private and only accessed mutably in the `AssetEvents` schedule
// - `resource_id` was obtained from the type ID of `AssetChanges<A::Asset>`.
let Some(changes) = (unsafe {
world
.get_resource_by_id(state.resource_id)
.map(|ptr| ptr.deref::<AssetChanges<A::Asset>>())
}) else {
error!(
"AssetChanges<{ty}> resource was removed, please do not remove \
AssetChanges<{ty}> when using the AssetChanged<{ty}> world query",
ty = ShortName::of::<A>()
);
return AssetChangedFetch {
inner: None,
check: AssetChangeCheck {
change_ticks: None,
last_run,
this_run,
},
};
};
let has_updates = changes.last_change_tick.is_newer_than(last_run, this_run);
AssetChangedFetch {
inner: has_updates.then(||
// SAFETY: We delegate to the inner `init_fetch` for `A`
unsafe {
<&A>::init_fetch(world, &state.asset_id, last_run, this_run)
}),
check: AssetChangeCheck::new(changes, last_run, this_run),
}
}
const IS_DENSE: bool = <&A>::IS_DENSE;
unsafe fn set_archetype<'w>(
fetch: &mut Self::Fetch<'w>,
state: &Self::State,
archetype: &'w Archetype,
table: &'w Table,
) {
if let Some(inner) = &mut fetch.inner {
// SAFETY: We delegate to the inner `set_archetype` for `A`
unsafe {
<&A>::set_archetype(inner, &state.asset_id, archetype, table);
}
}
}
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) {
if let Some(inner) = &mut fetch.inner {
// SAFETY: We delegate to the inner `set_table` for `A`
unsafe {
<&A>::set_table(inner, &state.asset_id, table);
}
}
}
#[inline]
fn update_component_access(state: &Self::State, access: &mut FilteredAccess<ComponentId>) {
<&A>::update_component_access(&state.asset_id, access);
access.add_resource_read(state.resource_id);
}
fn init_state(world: &mut World) -> AssetChangedState<A> {
let resource_id = world.init_resource::<AssetChanges<A::Asset>>();
let asset_id = world.register_component::<A>();
AssetChangedState {
asset_id,
resource_id,
_asset: PhantomData,
}
}
fn get_state(components: &Components) -> Option<Self::State> {
let resource_id = components.resource_id::<AssetChanges<A::Asset>>()?;
let asset_id = components.component_id::<A>()?;
Some(AssetChangedState {
asset_id,
resource_id,
_asset: PhantomData,
})
}
fn matches_component_set(
state: &Self::State,
set_contains_id: &impl Fn(ComponentId) -> bool,
) -> bool {
set_contains_id(state.asset_id)
}
}
#[expect(unsafe_code, reason = "QueryFilter is an unsafe trait.")]
/// SAFETY: read-only access
unsafe impl<A: AsAssetId> QueryFilter for AssetChanged<A> {
const IS_ARCHETYPAL: bool = false;
#[inline]
unsafe fn filter_fetch(
fetch: &mut Self::Fetch<'_>,
entity: Entity,
table_row: TableRow,
) -> bool {
fetch.inner.as_mut().is_some_and(|inner| {
// SAFETY: We delegate to the inner `fetch` for `A`
unsafe {
let handle = <&A>::fetch(inner, entity, table_row);
fetch.check.has_changed(handle)
}
})
}
}
#[cfg(test)]
#[expect(clippy::print_stdout, reason = "Allowed in tests.")]
mod tests {
use crate::{AssetEvents, AssetPlugin, Handle};
use alloc::{vec, vec::Vec};
use core::num::NonZero;
use std::println;
use crate::{AssetApp, Assets};
use bevy_app::{App, AppExit, PostUpdate, Startup, TaskPoolPlugin, Update};
use bevy_ecs::schedule::IntoScheduleConfigs;
use bevy_ecs::{
component::Component,
event::EventWriter,
resource::Resource,
system::{Commands, IntoSystem, Local, Query, Res, ResMut},
};
use bevy_reflect::TypePath;
use super::*;
#[derive(Asset, TypePath, Debug)]
struct MyAsset(usize, &'static str);
#[derive(Component)]
struct MyComponent(Handle<MyAsset>);
impl AsAssetId for MyComponent {
type Asset = MyAsset;
fn as_asset_id(&self) -> AssetId<Self::Asset> {
self.0.id()
}
}
fn run_app<Marker>(system: impl IntoSystem<(), (), Marker>) {
let mut app = App::new();
app.add_plugins((TaskPoolPlugin::default(), AssetPlugin::default()))
.init_asset::<MyAsset>()
.add_systems(Update, system);
app.update();
}
// According to a comment in QueryState::new in bevy_ecs, components on filter
// position shouldn't conflict with components on query position.
#[test]
fn handle_filter_pos_ok() {
fn compatible_filter(
_query: Query<&mut MyComponent, AssetChanged<MyComponent>>,
mut exit: EventWriter<AppExit>,
) {
exit.write(AppExit::Error(NonZero::<u8>::MIN));
}
run_app(compatible_filter);
}
#[derive(Default, PartialEq, Debug, Resource)]
struct Counter(Vec<u32>);
fn count_update(
mut counter: ResMut<Counter>,
assets: Res<Assets<MyAsset>>,
query: Query<&MyComponent, AssetChanged<MyComponent>>,
) {
for handle in query.iter() {
let asset = assets.get(&handle.0).unwrap();
counter.0[asset.0] += 1;
}
}
fn update_some(mut assets: ResMut<Assets<MyAsset>>, mut run_count: Local<u32>) {
let mut update_index = |i| {
let id = assets
.iter()
.find_map(|(h, a)| (a.0 == i).then_some(h))
.unwrap();
let asset = assets.get_mut(id).unwrap();
println!("setting new value for {}", asset.0);
asset.1 = "new_value";
};
match *run_count {
0 | 1 => update_index(0),
2 => {}
3 => {
update_index(0);
update_index(1);
}
4.. => update_index(1),
};
*run_count += 1;
}
fn add_some(
mut assets: ResMut<Assets<MyAsset>>,
mut cmds: Commands,
mut run_count: Local<u32>,
) {
match *run_count {
1 => {
cmds.spawn(MyComponent(assets.add(MyAsset(0, "init"))));
}
0 | 2 => {}
3 => {
cmds.spawn(MyComponent(assets.add(MyAsset(1, "init"))));
cmds.spawn(MyComponent(assets.add(MyAsset(2, "init"))));
}
4.. => {
cmds.spawn(MyComponent(assets.add(MyAsset(3, "init"))));
}
};
*run_count += 1;
}
#[track_caller]
fn assert_counter(app: &App, assert: Counter) {
assert_eq!(&assert, app.world().resource::<Counter>());
}
#[test]
fn added() {
let mut app = App::new();
app.add_plugins((TaskPoolPlugin::default(), AssetPlugin::default()))
.init_asset::<MyAsset>()
.insert_resource(Counter(vec![0, 0, 0, 0]))
.add_systems(Update, add_some)
.add_systems(PostUpdate, count_update.after(AssetEvents));
// First run of the app, `add_systems(Startup…)` runs.
app.update(); // run_count == 0
assert_counter(&app, Counter(vec![0, 0, 0, 0]));
app.update(); // run_count == 1
assert_counter(&app, Counter(vec![1, 0, 0, 0]));
app.update(); // run_count == 2
assert_counter(&app, Counter(vec![1, 0, 0, 0]));
app.update(); // run_count == 3
assert_counter(&app, Counter(vec![1, 1, 1, 0]));
app.update(); // run_count == 4
assert_counter(&app, Counter(vec![1, 1, 1, 1]));
}
#[test]
fn changed() {
let mut app = App::new();
app.add_plugins((TaskPoolPlugin::default(), AssetPlugin::default()))
.init_asset::<MyAsset>()
.insert_resource(Counter(vec![0, 0]))
.add_systems(
Startup,
|mut cmds: Commands, mut assets: ResMut<Assets<MyAsset>>| {
let asset0 = assets.add(MyAsset(0, "init"));
let asset1 = assets.add(MyAsset(1, "init"));
cmds.spawn(MyComponent(asset0.clone()));
cmds.spawn(MyComponent(asset0));
cmds.spawn(MyComponent(asset1.clone()));
cmds.spawn(MyComponent(asset1.clone()));
cmds.spawn(MyComponent(asset1));
},
)
.add_systems(Update, update_some)
.add_systems(PostUpdate, count_update.after(AssetEvents));
// First run of the app, `add_systems(Startup…)` runs.
app.update(); // run_count == 0
// First run: We count the entities that were added in the `Startup` schedule
assert_counter(&app, Counter(vec![2, 3]));
// Second run: `update_once` updates the first asset, which is
// associated with two entities, so `count_update` picks up two updates
app.update(); // run_count == 1
assert_counter(&app, Counter(vec![4, 3]));
// Third run: `update_once` doesn't update anything, same values as last
app.update(); // run_count == 2
assert_counter(&app, Counter(vec![4, 3]));
// Fourth run: We update the two assets (asset 0: 2 entities, asset 1: 3)
app.update(); // run_count == 3
assert_counter(&app, Counter(vec![6, 6]));
// Fifth run: only update second asset
app.update(); // run_count == 4
assert_counter(&app, Counter(vec![6, 9]));
// ibid
app.update(); // run_count == 5
assert_counter(&app, Counter(vec![6, 12]));
}
}

661
vendor/bevy_asset/src/assets.rs vendored Normal file
View File

@@ -0,0 +1,661 @@
use crate::asset_changed::AssetChanges;
use crate::{Asset, AssetEvent, AssetHandleProvider, AssetId, AssetServer, Handle, UntypedHandle};
use alloc::{sync::Arc, vec::Vec};
use bevy_ecs::{
prelude::EventWriter,
resource::Resource,
system::{Res, ResMut, SystemChangeTick},
};
use bevy_platform::collections::HashMap;
use bevy_reflect::{Reflect, TypePath};
use core::{any::TypeId, iter::Enumerate, marker::PhantomData, sync::atomic::AtomicU32};
use crossbeam_channel::{Receiver, Sender};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use uuid::Uuid;
/// A generational runtime-only identifier for a specific [`Asset`] stored in [`Assets`]. This is optimized for efficient runtime
/// usage and is not suitable for identifying assets across app runs.
#[derive(
Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Reflect, Serialize, Deserialize,
)]
pub struct AssetIndex {
pub(crate) generation: u32,
pub(crate) index: u32,
}
impl AssetIndex {
/// Convert the [`AssetIndex`] into an opaque blob of bits to transport it in circumstances where carrying a strongly typed index isn't possible.
///
/// The result of this function should not be relied upon for anything except putting it back into [`AssetIndex::from_bits`] to recover the index.
pub fn to_bits(self) -> u64 {
let Self { generation, index } = self;
((generation as u64) << 32) | index as u64
}
/// Convert an opaque `u64` acquired from [`AssetIndex::to_bits`] back into an [`AssetIndex`]. This should not be used with any inputs other than those
/// derived from [`AssetIndex::to_bits`], as there are no guarantees for what will happen with such inputs.
pub fn from_bits(bits: u64) -> Self {
let index = ((bits << 32) >> 32) as u32;
let generation = (bits >> 32) as u32;
Self { generation, index }
}
}
/// Allocates generational [`AssetIndex`] values and facilitates their reuse.
pub(crate) struct AssetIndexAllocator {
/// A monotonically increasing index.
next_index: AtomicU32,
recycled_queue_sender: Sender<AssetIndex>,
/// This receives every recycled [`AssetIndex`]. It serves as a buffer/queue to store indices ready for reuse.
recycled_queue_receiver: Receiver<AssetIndex>,
recycled_sender: Sender<AssetIndex>,
recycled_receiver: Receiver<AssetIndex>,
}
impl Default for AssetIndexAllocator {
fn default() -> Self {
let (recycled_queue_sender, recycled_queue_receiver) = crossbeam_channel::unbounded();
let (recycled_sender, recycled_receiver) = crossbeam_channel::unbounded();
Self {
recycled_queue_sender,
recycled_queue_receiver,
recycled_sender,
recycled_receiver,
next_index: Default::default(),
}
}
}
impl AssetIndexAllocator {
/// Reserves a new [`AssetIndex`], either by reusing a recycled index (with an incremented generation), or by creating a new index
/// by incrementing the index counter for a given asset type `A`.
pub fn reserve(&self) -> AssetIndex {
if let Ok(mut recycled) = self.recycled_queue_receiver.try_recv() {
recycled.generation += 1;
self.recycled_sender.send(recycled).unwrap();
recycled
} else {
AssetIndex {
index: self
.next_index
.fetch_add(1, core::sync::atomic::Ordering::Relaxed),
generation: 0,
}
}
}
/// Queues the given `index` for reuse. This should only be done if the `index` is no longer being used.
pub fn recycle(&self, index: AssetIndex) {
self.recycled_queue_sender.send(index).unwrap();
}
}
/// A "loaded asset" containing the untyped handle for an asset stored in a given [`AssetPath`].
///
/// [`AssetPath`]: crate::AssetPath
#[derive(Asset, TypePath)]
pub struct LoadedUntypedAsset {
/// The handle to the loaded asset.
#[dependency]
pub handle: UntypedHandle,
}
// PERF: do we actually need this to be an enum? Can we just use an "invalid" generation instead
#[derive(Default)]
enum Entry<A: Asset> {
/// None is an indicator that this entry does not have live handles.
#[default]
None,
/// Some is an indicator that there is a live handle active for the entry at this [`AssetIndex`]
Some { value: Option<A>, generation: u32 },
}
/// Stores [`Asset`] values in a Vec-like storage identified by [`AssetIndex`].
struct DenseAssetStorage<A: Asset> {
storage: Vec<Entry<A>>,
len: u32,
allocator: Arc<AssetIndexAllocator>,
}
impl<A: Asset> Default for DenseAssetStorage<A> {
fn default() -> Self {
Self {
len: 0,
storage: Default::default(),
allocator: Default::default(),
}
}
}
impl<A: Asset> DenseAssetStorage<A> {
// Returns the number of assets stored.
pub(crate) fn len(&self) -> usize {
self.len as usize
}
// Returns `true` if there are no assets stored.
pub(crate) fn is_empty(&self) -> bool {
self.len == 0
}
/// Insert the value at the given index. Returns true if a value already exists (and was replaced)
pub(crate) fn insert(
&mut self,
index: AssetIndex,
asset: A,
) -> Result<bool, InvalidGenerationError> {
self.flush();
let entry = &mut self.storage[index.index as usize];
if let Entry::Some { value, generation } = entry {
if *generation == index.generation {
let exists = value.is_some();
if !exists {
self.len += 1;
}
*value = Some(asset);
Ok(exists)
} else {
Err(InvalidGenerationError {
index,
current_generation: *generation,
})
}
} else {
unreachable!("entries should always be valid after a flush");
}
}
/// Removes the asset stored at the given `index` and returns it as [`Some`] (if the asset exists).
/// This will recycle the id and allow new entries to be inserted.
pub(crate) fn remove_dropped(&mut self, index: AssetIndex) -> Option<A> {
self.remove_internal(index, |dense_storage| {
dense_storage.storage[index.index as usize] = Entry::None;
dense_storage.allocator.recycle(index);
})
}
/// Removes the asset stored at the given `index` and returns it as [`Some`] (if the asset exists).
/// This will _not_ recycle the id. New values with the current ID can still be inserted. The ID will
/// not be reused until [`DenseAssetStorage::remove_dropped`] is called.
pub(crate) fn remove_still_alive(&mut self, index: AssetIndex) -> Option<A> {
self.remove_internal(index, |_| {})
}
fn remove_internal(
&mut self,
index: AssetIndex,
removed_action: impl FnOnce(&mut Self),
) -> Option<A> {
self.flush();
let value = match &mut self.storage[index.index as usize] {
Entry::None => return None,
Entry::Some { value, generation } => {
if *generation == index.generation {
value.take().inspect(|_| self.len -= 1)
} else {
return None;
}
}
};
removed_action(self);
value
}
pub(crate) fn get(&self, index: AssetIndex) -> Option<&A> {
let entry = self.storage.get(index.index as usize)?;
match entry {
Entry::None => None,
Entry::Some { value, generation } => {
if *generation == index.generation {
value.as_ref()
} else {
None
}
}
}
}
pub(crate) fn get_mut(&mut self, index: AssetIndex) -> Option<&mut A> {
let entry = self.storage.get_mut(index.index as usize)?;
match entry {
Entry::None => None,
Entry::Some { value, generation } => {
if *generation == index.generation {
value.as_mut()
} else {
None
}
}
}
}
pub(crate) fn flush(&mut self) {
// NOTE: this assumes the allocator index is monotonically increasing.
let new_len = self
.allocator
.next_index
.load(core::sync::atomic::Ordering::Relaxed);
self.storage.resize_with(new_len as usize, || Entry::Some {
value: None,
generation: 0,
});
while let Ok(recycled) = self.allocator.recycled_receiver.try_recv() {
let entry = &mut self.storage[recycled.index as usize];
*entry = Entry::Some {
value: None,
generation: recycled.generation,
};
}
}
pub(crate) fn get_index_allocator(&self) -> Arc<AssetIndexAllocator> {
self.allocator.clone()
}
pub(crate) fn ids(&self) -> impl Iterator<Item = AssetId<A>> + '_ {
self.storage
.iter()
.enumerate()
.filter_map(|(i, v)| match v {
Entry::None => None,
Entry::Some { value, generation } => {
if value.is_some() {
Some(AssetId::from(AssetIndex {
index: i as u32,
generation: *generation,
}))
} else {
None
}
}
})
}
}
/// Stores [`Asset`] values identified by their [`AssetId`].
///
/// Assets identified by [`AssetId::Index`] will be stored in a "dense" vec-like storage. This is more efficient, but it means that
/// the assets can only be identified at runtime. This is the default behavior.
///
/// Assets identified by [`AssetId::Uuid`] will be stored in a hashmap. This is less efficient, but it means that the assets can be referenced
/// at compile time.
///
/// This tracks (and queues) [`AssetEvent`] events whenever changes to the collection occur.
/// To check whether the asset used by a given component has changed (due to a change in the handle or the underlying asset)
/// use the [`AssetChanged`](crate::asset_changed::AssetChanged) query filter.
#[derive(Resource)]
pub struct Assets<A: Asset> {
dense_storage: DenseAssetStorage<A>,
hash_map: HashMap<Uuid, A>,
handle_provider: AssetHandleProvider,
queued_events: Vec<AssetEvent<A>>,
/// Assets managed by the `Assets` struct with live strong `Handle`s
/// originating from `get_strong_handle`.
duplicate_handles: HashMap<AssetId<A>, u16>,
}
impl<A: Asset> Default for Assets<A> {
fn default() -> Self {
let dense_storage = DenseAssetStorage::default();
let handle_provider =
AssetHandleProvider::new(TypeId::of::<A>(), dense_storage.get_index_allocator());
Self {
dense_storage,
handle_provider,
hash_map: Default::default(),
queued_events: Default::default(),
duplicate_handles: Default::default(),
}
}
}
impl<A: Asset> Assets<A> {
/// Retrieves an [`AssetHandleProvider`] capable of reserving new [`Handle`] values for assets that will be stored in this
/// collection.
pub fn get_handle_provider(&self) -> AssetHandleProvider {
self.handle_provider.clone()
}
/// Reserves a new [`Handle`] for an asset that will be stored in this collection.
pub fn reserve_handle(&self) -> Handle<A> {
self.handle_provider.reserve_handle().typed::<A>()
}
/// Inserts the given `asset`, identified by the given `id`. If an asset already exists for `id`, it will be replaced.
pub fn insert(&mut self, id: impl Into<AssetId<A>>, asset: A) {
match id.into() {
AssetId::Index { index, .. } => {
self.insert_with_index(index, asset).unwrap();
}
AssetId::Uuid { uuid } => {
self.insert_with_uuid(uuid, asset);
}
}
}
/// Retrieves an [`Asset`] stored for the given `id` if it exists. If it does not exist, it will be inserted using `insert_fn`.
// PERF: Optimize this or remove it
pub fn get_or_insert_with(
&mut self,
id: impl Into<AssetId<A>>,
insert_fn: impl FnOnce() -> A,
) -> &mut A {
let id: AssetId<A> = id.into();
if self.get(id).is_none() {
self.insert(id, insert_fn());
}
self.get_mut(id).unwrap()
}
/// Returns `true` if the `id` exists in this collection. Otherwise it returns `false`.
pub fn contains(&self, id: impl Into<AssetId<A>>) -> bool {
match id.into() {
AssetId::Index { index, .. } => self.dense_storage.get(index).is_some(),
AssetId::Uuid { uuid } => self.hash_map.contains_key(&uuid),
}
}
pub(crate) fn insert_with_uuid(&mut self, uuid: Uuid, asset: A) -> Option<A> {
let result = self.hash_map.insert(uuid, asset);
if result.is_some() {
self.queued_events
.push(AssetEvent::Modified { id: uuid.into() });
} else {
self.queued_events
.push(AssetEvent::Added { id: uuid.into() });
}
result
}
pub(crate) fn insert_with_index(
&mut self,
index: AssetIndex,
asset: A,
) -> Result<bool, InvalidGenerationError> {
let replaced = self.dense_storage.insert(index, asset)?;
if replaced {
self.queued_events
.push(AssetEvent::Modified { id: index.into() });
} else {
self.queued_events
.push(AssetEvent::Added { id: index.into() });
}
Ok(replaced)
}
/// Adds the given `asset` and allocates a new strong [`Handle`] for it.
#[inline]
pub fn add(&mut self, asset: impl Into<A>) -> Handle<A> {
let index = self.dense_storage.allocator.reserve();
self.insert_with_index(index, asset.into()).unwrap();
Handle::Strong(
self.handle_provider
.get_handle(index.into(), false, None, None),
)
}
/// Upgrade an `AssetId` into a strong `Handle` that will prevent asset drop.
///
/// Returns `None` if the provided `id` is not part of this `Assets` collection.
/// For example, it may have been dropped earlier.
#[inline]
pub fn get_strong_handle(&mut self, id: AssetId<A>) -> Option<Handle<A>> {
if !self.contains(id) {
return None;
}
*self.duplicate_handles.entry(id).or_insert(0) += 1;
let index = match id {
AssetId::Index { index, .. } => index.into(),
AssetId::Uuid { uuid } => uuid.into(),
};
Some(Handle::Strong(
self.handle_provider.get_handle(index, false, None, None),
))
}
/// Retrieves a reference to the [`Asset`] with the given `id`, if it exists.
/// Note that this supports anything that implements `Into<AssetId<A>>`, which includes [`Handle`] and [`AssetId`].
#[inline]
pub fn get(&self, id: impl Into<AssetId<A>>) -> Option<&A> {
match id.into() {
AssetId::Index { index, .. } => self.dense_storage.get(index),
AssetId::Uuid { uuid } => self.hash_map.get(&uuid),
}
}
/// Retrieves a mutable reference to the [`Asset`] with the given `id`, if it exists.
/// Note that this supports anything that implements `Into<AssetId<A>>`, which includes [`Handle`] and [`AssetId`].
#[inline]
pub fn get_mut(&mut self, id: impl Into<AssetId<A>>) -> Option<&mut A> {
let id: AssetId<A> = id.into();
let result = match id {
AssetId::Index { index, .. } => self.dense_storage.get_mut(index),
AssetId::Uuid { uuid } => self.hash_map.get_mut(&uuid),
};
if result.is_some() {
self.queued_events.push(AssetEvent::Modified { id });
}
result
}
/// Removes (and returns) the [`Asset`] with the given `id`, if it exists.
/// Note that this supports anything that implements `Into<AssetId<A>>`, which includes [`Handle`] and [`AssetId`].
pub fn remove(&mut self, id: impl Into<AssetId<A>>) -> Option<A> {
let id: AssetId<A> = id.into();
let result = self.remove_untracked(id);
if result.is_some() {
self.queued_events.push(AssetEvent::Removed { id });
}
result
}
/// Removes (and returns) the [`Asset`] with the given `id`, if it exists. This skips emitting [`AssetEvent::Removed`].
/// Note that this supports anything that implements `Into<AssetId<A>>`, which includes [`Handle`] and [`AssetId`].
pub fn remove_untracked(&mut self, id: impl Into<AssetId<A>>) -> Option<A> {
let id: AssetId<A> = id.into();
self.duplicate_handles.remove(&id);
match id {
AssetId::Index { index, .. } => self.dense_storage.remove_still_alive(index),
AssetId::Uuid { uuid } => self.hash_map.remove(&uuid),
}
}
/// Removes the [`Asset`] with the given `id`.
pub(crate) fn remove_dropped(&mut self, id: AssetId<A>) {
match self.duplicate_handles.get_mut(&id) {
None => {}
Some(0) => {
self.duplicate_handles.remove(&id);
}
Some(value) => {
*value -= 1;
return;
}
}
let existed = match id {
AssetId::Index { index, .. } => self.dense_storage.remove_dropped(index).is_some(),
AssetId::Uuid { uuid } => self.hash_map.remove(&uuid).is_some(),
};
self.queued_events.push(AssetEvent::Unused { id });
if existed {
self.queued_events.push(AssetEvent::Removed { id });
}
}
/// Returns `true` if there are no assets in this collection.
pub fn is_empty(&self) -> bool {
self.dense_storage.is_empty() && self.hash_map.is_empty()
}
/// Returns the number of assets currently stored in the collection.
pub fn len(&self) -> usize {
self.dense_storage.len() + self.hash_map.len()
}
/// Returns an iterator over the [`AssetId`] of every [`Asset`] stored in this collection.
pub fn ids(&self) -> impl Iterator<Item = AssetId<A>> + '_ {
self.dense_storage
.ids()
.chain(self.hash_map.keys().map(|uuid| AssetId::from(*uuid)))
}
/// Returns an iterator over the [`AssetId`] and [`Asset`] ref of every asset in this collection.
// PERF: this could be accelerated if we implement a skip list. Consider the cost/benefits
pub fn iter(&self) -> impl Iterator<Item = (AssetId<A>, &A)> {
self.dense_storage
.storage
.iter()
.enumerate()
.filter_map(|(i, v)| match v {
Entry::None => None,
Entry::Some { value, generation } => value.as_ref().map(|v| {
let id = AssetId::Index {
index: AssetIndex {
generation: *generation,
index: i as u32,
},
marker: PhantomData,
};
(id, v)
}),
})
.chain(
self.hash_map
.iter()
.map(|(i, v)| (AssetId::Uuid { uuid: *i }, v)),
)
}
/// Returns an iterator over the [`AssetId`] and mutable [`Asset`] ref of every asset in this collection.
// PERF: this could be accelerated if we implement a skip list. Consider the cost/benefits
pub fn iter_mut(&mut self) -> AssetsMutIterator<'_, A> {
AssetsMutIterator {
dense_storage: self.dense_storage.storage.iter_mut().enumerate(),
hash_map: self.hash_map.iter_mut(),
queued_events: &mut self.queued_events,
}
}
/// A system that synchronizes the state of assets in this collection with the [`AssetServer`]. This manages
/// [`Handle`] drop events.
pub fn track_assets(mut assets: ResMut<Self>, asset_server: Res<AssetServer>) {
let assets = &mut *assets;
// note that we must hold this lock for the entire duration of this function to ensure
// that `asset_server.load` calls that occur during it block, which ensures that
// re-loads are kicked off appropriately. This function must be "transactional" relative
// to other asset info operations
let mut infos = asset_server.data.infos.write();
while let Ok(drop_event) = assets.handle_provider.drop_receiver.try_recv() {
let id = drop_event.id.typed();
if drop_event.asset_server_managed {
let untyped_id = id.untyped();
// the process_handle_drop call checks whether new handles have been created since the drop event was fired, before removing the asset
if !infos.process_handle_drop(untyped_id) {
// a new handle has been created, or the asset doesn't exist
continue;
}
}
assets.remove_dropped(id);
}
}
/// A system that applies accumulated asset change events to the [`Events`] resource.
///
/// [`Events`]: bevy_ecs::event::Events
pub(crate) fn asset_events(
mut assets: ResMut<Self>,
mut events: EventWriter<AssetEvent<A>>,
asset_changes: Option<ResMut<AssetChanges<A>>>,
ticks: SystemChangeTick,
) {
use AssetEvent::{Added, LoadedWithDependencies, Modified, Removed};
if let Some(mut asset_changes) = asset_changes {
for new_event in &assets.queued_events {
match new_event {
Removed { id } | AssetEvent::Unused { id } => asset_changes.remove(id),
Added { id } | Modified { id } | LoadedWithDependencies { id } => {
asset_changes.insert(*id, ticks.this_run());
}
};
}
}
events.write_batch(assets.queued_events.drain(..));
}
/// A run condition for [`asset_events`]. The system will not run if there are no events to
/// flush.
///
/// [`asset_events`]: Self::asset_events
pub(crate) fn asset_events_condition(assets: Res<Self>) -> bool {
!assets.queued_events.is_empty()
}
}
/// A mutable iterator over [`Assets`].
pub struct AssetsMutIterator<'a, A: Asset> {
queued_events: &'a mut Vec<AssetEvent<A>>,
dense_storage: Enumerate<core::slice::IterMut<'a, Entry<A>>>,
hash_map: bevy_platform::collections::hash_map::IterMut<'a, Uuid, A>,
}
impl<'a, A: Asset> Iterator for AssetsMutIterator<'a, A> {
type Item = (AssetId<A>, &'a mut A);
fn next(&mut self) -> Option<Self::Item> {
for (i, entry) in &mut self.dense_storage {
match entry {
Entry::None => {
continue;
}
Entry::Some { value, generation } => {
let id = AssetId::Index {
index: AssetIndex {
generation: *generation,
index: i as u32,
},
marker: PhantomData,
};
self.queued_events.push(AssetEvent::Modified { id });
if let Some(value) = value {
return Some((id, value));
}
}
}
}
if let Some((key, value)) = self.hash_map.next() {
let id = AssetId::Uuid { uuid: *key };
self.queued_events.push(AssetEvent::Modified { id });
Some((id, value))
} else {
None
}
}
}
/// An error returned when an [`AssetIndex`] has an invalid generation.
#[derive(Error, Debug)]
#[error("AssetIndex {index:?} has an invalid generation. The current generation is: '{current_generation}'.")]
pub struct InvalidGenerationError {
index: AssetIndex,
current_generation: u32,
}
#[cfg(test)]
mod test {
use crate::AssetIndex;
#[test]
fn asset_index_round_trip() {
let asset_index = AssetIndex {
generation: 42,
index: 1337,
};
let roundtripped = AssetIndex::from_bits(asset_index.to_bits());
assert_eq!(asset_index, roundtripped);
}
}

View File

@@ -0,0 +1,51 @@
//! Add methods on `World` to simplify loading assets when all
//! you have is a `World`.
use bevy_ecs::world::World;
use crate::{meta::Settings, Asset, AssetPath, AssetServer, Assets, Handle};
/// An extension trait for methods for working with assets directly from a [`World`].
pub trait DirectAssetAccessExt {
/// Insert an asset similarly to [`Assets::add`].
fn add_asset<A: Asset>(&mut self, asset: impl Into<A>) -> Handle<A>;
/// Load an asset similarly to [`AssetServer::load`].
fn load_asset<'a, A: Asset>(&self, path: impl Into<AssetPath<'a>>) -> Handle<A>;
/// Load an asset with settings, similarly to [`AssetServer::load_with_settings`].
fn load_asset_with_settings<'a, A: Asset, S: Settings>(
&self,
path: impl Into<AssetPath<'a>>,
settings: impl Fn(&mut S) + Send + Sync + 'static,
) -> Handle<A>;
}
impl DirectAssetAccessExt for World {
/// Insert an asset similarly to [`Assets::add`].
///
/// # Panics
/// If `self` doesn't have an [`AssetServer`] resource initialized yet.
fn add_asset<'a, A: Asset>(&mut self, asset: impl Into<A>) -> Handle<A> {
self.resource_mut::<Assets<A>>().add(asset)
}
/// Load an asset similarly to [`AssetServer::load`].
///
/// # Panics
/// If `self` doesn't have an [`AssetServer`] resource initialized yet.
fn load_asset<'a, A: Asset>(&self, path: impl Into<AssetPath<'a>>) -> Handle<A> {
self.resource::<AssetServer>().load(path)
}
/// Load an asset with settings, similarly to [`AssetServer::load_with_settings`].
///
/// # Panics
/// If `self` doesn't have an [`AssetServer`] resource initialized yet.
fn load_asset_with_settings<'a, A: Asset, S: Settings>(
&self,
path: impl Into<AssetPath<'a>>,
settings: impl Fn(&mut S) + Send + Sync + 'static,
) -> Handle<A> {
self.resource::<AssetServer>()
.load_with_settings(path, settings)
}
}

129
vendor/bevy_asset/src/event.rs vendored Normal file
View File

@@ -0,0 +1,129 @@
use crate::{Asset, AssetId, AssetLoadError, AssetPath, UntypedAssetId};
use bevy_ecs::event::Event;
use bevy_reflect::Reflect;
use core::fmt::Debug;
/// An event emitted when a specific [`Asset`] fails to load.
///
/// For an untyped equivalent, see [`UntypedAssetLoadFailedEvent`].
#[derive(Event, Clone, Debug)]
pub struct AssetLoadFailedEvent<A: Asset> {
/// The stable identifier of the asset that failed to load.
pub id: AssetId<A>,
/// The asset path that was attempted.
pub path: AssetPath<'static>,
/// Why the asset failed to load.
pub error: AssetLoadError,
}
impl<A: Asset> AssetLoadFailedEvent<A> {
/// Converts this to an "untyped" / "generic-less" asset error event that stores the type information.
pub fn untyped(&self) -> UntypedAssetLoadFailedEvent {
self.into()
}
}
/// An untyped version of [`AssetLoadFailedEvent`].
#[derive(Event, Clone, Debug)]
pub struct UntypedAssetLoadFailedEvent {
/// The stable identifier of the asset that failed to load.
pub id: UntypedAssetId,
/// The asset path that was attempted.
pub path: AssetPath<'static>,
/// Why the asset failed to load.
pub error: AssetLoadError,
}
impl<A: Asset> From<&AssetLoadFailedEvent<A>> for UntypedAssetLoadFailedEvent {
fn from(value: &AssetLoadFailedEvent<A>) -> Self {
UntypedAssetLoadFailedEvent {
id: value.id.untyped(),
path: value.path.clone(),
error: value.error.clone(),
}
}
}
/// Events that occur for a specific loaded [`Asset`], such as "value changed" events and "dependency" events.
#[expect(missing_docs, reason = "Documenting the id fields is unhelpful.")]
#[derive(Event, Reflect)]
pub enum AssetEvent<A: Asset> {
/// Emitted whenever an [`Asset`] is added.
Added { id: AssetId<A> },
/// Emitted whenever an [`Asset`] value is modified.
Modified { id: AssetId<A> },
/// Emitted whenever an [`Asset`] is removed.
Removed { id: AssetId<A> },
/// Emitted when the last [`super::Handle::Strong`] of an [`Asset`] is dropped.
Unused { id: AssetId<A> },
/// Emitted whenever an [`Asset`] has been fully loaded (including its dependencies and all "recursive dependencies").
LoadedWithDependencies { id: AssetId<A> },
}
impl<A: Asset> AssetEvent<A> {
/// Returns `true` if this event is [`AssetEvent::LoadedWithDependencies`] and matches the given `id`.
pub fn is_loaded_with_dependencies(&self, asset_id: impl Into<AssetId<A>>) -> bool {
matches!(self, AssetEvent::LoadedWithDependencies { id } if *id == asset_id.into())
}
/// Returns `true` if this event is [`AssetEvent::Added`] and matches the given `id`.
pub fn is_added(&self, asset_id: impl Into<AssetId<A>>) -> bool {
matches!(self, AssetEvent::Added { id } if *id == asset_id.into())
}
/// Returns `true` if this event is [`AssetEvent::Modified`] and matches the given `id`.
pub fn is_modified(&self, asset_id: impl Into<AssetId<A>>) -> bool {
matches!(self, AssetEvent::Modified { id } if *id == asset_id.into())
}
/// Returns `true` if this event is [`AssetEvent::Removed`] and matches the given `id`.
pub fn is_removed(&self, asset_id: impl Into<AssetId<A>>) -> bool {
matches!(self, AssetEvent::Removed { id } if *id == asset_id.into())
}
/// Returns `true` if this event is [`AssetEvent::Unused`] and matches the given `id`.
pub fn is_unused(&self, asset_id: impl Into<AssetId<A>>) -> bool {
matches!(self, AssetEvent::Unused { id } if *id == asset_id.into())
}
}
impl<A: Asset> Clone for AssetEvent<A> {
fn clone(&self) -> Self {
*self
}
}
impl<A: Asset> Copy for AssetEvent<A> {}
impl<A: Asset> Debug for AssetEvent<A> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Added { id } => f.debug_struct("Added").field("id", id).finish(),
Self::Modified { id } => f.debug_struct("Modified").field("id", id).finish(),
Self::Removed { id } => f.debug_struct("Removed").field("id", id).finish(),
Self::Unused { id } => f.debug_struct("Unused").field("id", id).finish(),
Self::LoadedWithDependencies { id } => f
.debug_struct("LoadedWithDependencies")
.field("id", id)
.finish(),
}
}
}
impl<A: Asset> PartialEq for AssetEvent<A> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Added { id: l_id }, Self::Added { id: r_id })
| (Self::Modified { id: l_id }, Self::Modified { id: r_id })
| (Self::Removed { id: l_id }, Self::Removed { id: r_id })
| (Self::Unused { id: l_id }, Self::Unused { id: r_id })
| (
Self::LoadedWithDependencies { id: l_id },
Self::LoadedWithDependencies { id: r_id },
) => l_id == r_id,
_ => false,
}
}
}
impl<A: Asset> Eq for AssetEvent<A> {}

16
vendor/bevy_asset/src/folder.rs vendored Normal file
View File

@@ -0,0 +1,16 @@
use alloc::vec::Vec;
use crate::{Asset, UntypedHandle};
use bevy_reflect::TypePath;
/// A "loaded folder" containing handles for all assets stored in a given [`AssetPath`].
///
/// This is produced by [`AssetServer::load_folder`](crate::prelude::AssetServer::load_folder).
///
/// [`AssetPath`]: crate::AssetPath
#[derive(Asset, TypePath)]
pub struct LoadedFolder {
/// The handles of all assets stored in the folder.
#[dependency]
pub handles: Vec<UntypedHandle>,
}

723
vendor/bevy_asset/src/handle.rs vendored Normal file
View File

@@ -0,0 +1,723 @@
use crate::{
meta::MetaTransform, Asset, AssetId, AssetIndexAllocator, AssetPath, InternalAssetId,
UntypedAssetId,
};
use alloc::sync::Arc;
use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath};
use core::{
any::TypeId,
hash::{Hash, Hasher},
};
use crossbeam_channel::{Receiver, Sender};
use disqualified::ShortName;
use thiserror::Error;
use uuid::Uuid;
/// Provides [`Handle`] and [`UntypedHandle`] _for a specific asset type_.
/// This should _only_ be used for one specific asset type.
#[derive(Clone)]
pub struct AssetHandleProvider {
pub(crate) allocator: Arc<AssetIndexAllocator>,
pub(crate) drop_sender: Sender<DropEvent>,
pub(crate) drop_receiver: Receiver<DropEvent>,
pub(crate) type_id: TypeId,
}
#[derive(Debug)]
pub(crate) struct DropEvent {
pub(crate) id: InternalAssetId,
pub(crate) asset_server_managed: bool,
}
impl AssetHandleProvider {
pub(crate) fn new(type_id: TypeId, allocator: Arc<AssetIndexAllocator>) -> Self {
let (drop_sender, drop_receiver) = crossbeam_channel::unbounded();
Self {
type_id,
allocator,
drop_sender,
drop_receiver,
}
}
/// Reserves a new strong [`UntypedHandle`] (with a new [`UntypedAssetId`]). The stored [`Asset`] [`TypeId`] in the
/// [`UntypedHandle`] will match the [`Asset`] [`TypeId`] assigned to this [`AssetHandleProvider`].
pub fn reserve_handle(&self) -> UntypedHandle {
let index = self.allocator.reserve();
UntypedHandle::Strong(self.get_handle(InternalAssetId::Index(index), false, None, None))
}
pub(crate) fn get_handle(
&self,
id: InternalAssetId,
asset_server_managed: bool,
path: Option<AssetPath<'static>>,
meta_transform: Option<MetaTransform>,
) -> Arc<StrongHandle> {
Arc::new(StrongHandle {
id: id.untyped(self.type_id),
drop_sender: self.drop_sender.clone(),
meta_transform,
path,
asset_server_managed,
})
}
pub(crate) fn reserve_handle_internal(
&self,
asset_server_managed: bool,
path: Option<AssetPath<'static>>,
meta_transform: Option<MetaTransform>,
) -> Arc<StrongHandle> {
let index = self.allocator.reserve();
self.get_handle(
InternalAssetId::Index(index),
asset_server_managed,
path,
meta_transform,
)
}
}
/// The internal "strong" [`Asset`] handle storage for [`Handle::Strong`] and [`UntypedHandle::Strong`]. When this is dropped,
/// the [`Asset`] will be freed. It also stores some asset metadata for easy access from handles.
#[derive(TypePath)]
pub struct StrongHandle {
pub(crate) id: UntypedAssetId,
pub(crate) asset_server_managed: bool,
pub(crate) path: Option<AssetPath<'static>>,
/// Modifies asset meta. This is stored on the handle because it is:
/// 1. configuration tied to the lifetime of a specific asset load
/// 2. configuration that must be repeatable when the asset is hot-reloaded
pub(crate) meta_transform: Option<MetaTransform>,
pub(crate) drop_sender: Sender<DropEvent>,
}
impl Drop for StrongHandle {
fn drop(&mut self) {
let _ = self.drop_sender.send(DropEvent {
id: self.id.internal(),
asset_server_managed: self.asset_server_managed,
});
}
}
impl core::fmt::Debug for StrongHandle {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("StrongHandle")
.field("id", &self.id)
.field("asset_server_managed", &self.asset_server_managed)
.field("path", &self.path)
.field("drop_sender", &self.drop_sender)
.finish()
}
}
/// A handle to a specific [`Asset`] of type `A`. Handles act as abstract "references" to
/// assets, whose data are stored in the [`Assets<A>`](crate::prelude::Assets) resource,
/// avoiding the need to store multiple copies of the same data.
///
/// If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept
/// alive until the [`Handle`] is dropped. If a [`Handle`] is [`Handle::Weak`], it does not necessarily reference a live [`Asset`],
/// nor will it keep assets alive.
///
/// Modifying a *handle* will change which existing asset is referenced, but modifying the *asset*
/// (by mutating the [`Assets`](crate::prelude::Assets) resource) will change the asset for all handles referencing it.
///
/// [`Handle`] can be cloned. If a [`Handle::Strong`] is cloned, the referenced [`Asset`] will not be freed until _all_ instances
/// of the [`Handle`] are dropped.
///
/// [`Handle::Strong`], via [`StrongHandle`] also provides access to useful [`Asset`] metadata, such as the [`AssetPath`] (if it exists).
#[derive(Reflect)]
#[reflect(Default, Debug, Hash, PartialEq, Clone)]
pub enum Handle<A: Asset> {
/// A "strong" reference to a live (or loading) [`Asset`]. If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept
/// alive until the [`Handle`] is dropped. Strong handles also provide access to additional asset metadata.
Strong(Arc<StrongHandle>),
/// A "weak" reference to an [`Asset`]. If a [`Handle`] is [`Handle::Weak`], it does not necessarily reference a live [`Asset`],
/// nor will it keep assets alive.
Weak(AssetId<A>),
}
impl<T: Asset> Clone for Handle<T> {
fn clone(&self) -> Self {
match self {
Handle::Strong(handle) => Handle::Strong(handle.clone()),
Handle::Weak(id) => Handle::Weak(*id),
}
}
}
impl<A: Asset> Handle<A> {
/// Create a new [`Handle::Weak`] with the given [`u128`] encoding of a [`Uuid`].
#[deprecated(
since = "0.16.0",
note = "use the `weak_handle!` macro with a UUID string instead"
)]
pub const fn weak_from_u128(value: u128) -> Self {
Handle::Weak(AssetId::Uuid {
uuid: Uuid::from_u128(value),
})
}
/// Returns the [`AssetId`] of this [`Asset`].
#[inline]
pub fn id(&self) -> AssetId<A> {
match self {
Handle::Strong(handle) => handle.id.typed_unchecked(),
Handle::Weak(id) => *id,
}
}
/// Returns the path if this is (1) a strong handle and (2) the asset has a path
#[inline]
pub fn path(&self) -> Option<&AssetPath<'static>> {
match self {
Handle::Strong(handle) => handle.path.as_ref(),
Handle::Weak(_) => None,
}
}
/// Returns `true` if this is a weak handle.
#[inline]
pub fn is_weak(&self) -> bool {
matches!(self, Handle::Weak(_))
}
/// Returns `true` if this is a strong handle.
#[inline]
pub fn is_strong(&self) -> bool {
matches!(self, Handle::Strong(_))
}
/// Creates a [`Handle::Weak`] clone of this [`Handle`], which will not keep the referenced [`Asset`] alive.
#[inline]
pub fn clone_weak(&self) -> Self {
match self {
Handle::Strong(handle) => Handle::Weak(handle.id.typed_unchecked::<A>()),
Handle::Weak(id) => Handle::Weak(*id),
}
}
/// Converts this [`Handle`] to an "untyped" / "generic-less" [`UntypedHandle`], which stores the [`Asset`] type information
/// _inside_ [`UntypedHandle`]. This will return [`UntypedHandle::Strong`] for [`Handle::Strong`] and [`UntypedHandle::Weak`] for
/// [`Handle::Weak`].
#[inline]
pub fn untyped(self) -> UntypedHandle {
self.into()
}
}
impl<A: Asset> Default for Handle<A> {
fn default() -> Self {
Handle::Weak(AssetId::default())
}
}
impl<A: Asset> core::fmt::Debug for Handle<A> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let name = ShortName::of::<A>();
match self {
Handle::Strong(handle) => {
write!(
f,
"StrongHandle<{name}>{{ id: {:?}, path: {:?} }}",
handle.id.internal(),
handle.path
)
}
Handle::Weak(id) => write!(f, "WeakHandle<{name}>({:?})", id.internal()),
}
}
}
impl<A: Asset> Hash for Handle<A> {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.id().hash(state);
}
}
impl<A: Asset> PartialOrd for Handle<A> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<A: Asset> Ord for Handle<A> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.id().cmp(&other.id())
}
}
impl<A: Asset> PartialEq for Handle<A> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.id() == other.id()
}
}
impl<A: Asset> Eq for Handle<A> {}
impl<A: Asset> From<&Handle<A>> for AssetId<A> {
#[inline]
fn from(value: &Handle<A>) -> Self {
value.id()
}
}
impl<A: Asset> From<&Handle<A>> for UntypedAssetId {
#[inline]
fn from(value: &Handle<A>) -> Self {
value.id().into()
}
}
impl<A: Asset> From<&mut Handle<A>> for AssetId<A> {
#[inline]
fn from(value: &mut Handle<A>) -> Self {
value.id()
}
}
impl<A: Asset> From<&mut Handle<A>> for UntypedAssetId {
#[inline]
fn from(value: &mut Handle<A>) -> Self {
value.id().into()
}
}
/// An untyped variant of [`Handle`], which internally stores the [`Asset`] type information at runtime
/// as a [`TypeId`] instead of encoding it in the compile-time type. This allows handles across [`Asset`] types
/// to be stored together and compared.
///
/// See [`Handle`] for more information.
#[derive(Clone)]
pub enum UntypedHandle {
/// A strong handle, which will keep the referenced [`Asset`] alive until all strong handles are dropped.
Strong(Arc<StrongHandle>),
/// A weak handle, which does not keep the referenced [`Asset`] alive.
Weak(UntypedAssetId),
}
impl UntypedHandle {
/// Returns the [`UntypedAssetId`] for the referenced asset.
#[inline]
pub fn id(&self) -> UntypedAssetId {
match self {
UntypedHandle::Strong(handle) => handle.id,
UntypedHandle::Weak(id) => *id,
}
}
/// Returns the path if this is (1) a strong handle and (2) the asset has a path
#[inline]
pub fn path(&self) -> Option<&AssetPath<'static>> {
match self {
UntypedHandle::Strong(handle) => handle.path.as_ref(),
UntypedHandle::Weak(_) => None,
}
}
/// Creates an [`UntypedHandle::Weak`] clone of this [`UntypedHandle`], which will not keep the referenced [`Asset`] alive.
#[inline]
pub fn clone_weak(&self) -> UntypedHandle {
match self {
UntypedHandle::Strong(handle) => UntypedHandle::Weak(handle.id),
UntypedHandle::Weak(id) => UntypedHandle::Weak(*id),
}
}
/// Returns the [`TypeId`] of the referenced [`Asset`].
#[inline]
pub fn type_id(&self) -> TypeId {
match self {
UntypedHandle::Strong(handle) => handle.id.type_id(),
UntypedHandle::Weak(id) => id.type_id(),
}
}
/// Converts to a typed Handle. This _will not check if the target Handle type matches_.
#[inline]
pub fn typed_unchecked<A: Asset>(self) -> Handle<A> {
match self {
UntypedHandle::Strong(handle) => Handle::Strong(handle),
UntypedHandle::Weak(id) => Handle::Weak(id.typed_unchecked::<A>()),
}
}
/// Converts to a typed Handle. This will check the type when compiled with debug asserts, but it
/// _will not check if the target Handle type matches in release builds_. Use this as an optimization
/// when you want some degree of validation at dev-time, but you are also very certain that the type
/// actually matches.
#[inline]
pub fn typed_debug_checked<A: Asset>(self) -> Handle<A> {
debug_assert_eq!(
self.type_id(),
TypeId::of::<A>(),
"The target Handle<A>'s TypeId does not match the TypeId of this UntypedHandle"
);
match self {
UntypedHandle::Strong(handle) => Handle::Strong(handle),
UntypedHandle::Weak(id) => Handle::Weak(id.typed_unchecked::<A>()),
}
}
/// Converts to a typed Handle. This will panic if the internal [`TypeId`] does not match the given asset type `A`
#[inline]
pub fn typed<A: Asset>(self) -> Handle<A> {
let Ok(handle) = self.try_typed() else {
panic!(
"The target Handle<{}>'s TypeId does not match the TypeId of this UntypedHandle",
core::any::type_name::<A>()
)
};
handle
}
/// Converts to a typed Handle. This will panic if the internal [`TypeId`] does not match the given asset type `A`
#[inline]
pub fn try_typed<A: Asset>(self) -> Result<Handle<A>, UntypedAssetConversionError> {
Handle::try_from(self)
}
/// The "meta transform" for the strong handle. This will only be [`Some`] if the handle is strong and there is a meta transform
/// associated with it.
#[inline]
pub fn meta_transform(&self) -> Option<&MetaTransform> {
match self {
UntypedHandle::Strong(handle) => handle.meta_transform.as_ref(),
UntypedHandle::Weak(_) => None,
}
}
}
impl PartialEq for UntypedHandle {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.id() == other.id() && self.type_id() == other.type_id()
}
}
impl Eq for UntypedHandle {}
impl Hash for UntypedHandle {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.id().hash(state);
}
}
impl core::fmt::Debug for UntypedHandle {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
UntypedHandle::Strong(handle) => {
write!(
f,
"StrongHandle{{ type_id: {:?}, id: {:?}, path: {:?} }}",
handle.id.type_id(),
handle.id.internal(),
handle.path
)
}
UntypedHandle::Weak(id) => write!(
f,
"WeakHandle{{ type_id: {:?}, id: {:?} }}",
id.type_id(),
id.internal()
),
}
}
}
impl PartialOrd for UntypedHandle {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
if self.type_id() == other.type_id() {
self.id().partial_cmp(&other.id())
} else {
None
}
}
}
impl From<&UntypedHandle> for UntypedAssetId {
#[inline]
fn from(value: &UntypedHandle) -> Self {
value.id()
}
}
// Cross Operations
impl<A: Asset> PartialEq<UntypedHandle> for Handle<A> {
#[inline]
fn eq(&self, other: &UntypedHandle) -> bool {
TypeId::of::<A>() == other.type_id() && self.id() == other.id()
}
}
impl<A: Asset> PartialEq<Handle<A>> for UntypedHandle {
#[inline]
fn eq(&self, other: &Handle<A>) -> bool {
other.eq(self)
}
}
impl<A: Asset> PartialOrd<UntypedHandle> for Handle<A> {
#[inline]
fn partial_cmp(&self, other: &UntypedHandle) -> Option<core::cmp::Ordering> {
if TypeId::of::<A>() != other.type_id() {
None
} else {
self.id().partial_cmp(&other.id())
}
}
}
impl<A: Asset> PartialOrd<Handle<A>> for UntypedHandle {
#[inline]
fn partial_cmp(&self, other: &Handle<A>) -> Option<core::cmp::Ordering> {
Some(other.partial_cmp(self)?.reverse())
}
}
impl<A: Asset> From<Handle<A>> for UntypedHandle {
fn from(value: Handle<A>) -> Self {
match value {
Handle::Strong(handle) => UntypedHandle::Strong(handle),
Handle::Weak(id) => UntypedHandle::Weak(id.into()),
}
}
}
impl<A: Asset> TryFrom<UntypedHandle> for Handle<A> {
type Error = UntypedAssetConversionError;
fn try_from(value: UntypedHandle) -> Result<Self, Self::Error> {
let found = value.type_id();
let expected = TypeId::of::<A>();
if found != expected {
return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found });
}
match value {
UntypedHandle::Strong(handle) => Ok(Handle::Strong(handle)),
UntypedHandle::Weak(id) => {
let Ok(id) = id.try_into() else {
return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found });
};
Ok(Handle::Weak(id))
}
}
}
}
/// Creates a weak [`Handle`] from a string literal containing a UUID.
///
/// # Examples
///
/// ```
/// # use bevy_asset::{Handle, weak_handle};
/// # type Shader = ();
/// const SHADER: Handle<Shader> = weak_handle!("1347c9b7-c46a-48e7-b7b8-023a354b7cac");
/// ```
#[macro_export]
macro_rules! weak_handle {
($uuid:expr) => {{
$crate::Handle::Weak($crate::AssetId::Uuid {
uuid: $crate::uuid::uuid!($uuid),
})
}};
}
/// Errors preventing the conversion of to/from an [`UntypedHandle`] and a [`Handle`].
#[derive(Error, Debug, PartialEq, Clone)]
#[non_exhaustive]
pub enum UntypedAssetConversionError {
/// Caused when trying to convert an [`UntypedHandle`] into a [`Handle`] of the wrong type.
#[error(
"This UntypedHandle is for {found:?} and cannot be converted into a Handle<{expected:?}>"
)]
TypeIdMismatch {
/// The expected [`TypeId`] of the [`Handle`] being converted to.
expected: TypeId,
/// The [`TypeId`] of the [`UntypedHandle`] being converted from.
found: TypeId,
},
}
#[cfg(test)]
mod tests {
use alloc::boxed::Box;
use bevy_platform::hash::FixedHasher;
use bevy_reflect::PartialReflect;
use core::hash::BuildHasher;
use super::*;
type TestAsset = ();
const UUID_1: Uuid = Uuid::from_u128(123);
const UUID_2: Uuid = Uuid::from_u128(456);
/// Simple utility to directly hash a value using a fixed hasher
fn hash<T: Hash>(data: &T) -> u64 {
FixedHasher.hash_one(data)
}
/// Typed and Untyped `Handles` should be equivalent to each other and themselves
#[test]
fn equality() {
let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
let untyped = UntypedAssetId::Uuid {
type_id: TypeId::of::<TestAsset>(),
uuid: UUID_1,
};
let typed = Handle::Weak(typed);
let untyped = UntypedHandle::Weak(untyped);
assert_eq!(
Ok(typed.clone()),
Handle::<TestAsset>::try_from(untyped.clone())
);
assert_eq!(UntypedHandle::from(typed.clone()), untyped);
assert_eq!(typed, untyped);
}
/// Typed and Untyped `Handles` should be orderable amongst each other and themselves
#[test]
#[expect(
clippy::cmp_owned,
reason = "This lints on the assertion that a typed handle converted to an untyped handle maintains its ordering compared to an untyped handle. While the conversion would normally be useless, we need to ensure that converted handles maintain their ordering, making the conversion necessary here."
)]
fn ordering() {
assert!(UUID_1 < UUID_2);
let typed_1 = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
let typed_2 = AssetId::<TestAsset>::Uuid { uuid: UUID_2 };
let untyped_1 = UntypedAssetId::Uuid {
type_id: TypeId::of::<TestAsset>(),
uuid: UUID_1,
};
let untyped_2 = UntypedAssetId::Uuid {
type_id: TypeId::of::<TestAsset>(),
uuid: UUID_2,
};
let typed_1 = Handle::Weak(typed_1);
let typed_2 = Handle::Weak(typed_2);
let untyped_1 = UntypedHandle::Weak(untyped_1);
let untyped_2 = UntypedHandle::Weak(untyped_2);
assert!(typed_1 < typed_2);
assert!(untyped_1 < untyped_2);
assert!(UntypedHandle::from(typed_1.clone()) < untyped_2);
assert!(untyped_1 < UntypedHandle::from(typed_2.clone()));
assert!(Handle::<TestAsset>::try_from(untyped_1.clone()).unwrap() < typed_2);
assert!(typed_1 < Handle::<TestAsset>::try_from(untyped_2.clone()).unwrap());
assert!(typed_1 < untyped_2);
assert!(untyped_1 < typed_2);
}
/// Typed and Untyped `Handles` should be equivalently hashable to each other and themselves
#[test]
fn hashing() {
let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
let untyped = UntypedAssetId::Uuid {
type_id: TypeId::of::<TestAsset>(),
uuid: UUID_1,
};
let typed = Handle::Weak(typed);
let untyped = UntypedHandle::Weak(untyped);
assert_eq!(
hash(&typed),
hash(&Handle::<TestAsset>::try_from(untyped.clone()).unwrap())
);
assert_eq!(hash(&UntypedHandle::from(typed.clone())), hash(&untyped));
assert_eq!(hash(&typed), hash(&untyped));
}
/// Typed and Untyped `Handles` should be interchangeable
#[test]
fn conversion() {
let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
let untyped = UntypedAssetId::Uuid {
type_id: TypeId::of::<TestAsset>(),
uuid: UUID_1,
};
let typed = Handle::Weak(typed);
let untyped = UntypedHandle::Weak(untyped);
assert_eq!(typed, Handle::try_from(untyped.clone()).unwrap());
assert_eq!(UntypedHandle::from(typed.clone()), untyped);
}
/// `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 bevy_app::App;
use bevy_reflect::FromReflect;
#[derive(Reflect)]
struct MyAsset {
value: u32,
}
impl Asset for MyAsset {}
impl VisitAssetDependencies for MyAsset {
fn visit_dependencies(&self, _visit: &mut impl FnMut(UntypedAssetId)) {}
}
let mut app = App::new();
app.add_plugins(AssetPlugin::default())
.init_asset::<MyAsset>();
let mut assets = app.world_mut().resource_mut::<Assets<MyAsset>>();
let handle: Handle<MyAsset> = assets.add(MyAsset { value: 1 });
match &handle {
Handle::Strong(strong) => {
assert_eq!(
Arc::strong_count(strong),
1,
"Inserting the asset should result in a strong count of 1"
);
let reflected: &dyn Reflect = &handle;
let _cloned_handle: Box<dyn Reflect> = reflected.reflect_clone().unwrap();
assert_eq!(
Arc::strong_count(strong),
2,
"Cloning the handle with reflect should increase the strong count to 2"
);
let dynamic_handle: Box<dyn PartialReflect> = reflected.to_dynamic();
assert_eq!(
Arc::strong_count(strong),
3,
"Converting the handle to a dynamic should increase the strong count to 3"
);
let from_reflect_handle: Handle<MyAsset> =
FromReflect::from_reflect(&*dynamic_handle).unwrap();
assert_eq!(Arc::strong_count(strong), 4, "Converting the reflected value back to a handle should increase the strong count to 4");
assert!(
from_reflect_handle.is_strong(),
"The cloned handle should still be strong"
);
}
_ => panic!("Expected a strong handle"),
}
}
}

519
vendor/bevy_asset/src/id.rs vendored Normal file
View File

@@ -0,0 +1,519 @@
use crate::{Asset, AssetIndex};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use core::{
any::TypeId,
fmt::{Debug, Display},
hash::Hash,
marker::PhantomData,
};
use derive_more::derive::From;
use thiserror::Error;
/// A unique runtime-only identifier for an [`Asset`]. This is cheap to [`Copy`]/[`Clone`] and is not directly tied to the
/// lifetime of the Asset. This means it _can_ point to an [`Asset`] that no longer exists.
///
/// For an identifier tied to the lifetime of an asset, see [`Handle`](`crate::Handle`).
///
/// For an "untyped" / "generic-less" id, see [`UntypedAssetId`].
#[derive(Reflect, Serialize, Deserialize, From)]
#[reflect(Clone, Default, Debug, PartialEq, Hash)]
pub enum AssetId<A: Asset> {
/// A small / efficient runtime identifier that can be used to efficiently look up an asset stored in [`Assets`]. This is
/// the "default" identifier used for assets. The alternative(s) (ex: [`AssetId::Uuid`]) will only be used if assets are
/// explicitly registered that way.
///
/// [`Assets`]: crate::Assets
Index {
/// The unstable, opaque index of the asset.
index: AssetIndex,
/// A marker to store the type information of the asset.
#[reflect(ignore, clone)]
marker: PhantomData<fn() -> A>,
},
/// A stable-across-runs / const asset identifier. This will only be used if an asset is explicitly registered in [`Assets`]
/// with one.
///
/// [`Assets`]: crate::Assets
Uuid {
/// The UUID provided during asset registration.
uuid: Uuid,
},
}
impl<A: Asset> AssetId<A> {
/// The uuid for the default [`AssetId`]. It is valid to assign a value to this in [`Assets`](crate::Assets)
/// and by convention (where appropriate) assets should support this pattern.
pub const DEFAULT_UUID: Uuid = Uuid::from_u128(200809721996911295814598172825939264631);
/// This asset id _should_ never be valid. Assigning a value to this in [`Assets`](crate::Assets) will
/// produce undefined behavior, so don't do it!
pub const INVALID_UUID: Uuid = Uuid::from_u128(108428345662029828789348721013522787528);
/// Returns an [`AssetId`] with [`Self::INVALID_UUID`], which _should_ never be assigned to.
#[inline]
pub const fn invalid() -> Self {
Self::Uuid {
uuid: Self::INVALID_UUID,
}
}
/// Converts this to an "untyped" / "generic-less" [`Asset`] identifier that stores the type information
/// _inside_ the [`UntypedAssetId`].
#[inline]
pub fn untyped(self) -> UntypedAssetId {
self.into()
}
#[inline]
pub(crate) fn internal(self) -> InternalAssetId {
match self {
AssetId::Index { index, .. } => InternalAssetId::Index(index),
AssetId::Uuid { uuid } => InternalAssetId::Uuid(uuid),
}
}
}
impl<A: Asset> Default for AssetId<A> {
fn default() -> Self {
AssetId::Uuid {
uuid: Self::DEFAULT_UUID,
}
}
}
impl<A: Asset> Clone for AssetId<A> {
fn clone(&self) -> Self {
*self
}
}
impl<A: Asset> Copy for AssetId<A> {}
impl<A: Asset> Display for AssetId<A> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Debug::fmt(self, f)
}
}
impl<A: Asset> Debug for AssetId<A> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
AssetId::Index { index, .. } => {
write!(
f,
"AssetId<{}>{{ index: {}, generation: {}}}",
core::any::type_name::<A>(),
index.index,
index.generation
)
}
AssetId::Uuid { uuid } => {
write!(
f,
"AssetId<{}>{{uuid: {}}}",
core::any::type_name::<A>(),
uuid
)
}
}
}
}
impl<A: Asset> Hash for AssetId<A> {
#[inline]
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.internal().hash(state);
TypeId::of::<A>().hash(state);
}
}
impl<A: Asset> PartialEq for AssetId<A> {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.internal().eq(&other.internal())
}
}
impl<A: Asset> Eq for AssetId<A> {}
impl<A: Asset> PartialOrd for AssetId<A> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<A: Asset> Ord for AssetId<A> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.internal().cmp(&other.internal())
}
}
impl<A: Asset> From<AssetIndex> for AssetId<A> {
#[inline]
fn from(value: AssetIndex) -> Self {
Self::Index {
index: value,
marker: PhantomData,
}
}
}
/// An "untyped" / "generic-less" [`Asset`] identifier that behaves much like [`AssetId`], but stores the [`Asset`] type
/// information at runtime instead of compile-time. This increases the size of the type, but it enables storing asset ids
/// across asset types together and enables comparisons between them.
#[derive(Debug, Copy, Clone)]
pub enum UntypedAssetId {
/// A small / efficient runtime identifier that can be used to efficiently look up an asset stored in [`Assets`]. This is
/// the "default" identifier used for assets. The alternative(s) (ex: [`UntypedAssetId::Uuid`]) will only be used if assets are
/// explicitly registered that way.
///
/// [`Assets`]: crate::Assets
Index {
/// An identifier that records the underlying asset type.
type_id: TypeId,
/// The unstable, opaque index of the asset.
index: AssetIndex,
},
/// A stable-across-runs / const asset identifier. This will only be used if an asset is explicitly registered in [`Assets`]
/// with one.
///
/// [`Assets`]: crate::Assets
Uuid {
/// An identifier that records the underlying asset type.
type_id: TypeId,
/// The UUID provided during asset registration.
uuid: Uuid,
},
}
impl UntypedAssetId {
/// Converts this to a "typed" [`AssetId`] without checking the stored type to see if it matches the target `A` [`Asset`] type.
/// This should only be called if you are _absolutely certain_ the asset type matches the stored type. And even then, you should
/// consider using [`UntypedAssetId::typed_debug_checked`] instead.
#[inline]
pub fn typed_unchecked<A: Asset>(self) -> AssetId<A> {
match self {
UntypedAssetId::Index { index, .. } => AssetId::Index {
index,
marker: PhantomData,
},
UntypedAssetId::Uuid { uuid, .. } => AssetId::Uuid { uuid },
}
}
/// Converts this to a "typed" [`AssetId`]. When compiled in debug-mode it will check to see if the stored type
/// matches the target `A` [`Asset`] type. When compiled in release-mode, this check will be skipped.
///
/// # Panics
///
/// Panics if compiled in debug mode and the [`TypeId`] of `A` does not match the stored [`TypeId`].
#[inline]
pub fn typed_debug_checked<A: Asset>(self) -> AssetId<A> {
debug_assert_eq!(
self.type_id(),
TypeId::of::<A>(),
"The target AssetId<{}>'s TypeId does not match the TypeId of this UntypedAssetId",
core::any::type_name::<A>()
);
self.typed_unchecked()
}
/// Converts this to a "typed" [`AssetId`].
///
/// # Panics
///
/// Panics if the [`TypeId`] of `A` does not match the stored type id.
#[inline]
pub fn typed<A: Asset>(self) -> AssetId<A> {
let Ok(id) = self.try_typed() else {
panic!(
"The target AssetId<{}>'s TypeId does not match the TypeId of this UntypedAssetId",
core::any::type_name::<A>()
)
};
id
}
/// Try to convert this to a "typed" [`AssetId`].
#[inline]
pub fn try_typed<A: Asset>(self) -> Result<AssetId<A>, UntypedAssetIdConversionError> {
AssetId::try_from(self)
}
/// Returns the stored [`TypeId`] of the referenced [`Asset`].
#[inline]
pub fn type_id(&self) -> TypeId {
match self {
UntypedAssetId::Index { type_id, .. } | UntypedAssetId::Uuid { type_id, .. } => {
*type_id
}
}
}
#[inline]
pub(crate) fn internal(self) -> InternalAssetId {
match self {
UntypedAssetId::Index { index, .. } => InternalAssetId::Index(index),
UntypedAssetId::Uuid { uuid, .. } => InternalAssetId::Uuid(uuid),
}
}
}
impl Display for UntypedAssetId {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let mut writer = f.debug_struct("UntypedAssetId");
match self {
UntypedAssetId::Index { index, type_id } => {
writer
.field("type_id", type_id)
.field("index", &index.index)
.field("generation", &index.generation);
}
UntypedAssetId::Uuid { uuid, type_id } => {
writer.field("type_id", type_id).field("uuid", uuid);
}
}
writer.finish()
}
}
impl PartialEq for UntypedAssetId {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.type_id() == other.type_id() && self.internal().eq(&other.internal())
}
}
impl Eq for UntypedAssetId {}
impl Hash for UntypedAssetId {
#[inline]
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.internal().hash(state);
self.type_id().hash(state);
}
}
impl Ord for UntypedAssetId {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.type_id()
.cmp(&other.type_id())
.then_with(|| self.internal().cmp(&other.internal()))
}
}
impl PartialOrd for UntypedAssetId {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
/// An asset id without static or dynamic types associated with it.
///
/// This exist to support efficient type erased id drop tracking. We
/// could use [`UntypedAssetId`] for this, but the [`TypeId`] is unnecessary.
///
/// Do not _ever_ use this across asset types for comparison.
/// [`InternalAssetId`] contains no type information and will happily collide
/// with indices across types.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, PartialOrd, Ord, From)]
pub(crate) enum InternalAssetId {
Index(AssetIndex),
Uuid(Uuid),
}
impl InternalAssetId {
#[inline]
pub(crate) fn typed<A: Asset>(self) -> AssetId<A> {
match self {
InternalAssetId::Index(index) => AssetId::Index {
index,
marker: PhantomData,
},
InternalAssetId::Uuid(uuid) => AssetId::Uuid { uuid },
}
}
#[inline]
pub(crate) fn untyped(self, type_id: TypeId) -> UntypedAssetId {
match self {
InternalAssetId::Index(index) => UntypedAssetId::Index { index, type_id },
InternalAssetId::Uuid(uuid) => UntypedAssetId::Uuid { uuid, type_id },
}
}
}
// Cross Operations
impl<A: Asset> PartialEq<UntypedAssetId> for AssetId<A> {
#[inline]
fn eq(&self, other: &UntypedAssetId) -> bool {
TypeId::of::<A>() == other.type_id() && self.internal().eq(&other.internal())
}
}
impl<A: Asset> PartialEq<AssetId<A>> for UntypedAssetId {
#[inline]
fn eq(&self, other: &AssetId<A>) -> bool {
other.eq(self)
}
}
impl<A: Asset> PartialOrd<UntypedAssetId> for AssetId<A> {
#[inline]
fn partial_cmp(&self, other: &UntypedAssetId) -> Option<core::cmp::Ordering> {
if TypeId::of::<A>() != other.type_id() {
None
} else {
Some(self.internal().cmp(&other.internal()))
}
}
}
impl<A: Asset> PartialOrd<AssetId<A>> for UntypedAssetId {
#[inline]
fn partial_cmp(&self, other: &AssetId<A>) -> Option<core::cmp::Ordering> {
Some(other.partial_cmp(self)?.reverse())
}
}
impl<A: Asset> From<AssetId<A>> for UntypedAssetId {
#[inline]
fn from(value: AssetId<A>) -> Self {
let type_id = TypeId::of::<A>();
match value {
AssetId::Index { index, .. } => UntypedAssetId::Index { type_id, index },
AssetId::Uuid { uuid } => UntypedAssetId::Uuid { type_id, uuid },
}
}
}
impl<A: Asset> TryFrom<UntypedAssetId> for AssetId<A> {
type Error = UntypedAssetIdConversionError;
#[inline]
fn try_from(value: UntypedAssetId) -> Result<Self, Self::Error> {
let found = value.type_id();
let expected = TypeId::of::<A>();
match value {
UntypedAssetId::Index { index, type_id } if type_id == expected => Ok(AssetId::Index {
index,
marker: PhantomData,
}),
UntypedAssetId::Uuid { uuid, type_id } if type_id == expected => {
Ok(AssetId::Uuid { uuid })
}
_ => Err(UntypedAssetIdConversionError::TypeIdMismatch { expected, found }),
}
}
}
/// Errors preventing the conversion of to/from an [`UntypedAssetId`] and an [`AssetId`].
#[derive(Error, Debug, PartialEq, Clone)]
#[non_exhaustive]
pub enum UntypedAssetIdConversionError {
/// Caused when trying to convert an [`UntypedAssetId`] into an [`AssetId`] of the wrong type.
#[error("This UntypedAssetId is for {found:?} and cannot be converted into an AssetId<{expected:?}>")]
TypeIdMismatch {
/// The [`TypeId`] of the asset that we are trying to convert to.
expected: TypeId,
/// The [`TypeId`] of the asset that we are trying to convert from.
found: TypeId,
},
}
#[cfg(test)]
mod tests {
use super::*;
type TestAsset = ();
const UUID_1: Uuid = Uuid::from_u128(123);
const UUID_2: Uuid = Uuid::from_u128(456);
/// Simple utility to directly hash a value using a fixed hasher
fn hash<T: Hash>(data: &T) -> u64 {
use core::hash::BuildHasher;
bevy_platform::hash::FixedHasher.hash_one(data)
}
/// Typed and Untyped `AssetIds` should be equivalent to each other and themselves
#[test]
fn equality() {
let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
let untyped = UntypedAssetId::Uuid {
type_id: TypeId::of::<TestAsset>(),
uuid: UUID_1,
};
assert_eq!(Ok(typed), AssetId::try_from(untyped));
assert_eq!(UntypedAssetId::from(typed), untyped);
assert_eq!(typed, untyped);
}
/// Typed and Untyped `AssetIds` should be orderable amongst each other and themselves
#[test]
fn ordering() {
assert!(UUID_1 < UUID_2);
let typed_1 = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
let typed_2 = AssetId::<TestAsset>::Uuid { uuid: UUID_2 };
let untyped_1 = UntypedAssetId::Uuid {
type_id: TypeId::of::<TestAsset>(),
uuid: UUID_1,
};
let untyped_2 = UntypedAssetId::Uuid {
type_id: TypeId::of::<TestAsset>(),
uuid: UUID_2,
};
assert!(typed_1 < typed_2);
assert!(untyped_1 < untyped_2);
assert!(UntypedAssetId::from(typed_1) < untyped_2);
assert!(untyped_1 < UntypedAssetId::from(typed_2));
assert!(AssetId::try_from(untyped_1).unwrap() < typed_2);
assert!(typed_1 < AssetId::try_from(untyped_2).unwrap());
assert!(typed_1 < untyped_2);
assert!(untyped_1 < typed_2);
}
/// Typed and Untyped `AssetIds` should be equivalently hashable to each other and themselves
#[test]
fn hashing() {
let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
let untyped = UntypedAssetId::Uuid {
type_id: TypeId::of::<TestAsset>(),
uuid: UUID_1,
};
assert_eq!(
hash(&typed),
hash(&AssetId::<TestAsset>::try_from(untyped).unwrap())
);
assert_eq!(hash(&UntypedAssetId::from(typed)), hash(&untyped));
assert_eq!(hash(&typed), hash(&untyped));
}
/// Typed and Untyped `AssetIds` should be interchangeable
#[test]
fn conversion() {
let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
let untyped = UntypedAssetId::Uuid {
type_id: TypeId::of::<TestAsset>(),
uuid: UUID_1,
};
assert_eq!(Ok(typed), AssetId::try_from(untyped));
assert_eq!(UntypedAssetId::from(typed), untyped);
}
}

94
vendor/bevy_asset/src/io/android.rs vendored Normal file
View File

@@ -0,0 +1,94 @@
use crate::io::{get_meta_path, AssetReader, AssetReaderError, PathStream, Reader, VecReader};
use alloc::{borrow::ToOwned, boxed::Box, ffi::CString, vec::Vec};
use futures_lite::stream;
use std::path::Path;
/// [`AssetReader`] implementation for Android devices, built on top of Android's [`AssetManager`].
///
/// Implementation details:
///
/// - All functions use the [`AssetManager`] to load files.
/// - [`is_directory`](AssetReader::is_directory) tries to open the path
/// as a normal file and treats an error as if the path is a directory.
/// - Watching for changes is not supported. The watcher method will do nothing.
///
/// [AssetManager]: https://developer.android.com/reference/android/content/res/AssetManager
pub struct AndroidAssetReader;
impl AssetReader for AndroidAssetReader {
async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
let asset_manager = bevy_window::ANDROID_APP
.get()
.expect("Bevy must be setup with the #[bevy_main] macro on Android")
.asset_manager();
let mut opened_asset = asset_manager
.open(&CString::new(path.to_str().unwrap()).unwrap())
.ok_or(AssetReaderError::NotFound(path.to_path_buf()))?;
let bytes = opened_asset.buffer()?;
let reader = VecReader::new(bytes.to_vec());
Ok(reader)
}
async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
let meta_path = get_meta_path(path);
let asset_manager = bevy_window::ANDROID_APP
.get()
.expect("Bevy must be setup with the #[bevy_main] macro on Android")
.asset_manager();
let mut opened_asset = asset_manager
.open(&CString::new(meta_path.to_str().unwrap()).unwrap())
.ok_or(AssetReaderError::NotFound(meta_path))?;
let bytes = opened_asset.buffer()?;
let reader = VecReader::new(bytes.to_vec());
Ok(reader)
}
async fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> Result<Box<PathStream>, AssetReaderError> {
let asset_manager = bevy_window::ANDROID_APP
.get()
.expect("Bevy must be setup with the #[bevy_main] macro on Android")
.asset_manager();
let opened_assets_dir = asset_manager
.open_dir(&CString::new(path.to_str().unwrap()).unwrap())
.ok_or(AssetReaderError::NotFound(path.to_path_buf()))?;
let mapped_stream = opened_assets_dir
.filter_map(move |f| {
let file_path = path.join(Path::new(f.to_str().unwrap()));
// filter out meta files as they are not considered assets
if let Some(ext) = file_path.extension().and_then(|e| e.to_str()) {
if ext.eq_ignore_ascii_case("meta") {
return None;
}
}
Some(file_path.to_owned())
})
.collect::<Vec<_>>();
let read_dir: Box<PathStream> = Box::new(stream::iter(mapped_stream));
Ok(read_dir)
}
async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
let asset_manager = bevy_window::ANDROID_APP
.get()
.expect("Bevy must be setup with the #[bevy_main] macro on Android")
.asset_manager();
// HACK: `AssetManager` does not provide a way to check if path
// points to a directory or a file
// `open_dir` succeeds for both files and directories and will only
// fail if the path does not exist at all
// `open` will fail for directories, but it will work for files
// The solution here was to first use `open_dir` to eliminate the case
// when the path does not exist at all, and then to use `open` to
// see if that path is a file or a directory
let cpath = CString::new(path.to_str().unwrap()).unwrap();
let _ = asset_manager
.open_dir(&cpath)
.ok_or(AssetReaderError::NotFound(path.to_path_buf()))?;
Ok(asset_manager.open(&cpath).is_none())
}
}

View File

@@ -0,0 +1,90 @@
use crate::io::{
file::{get_asset_path, get_base_path, new_asset_event_debouncer, FilesystemEventHandler},
memory::Dir,
AssetSourceEvent, AssetWatcher,
};
use alloc::{boxed::Box, sync::Arc, vec::Vec};
use bevy_platform::collections::HashMap;
use core::time::Duration;
use notify_debouncer_full::{notify::RecommendedWatcher, Debouncer, RecommendedCache};
use parking_lot::RwLock;
use std::{
fs::File,
io::{BufReader, Read},
path::{Path, PathBuf},
};
use tracing::warn;
/// A watcher for assets stored in the `embedded` asset source. Embedded assets are assets whose
/// bytes have been embedded into the Rust binary using the [`embedded_asset`](crate::embedded_asset) macro.
/// This watcher will watch for changes to the "source files", read the contents of changed files from the file system
/// and overwrite the initial static bytes of the file embedded in the binary with the new dynamically loaded bytes.
pub struct EmbeddedWatcher {
_watcher: Debouncer<RecommendedWatcher, RecommendedCache>,
}
impl EmbeddedWatcher {
/// Creates a new `EmbeddedWatcher` that watches for changes to the embedded assets in the given `dir`.
pub fn new(
dir: Dir,
root_paths: Arc<RwLock<HashMap<Box<Path>, PathBuf>>>,
sender: crossbeam_channel::Sender<AssetSourceEvent>,
debounce_wait_time: Duration,
) -> Self {
let root = get_base_path();
let handler = EmbeddedEventHandler {
dir,
root: root.clone(),
sender,
root_paths,
last_event: None,
};
let watcher = new_asset_event_debouncer(root, debounce_wait_time, handler).unwrap();
Self { _watcher: watcher }
}
}
impl AssetWatcher for EmbeddedWatcher {}
/// A [`FilesystemEventHandler`] that uses [`EmbeddedAssetRegistry`](crate::io::embedded::EmbeddedAssetRegistry) to hot-reload
/// binary-embedded Rust source files. This will read the contents of changed files from the file system and overwrite
/// the initial static bytes from the file embedded in the binary.
pub(crate) struct EmbeddedEventHandler {
sender: crossbeam_channel::Sender<AssetSourceEvent>,
root_paths: Arc<RwLock<HashMap<Box<Path>, PathBuf>>>,
root: PathBuf,
dir: Dir,
last_event: Option<AssetSourceEvent>,
}
impl FilesystemEventHandler for EmbeddedEventHandler {
fn begin(&mut self) {
self.last_event = None;
}
fn get_path(&self, absolute_path: &Path) -> Option<(PathBuf, bool)> {
let (local_path, is_meta) = get_asset_path(&self.root, absolute_path);
let final_path = self.root_paths.read().get(local_path.as_path())?.clone();
if is_meta {
warn!("Meta file asset hot-reloading is not supported yet: {final_path:?}");
}
Some((final_path, false))
}
fn handle(&mut self, absolute_paths: &[PathBuf], event: AssetSourceEvent) {
if self.last_event.as_ref() != Some(&event) {
if let AssetSourceEvent::ModifiedAsset(path) = &event {
if let Ok(file) = File::open(&absolute_paths[0]) {
let mut reader = BufReader::new(file);
let mut buffer = Vec::new();
// Read file into vector.
if reader.read_to_end(&mut buffer).is_ok() {
self.dir.insert_asset(path, buffer);
}
}
}
self.last_event = Some(event.clone());
self.sender.send(event).unwrap();
}
}
}

451
vendor/bevy_asset/src/io/embedded/mod.rs vendored Normal file
View File

@@ -0,0 +1,451 @@
#[cfg(feature = "embedded_watcher")]
mod embedded_watcher;
#[cfg(feature = "embedded_watcher")]
pub use embedded_watcher::*;
use crate::io::{
memory::{Dir, MemoryAssetReader, Value},
AssetSource, AssetSourceBuilders,
};
use alloc::boxed::Box;
use bevy_ecs::resource::Resource;
use std::path::{Path, PathBuf};
#[cfg(feature = "embedded_watcher")]
use alloc::borrow::ToOwned;
/// The name of the `embedded` [`AssetSource`],
/// as stored in the [`AssetSourceBuilders`] resource.
pub const EMBEDDED: &str = "embedded";
/// A [`Resource`] that manages "rust source files" in a virtual in memory [`Dir`], which is intended
/// to be shared with a [`MemoryAssetReader`].
/// Generally this should not be interacted with directly. The [`embedded_asset`] will populate this.
///
/// [`embedded_asset`]: crate::embedded_asset
#[derive(Resource, Default)]
pub struct EmbeddedAssetRegistry {
dir: Dir,
#[cfg(feature = "embedded_watcher")]
root_paths: alloc::sync::Arc<
parking_lot::RwLock<bevy_platform::collections::HashMap<Box<Path>, PathBuf>>,
>,
}
impl EmbeddedAssetRegistry {
/// Inserts a new asset. `full_path` is the full path (as [`file`] would return for that file, if it was capable of
/// running in a non-rust file). `asset_path` is the path that will be used to identify the asset in the `embedded`
/// [`AssetSource`]. `value` is the bytes that will be returned for the asset. This can be _either_ a `&'static [u8]`
/// or a [`Vec<u8>`](alloc::vec::Vec).
#[cfg_attr(
not(feature = "embedded_watcher"),
expect(
unused_variables,
reason = "The `full_path` argument is not used when `embedded_watcher` is disabled."
)
)]
pub fn insert_asset(&self, full_path: PathBuf, asset_path: &Path, value: impl Into<Value>) {
#[cfg(feature = "embedded_watcher")]
self.root_paths
.write()
.insert(full_path.into(), asset_path.to_owned());
self.dir.insert_asset(asset_path, value);
}
/// Inserts new asset metadata. `full_path` is the full path (as [`file`] would return for that file, if it was capable of
/// running in a non-rust file). `asset_path` is the path that will be used to identify the asset in the `embedded`
/// [`AssetSource`]. `value` is the bytes that will be returned for the asset. This can be _either_ a `&'static [u8]`
/// or a [`Vec<u8>`](alloc::vec::Vec).
#[cfg_attr(
not(feature = "embedded_watcher"),
expect(
unused_variables,
reason = "The `full_path` argument is not used when `embedded_watcher` is disabled."
)
)]
pub fn insert_meta(&self, full_path: &Path, asset_path: &Path, value: impl Into<Value>) {
#[cfg(feature = "embedded_watcher")]
self.root_paths
.write()
.insert(full_path.into(), asset_path.to_owned());
self.dir.insert_meta(asset_path, value);
}
/// Removes an asset stored using `full_path` (the full path as [`file`] would return for that file, if it was capable of
/// running in a non-rust file). If no asset is stored with at `full_path` its a no-op.
/// It returning `Option` contains the originally stored `Data` or `None`.
pub fn remove_asset(&self, full_path: &Path) -> Option<super::memory::Data> {
self.dir.remove_asset(full_path)
}
/// Registers the [`EMBEDDED`] [`AssetSource`] with the given [`AssetSourceBuilders`].
pub fn register_source(&self, sources: &mut AssetSourceBuilders) {
let dir = self.dir.clone();
let processed_dir = self.dir.clone();
#[cfg_attr(
not(feature = "embedded_watcher"),
expect(
unused_mut,
reason = "Variable is only mutated when `embedded_watcher` feature is enabled."
)
)]
let mut source = AssetSource::build()
.with_reader(move || Box::new(MemoryAssetReader { root: dir.clone() }))
.with_processed_reader(move || {
Box::new(MemoryAssetReader {
root: processed_dir.clone(),
})
})
// Note that we only add a processed watch warning because we don't want to warn
// noisily about embedded watching (which is niche) when users enable file watching.
.with_processed_watch_warning(
"Consider enabling the `embedded_watcher` cargo feature.",
);
#[cfg(feature = "embedded_watcher")]
{
let root_paths = self.root_paths.clone();
let dir = self.dir.clone();
let processed_root_paths = self.root_paths.clone();
let processed_dir = self.dir.clone();
source = source
.with_watcher(move |sender| {
Some(Box::new(EmbeddedWatcher::new(
dir.clone(),
root_paths.clone(),
sender,
core::time::Duration::from_millis(300),
)))
})
.with_processed_watcher(move |sender| {
Some(Box::new(EmbeddedWatcher::new(
processed_dir.clone(),
processed_root_paths.clone(),
sender,
core::time::Duration::from_millis(300),
)))
});
}
sources.insert(EMBEDDED, source);
}
}
/// Returns the [`Path`] for a given `embedded` asset.
/// This is used internally by [`embedded_asset`] and can be used to get a [`Path`]
/// that matches the [`AssetPath`](crate::AssetPath) used by that asset.
///
/// [`embedded_asset`]: crate::embedded_asset
#[macro_export]
macro_rules! embedded_path {
($path_str: expr) => {{
embedded_path!("src", $path_str)
}};
($source_path: expr, $path_str: expr) => {{
let crate_name = module_path!().split(':').next().unwrap();
$crate::io::embedded::_embedded_asset_path(
crate_name,
$source_path.as_ref(),
file!().as_ref(),
$path_str.as_ref(),
)
}};
}
/// Implementation detail of `embedded_path`, do not use this!
///
/// Returns an embedded asset path, given:
/// - `crate_name`: name of the crate where the asset is embedded
/// - `src_prefix`: path prefix of the crate's source directory, relative to the workspace root
/// - `file_path`: `std::file!()` path of the source file where `embedded_path!` is called
/// - `asset_path`: path of the embedded asset relative to `file_path`
#[doc(hidden)]
pub fn _embedded_asset_path(
crate_name: &str,
src_prefix: &Path,
file_path: &Path,
asset_path: &Path,
) -> PathBuf {
let mut maybe_parent = file_path.parent();
let after_src = loop {
let Some(parent) = maybe_parent else {
panic!("Failed to find src_prefix {src_prefix:?} in {file_path:?}")
};
if parent.ends_with(src_prefix) {
break file_path.strip_prefix(parent).unwrap();
}
maybe_parent = parent.parent();
};
let asset_path = after_src.parent().unwrap().join(asset_path);
Path::new(crate_name).join(asset_path)
}
/// Creates a new `embedded` asset by embedding the bytes of the given path into the current binary
/// and registering those bytes with the `embedded` [`AssetSource`].
///
/// This accepts the current [`App`](bevy_app::App) as the first parameter and a path `&str` (relative to the current file) as the second.
///
/// By default this will generate an [`AssetPath`] using the following rules:
///
/// 1. Search for the first `$crate_name/src/` in the path and trim to the path past that point.
/// 2. Re-add the current `$crate_name` to the front of the path
///
/// For example, consider the following file structure in the theoretical `bevy_rock` crate, which provides a Bevy [`Plugin`](bevy_app::Plugin)
/// that renders fancy rocks for scenes.
///
/// ```text
/// bevy_rock
/// ├── src
/// │   ├── render
/// │  │ ├── rock.wgsl
/// │  │ └── mod.rs
/// │   └── lib.rs
/// └── Cargo.toml
/// ```
///
/// `rock.wgsl` is a WGSL shader asset that the `bevy_rock` plugin author wants to bundle with their crate. They invoke the following
/// in `bevy_rock/src/render/mod.rs`:
///
/// `embedded_asset!(app, "rock.wgsl")`
///
/// `rock.wgsl` can now be loaded by the [`AssetServer`](crate::AssetServer) with the following path:
///
/// ```no_run
/// # use bevy_asset::{Asset, AssetServer};
/// # use bevy_reflect::TypePath;
/// # let asset_server: AssetServer = panic!();
/// # #[derive(Asset, TypePath)]
/// # struct Shader;
/// let shader = asset_server.load::<Shader>("embedded://bevy_rock/render/rock.wgsl");
/// ```
///
/// Some things to note in the path:
/// 1. The non-default `embedded://` [`AssetSource`]
/// 2. `src` is trimmed from the path
///
/// The default behavior also works for cargo workspaces. Pretend the `bevy_rock` crate now exists in a larger workspace in
/// `$SOME_WORKSPACE/crates/bevy_rock`. The asset path would remain the same, because [`embedded_asset`] searches for the
/// _first instance_ of `bevy_rock/src` in the path.
///
/// For most "standard crate structures" the default works just fine. But for some niche cases (such as cargo examples),
/// the `src` path will not be present. You can override this behavior by adding it as the second argument to [`embedded_asset`]:
///
/// `embedded_asset!(app, "/examples/rock_stuff/", "rock.wgsl")`
///
/// When there are three arguments, the second argument will replace the default `/src/` value. Note that these two are
/// equivalent:
///
/// `embedded_asset!(app, "rock.wgsl")`
/// `embedded_asset!(app, "/src/", "rock.wgsl")`
///
/// This macro uses the [`include_bytes`] macro internally and _will not_ reallocate the bytes.
/// Generally the [`AssetPath`] generated will be predictable, but if your asset isn't
/// available for some reason, you can use the [`embedded_path`] macro to debug.
///
/// Hot-reloading `embedded` assets is supported. Just enable the `embedded_watcher` cargo feature.
///
/// [`AssetPath`]: crate::AssetPath
/// [`embedded_asset`]: crate::embedded_asset
/// [`embedded_path`]: crate::embedded_path
#[macro_export]
macro_rules! embedded_asset {
($app: ident, $path: expr) => {{
$crate::embedded_asset!($app, "src", $path)
}};
($app: ident, $source_path: expr, $path: expr) => {{
let mut embedded = $app
.world_mut()
.resource_mut::<$crate::io::embedded::EmbeddedAssetRegistry>();
let path = $crate::embedded_path!($source_path, $path);
let watched_path = $crate::io::embedded::watched_path(file!(), $path);
embedded.insert_asset(watched_path, &path, include_bytes!($path));
}};
}
/// Returns the path used by the watcher.
#[doc(hidden)]
#[cfg(feature = "embedded_watcher")]
pub fn watched_path(source_file_path: &'static str, asset_path: &'static str) -> PathBuf {
PathBuf::from(source_file_path)
.parent()
.unwrap()
.join(asset_path)
}
/// Returns an empty PathBuf.
#[doc(hidden)]
#[cfg(not(feature = "embedded_watcher"))]
pub fn watched_path(_source_file_path: &'static str, _asset_path: &'static str) -> PathBuf {
PathBuf::from("")
}
/// Loads an "internal" asset by embedding the string stored in the given `path_str` and associates it with the given handle.
#[macro_export]
macro_rules! load_internal_asset {
($app: ident, $handle: expr, $path_str: expr, $loader: expr) => {{
let mut assets = $app.world_mut().resource_mut::<$crate::Assets<_>>();
assets.insert($handle.id(), ($loader)(
include_str!($path_str),
std::path::Path::new(file!())
.parent()
.unwrap()
.join($path_str)
.to_string_lossy()
));
}};
// we can't support params without variadic arguments, so internal assets with additional params can't be hot-reloaded
($app: ident, $handle: ident, $path_str: expr, $loader: expr $(, $param:expr)+) => {{
let mut assets = $app.world_mut().resource_mut::<$crate::Assets<_>>();
assets.insert($handle.id(), ($loader)(
include_str!($path_str),
std::path::Path::new(file!())
.parent()
.unwrap()
.join($path_str)
.to_string_lossy(),
$($param),+
));
}};
}
/// Loads an "internal" binary asset by embedding the bytes stored in the given `path_str` and associates it with the given handle.
#[macro_export]
macro_rules! load_internal_binary_asset {
($app: ident, $handle: expr, $path_str: expr, $loader: expr) => {{
let mut assets = $app.world_mut().resource_mut::<$crate::Assets<_>>();
assets.insert(
$handle.id(),
($loader)(
include_bytes!($path_str).as_ref(),
std::path::Path::new(file!())
.parent()
.unwrap()
.join($path_str)
.to_string_lossy()
.into(),
),
);
}};
}
#[cfg(test)]
mod tests {
use super::{EmbeddedAssetRegistry, _embedded_asset_path};
use std::path::Path;
// Relative paths show up if this macro is being invoked by a local crate.
// In this case we know the relative path is a sub- path of the workspace
// root.
#[test]
fn embedded_asset_path_from_local_crate() {
let asset_path = _embedded_asset_path(
"my_crate",
"src".as_ref(),
"src/foo/plugin.rs".as_ref(),
"the/asset.png".as_ref(),
);
assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png"));
}
// A blank src_path removes the embedded's file path altogether only the
// asset path remains.
#[test]
fn embedded_asset_path_from_local_crate_blank_src_path_questionable() {
let asset_path = _embedded_asset_path(
"my_crate",
"".as_ref(),
"src/foo/some/deep/path/plugin.rs".as_ref(),
"the/asset.png".as_ref(),
);
assert_eq!(asset_path, Path::new("my_crate/the/asset.png"));
}
#[test]
#[should_panic(expected = "Failed to find src_prefix \"NOT-THERE\" in \"src")]
fn embedded_asset_path_from_local_crate_bad_src() {
let _asset_path = _embedded_asset_path(
"my_crate",
"NOT-THERE".as_ref(),
"src/foo/plugin.rs".as_ref(),
"the/asset.png".as_ref(),
);
}
#[test]
fn embedded_asset_path_from_local_example_crate() {
let asset_path = _embedded_asset_path(
"example_name",
"examples/foo".as_ref(),
"examples/foo/example.rs".as_ref(),
"the/asset.png".as_ref(),
);
assert_eq!(asset_path, Path::new("example_name/the/asset.png"));
}
// Absolute paths show up if this macro is being invoked by an external
// dependency, e.g. one that's being checked out from a crates repo or git.
#[test]
fn embedded_asset_path_from_external_crate() {
let asset_path = _embedded_asset_path(
"my_crate",
"src".as_ref(),
"/path/to/crate/src/foo/plugin.rs".as_ref(),
"the/asset.png".as_ref(),
);
assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png"));
}
#[test]
fn embedded_asset_path_from_external_crate_root_src_path() {
let asset_path = _embedded_asset_path(
"my_crate",
"/path/to/crate/src".as_ref(),
"/path/to/crate/src/foo/plugin.rs".as_ref(),
"the/asset.png".as_ref(),
);
assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png"));
}
// Although extraneous slashes are permitted at the end, e.g., "src////",
// one or more slashes at the beginning are not.
#[test]
#[should_panic(expected = "Failed to find src_prefix \"////src\" in")]
fn embedded_asset_path_from_external_crate_extraneous_beginning_slashes() {
let asset_path = _embedded_asset_path(
"my_crate",
"////src".as_ref(),
"/path/to/crate/src/foo/plugin.rs".as_ref(),
"the/asset.png".as_ref(),
);
assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png"));
}
// We don't handle this edge case because it is ambiguous with the
// information currently available to the embedded_path macro.
#[test]
fn embedded_asset_path_from_external_crate_is_ambiguous() {
let asset_path = _embedded_asset_path(
"my_crate",
"src".as_ref(),
"/path/to/.cargo/registry/src/crate/src/src/plugin.rs".as_ref(),
"the/asset.png".as_ref(),
);
// Really, should be "my_crate/src/the/asset.png"
assert_eq!(asset_path, Path::new("my_crate/the/asset.png"));
}
#[test]
fn remove_embedded_asset() {
let reg = EmbeddedAssetRegistry::default();
let path = std::path::PathBuf::from("a/b/asset.png");
reg.insert_asset(path.clone(), &path, &[]);
assert!(reg.dir.get_asset(&path).is_some());
assert!(reg.remove_asset(&path).is_some());
assert!(reg.dir.get_asset(&path).is_none());
assert!(reg.remove_asset(&path).is_none());
}
}

View File

@@ -0,0 +1,195 @@
use crate::io::{
get_meta_path, AssetReader, AssetReaderError, AssetWriter, AssetWriterError, AsyncSeekForward,
PathStream, Reader, Writer,
};
use async_fs::{read_dir, File};
use futures_io::AsyncSeek;
use futures_lite::StreamExt;
use alloc::{borrow::ToOwned, boxed::Box};
use core::{pin::Pin, task, task::Poll};
use std::path::Path;
use super::{FileAssetReader, FileAssetWriter};
impl AsyncSeekForward for File {
fn poll_seek_forward(
mut self: Pin<&mut Self>,
cx: &mut task::Context<'_>,
offset: u64,
) -> Poll<futures_io::Result<u64>> {
let offset: Result<i64, _> = offset.try_into();
if let Ok(offset) = offset {
Pin::new(&mut self).poll_seek(cx, futures_io::SeekFrom::Current(offset))
} else {
Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"seek position is out of range",
)))
}
}
}
impl Reader for File {}
impl AssetReader for FileAssetReader {
async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
let full_path = self.root_path.join(path);
File::open(&full_path).await.map_err(|e| {
if e.kind() == std::io::ErrorKind::NotFound {
AssetReaderError::NotFound(full_path)
} else {
e.into()
}
})
}
async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
let meta_path = get_meta_path(path);
let full_path = self.root_path.join(meta_path);
File::open(&full_path).await.map_err(|e| {
if e.kind() == std::io::ErrorKind::NotFound {
AssetReaderError::NotFound(full_path)
} else {
e.into()
}
})
}
async fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> Result<Box<PathStream>, AssetReaderError> {
let full_path = self.root_path.join(path);
match read_dir(&full_path).await {
Ok(read_dir) => {
let root_path = self.root_path.clone();
let mapped_stream = read_dir.filter_map(move |f| {
f.ok().and_then(|dir_entry| {
let path = dir_entry.path();
// filter out meta files as they are not considered assets
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
if ext.eq_ignore_ascii_case("meta") {
return None;
}
}
let relative_path = path.strip_prefix(&root_path).unwrap();
Some(relative_path.to_owned())
})
});
let read_dir: Box<PathStream> = Box::new(mapped_stream);
Ok(read_dir)
}
Err(e) => {
if e.kind() == std::io::ErrorKind::NotFound {
Err(AssetReaderError::NotFound(full_path))
} else {
Err(e.into())
}
}
}
}
async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
let full_path = self.root_path.join(path);
let metadata = full_path
.metadata()
.map_err(|_e| AssetReaderError::NotFound(path.to_owned()))?;
Ok(metadata.file_type().is_dir())
}
}
impl AssetWriter for FileAssetWriter {
async fn write<'a>(&'a self, path: &'a Path) -> Result<Box<Writer>, AssetWriterError> {
let full_path = self.root_path.join(path);
if let Some(parent) = full_path.parent() {
async_fs::create_dir_all(parent).await?;
}
let file = File::create(&full_path).await?;
let writer: Box<Writer> = Box::new(file);
Ok(writer)
}
async fn write_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Writer>, AssetWriterError> {
let meta_path = get_meta_path(path);
let full_path = self.root_path.join(meta_path);
if let Some(parent) = full_path.parent() {
async_fs::create_dir_all(parent).await?;
}
let file = File::create(&full_path).await?;
let writer: Box<Writer> = Box::new(file);
Ok(writer)
}
async fn remove<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
let full_path = self.root_path.join(path);
async_fs::remove_file(full_path).await?;
Ok(())
}
async fn remove_meta<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
let meta_path = get_meta_path(path);
let full_path = self.root_path.join(meta_path);
async_fs::remove_file(full_path).await?;
Ok(())
}
async fn rename<'a>(
&'a self,
old_path: &'a Path,
new_path: &'a Path,
) -> Result<(), AssetWriterError> {
let full_old_path = self.root_path.join(old_path);
let full_new_path = self.root_path.join(new_path);
if let Some(parent) = full_new_path.parent() {
async_fs::create_dir_all(parent).await?;
}
async_fs::rename(full_old_path, full_new_path).await?;
Ok(())
}
async fn rename_meta<'a>(
&'a self,
old_path: &'a Path,
new_path: &'a Path,
) -> Result<(), AssetWriterError> {
let old_meta_path = get_meta_path(old_path);
let new_meta_path = get_meta_path(new_path);
let full_old_path = self.root_path.join(old_meta_path);
let full_new_path = self.root_path.join(new_meta_path);
if let Some(parent) = full_new_path.parent() {
async_fs::create_dir_all(parent).await?;
}
async_fs::rename(full_old_path, full_new_path).await?;
Ok(())
}
async fn create_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
let full_path = self.root_path.join(path);
async_fs::create_dir_all(full_path).await?;
Ok(())
}
async fn remove_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
let full_path = self.root_path.join(path);
async_fs::remove_dir_all(full_path).await?;
Ok(())
}
async fn remove_empty_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
let full_path = self.root_path.join(path);
async_fs::remove_dir(full_path).await?;
Ok(())
}
async fn remove_assets_in_directory<'a>(
&'a self,
path: &'a Path,
) -> Result<(), AssetWriterError> {
let full_path = self.root_path.join(path);
async_fs::remove_dir_all(&full_path).await?;
async_fs::create_dir_all(&full_path).await?;
Ok(())
}
}

View File

@@ -0,0 +1,285 @@
use crate::{
io::{AssetSourceEvent, AssetWatcher},
path::normalize_path,
};
use alloc::borrow::ToOwned;
use core::time::Duration;
use crossbeam_channel::Sender;
use notify_debouncer_full::{
new_debouncer,
notify::{
self,
event::{AccessKind, AccessMode, CreateKind, ModifyKind, RemoveKind, RenameMode},
RecommendedWatcher, RecursiveMode,
},
DebounceEventResult, Debouncer, RecommendedCache,
};
use std::path::{Path, PathBuf};
use tracing::error;
/// An [`AssetWatcher`] that watches the filesystem for changes to asset files in a given root folder and emits [`AssetSourceEvent`]
/// for each relevant change.
///
/// This uses [`notify_debouncer_full`] to retrieve "debounced" filesystem events.
/// "Debouncing" defines a time window to hold on to events and then removes duplicate events that fall into this window.
/// This introduces a small delay in processing events, but it helps reduce event duplicates. A small delay is also necessary
/// on some systems to avoid processing a change event before it has actually been applied.
pub struct FileWatcher {
_watcher: Debouncer<RecommendedWatcher, RecommendedCache>,
}
impl FileWatcher {
/// Creates a new [`FileWatcher`] that watches for changes to the asset files in the given `path`.
pub fn new(
path: PathBuf,
sender: Sender<AssetSourceEvent>,
debounce_wait_time: Duration,
) -> Result<Self, notify::Error> {
let root = normalize_path(&path).canonicalize()?;
let watcher = new_asset_event_debouncer(
path.clone(),
debounce_wait_time,
FileEventHandler {
root,
sender,
last_event: None,
},
)?;
Ok(FileWatcher { _watcher: watcher })
}
}
impl AssetWatcher for FileWatcher {}
pub(crate) fn get_asset_path(root: &Path, absolute_path: &Path) -> (PathBuf, bool) {
let relative_path = absolute_path.strip_prefix(root).unwrap_or_else(|_| {
panic!(
"FileWatcher::get_asset_path() failed to strip prefix from absolute path: absolute_path={}, root={}",
absolute_path.display(),
root.display()
)
});
let is_meta = relative_path.extension().is_some_and(|e| e == "meta");
let asset_path = if is_meta {
relative_path.with_extension("")
} else {
relative_path.to_owned()
};
(asset_path, is_meta)
}
/// This is a bit more abstracted than it normally would be because we want to try _very hard_ not to duplicate this
/// event management logic across filesystem-driven [`AssetWatcher`] impls. Each operating system / platform behaves
/// a little differently and this is the result of a delicate balancing act that we should only perform once.
pub(crate) fn new_asset_event_debouncer(
root: PathBuf,
debounce_wait_time: Duration,
mut handler: impl FilesystemEventHandler,
) -> Result<Debouncer<RecommendedWatcher, RecommendedCache>, notify::Error> {
let root = super::get_base_path().join(root);
let mut debouncer = new_debouncer(
debounce_wait_time,
None,
move |result: DebounceEventResult| {
match result {
Ok(events) => {
handler.begin();
for event in events.iter() {
match event.kind {
notify::EventKind::Create(CreateKind::File) => {
if let Some((path, is_meta)) = handler.get_path(&event.paths[0]) {
if is_meta {
handler.handle(
&event.paths,
AssetSourceEvent::AddedMeta(path),
);
} else {
handler.handle(
&event.paths,
AssetSourceEvent::AddedAsset(path),
);
}
}
}
notify::EventKind::Create(CreateKind::Folder) => {
if let Some((path, _)) = handler.get_path(&event.paths[0]) {
handler
.handle(&event.paths, AssetSourceEvent::AddedFolder(path));
}
}
notify::EventKind::Access(AccessKind::Close(AccessMode::Write)) => {
if let Some((path, is_meta)) = handler.get_path(&event.paths[0]) {
if is_meta {
handler.handle(
&event.paths,
AssetSourceEvent::ModifiedMeta(path),
);
} else {
handler.handle(
&event.paths,
AssetSourceEvent::ModifiedAsset(path),
);
}
}
}
// Because this is debounced over a reasonable period of time, Modify(ModifyKind::Name(RenameMode::From)
// events are assumed to be "dangling" without a follow up "To" event. Without debouncing, "From" -> "To" -> "Both"
// events are emitted for renames. If a From is dangling, it is assumed to be "removed" from the context of the asset
// system.
notify::EventKind::Remove(RemoveKind::Any)
| notify::EventKind::Modify(ModifyKind::Name(RenameMode::From)) => {
if let Some((path, is_meta)) = handler.get_path(&event.paths[0]) {
handler.handle(
&event.paths,
AssetSourceEvent::RemovedUnknown { path, is_meta },
);
}
}
notify::EventKind::Create(CreateKind::Any)
| notify::EventKind::Modify(ModifyKind::Name(RenameMode::To)) => {
if let Some((path, is_meta)) = handler.get_path(&event.paths[0]) {
let asset_event = if event.paths[0].is_dir() {
AssetSourceEvent::AddedFolder(path)
} else if is_meta {
AssetSourceEvent::AddedMeta(path)
} else {
AssetSourceEvent::AddedAsset(path)
};
handler.handle(&event.paths, asset_event);
}
}
notify::EventKind::Modify(ModifyKind::Name(RenameMode::Both)) => {
let Some((old_path, old_is_meta)) =
handler.get_path(&event.paths[0])
else {
continue;
};
let Some((new_path, new_is_meta)) =
handler.get_path(&event.paths[1])
else {
continue;
};
// only the new "real" path is considered a directory
if event.paths[1].is_dir() {
handler.handle(
&event.paths,
AssetSourceEvent::RenamedFolder {
old: old_path,
new: new_path,
},
);
} else {
match (old_is_meta, new_is_meta) {
(true, true) => {
handler.handle(
&event.paths,
AssetSourceEvent::RenamedMeta {
old: old_path,
new: new_path,
},
);
}
(false, false) => {
handler.handle(
&event.paths,
AssetSourceEvent::RenamedAsset {
old: old_path,
new: new_path,
},
);
}
(true, false) => {
error!(
"Asset metafile {old_path:?} was changed to asset file {new_path:?}, which is not supported. Try restarting your app to see if configuration is still valid"
);
}
(false, true) => {
error!(
"Asset file {old_path:?} was changed to meta file {new_path:?}, which is not supported. Try restarting your app to see if configuration is still valid"
);
}
}
}
}
notify::EventKind::Modify(_) => {
let Some((path, is_meta)) = handler.get_path(&event.paths[0])
else {
continue;
};
if event.paths[0].is_dir() {
// modified folder means nothing in this case
} else if is_meta {
handler
.handle(&event.paths, AssetSourceEvent::ModifiedMeta(path));
} else {
handler.handle(
&event.paths,
AssetSourceEvent::ModifiedAsset(path),
);
};
}
notify::EventKind::Remove(RemoveKind::File) => {
let Some((path, is_meta)) = handler.get_path(&event.paths[0])
else {
continue;
};
if is_meta {
handler
.handle(&event.paths, AssetSourceEvent::RemovedMeta(path));
} else {
handler
.handle(&event.paths, AssetSourceEvent::RemovedAsset(path));
}
}
notify::EventKind::Remove(RemoveKind::Folder) => {
let Some((path, _)) = handler.get_path(&event.paths[0]) else {
continue;
};
handler.handle(&event.paths, AssetSourceEvent::RemovedFolder(path));
}
_ => {}
}
}
}
Err(errors) => errors.iter().for_each(|error| {
error!("Encountered a filesystem watcher error {error:?}");
}),
}
},
)?;
debouncer.watch(&root, RecursiveMode::Recursive)?;
Ok(debouncer)
}
pub(crate) struct FileEventHandler {
sender: Sender<AssetSourceEvent>,
root: PathBuf,
last_event: Option<AssetSourceEvent>,
}
impl FilesystemEventHandler for FileEventHandler {
fn begin(&mut self) {
self.last_event = None;
}
fn get_path(&self, absolute_path: &Path) -> Option<(PathBuf, bool)> {
let absolute_path = absolute_path.canonicalize().ok()?;
Some(get_asset_path(&self.root, &absolute_path))
}
fn handle(&mut self, _absolute_paths: &[PathBuf], event: AssetSourceEvent) {
if self.last_event.as_ref() != Some(&event) {
self.last_event = Some(event.clone());
self.sender.send(event).unwrap();
}
}
}
pub(crate) trait FilesystemEventHandler: Send + Sync + 'static {
/// Called each time a set of debounced events is processed
fn begin(&mut self);
/// Returns an actual asset path (if one exists for the given `absolute_path`), as well as a [`bool`] that is
/// true if the `absolute_path` corresponds to a meta file.
fn get_path(&self, absolute_path: &Path) -> Option<(PathBuf, bool)>;
/// Handle the given event
fn handle(&mut self, absolute_paths: &[PathBuf], event: AssetSourceEvent);
}

89
vendor/bevy_asset/src/io/file/mod.rs vendored Normal file
View File

@@ -0,0 +1,89 @@
#[cfg(feature = "file_watcher")]
mod file_watcher;
#[cfg(feature = "multi_threaded")]
mod file_asset;
#[cfg(not(feature = "multi_threaded"))]
mod sync_file_asset;
#[cfg(feature = "file_watcher")]
pub use file_watcher::*;
use tracing::{debug, error};
use alloc::borrow::ToOwned;
use std::{
env,
path::{Path, PathBuf},
};
pub(crate) fn get_base_path() -> PathBuf {
if let Ok(manifest_dir) = env::var("BEVY_ASSET_ROOT") {
PathBuf::from(manifest_dir)
} else if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
PathBuf::from(manifest_dir)
} else {
env::current_exe()
.map(|path| path.parent().map(ToOwned::to_owned).unwrap())
.unwrap()
}
}
/// I/O implementation for the local filesystem.
///
/// This asset I/O is fully featured but it's not available on `android` and `wasm` targets.
pub struct FileAssetReader {
root_path: PathBuf,
}
impl FileAssetReader {
/// Creates a new `FileAssetIo` at a path relative to the executable's directory, optionally
/// watching for changes.
///
/// See `get_base_path` below.
pub fn new<P: AsRef<Path>>(path: P) -> Self {
let root_path = Self::get_base_path().join(path.as_ref());
debug!(
"Asset Server using {} as its base path.",
root_path.display()
);
Self { root_path }
}
/// Returns the base path of the assets directory, which is normally the executable's parent
/// directory.
///
/// To change this, set [`AssetPlugin.file_path`].
pub fn get_base_path() -> PathBuf {
get_base_path()
}
/// Returns the root directory where assets are loaded from.
///
/// See `get_base_path`.
pub fn root_path(&self) -> &PathBuf {
&self.root_path
}
}
/// A writer for the local filesystem.
pub struct FileAssetWriter {
root_path: PathBuf,
}
impl FileAssetWriter {
/// Creates a new [`FileAssetWriter`] at a path relative to the executable's directory, optionally
/// watching for changes.
pub fn new<P: AsRef<Path> + core::fmt::Debug>(path: P, create_root: bool) -> Self {
let root_path = get_base_path().join(path.as_ref());
if create_root {
if let Err(e) = std::fs::create_dir_all(&root_path) {
error!(
"Failed to create root directory {} for file asset writer: {}",
root_path.display(),
e
);
}
}
Self { root_path }
}
}

View File

@@ -0,0 +1,266 @@
use futures_io::{AsyncRead, AsyncWrite};
use futures_lite::Stream;
use crate::io::{
get_meta_path, AssetReader, AssetReaderError, AssetWriter, AssetWriterError, AsyncSeekForward,
PathStream, Reader, Writer,
};
use alloc::{borrow::ToOwned, boxed::Box, vec::Vec};
use core::{pin::Pin, task::Poll};
use std::{
fs::{read_dir, File},
io::{Read, Seek, Write},
path::{Path, PathBuf},
};
use super::{FileAssetReader, FileAssetWriter};
struct FileReader(File);
impl AsyncRead for FileReader {
fn poll_read(
self: Pin<&mut Self>,
_cx: &mut core::task::Context<'_>,
buf: &mut [u8],
) -> Poll<std::io::Result<usize>> {
let this = self.get_mut();
let read = this.0.read(buf);
Poll::Ready(read)
}
}
impl AsyncSeekForward for FileReader {
fn poll_seek_forward(
self: Pin<&mut Self>,
_cx: &mut core::task::Context<'_>,
offset: u64,
) -> Poll<std::io::Result<u64>> {
let this = self.get_mut();
let current = this.0.stream_position()?;
let seek = this.0.seek(std::io::SeekFrom::Start(current + offset));
Poll::Ready(seek)
}
}
impl Reader for FileReader {
fn read_to_end<'a>(
&'a mut self,
buf: &'a mut Vec<u8>,
) -> stackfuture::StackFuture<'a, std::io::Result<usize>, { crate::io::STACK_FUTURE_SIZE }>
{
stackfuture::StackFuture::from(async { self.0.read_to_end(buf) })
}
}
struct FileWriter(File);
impl AsyncWrite for FileWriter {
fn poll_write(
self: Pin<&mut Self>,
_cx: &mut core::task::Context<'_>,
buf: &[u8],
) -> Poll<std::io::Result<usize>> {
let this = self.get_mut();
let wrote = this.0.write(buf);
Poll::Ready(wrote)
}
fn poll_flush(
self: Pin<&mut Self>,
_cx: &mut core::task::Context<'_>,
) -> Poll<std::io::Result<()>> {
let this = self.get_mut();
let flushed = this.0.flush();
Poll::Ready(flushed)
}
fn poll_close(
self: Pin<&mut Self>,
_cx: &mut core::task::Context<'_>,
) -> Poll<std::io::Result<()>> {
Poll::Ready(Ok(()))
}
}
struct DirReader(Vec<PathBuf>);
impl Stream for DirReader {
type Item = PathBuf;
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut core::task::Context<'_>,
) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
Poll::Ready(this.0.pop())
}
}
impl AssetReader for FileAssetReader {
async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
let full_path = self.root_path.join(path);
match File::open(&full_path) {
Ok(file) => Ok(FileReader(file)),
Err(e) => {
if e.kind() == std::io::ErrorKind::NotFound {
Err(AssetReaderError::NotFound(full_path))
} else {
Err(e.into())
}
}
}
}
async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
let meta_path = get_meta_path(path);
let full_path = self.root_path.join(meta_path);
match File::open(&full_path) {
Ok(file) => Ok(FileReader(file)),
Err(e) => {
if e.kind() == std::io::ErrorKind::NotFound {
Err(AssetReaderError::NotFound(full_path))
} else {
Err(e.into())
}
}
}
}
async fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> Result<Box<PathStream>, AssetReaderError> {
let full_path = self.root_path.join(path);
match read_dir(&full_path) {
Ok(read_dir) => {
let root_path = self.root_path.clone();
let mapped_stream = read_dir.filter_map(move |f| {
f.ok().and_then(|dir_entry| {
let path = dir_entry.path();
// filter out meta files as they are not considered assets
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
if ext.eq_ignore_ascii_case("meta") {
return None;
}
}
let relative_path = path.strip_prefix(&root_path).unwrap();
Some(relative_path.to_owned())
})
});
let read_dir: Box<PathStream> = Box::new(DirReader(mapped_stream.collect()));
Ok(read_dir)
}
Err(e) => {
if e.kind() == std::io::ErrorKind::NotFound {
Err(AssetReaderError::NotFound(full_path))
} else {
Err(e.into())
}
}
}
}
async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
let full_path = self.root_path.join(path);
let metadata = full_path
.metadata()
.map_err(|_e| AssetReaderError::NotFound(path.to_owned()))?;
Ok(metadata.file_type().is_dir())
}
}
impl AssetWriter for FileAssetWriter {
async fn write<'a>(&'a self, path: &'a Path) -> Result<Box<Writer>, AssetWriterError> {
let full_path = self.root_path.join(path);
if let Some(parent) = full_path.parent() {
std::fs::create_dir_all(parent)?;
}
let file = File::create(&full_path)?;
let writer: Box<Writer> = Box::new(FileWriter(file));
Ok(writer)
}
async fn write_meta<'a>(&'a self, path: &'a Path) -> Result<Box<Writer>, AssetWriterError> {
let meta_path = get_meta_path(path);
let full_path = self.root_path.join(meta_path);
if let Some(parent) = full_path.parent() {
std::fs::create_dir_all(parent)?;
}
let file = File::create(&full_path)?;
let writer: Box<Writer> = Box::new(FileWriter(file));
Ok(writer)
}
async fn remove<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
let full_path = self.root_path.join(path);
std::fs::remove_file(full_path)?;
Ok(())
}
async fn remove_meta<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
let meta_path = get_meta_path(path);
let full_path = self.root_path.join(meta_path);
std::fs::remove_file(full_path)?;
Ok(())
}
async fn create_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
let full_path = self.root_path.join(path);
std::fs::create_dir_all(full_path)?;
Ok(())
}
async fn remove_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
let full_path = self.root_path.join(path);
std::fs::remove_dir_all(full_path)?;
Ok(())
}
async fn remove_empty_directory<'a>(&'a self, path: &'a Path) -> Result<(), AssetWriterError> {
let full_path = self.root_path.join(path);
std::fs::remove_dir(full_path)?;
Ok(())
}
async fn remove_assets_in_directory<'a>(
&'a self,
path: &'a Path,
) -> Result<(), AssetWriterError> {
let full_path = self.root_path.join(path);
std::fs::remove_dir_all(&full_path)?;
std::fs::create_dir_all(&full_path)?;
Ok(())
}
async fn rename<'a>(
&'a self,
old_path: &'a Path,
new_path: &'a Path,
) -> Result<(), AssetWriterError> {
let full_old_path = self.root_path.join(old_path);
let full_new_path = self.root_path.join(new_path);
if let Some(parent) = full_new_path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::rename(full_old_path, full_new_path)?;
Ok(())
}
async fn rename_meta<'a>(
&'a self,
old_path: &'a Path,
new_path: &'a Path,
) -> Result<(), AssetWriterError> {
let old_meta_path = get_meta_path(old_path);
let new_meta_path = get_meta_path(new_path);
let full_old_path = self.root_path.join(old_meta_path);
let full_new_path = self.root_path.join(new_meta_path);
if let Some(parent) = full_new_path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::rename(full_old_path, full_new_path)?;
Ok(())
}
}

86
vendor/bevy_asset/src/io/gated.rs vendored Normal file
View File

@@ -0,0 +1,86 @@
use crate::io::{AssetReader, AssetReaderError, PathStream, Reader};
use alloc::{boxed::Box, sync::Arc};
use bevy_platform::collections::HashMap;
use crossbeam_channel::{Receiver, Sender};
use parking_lot::RwLock;
use std::path::Path;
/// A "gated" reader that will prevent asset reads from returning until
/// a given path has been "opened" using [`GateOpener`].
///
/// This is built primarily for unit tests.
pub struct GatedReader<R: AssetReader> {
reader: R,
gates: Arc<RwLock<HashMap<Box<Path>, (Sender<()>, Receiver<()>)>>>,
}
impl<R: AssetReader + Clone> Clone for GatedReader<R> {
fn clone(&self) -> Self {
Self {
reader: self.reader.clone(),
gates: self.gates.clone(),
}
}
}
/// Opens path "gates" for a [`GatedReader`].
pub struct GateOpener {
gates: Arc<RwLock<HashMap<Box<Path>, (Sender<()>, Receiver<()>)>>>,
}
impl GateOpener {
/// Opens the `path` "gate", allowing a _single_ [`AssetReader`] operation to return for that path.
/// If multiple operations are expected, call `open` the expected number of calls.
pub fn open<P: AsRef<Path>>(&self, path: P) {
let mut gates = self.gates.write();
let gates = gates
.entry_ref(path.as_ref())
.or_insert_with(crossbeam_channel::unbounded);
gates.0.send(()).unwrap();
}
}
impl<R: AssetReader> GatedReader<R> {
/// Creates a new [`GatedReader`], which wraps the given `reader`. Also returns a [`GateOpener`] which
/// can be used to open "path gates" for this [`GatedReader`].
pub fn new(reader: R) -> (Self, GateOpener) {
let gates = Arc::new(RwLock::new(HashMap::default()));
(
Self {
reader,
gates: gates.clone(),
},
GateOpener { gates },
)
}
}
impl<R: AssetReader> AssetReader for GatedReader<R> {
async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
let receiver = {
let mut gates = self.gates.write();
let gates = gates
.entry_ref(path.as_ref())
.or_insert_with(crossbeam_channel::unbounded);
gates.1.clone()
};
receiver.recv().unwrap();
let result = self.reader.read(path).await?;
Ok(result)
}
async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
self.reader.read_meta(path).await
}
async fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> Result<Box<PathStream>, AssetReaderError> {
self.reader.read_directory(path).await
}
async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
self.reader.is_directory(path).await
}
}

364
vendor/bevy_asset/src/io/memory.rs vendored Normal file
View File

@@ -0,0 +1,364 @@
use crate::io::{AssetReader, AssetReaderError, PathStream, Reader};
use alloc::{borrow::ToOwned, boxed::Box, sync::Arc, vec::Vec};
use bevy_platform::collections::HashMap;
use core::{pin::Pin, task::Poll};
use futures_io::AsyncRead;
use futures_lite::{ready, Stream};
use parking_lot::RwLock;
use std::path::{Path, PathBuf};
use super::AsyncSeekForward;
#[derive(Default, Debug)]
struct DirInternal {
assets: HashMap<Box<str>, Data>,
metadata: HashMap<Box<str>, Data>,
dirs: HashMap<Box<str>, Dir>,
path: PathBuf,
}
/// A clone-able (internally Arc-ed) / thread-safe "in memory" filesystem.
/// This is built for [`MemoryAssetReader`] and is primarily intended for unit tests.
#[derive(Default, Clone, Debug)]
pub struct Dir(Arc<RwLock<DirInternal>>);
impl Dir {
/// Creates a new [`Dir`] for the given `path`.
pub fn new(path: PathBuf) -> Self {
Self(Arc::new(RwLock::new(DirInternal {
path,
..Default::default()
})))
}
pub fn insert_asset_text(&self, path: &Path, asset: &str) {
self.insert_asset(path, asset.as_bytes().to_vec());
}
pub fn insert_meta_text(&self, path: &Path, asset: &str) {
self.insert_meta(path, asset.as_bytes().to_vec());
}
pub fn insert_asset(&self, path: &Path, value: impl Into<Value>) {
let mut dir = self.clone();
if let Some(parent) = path.parent() {
dir = self.get_or_insert_dir(parent);
}
dir.0.write().assets.insert(
path.file_name().unwrap().to_string_lossy().into(),
Data {
value: value.into(),
path: path.to_owned(),
},
);
}
/// Removes the stored asset at `path` and returns the `Data` stored if found and otherwise `None`.
pub fn remove_asset(&self, path: &Path) -> Option<Data> {
let mut dir = self.clone();
if let Some(parent) = path.parent() {
dir = self.get_or_insert_dir(parent);
}
let key: Box<str> = path.file_name().unwrap().to_string_lossy().into();
dir.0.write().assets.remove(&key)
}
pub fn insert_meta(&self, path: &Path, value: impl Into<Value>) {
let mut dir = self.clone();
if let Some(parent) = path.parent() {
dir = self.get_or_insert_dir(parent);
}
dir.0.write().metadata.insert(
path.file_name().unwrap().to_string_lossy().into(),
Data {
value: value.into(),
path: path.to_owned(),
},
);
}
pub fn get_or_insert_dir(&self, path: &Path) -> Dir {
let mut dir = self.clone();
let mut full_path = PathBuf::new();
for c in path.components() {
full_path.push(c);
let name = c.as_os_str().to_string_lossy().into();
dir = {
let dirs = &mut dir.0.write().dirs;
dirs.entry(name)
.or_insert_with(|| Dir::new(full_path.clone()))
.clone()
};
}
dir
}
pub fn get_dir(&self, path: &Path) -> Option<Dir> {
let mut dir = self.clone();
for p in path.components() {
let component = p.as_os_str().to_str().unwrap();
let next_dir = dir.0.read().dirs.get(component)?.clone();
dir = next_dir;
}
Some(dir)
}
pub fn get_asset(&self, path: &Path) -> Option<Data> {
let mut dir = self.clone();
if let Some(parent) = path.parent() {
dir = dir.get_dir(parent)?;
}
path.file_name()
.and_then(|f| dir.0.read().assets.get(f.to_str().unwrap()).cloned())
}
pub fn get_metadata(&self, path: &Path) -> Option<Data> {
let mut dir = self.clone();
if let Some(parent) = path.parent() {
dir = dir.get_dir(parent)?;
}
path.file_name()
.and_then(|f| dir.0.read().metadata.get(f.to_str().unwrap()).cloned())
}
pub fn path(&self) -> PathBuf {
self.0.read().path.to_owned()
}
}
pub struct DirStream {
dir: Dir,
index: usize,
dir_index: usize,
}
impl DirStream {
fn new(dir: Dir) -> Self {
Self {
dir,
index: 0,
dir_index: 0,
}
}
}
impl Stream for DirStream {
type Item = PathBuf;
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut core::task::Context<'_>,
) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
let dir = this.dir.0.read();
let dir_index = this.dir_index;
if let Some(dir_path) = dir
.dirs
.keys()
.nth(dir_index)
.map(|d| dir.path.join(d.as_ref()))
{
this.dir_index += 1;
Poll::Ready(Some(dir_path))
} else {
let index = this.index;
this.index += 1;
Poll::Ready(dir.assets.values().nth(index).map(|d| d.path().to_owned()))
}
}
}
/// In-memory [`AssetReader`] implementation.
/// This is primarily intended for unit tests.
#[derive(Default, Clone)]
pub struct MemoryAssetReader {
pub root: Dir,
}
/// Asset data stored in a [`Dir`].
#[derive(Clone, Debug)]
pub struct Data {
path: PathBuf,
value: Value,
}
/// Stores either an allocated vec of bytes or a static array of bytes.
#[derive(Clone, Debug)]
pub enum Value {
Vec(Arc<Vec<u8>>),
Static(&'static [u8]),
}
impl Data {
fn path(&self) -> &Path {
&self.path
}
fn value(&self) -> &[u8] {
match &self.value {
Value::Vec(vec) => vec,
Value::Static(value) => value,
}
}
}
impl From<Vec<u8>> for Value {
fn from(value: Vec<u8>) -> Self {
Self::Vec(Arc::new(value))
}
}
impl From<&'static [u8]> for Value {
fn from(value: &'static [u8]) -> Self {
Self::Static(value)
}
}
impl<const N: usize> From<&'static [u8; N]> for Value {
fn from(value: &'static [u8; N]) -> Self {
Self::Static(value)
}
}
struct DataReader {
data: Data,
bytes_read: usize,
}
impl AsyncRead for DataReader {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
buf: &mut [u8],
) -> Poll<futures_io::Result<usize>> {
if self.bytes_read >= self.data.value().len() {
Poll::Ready(Ok(0))
} else {
let n =
ready!(Pin::new(&mut &self.data.value()[self.bytes_read..]).poll_read(cx, buf))?;
self.bytes_read += n;
Poll::Ready(Ok(n))
}
}
}
impl AsyncSeekForward for DataReader {
fn poll_seek_forward(
mut self: Pin<&mut Self>,
_cx: &mut core::task::Context<'_>,
offset: u64,
) -> Poll<std::io::Result<u64>> {
let result = self
.bytes_read
.try_into()
.map(|bytes_read: u64| bytes_read + offset);
if let Ok(new_pos) = result {
self.bytes_read = new_pos as _;
Poll::Ready(Ok(new_pos as _))
} else {
Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"seek position is out of range",
)))
}
}
}
impl Reader for DataReader {
fn read_to_end<'a>(
&'a mut self,
buf: &'a mut Vec<u8>,
) -> stackfuture::StackFuture<'a, std::io::Result<usize>, { super::STACK_FUTURE_SIZE }> {
stackfuture::StackFuture::from(async {
if self.bytes_read >= self.data.value().len() {
Ok(0)
} else {
buf.extend_from_slice(&self.data.value()[self.bytes_read..]);
let n = self.data.value().len() - self.bytes_read;
self.bytes_read = self.data.value().len();
Ok(n)
}
})
}
}
impl AssetReader for MemoryAssetReader {
async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
self.root
.get_asset(path)
.map(|data| DataReader {
data,
bytes_read: 0,
})
.ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf()))
}
async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
self.root
.get_metadata(path)
.map(|data| DataReader {
data,
bytes_read: 0,
})
.ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf()))
}
async fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> Result<Box<PathStream>, AssetReaderError> {
self.root
.get_dir(path)
.map(|dir| {
let stream: Box<PathStream> = Box::new(DirStream::new(dir));
stream
})
.ok_or_else(|| AssetReaderError::NotFound(path.to_path_buf()))
}
async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
Ok(self.root.get_dir(path).is_some())
}
}
#[cfg(test)]
pub mod test {
use super::Dir;
use std::path::Path;
#[test]
fn memory_dir() {
let dir = Dir::default();
let a_path = Path::new("a.txt");
let a_data = "a".as_bytes().to_vec();
let a_meta = "ameta".as_bytes().to_vec();
dir.insert_asset(a_path, a_data.clone());
let asset = dir.get_asset(a_path).unwrap();
assert_eq!(asset.path(), a_path);
assert_eq!(asset.value(), a_data);
dir.insert_meta(a_path, a_meta.clone());
let meta = dir.get_metadata(a_path).unwrap();
assert_eq!(meta.path(), a_path);
assert_eq!(meta.value(), a_meta);
let b_path = Path::new("x/y/b.txt");
let b_data = "b".as_bytes().to_vec();
let b_meta = "meta".as_bytes().to_vec();
dir.insert_asset(b_path, b_data.clone());
dir.insert_meta(b_path, b_meta.clone());
let asset = dir.get_asset(b_path).unwrap();
assert_eq!(asset.path(), b_path);
assert_eq!(asset.value(), b_data);
let meta = dir.get_metadata(b_path).unwrap();
assert_eq!(meta.path(), b_path);
assert_eq!(meta.value(), b_meta);
}
}

785
vendor/bevy_asset/src/io/mod.rs vendored Normal file
View File

@@ -0,0 +1,785 @@
#[cfg(all(feature = "file_watcher", target_arch = "wasm32"))]
compile_error!(
"The \"file_watcher\" feature for hot reloading does not work \
on Wasm.\nDisable \"file_watcher\" \
when compiling to Wasm"
);
#[cfg(target_os = "android")]
pub mod android;
pub mod embedded;
#[cfg(not(target_arch = "wasm32"))]
pub mod file;
pub mod gated;
pub mod memory;
pub mod processor_gated;
#[cfg(target_arch = "wasm32")]
pub mod wasm;
mod source;
pub use futures_lite::AsyncWriteExt;
pub use source::*;
use alloc::{boxed::Box, sync::Arc, vec::Vec};
use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
use core::future::Future;
use core::{
mem::size_of,
pin::Pin,
task::{Context, Poll},
};
use futures_io::{AsyncRead, AsyncWrite};
use futures_lite::{ready, Stream};
use std::path::{Path, PathBuf};
use thiserror::Error;
/// Errors that occur while loading assets.
#[derive(Error, Debug, Clone)]
pub enum AssetReaderError {
/// Path not found.
#[error("Path not found: {}", _0.display())]
NotFound(PathBuf),
/// Encountered an I/O error while loading an asset.
#[error("Encountered an I/O error while loading asset: {0}")]
Io(Arc<std::io::Error>),
/// The HTTP request completed but returned an unhandled [HTTP response status code](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status).
/// If the request fails before getting a status code (e.g. request timeout, interrupted connection, etc), expect [`AssetReaderError::Io`].
#[error("Encountered HTTP status {0:?} when loading asset")]
HttpError(u16),
}
impl PartialEq for AssetReaderError {
/// Equality comparison for `AssetReaderError::Io` is not full (only through `ErrorKind` of inner error)
#[inline]
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::NotFound(path), Self::NotFound(other_path)) => path == other_path,
(Self::Io(error), Self::Io(other_error)) => error.kind() == other_error.kind(),
(Self::HttpError(code), Self::HttpError(other_code)) => code == other_code,
_ => false,
}
}
}
impl Eq for AssetReaderError {}
impl From<std::io::Error> for AssetReaderError {
fn from(value: std::io::Error) -> Self {
Self::Io(Arc::new(value))
}
}
/// The maximum size of a future returned from [`Reader::read_to_end`].
/// This is large enough to fit ten references.
// Ideally this would be even smaller (ReadToEndFuture only needs space for two references based on its definition),
// but compiler optimizations can apparently inflate the stack size of futures due to inlining, which makes
// a higher maximum necessary.
pub const STACK_FUTURE_SIZE: usize = 10 * size_of::<&()>();
pub use stackfuture::StackFuture;
/// Asynchronously advances the cursor position by a specified number of bytes.
///
/// This trait is a simplified version of the [`futures_io::AsyncSeek`] trait, providing
/// support exclusively for the [`futures_io::SeekFrom::Current`] variant. It allows for relative
/// seeking from the current cursor position.
pub trait AsyncSeekForward {
/// Attempts to asynchronously seek forward by a specified number of bytes from the current cursor position.
///
/// Seeking beyond the end of the stream is allowed and the behavior for this case is defined by the implementation.
/// The new position, relative to the beginning of the stream, should be returned upon successful completion
/// of the seek operation.
///
/// If the seek operation completes successfully,
/// the new position relative to the beginning of the stream should be returned.
///
/// # Implementation
///
/// Implementations of this trait should handle [`Poll::Pending`] correctly, converting
/// [`std::io::ErrorKind::WouldBlock`] errors into [`Poll::Pending`] to indicate that the operation is not
/// yet complete and should be retried, and either internally retry or convert
/// [`std::io::ErrorKind::Interrupted`] into another error kind.
fn poll_seek_forward(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
offset: u64,
) -> Poll<futures_io::Result<u64>>;
}
impl<T: ?Sized + AsyncSeekForward + Unpin> AsyncSeekForward for Box<T> {
fn poll_seek_forward(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
offset: u64,
) -> Poll<futures_io::Result<u64>> {
Pin::new(&mut **self).poll_seek_forward(cx, offset)
}
}
/// Extension trait for [`AsyncSeekForward`].
pub trait AsyncSeekForwardExt: AsyncSeekForward {
/// Seek by the provided `offset` in the forwards direction, using the [`AsyncSeekForward`] trait.
fn seek_forward(&mut self, offset: u64) -> SeekForwardFuture<'_, Self>
where
Self: Unpin,
{
SeekForwardFuture {
seeker: self,
offset,
}
}
}
impl<R: AsyncSeekForward + ?Sized> AsyncSeekForwardExt for R {}
#[derive(Debug)]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct SeekForwardFuture<'a, S: Unpin + ?Sized> {
seeker: &'a mut S,
offset: u64,
}
impl<S: Unpin + ?Sized> Unpin for SeekForwardFuture<'_, S> {}
impl<S: AsyncSeekForward + Unpin + ?Sized> Future for SeekForwardFuture<'_, S> {
type Output = futures_lite::io::Result<u64>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let offset = self.offset;
Pin::new(&mut *self.seeker).poll_seek_forward(cx, offset)
}
}
/// A type returned from [`AssetReader::read`], which is used to read the contents of a file
/// (or virtual file) corresponding to an asset.
///
/// This is essentially a trait alias for types implementing [`AsyncRead`] and [`AsyncSeekForward`].
/// The only reason a blanket implementation is not provided for applicable types is to allow
/// implementors to override the provided implementation of [`Reader::read_to_end`].
pub trait Reader: AsyncRead + AsyncSeekForward + Unpin + Send + Sync {
/// Reads the entire contents of this reader and appends them to a vec.
///
/// # Note for implementors
/// You should override the provided implementation if you can fill up the buffer more
/// efficiently than the default implementation, which calls `poll_read` repeatedly to
/// fill up the buffer 32 bytes at a time.
fn read_to_end<'a>(
&'a mut self,
buf: &'a mut Vec<u8>,
) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
let future = futures_lite::AsyncReadExt::read_to_end(self, buf);
StackFuture::from(future)
}
}
impl Reader for Box<dyn Reader + '_> {
fn read_to_end<'a>(
&'a mut self,
buf: &'a mut Vec<u8>,
) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
(**self).read_to_end(buf)
}
}
/// A future that returns a value or an [`AssetReaderError`]
pub trait AssetReaderFuture:
ConditionalSendFuture<Output = Result<Self::Value, AssetReaderError>>
{
type Value;
}
impl<F, T> AssetReaderFuture for F
where
F: ConditionalSendFuture<Output = Result<T, AssetReaderError>>,
{
type Value = T;
}
/// Performs read operations on an asset storage. [`AssetReader`] exposes a "virtual filesystem"
/// API, where asset bytes and asset metadata bytes are both stored and accessible for a given
/// `path`. This trait is not object safe, if needed use a dyn [`ErasedAssetReader`] instead.
///
/// This trait defines asset-agnostic mechanisms to read bytes from a storage system.
/// For the per-asset-type saving/loading logic, see [`AssetSaver`](crate::saver::AssetSaver) and [`AssetLoader`](crate::loader::AssetLoader).
///
/// For a complementary version of this trait that can write assets to storage, see [`AssetWriter`].
pub trait AssetReader: Send + Sync + 'static {
/// Returns a future to load the full file data at the provided path.
///
/// # Note for implementors
/// The preferred style for implementing this method is an `async fn` returning an opaque type.
///
/// ```no_run
/// # use std::path::Path;
/// # use bevy_asset::{prelude::*, io::{AssetReader, PathStream, Reader, AssetReaderError}};
/// # struct MyReader;
/// impl AssetReader for MyReader {
/// async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
/// // ...
/// # let val: Box<dyn Reader> = unimplemented!(); Ok(val)
/// }
/// # async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
/// # let val: Box<dyn Reader> = unimplemented!(); Ok(val) }
/// # async fn read_directory<'a>(&'a self, path: &'a Path) -> Result<Box<PathStream>, AssetReaderError> { unimplemented!() }
/// # async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> { unimplemented!() }
/// # async fn read_meta_bytes<'a>(&'a self, path: &'a Path) -> Result<Vec<u8>, AssetReaderError> { unimplemented!() }
/// }
/// ```
fn read<'a>(&'a self, path: &'a Path) -> impl AssetReaderFuture<Value: Reader + 'a>;
/// Returns a future to load the full file data at the provided path.
fn read_meta<'a>(&'a self, path: &'a Path) -> impl AssetReaderFuture<Value: Reader + 'a>;
/// Returns an iterator of directory entry names at the provided path.
fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<Box<PathStream>, AssetReaderError>>;
/// Returns true if the provided path points to a directory.
fn is_directory<'a>(
&'a self,
path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<bool, AssetReaderError>>;
/// Reads asset metadata bytes at the given `path` into a [`Vec<u8>`]. This is a convenience
/// function that wraps [`AssetReader::read_meta`] by default.
fn read_meta_bytes<'a>(
&'a self,
path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<Vec<u8>, AssetReaderError>> {
async {
let mut meta_reader = self.read_meta(path).await?;
let mut meta_bytes = Vec::new();
meta_reader.read_to_end(&mut meta_bytes).await?;
Ok(meta_bytes)
}
}
}
/// Equivalent to an [`AssetReader`] but using boxed futures, necessary eg. when using a `dyn AssetReader`,
/// as [`AssetReader`] isn't currently object safe.
pub trait ErasedAssetReader: Send + Sync + 'static {
/// Returns a future to load the full file data at the provided path.
fn read<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>>;
/// Returns a future to load the full file data at the provided path.
fn read_meta<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>>;
/// Returns an iterator of directory entry names at the provided path.
fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>>;
/// Returns true if the provided path points to a directory.
fn is_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<bool, AssetReaderError>>;
/// Reads asset metadata bytes at the given `path` into a [`Vec<u8>`]. This is a convenience
/// function that wraps [`ErasedAssetReader::read_meta`] by default.
fn read_meta_bytes<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Vec<u8>, AssetReaderError>>;
}
impl<T: AssetReader> ErasedAssetReader for T {
fn read<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>> {
Box::pin(async {
let reader = Self::read(self, path).await?;
Ok(Box::new(reader) as Box<dyn Reader>)
})
}
fn read_meta<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<dyn Reader + 'a>, AssetReaderError>> {
Box::pin(async {
let reader = Self::read_meta(self, path).await?;
Ok(Box::new(reader) as Box<dyn Reader>)
})
}
fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>> {
Box::pin(Self::read_directory(self, path))
}
fn is_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<bool, AssetReaderError>> {
Box::pin(Self::is_directory(self, path))
}
fn read_meta_bytes<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Vec<u8>, AssetReaderError>> {
Box::pin(Self::read_meta_bytes(self, path))
}
}
pub type Writer = dyn AsyncWrite + Unpin + Send + Sync;
pub type PathStream = dyn Stream<Item = PathBuf> + Unpin + Send;
/// Errors that occur while loading assets.
#[derive(Error, Debug)]
pub enum AssetWriterError {
/// Encountered an I/O error while loading an asset.
#[error("encountered an io error while loading asset: {0}")]
Io(#[from] std::io::Error),
}
/// Preforms write operations on an asset storage. [`AssetWriter`] exposes a "virtual filesystem"
/// API, where asset bytes and asset metadata bytes are both stored and accessible for a given
/// `path`. This trait is not object safe, if needed use a dyn [`ErasedAssetWriter`] instead.
///
/// This trait defines asset-agnostic mechanisms to write bytes to a storage system.
/// For the per-asset-type saving/loading logic, see [`AssetSaver`](crate::saver::AssetSaver) and [`AssetLoader`](crate::loader::AssetLoader).
///
/// For a complementary version of this trait that can read assets from storage, see [`AssetReader`].
pub trait AssetWriter: Send + Sync + 'static {
/// Writes the full asset bytes at the provided path.
fn write<'a>(
&'a self,
path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<Box<Writer>, AssetWriterError>>;
/// Writes the full asset meta bytes at the provided path.
/// This _should not_ include storage specific extensions like `.meta`.
fn write_meta<'a>(
&'a self,
path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<Box<Writer>, AssetWriterError>>;
/// Removes the asset stored at the given path.
fn remove<'a>(
&'a self,
path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
/// Removes the asset meta stored at the given path.
/// This _should not_ include storage specific extensions like `.meta`.
fn remove_meta<'a>(
&'a self,
path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
/// Renames the asset at `old_path` to `new_path`
fn rename<'a>(
&'a self,
old_path: &'a Path,
new_path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
/// Renames the asset meta for the asset at `old_path` to `new_path`.
/// This _should not_ include storage specific extensions like `.meta`.
fn rename_meta<'a>(
&'a self,
old_path: &'a Path,
new_path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
/// Creates a directory at the given path, including all parent directories if they do not
/// already exist.
fn create_directory<'a>(
&'a self,
path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
/// Removes the directory at the given path, including all assets _and_ directories in that directory.
fn remove_directory<'a>(
&'a self,
path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
/// Removes the directory at the given path, but only if it is completely empty. This will return an error if the
/// directory is not empty.
fn remove_empty_directory<'a>(
&'a self,
path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
/// Removes all assets (and directories) in this directory, resulting in an empty directory.
fn remove_assets_in_directory<'a>(
&'a self,
path: &'a Path,
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>>;
/// Writes the asset `bytes` to the given `path`.
fn write_bytes<'a>(
&'a self,
path: &'a Path,
bytes: &'a [u8],
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>> {
async {
let mut writer = self.write(path).await?;
writer.write_all(bytes).await?;
writer.flush().await?;
Ok(())
}
}
/// Writes the asset meta `bytes` to the given `path`.
fn write_meta_bytes<'a>(
&'a self,
path: &'a Path,
bytes: &'a [u8],
) -> impl ConditionalSendFuture<Output = Result<(), AssetWriterError>> {
async {
let mut meta_writer = self.write_meta(path).await?;
meta_writer.write_all(bytes).await?;
meta_writer.flush().await?;
Ok(())
}
}
}
/// Equivalent to an [`AssetWriter`] but using boxed futures, necessary eg. when using a `dyn AssetWriter`,
/// as [`AssetWriter`] isn't currently object safe.
pub trait ErasedAssetWriter: Send + Sync + 'static {
/// Writes the full asset bytes at the provided path.
fn write<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>>;
/// Writes the full asset meta bytes at the provided path.
/// This _should not_ include storage specific extensions like `.meta`.
fn write_meta<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>>;
/// Removes the asset stored at the given path.
fn remove<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
/// Removes the asset meta stored at the given path.
/// This _should not_ include storage specific extensions like `.meta`.
fn remove_meta<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
/// Renames the asset at `old_path` to `new_path`
fn rename<'a>(
&'a self,
old_path: &'a Path,
new_path: &'a Path,
) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
/// Renames the asset meta for the asset at `old_path` to `new_path`.
/// This _should not_ include storage specific extensions like `.meta`.
fn rename_meta<'a>(
&'a self,
old_path: &'a Path,
new_path: &'a Path,
) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
/// Creates a directory at the given path, including all parent directories if they do not
/// already exist.
fn create_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
/// Removes the directory at the given path, including all assets _and_ directories in that directory.
fn remove_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
/// Removes the directory at the given path, but only if it is completely empty. This will return an error if the
/// directory is not empty.
fn remove_empty_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
/// Removes all assets (and directories) in this directory, resulting in an empty directory.
fn remove_assets_in_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
/// Writes the asset `bytes` to the given `path`.
fn write_bytes<'a>(
&'a self,
path: &'a Path,
bytes: &'a [u8],
) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
/// Writes the asset meta `bytes` to the given `path`.
fn write_meta_bytes<'a>(
&'a self,
path: &'a Path,
bytes: &'a [u8],
) -> BoxedFuture<'a, Result<(), AssetWriterError>>;
}
impl<T: AssetWriter> ErasedAssetWriter for T {
fn write<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>> {
Box::pin(Self::write(self, path))
}
fn write_meta<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Writer>, AssetWriterError>> {
Box::pin(Self::write_meta(self, path))
}
fn remove<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
Box::pin(Self::remove(self, path))
}
fn remove_meta<'a>(&'a self, path: &'a Path) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
Box::pin(Self::remove_meta(self, path))
}
fn rename<'a>(
&'a self,
old_path: &'a Path,
new_path: &'a Path,
) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
Box::pin(Self::rename(self, old_path, new_path))
}
fn rename_meta<'a>(
&'a self,
old_path: &'a Path,
new_path: &'a Path,
) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
Box::pin(Self::rename_meta(self, old_path, new_path))
}
fn create_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
Box::pin(Self::create_directory(self, path))
}
fn remove_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
Box::pin(Self::remove_directory(self, path))
}
fn remove_empty_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
Box::pin(Self::remove_empty_directory(self, path))
}
fn remove_assets_in_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
Box::pin(Self::remove_assets_in_directory(self, path))
}
fn write_bytes<'a>(
&'a self,
path: &'a Path,
bytes: &'a [u8],
) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
Box::pin(Self::write_bytes(self, path, bytes))
}
fn write_meta_bytes<'a>(
&'a self,
path: &'a Path,
bytes: &'a [u8],
) -> BoxedFuture<'a, Result<(), AssetWriterError>> {
Box::pin(Self::write_meta_bytes(self, path, bytes))
}
}
/// An "asset source change event" that occurs whenever asset (or asset metadata) is created/added/removed
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AssetSourceEvent {
/// An asset at this path was added.
AddedAsset(PathBuf),
/// An asset at this path was modified.
ModifiedAsset(PathBuf),
/// An asset at this path was removed.
RemovedAsset(PathBuf),
/// An asset at this path was renamed.
RenamedAsset { old: PathBuf, new: PathBuf },
/// Asset metadata at this path was added.
AddedMeta(PathBuf),
/// Asset metadata at this path was modified.
ModifiedMeta(PathBuf),
/// Asset metadata at this path was removed.
RemovedMeta(PathBuf),
/// Asset metadata at this path was renamed.
RenamedMeta { old: PathBuf, new: PathBuf },
/// A folder at the given path was added.
AddedFolder(PathBuf),
/// A folder at the given path was removed.
RemovedFolder(PathBuf),
/// A folder at the given path was renamed.
RenamedFolder { old: PathBuf, new: PathBuf },
/// Something of unknown type was removed. It is the job of the event handler to determine the type.
/// This exists because notify-rs produces "untyped" rename events without destination paths for unwatched folders, so we can't determine the type of
/// the rename.
RemovedUnknown {
/// The path of the removed asset or folder (undetermined). This could be an asset path or a folder. This will not be a "meta file" path.
path: PathBuf,
/// This field is only relevant if `path` is determined to be an asset path (and therefore not a folder). If this field is `true`,
/// then this event corresponds to a meta removal (not an asset removal) . If `false`, then this event corresponds to an asset removal
/// (not a meta removal).
is_meta: bool,
},
}
/// A handle to an "asset watcher" process, that will listen for and emit [`AssetSourceEvent`] values for as long as
/// [`AssetWatcher`] has not been dropped.
pub trait AssetWatcher: Send + Sync + 'static {}
/// An [`AsyncRead`] implementation capable of reading a [`Vec<u8>`].
pub struct VecReader {
bytes: Vec<u8>,
bytes_read: usize,
}
impl VecReader {
/// Create a new [`VecReader`] for `bytes`.
pub fn new(bytes: Vec<u8>) -> Self {
Self {
bytes_read: 0,
bytes,
}
}
}
impl AsyncRead for VecReader {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<futures_io::Result<usize>> {
if self.bytes_read >= self.bytes.len() {
Poll::Ready(Ok(0))
} else {
let n = ready!(Pin::new(&mut &self.bytes[self.bytes_read..]).poll_read(cx, buf))?;
self.bytes_read += n;
Poll::Ready(Ok(n))
}
}
}
impl AsyncSeekForward for VecReader {
fn poll_seek_forward(
mut self: Pin<&mut Self>,
_cx: &mut Context<'_>,
offset: u64,
) -> Poll<std::io::Result<u64>> {
let result = self
.bytes_read
.try_into()
.map(|bytes_read: u64| bytes_read + offset);
if let Ok(new_pos) = result {
self.bytes_read = new_pos as _;
Poll::Ready(Ok(new_pos as _))
} else {
Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"seek position is out of range",
)))
}
}
}
impl Reader for VecReader {
fn read_to_end<'a>(
&'a mut self,
buf: &'a mut Vec<u8>,
) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
StackFuture::from(async {
if self.bytes_read >= self.bytes.len() {
Ok(0)
} else {
buf.extend_from_slice(&self.bytes[self.bytes_read..]);
let n = self.bytes.len() - self.bytes_read;
self.bytes_read = self.bytes.len();
Ok(n)
}
})
}
}
/// An [`AsyncRead`] implementation capable of reading a [`&[u8]`].
pub struct SliceReader<'a> {
bytes: &'a [u8],
bytes_read: usize,
}
impl<'a> SliceReader<'a> {
/// Create a new [`SliceReader`] for `bytes`.
pub fn new(bytes: &'a [u8]) -> Self {
Self {
bytes,
bytes_read: 0,
}
}
}
impl<'a> AsyncRead for SliceReader<'a> {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<std::io::Result<usize>> {
if self.bytes_read >= self.bytes.len() {
Poll::Ready(Ok(0))
} else {
let n = ready!(Pin::new(&mut &self.bytes[self.bytes_read..]).poll_read(cx, buf))?;
self.bytes_read += n;
Poll::Ready(Ok(n))
}
}
}
impl<'a> AsyncSeekForward for SliceReader<'a> {
fn poll_seek_forward(
mut self: Pin<&mut Self>,
_cx: &mut Context<'_>,
offset: u64,
) -> Poll<std::io::Result<u64>> {
let result = self
.bytes_read
.try_into()
.map(|bytes_read: u64| bytes_read + offset);
if let Ok(new_pos) = result {
self.bytes_read = new_pos as _;
Poll::Ready(Ok(new_pos as _))
} else {
Poll::Ready(Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"seek position is out of range",
)))
}
}
}
impl Reader for SliceReader<'_> {
fn read_to_end<'a>(
&'a mut self,
buf: &'a mut Vec<u8>,
) -> StackFuture<'a, std::io::Result<usize>, STACK_FUTURE_SIZE> {
StackFuture::from(async {
if self.bytes_read >= self.bytes.len() {
Ok(0)
} else {
buf.extend_from_slice(&self.bytes[self.bytes_read..]);
let n = self.bytes.len() - self.bytes_read;
self.bytes_read = self.bytes.len();
Ok(n)
}
})
}
}
/// Appends `.meta` to the given path.
pub(crate) fn get_meta_path(path: &Path) -> PathBuf {
let mut meta_path = path.to_path_buf();
let mut extension = path.extension().unwrap_or_default().to_os_string();
extension.push(".meta");
meta_path.set_extension(extension);
meta_path
}
#[cfg(any(target_arch = "wasm32", target_os = "android"))]
/// A [`PathBuf`] [`Stream`] implementation that immediately returns nothing.
struct EmptyPathStream;
#[cfg(any(target_arch = "wasm32", target_os = "android"))]
impl Stream for EmptyPathStream {
type Item = PathBuf;
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
Poll::Ready(None)
}
}

View File

@@ -0,0 +1,162 @@
use crate::{
io::{AssetReader, AssetReaderError, AssetSourceId, PathStream, Reader},
processor::{AssetProcessorData, ProcessStatus},
AssetPath,
};
use alloc::{borrow::ToOwned, boxed::Box, sync::Arc, vec::Vec};
use async_lock::RwLockReadGuardArc;
use core::{pin::Pin, task::Poll};
use futures_io::AsyncRead;
use std::path::Path;
use tracing::trace;
use super::{AsyncSeekForward, ErasedAssetReader};
/// An [`AssetReader`] that will prevent asset (and asset metadata) read futures from returning for a
/// given path until that path has been processed by [`AssetProcessor`].
///
/// [`AssetProcessor`]: crate::processor::AssetProcessor
pub struct ProcessorGatedReader {
reader: Box<dyn ErasedAssetReader>,
source: AssetSourceId<'static>,
processor_data: Arc<AssetProcessorData>,
}
impl ProcessorGatedReader {
/// Creates a new [`ProcessorGatedReader`].
pub fn new(
source: AssetSourceId<'static>,
reader: Box<dyn ErasedAssetReader>,
processor_data: Arc<AssetProcessorData>,
) -> Self {
Self {
source,
processor_data,
reader,
}
}
/// Gets a "transaction lock" that can be used to ensure no writes to asset or asset meta occur
/// while it is held.
async fn get_transaction_lock(
&self,
path: &AssetPath<'static>,
) -> Result<RwLockReadGuardArc<()>, AssetReaderError> {
let infos = self.processor_data.asset_infos.read().await;
let info = infos
.get(path)
.ok_or_else(|| AssetReaderError::NotFound(path.path().to_owned()))?;
Ok(info.file_transaction_lock.read_arc().await)
}
}
impl AssetReader for ProcessorGatedReader {
async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
let asset_path = AssetPath::from(path.to_path_buf()).with_source(self.source.clone());
trace!("Waiting for processing to finish before reading {asset_path}");
let process_result = self
.processor_data
.wait_until_processed(asset_path.clone())
.await;
match process_result {
ProcessStatus::Processed => {}
ProcessStatus::Failed | ProcessStatus::NonExistent => {
return Err(AssetReaderError::NotFound(path.to_owned()));
}
}
trace!("Processing finished with {asset_path}, reading {process_result:?}",);
let lock = self.get_transaction_lock(&asset_path).await?;
let asset_reader = self.reader.read(path).await?;
let reader = TransactionLockedReader::new(asset_reader, lock);
Ok(reader)
}
async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
let asset_path = AssetPath::from(path.to_path_buf()).with_source(self.source.clone());
trace!("Waiting for processing to finish before reading meta for {asset_path}",);
let process_result = self
.processor_data
.wait_until_processed(asset_path.clone())
.await;
match process_result {
ProcessStatus::Processed => {}
ProcessStatus::Failed | ProcessStatus::NonExistent => {
return Err(AssetReaderError::NotFound(path.to_owned()));
}
}
trace!("Processing finished with {process_result:?}, reading meta for {asset_path}",);
let lock = self.get_transaction_lock(&asset_path).await?;
let meta_reader = self.reader.read_meta(path).await?;
let reader = TransactionLockedReader::new(meta_reader, lock);
Ok(reader)
}
async fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> Result<Box<PathStream>, AssetReaderError> {
trace!(
"Waiting for processing to finish before reading directory {:?}",
path
);
self.processor_data.wait_until_finished().await;
trace!("Processing finished, reading directory {:?}", path);
let result = self.reader.read_directory(path).await?;
Ok(result)
}
async fn is_directory<'a>(&'a self, path: &'a Path) -> Result<bool, AssetReaderError> {
trace!(
"Waiting for processing to finish before reading directory {:?}",
path
);
self.processor_data.wait_until_finished().await;
trace!("Processing finished, getting directory status {:?}", path);
let result = self.reader.is_directory(path).await?;
Ok(result)
}
}
/// An [`AsyncRead`] impl that will hold its asset's transaction lock until [`TransactionLockedReader`] is dropped.
pub struct TransactionLockedReader<'a> {
reader: Box<dyn Reader + 'a>,
_file_transaction_lock: RwLockReadGuardArc<()>,
}
impl<'a> TransactionLockedReader<'a> {
fn new(reader: Box<dyn Reader + 'a>, file_transaction_lock: RwLockReadGuardArc<()>) -> Self {
Self {
reader,
_file_transaction_lock: file_transaction_lock,
}
}
}
impl AsyncRead for TransactionLockedReader<'_> {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
buf: &mut [u8],
) -> Poll<futures_io::Result<usize>> {
Pin::new(&mut self.reader).poll_read(cx, buf)
}
}
impl AsyncSeekForward for TransactionLockedReader<'_> {
fn poll_seek_forward(
mut self: Pin<&mut Self>,
cx: &mut core::task::Context<'_>,
offset: u64,
) -> Poll<std::io::Result<u64>> {
Pin::new(&mut self.reader).poll_seek_forward(cx, offset)
}
}
impl Reader for TransactionLockedReader<'_> {
fn read_to_end<'a>(
&'a mut self,
buf: &'a mut Vec<u8>,
) -> stackfuture::StackFuture<'a, std::io::Result<usize>, { super::STACK_FUTURE_SIZE }> {
self.reader.read_to_end(buf)
}
}

662
vendor/bevy_asset/src/io/source.rs vendored Normal file
View File

@@ -0,0 +1,662 @@
use crate::{
io::{processor_gated::ProcessorGatedReader, AssetSourceEvent, AssetWatcher},
processor::AssetProcessorData,
};
use alloc::{
boxed::Box,
string::{String, ToString},
sync::Arc,
};
use atomicow::CowArc;
use bevy_ecs::resource::Resource;
use bevy_platform::collections::HashMap;
use core::{fmt::Display, hash::Hash, time::Duration};
use thiserror::Error;
use tracing::{error, warn};
use super::{ErasedAssetReader, ErasedAssetWriter};
/// A reference to an "asset source", which maps to an [`AssetReader`](crate::io::AssetReader) and/or [`AssetWriter`](crate::io::AssetWriter).
///
/// * [`AssetSourceId::Default`] corresponds to "default asset paths" that don't specify a source: `/path/to/asset.png`
/// * [`AssetSourceId::Name`] corresponds to asset paths that _do_ specify a source: `remote://path/to/asset.png`, where `remote` is the name.
#[derive(Default, Clone, Debug, Eq)]
pub enum AssetSourceId<'a> {
/// The default asset source.
#[default]
Default,
/// A non-default named asset source.
Name(CowArc<'a, str>),
}
impl<'a> Display for AssetSourceId<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self.as_str() {
None => write!(f, "AssetSourceId::Default"),
Some(v) => write!(f, "AssetSourceId::Name({v})"),
}
}
}
impl<'a> AssetSourceId<'a> {
/// Creates a new [`AssetSourceId`]
pub fn new(source: Option<impl Into<CowArc<'a, str>>>) -> AssetSourceId<'a> {
match source {
Some(source) => AssetSourceId::Name(source.into()),
None => AssetSourceId::Default,
}
}
/// Returns [`None`] if this is [`AssetSourceId::Default`] and [`Some`] containing the
/// name if this is [`AssetSourceId::Name`].
pub fn as_str(&self) -> Option<&str> {
match self {
AssetSourceId::Default => None,
AssetSourceId::Name(v) => Some(v),
}
}
/// If this is not already an owned / static id, create one. Otherwise, it will return itself (with a static lifetime).
pub fn into_owned(self) -> AssetSourceId<'static> {
match self {
AssetSourceId::Default => AssetSourceId::Default,
AssetSourceId::Name(v) => AssetSourceId::Name(v.into_owned()),
}
}
/// Clones into an owned [`AssetSourceId<'static>`].
/// This is equivalent to `.clone().into_owned()`.
#[inline]
pub fn clone_owned(&self) -> AssetSourceId<'static> {
self.clone().into_owned()
}
}
impl AssetSourceId<'static> {
/// Indicates this [`AssetSourceId`] should have a static lifetime.
#[inline]
pub fn as_static(self) -> Self {
match self {
Self::Default => Self::Default,
Self::Name(value) => Self::Name(value.as_static()),
}
}
/// Constructs an [`AssetSourceId`] with a static lifetime.
#[inline]
pub fn from_static(value: impl Into<Self>) -> Self {
value.into().as_static()
}
}
impl<'a> From<&'a str> for AssetSourceId<'a> {
fn from(value: &'a str) -> Self {
AssetSourceId::Name(CowArc::Borrowed(value))
}
}
impl<'a, 'b> From<&'a AssetSourceId<'b>> for AssetSourceId<'b> {
fn from(value: &'a AssetSourceId<'b>) -> Self {
value.clone()
}
}
impl<'a> From<Option<&'a str>> for AssetSourceId<'a> {
fn from(value: Option<&'a str>) -> Self {
match value {
Some(value) => AssetSourceId::Name(CowArc::Borrowed(value)),
None => AssetSourceId::Default,
}
}
}
impl From<String> for AssetSourceId<'static> {
fn from(value: String) -> Self {
AssetSourceId::Name(value.into())
}
}
impl<'a> Hash for AssetSourceId<'a> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.as_str().hash(state);
}
}
impl<'a> PartialEq for AssetSourceId<'a> {
fn eq(&self, other: &Self) -> bool {
self.as_str().eq(&other.as_str())
}
}
/// Metadata about an "asset source", such as how to construct the [`AssetReader`](crate::io::AssetReader) and [`AssetWriter`](crate::io::AssetWriter) for the source,
/// and whether or not the source is processed.
#[derive(Default)]
pub struct AssetSourceBuilder {
/// The [`ErasedAssetReader`] to use on the unprocessed asset.
pub reader: Option<Box<dyn FnMut() -> Box<dyn ErasedAssetReader> + Send + Sync>>,
/// The [`ErasedAssetWriter`] to use on the unprocessed asset.
pub writer: Option<Box<dyn FnMut(bool) -> Option<Box<dyn ErasedAssetWriter>> + Send + Sync>>,
/// The [`AssetWatcher`] to use for unprocessed assets, if any.
pub watcher: Option<
Box<
dyn FnMut(crossbeam_channel::Sender<AssetSourceEvent>) -> Option<Box<dyn AssetWatcher>>
+ Send
+ Sync,
>,
>,
/// The [`ErasedAssetReader`] to use for processed assets.
pub processed_reader: Option<Box<dyn FnMut() -> Box<dyn ErasedAssetReader> + Send + Sync>>,
/// The [`ErasedAssetWriter`] to use for processed assets.
pub processed_writer:
Option<Box<dyn FnMut(bool) -> Option<Box<dyn ErasedAssetWriter>> + Send + Sync>>,
/// The [`AssetWatcher`] to use for processed assets, if any.
pub processed_watcher: Option<
Box<
dyn FnMut(crossbeam_channel::Sender<AssetSourceEvent>) -> Option<Box<dyn AssetWatcher>>
+ Send
+ Sync,
>,
>,
/// The warning message to display when watching an unprocessed asset fails.
pub watch_warning: Option<&'static str>,
/// The warning message to display when watching a processed asset fails.
pub processed_watch_warning: Option<&'static str>,
}
impl AssetSourceBuilder {
/// Builds a new [`AssetSource`] with the given `id`. If `watch` is true, the unprocessed source will watch for changes.
/// If `watch_processed` is true, the processed source will watch for changes.
pub fn build(
&mut self,
id: AssetSourceId<'static>,
watch: bool,
watch_processed: bool,
) -> Option<AssetSource> {
let reader = self.reader.as_mut()?();
let writer = self.writer.as_mut().and_then(|w| w(false));
let processed_writer = self.processed_writer.as_mut().and_then(|w| w(true));
let mut source = AssetSource {
id: id.clone(),
reader,
writer,
processed_reader: self.processed_reader.as_mut().map(|r| r()),
processed_writer,
event_receiver: None,
watcher: None,
processed_event_receiver: None,
processed_watcher: None,
};
if watch {
let (sender, receiver) = crossbeam_channel::unbounded();
match self.watcher.as_mut().and_then(|w| w(sender)) {
Some(w) => {
source.watcher = Some(w);
source.event_receiver = Some(receiver);
}
None => {
if let Some(warning) = self.watch_warning {
warn!("{id} does not have an AssetWatcher configured. {warning}");
}
}
}
}
if watch_processed {
let (sender, receiver) = crossbeam_channel::unbounded();
match self.processed_watcher.as_mut().and_then(|w| w(sender)) {
Some(w) => {
source.processed_watcher = Some(w);
source.processed_event_receiver = Some(receiver);
}
None => {
if let Some(warning) = self.processed_watch_warning {
warn!("{id} does not have a processed AssetWatcher configured. {warning}");
}
}
}
}
Some(source)
}
/// Will use the given `reader` function to construct unprocessed [`AssetReader`](crate::io::AssetReader) instances.
pub fn with_reader(
mut self,
reader: impl FnMut() -> Box<dyn ErasedAssetReader> + Send + Sync + 'static,
) -> Self {
self.reader = Some(Box::new(reader));
self
}
/// Will use the given `writer` function to construct unprocessed [`AssetWriter`](crate::io::AssetWriter) instances.
pub fn with_writer(
mut self,
writer: impl FnMut(bool) -> Option<Box<dyn ErasedAssetWriter>> + Send + Sync + 'static,
) -> Self {
self.writer = Some(Box::new(writer));
self
}
/// Will use the given `watcher` function to construct unprocessed [`AssetWatcher`] instances.
pub fn with_watcher(
mut self,
watcher: impl FnMut(crossbeam_channel::Sender<AssetSourceEvent>) -> Option<Box<dyn AssetWatcher>>
+ Send
+ Sync
+ 'static,
) -> Self {
self.watcher = Some(Box::new(watcher));
self
}
/// Will use the given `reader` function to construct processed [`AssetReader`](crate::io::AssetReader) instances.
pub fn with_processed_reader(
mut self,
reader: impl FnMut() -> Box<dyn ErasedAssetReader> + Send + Sync + 'static,
) -> Self {
self.processed_reader = Some(Box::new(reader));
self
}
/// Will use the given `writer` function to construct processed [`AssetWriter`](crate::io::AssetWriter) instances.
pub fn with_processed_writer(
mut self,
writer: impl FnMut(bool) -> Option<Box<dyn ErasedAssetWriter>> + Send + Sync + 'static,
) -> Self {
self.processed_writer = Some(Box::new(writer));
self
}
/// Will use the given `watcher` function to construct processed [`AssetWatcher`] instances.
pub fn with_processed_watcher(
mut self,
watcher: impl FnMut(crossbeam_channel::Sender<AssetSourceEvent>) -> Option<Box<dyn AssetWatcher>>
+ Send
+ Sync
+ 'static,
) -> Self {
self.processed_watcher = Some(Box::new(watcher));
self
}
/// Enables a warning for the unprocessed source watcher, which will print when watching is enabled and the unprocessed source doesn't have a watcher.
pub fn with_watch_warning(mut self, warning: &'static str) -> Self {
self.watch_warning = Some(warning);
self
}
/// Enables a warning for the processed source watcher, which will print when watching is enabled and the processed source doesn't have a watcher.
pub fn with_processed_watch_warning(mut self, warning: &'static str) -> Self {
self.processed_watch_warning = Some(warning);
self
}
/// Returns a builder containing the "platform default source" for the given `path` and `processed_path`.
/// For most platforms, this will use [`FileAssetReader`](crate::io::file::FileAssetReader) / [`FileAssetWriter`](crate::io::file::FileAssetWriter),
/// but some platforms (such as Android) have their own default readers / writers / watchers.
pub fn platform_default(path: &str, processed_path: Option<&str>) -> Self {
let default = Self::default()
.with_reader(AssetSource::get_default_reader(path.to_string()))
.with_writer(AssetSource::get_default_writer(path.to_string()))
.with_watcher(AssetSource::get_default_watcher(
path.to_string(),
Duration::from_millis(300),
))
.with_watch_warning(AssetSource::get_default_watch_warning());
if let Some(processed_path) = processed_path {
default
.with_processed_reader(AssetSource::get_default_reader(processed_path.to_string()))
.with_processed_writer(AssetSource::get_default_writer(processed_path.to_string()))
.with_processed_watcher(AssetSource::get_default_watcher(
processed_path.to_string(),
Duration::from_millis(300),
))
.with_processed_watch_warning(AssetSource::get_default_watch_warning())
} else {
default
}
}
}
/// A [`Resource`] that hold (repeatable) functions capable of producing new [`AssetReader`](crate::io::AssetReader) and [`AssetWriter`](crate::io::AssetWriter) instances
/// for a given asset source.
#[derive(Resource, Default)]
pub struct AssetSourceBuilders {
sources: HashMap<CowArc<'static, str>, AssetSourceBuilder>,
default: Option<AssetSourceBuilder>,
}
impl AssetSourceBuilders {
/// Inserts a new builder with the given `id`
pub fn insert(&mut self, id: impl Into<AssetSourceId<'static>>, source: AssetSourceBuilder) {
match AssetSourceId::from_static(id) {
AssetSourceId::Default => {
self.default = Some(source);
}
AssetSourceId::Name(name) => {
self.sources.insert(name, source);
}
}
}
/// Gets a mutable builder with the given `id`, if it exists.
pub fn get_mut<'a, 'b>(
&'a mut self,
id: impl Into<AssetSourceId<'b>>,
) -> Option<&'a mut AssetSourceBuilder> {
match id.into() {
AssetSourceId::Default => self.default.as_mut(),
AssetSourceId::Name(name) => self.sources.get_mut(&name.into_owned()),
}
}
/// Builds a new [`AssetSources`] collection. If `watch` is true, the unprocessed sources will watch for changes.
/// If `watch_processed` is true, the processed sources will watch for changes.
pub fn build_sources(&mut self, watch: bool, watch_processed: bool) -> AssetSources {
let mut sources = <HashMap<_, _>>::default();
for (id, source) in &mut self.sources {
if let Some(data) = source.build(
AssetSourceId::Name(id.clone_owned()),
watch,
watch_processed,
) {
sources.insert(id.clone_owned(), data);
}
}
AssetSources {
sources,
default: self
.default
.as_mut()
.and_then(|p| p.build(AssetSourceId::Default, watch, watch_processed))
.expect(MISSING_DEFAULT_SOURCE),
}
}
/// Initializes the default [`AssetSourceBuilder`] if it has not already been set.
pub fn init_default_source(&mut self, path: &str, processed_path: Option<&str>) {
self.default
.get_or_insert_with(|| AssetSourceBuilder::platform_default(path, processed_path));
}
}
/// A collection of unprocessed and processed [`AssetReader`](crate::io::AssetReader), [`AssetWriter`](crate::io::AssetWriter), and [`AssetWatcher`] instances
/// for a specific asset source, identified by an [`AssetSourceId`].
pub struct AssetSource {
id: AssetSourceId<'static>,
reader: Box<dyn ErasedAssetReader>,
writer: Option<Box<dyn ErasedAssetWriter>>,
processed_reader: Option<Box<dyn ErasedAssetReader>>,
processed_writer: Option<Box<dyn ErasedAssetWriter>>,
watcher: Option<Box<dyn AssetWatcher>>,
processed_watcher: Option<Box<dyn AssetWatcher>>,
event_receiver: Option<crossbeam_channel::Receiver<AssetSourceEvent>>,
processed_event_receiver: Option<crossbeam_channel::Receiver<AssetSourceEvent>>,
}
impl AssetSource {
/// Starts building a new [`AssetSource`].
pub fn build() -> AssetSourceBuilder {
AssetSourceBuilder::default()
}
/// Returns this source's id.
#[inline]
pub fn id(&self) -> AssetSourceId<'static> {
self.id.clone()
}
/// Return's this source's unprocessed [`AssetReader`](crate::io::AssetReader).
#[inline]
pub fn reader(&self) -> &dyn ErasedAssetReader {
&*self.reader
}
/// Return's this source's unprocessed [`AssetWriter`](crate::io::AssetWriter), if it exists.
#[inline]
pub fn writer(&self) -> Result<&dyn ErasedAssetWriter, MissingAssetWriterError> {
self.writer
.as_deref()
.ok_or_else(|| MissingAssetWriterError(self.id.clone_owned()))
}
/// Return's this source's processed [`AssetReader`](crate::io::AssetReader), if it exists.
#[inline]
pub fn processed_reader(
&self,
) -> Result<&dyn ErasedAssetReader, MissingProcessedAssetReaderError> {
self.processed_reader
.as_deref()
.ok_or_else(|| MissingProcessedAssetReaderError(self.id.clone_owned()))
}
/// Return's this source's processed [`AssetWriter`](crate::io::AssetWriter), if it exists.
#[inline]
pub fn processed_writer(
&self,
) -> Result<&dyn ErasedAssetWriter, MissingProcessedAssetWriterError> {
self.processed_writer
.as_deref()
.ok_or_else(|| MissingProcessedAssetWriterError(self.id.clone_owned()))
}
/// Return's this source's unprocessed event receiver, if the source is currently watching for changes.
#[inline]
pub fn event_receiver(&self) -> Option<&crossbeam_channel::Receiver<AssetSourceEvent>> {
self.event_receiver.as_ref()
}
/// Return's this source's processed event receiver, if the source is currently watching for changes.
#[inline]
pub fn processed_event_receiver(
&self,
) -> Option<&crossbeam_channel::Receiver<AssetSourceEvent>> {
self.processed_event_receiver.as_ref()
}
/// Returns true if the assets in this source should be processed.
#[inline]
pub fn should_process(&self) -> bool {
self.processed_writer.is_some()
}
/// Returns a builder function for this platform's default [`AssetReader`](crate::io::AssetReader). `path` is the relative path to
/// the asset root.
pub fn get_default_reader(
_path: String,
) -> impl FnMut() -> Box<dyn ErasedAssetReader> + Send + Sync {
move || {
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
return Box::new(super::file::FileAssetReader::new(&_path));
#[cfg(target_arch = "wasm32")]
return Box::new(super::wasm::HttpWasmAssetReader::new(&_path));
#[cfg(target_os = "android")]
return Box::new(super::android::AndroidAssetReader);
}
}
/// Returns a builder function for this platform's default [`AssetWriter`](crate::io::AssetWriter). `path` is the relative path to
/// the asset root. This will return [`None`] if this platform does not support writing assets by default.
pub fn get_default_writer(
_path: String,
) -> impl FnMut(bool) -> Option<Box<dyn ErasedAssetWriter>> + Send + Sync {
move |_create_root: bool| {
#[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
return Some(Box::new(super::file::FileAssetWriter::new(
&_path,
_create_root,
)));
#[cfg(any(target_arch = "wasm32", target_os = "android"))]
return None;
}
}
/// Returns the default non-existent [`AssetWatcher`] warning for the current platform.
pub fn get_default_watch_warning() -> &'static str {
#[cfg(target_arch = "wasm32")]
return "Web does not currently support watching assets.";
#[cfg(target_os = "android")]
return "Android does not currently support watching assets.";
#[cfg(all(
not(target_arch = "wasm32"),
not(target_os = "android"),
not(feature = "file_watcher")
))]
return "Consider enabling the `file_watcher` feature.";
#[cfg(all(
not(target_arch = "wasm32"),
not(target_os = "android"),
feature = "file_watcher"
))]
return "Consider adding an \"assets\" directory.";
}
/// Returns a builder function for this platform's default [`AssetWatcher`]. `path` is the relative path to
/// the asset root. This will return [`None`] if this platform does not support watching assets by default.
/// `file_debounce_time` is the amount of time to wait (and debounce duplicate events) before returning an event.
/// Higher durations reduce duplicates but increase the amount of time before a change event is processed. If the
/// duration is set too low, some systems might surface events _before_ their filesystem has the changes.
#[cfg_attr(
any(
not(feature = "file_watcher"),
target_arch = "wasm32",
target_os = "android"
),
expect(
unused_variables,
reason = "The `path` and `file_debounce_wait_time` arguments are unused when on WASM, Android, or if the `file_watcher` feature is disabled."
)
)]
pub fn get_default_watcher(
path: String,
file_debounce_wait_time: Duration,
) -> impl FnMut(crossbeam_channel::Sender<AssetSourceEvent>) -> Option<Box<dyn AssetWatcher>>
+ Send
+ Sync {
move |sender: crossbeam_channel::Sender<AssetSourceEvent>| {
#[cfg(all(
feature = "file_watcher",
not(target_arch = "wasm32"),
not(target_os = "android")
))]
{
let path = super::file::get_base_path().join(path.clone());
if path.exists() {
Some(Box::new(
super::file::FileWatcher::new(
path.clone(),
sender,
file_debounce_wait_time,
)
.unwrap_or_else(|e| {
panic!("Failed to create file watcher from path {path:?}, {e:?}")
}),
))
} else {
warn!("Skip creating file watcher because path {path:?} does not exist.");
None
}
}
#[cfg(any(
not(feature = "file_watcher"),
target_arch = "wasm32",
target_os = "android"
))]
return None;
}
}
/// This will cause processed [`AssetReader`](crate::io::AssetReader) futures (such as [`AssetReader::read`](crate::io::AssetReader::read)) to wait until
/// the [`AssetProcessor`](crate::AssetProcessor) has finished processing the requested asset.
pub fn gate_on_processor(&mut self, processor_data: Arc<AssetProcessorData>) {
if let Some(reader) = self.processed_reader.take() {
self.processed_reader = Some(Box::new(ProcessorGatedReader::new(
self.id(),
reader,
processor_data,
)));
}
}
}
/// A collection of [`AssetSource`]s.
pub struct AssetSources {
sources: HashMap<CowArc<'static, str>, AssetSource>,
default: AssetSource,
}
impl AssetSources {
/// Gets the [`AssetSource`] with the given `id`, if it exists.
pub fn get<'a, 'b>(
&'a self,
id: impl Into<AssetSourceId<'b>>,
) -> Result<&'a AssetSource, MissingAssetSourceError> {
match id.into().into_owned() {
AssetSourceId::Default => Ok(&self.default),
AssetSourceId::Name(name) => self
.sources
.get(&name)
.ok_or(MissingAssetSourceError(AssetSourceId::Name(name))),
}
}
/// Iterates all asset sources in the collection (including the default source).
pub fn iter(&self) -> impl Iterator<Item = &AssetSource> {
self.sources.values().chain(Some(&self.default))
}
/// Mutably iterates all asset sources in the collection (including the default source).
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut AssetSource> {
self.sources.values_mut().chain(Some(&mut self.default))
}
/// Iterates all processed asset sources in the collection (including the default source).
pub fn iter_processed(&self) -> impl Iterator<Item = &AssetSource> {
self.iter().filter(|p| p.should_process())
}
/// Mutably iterates all processed asset sources in the collection (including the default source).
pub fn iter_processed_mut(&mut self) -> impl Iterator<Item = &mut AssetSource> {
self.iter_mut().filter(|p| p.should_process())
}
/// Iterates over the [`AssetSourceId`] of every [`AssetSource`] in the collection (including the default source).
pub fn ids(&self) -> impl Iterator<Item = AssetSourceId<'static>> + '_ {
self.sources
.keys()
.map(|k| AssetSourceId::Name(k.clone_owned()))
.chain(Some(AssetSourceId::Default))
}
/// This will cause processed [`AssetReader`](crate::io::AssetReader) futures (such as [`AssetReader::read`](crate::io::AssetReader::read)) to wait until
/// the [`AssetProcessor`](crate::AssetProcessor) has finished processing the requested asset.
pub fn gate_on_processor(&mut self, processor_data: Arc<AssetProcessorData>) {
for source in self.iter_processed_mut() {
source.gate_on_processor(processor_data.clone());
}
}
}
/// An error returned when an [`AssetSource`] does not exist for a given id.
#[derive(Error, Debug, Clone, PartialEq, Eq)]
#[error("Asset Source '{0}' does not exist")]
pub struct MissingAssetSourceError(AssetSourceId<'static>);
/// An error returned when an [`AssetWriter`](crate::io::AssetWriter) does not exist for a given id.
#[derive(Error, Debug, Clone)]
#[error("Asset Source '{0}' does not have an AssetWriter.")]
pub struct MissingAssetWriterError(AssetSourceId<'static>);
/// An error returned when a processed [`AssetReader`](crate::io::AssetReader) does not exist for a given id.
#[derive(Error, Debug, Clone, PartialEq, Eq)]
#[error("Asset Source '{0}' does not have a processed AssetReader.")]
pub struct MissingProcessedAssetReaderError(AssetSourceId<'static>);
/// An error returned when a processed [`AssetWriter`](crate::io::AssetWriter) does not exist for a given id.
#[derive(Error, Debug, Clone)]
#[error("Asset Source '{0}' does not have a processed AssetWriter.")]
pub struct MissingProcessedAssetWriterError(AssetSourceId<'static>);
const MISSING_DEFAULT_SOURCE: &str =
"A default AssetSource is required. Add one to `AssetSourceBuilders`";

117
vendor/bevy_asset/src/io/wasm.rs vendored Normal file
View File

@@ -0,0 +1,117 @@
use crate::io::{
get_meta_path, AssetReader, AssetReaderError, EmptyPathStream, PathStream, Reader, VecReader,
};
use alloc::{borrow::ToOwned, boxed::Box, format};
use js_sys::{Uint8Array, JSON};
use std::path::{Path, PathBuf};
use tracing::error;
use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue};
use wasm_bindgen_futures::JsFuture;
use web_sys::Response;
/// Represents the global object in the JavaScript context
#[wasm_bindgen]
extern "C" {
/// The [Global](https://developer.mozilla.org/en-US/docs/Glossary/Global_object) object.
type Global;
/// The [window](https://developer.mozilla.org/en-US/docs/Web/API/Window) global object.
#[wasm_bindgen(method, getter, js_name = Window)]
fn window(this: &Global) -> JsValue;
/// The [WorkerGlobalScope](https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope) global object.
#[wasm_bindgen(method, getter, js_name = WorkerGlobalScope)]
fn worker(this: &Global) -> JsValue;
}
/// Reader implementation for loading assets via HTTP in Wasm.
pub struct HttpWasmAssetReader {
root_path: PathBuf,
}
impl HttpWasmAssetReader {
/// Creates a new `WasmAssetReader`. The path provided will be used to build URLs to query for assets.
pub fn new<P: AsRef<Path>>(path: P) -> Self {
Self {
root_path: path.as_ref().to_owned(),
}
}
}
fn js_value_to_err(context: &str) -> impl FnOnce(JsValue) -> std::io::Error + '_ {
move |value| {
let message = match JSON::stringify(&value) {
Ok(js_str) => format!("Failed to {context}: {js_str}"),
Err(_) => {
format!("Failed to {context} and also failed to stringify the JSValue of the error")
}
};
std::io::Error::new(std::io::ErrorKind::Other, message)
}
}
impl HttpWasmAssetReader {
async fn fetch_bytes(&self, path: PathBuf) -> Result<impl Reader, AssetReaderError> {
// The JS global scope includes a self-reference via a specializing name, which can be used to determine the type of global context available.
let global: Global = js_sys::global().unchecked_into();
let promise = if !global.window().is_undefined() {
let window: web_sys::Window = global.unchecked_into();
window.fetch_with_str(path.to_str().unwrap())
} else if !global.worker().is_undefined() {
let worker: web_sys::WorkerGlobalScope = global.unchecked_into();
worker.fetch_with_str(path.to_str().unwrap())
} else {
let error = std::io::Error::new(
std::io::ErrorKind::Other,
"Unsupported JavaScript global context",
);
return Err(AssetReaderError::Io(error.into()));
};
let resp_value = JsFuture::from(promise)
.await
.map_err(js_value_to_err("fetch path"))?;
let resp = resp_value
.dyn_into::<Response>()
.map_err(js_value_to_err("convert fetch to Response"))?;
match resp.status() {
200 => {
let data = JsFuture::from(resp.array_buffer().unwrap()).await.unwrap();
let bytes = Uint8Array::new(&data).to_vec();
let reader = VecReader::new(bytes);
Ok(reader)
}
// Some web servers, including itch.io's CDN, return 403 when a requested file isn't present.
// TODO: remove handling of 403 as not found when it's easier to configure
// see https://github.com/bevyengine/bevy/pull/19268#pullrequestreview-2882410105
403 | 404 => Err(AssetReaderError::NotFound(path)),
status => Err(AssetReaderError::HttpError(status)),
}
}
}
impl AssetReader for HttpWasmAssetReader {
async fn read<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
let path = self.root_path.join(path);
self.fetch_bytes(path).await
}
async fn read_meta<'a>(&'a self, path: &'a Path) -> Result<impl Reader + 'a, AssetReaderError> {
let meta_path = get_meta_path(&self.root_path.join(path));
self.fetch_bytes(meta_path).await
}
async fn read_directory<'a>(
&'a self,
_path: &'a Path,
) -> Result<Box<PathStream>, AssetReaderError> {
let stream: Box<PathStream> = Box::new(EmptyPathStream);
error!("Reading directories is not supported with the HttpWasmAssetReader");
Ok(stream)
}
async fn is_directory<'a>(&'a self, _path: &'a Path) -> Result<bool, AssetReaderError> {
error!("Reading directories is not supported with the HttpWasmAssetReader");
Ok(false)
}
}

1996
vendor/bevy_asset/src/lib.rs vendored Normal file

File diff suppressed because it is too large Load Diff

592
vendor/bevy_asset/src/loader.rs vendored Normal file
View File

@@ -0,0 +1,592 @@
use crate::{
io::{AssetReaderError, MissingAssetSourceError, MissingProcessedAssetReaderError, Reader},
loader_builders::{Deferred, NestedLoader, StaticTyped},
meta::{AssetHash, AssetMeta, AssetMetaDyn, ProcessedInfoMinimal, Settings},
path::AssetPath,
Asset, AssetLoadError, AssetServer, AssetServerMode, Assets, Handle, UntypedAssetId,
UntypedHandle,
};
use alloc::{
boxed::Box,
string::{String, ToString},
vec::Vec,
};
use atomicow::CowArc;
use bevy_ecs::world::World;
use bevy_platform::collections::{HashMap, HashSet};
use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
use core::any::{Any, TypeId};
use downcast_rs::{impl_downcast, Downcast};
use ron::error::SpannedError;
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use thiserror::Error;
/// Loads an [`Asset`] from a given byte [`Reader`]. This can accept [`AssetLoader::Settings`], which configure how the [`Asset`]
/// should be loaded.
///
/// This trait is generally used in concert with [`AssetReader`](crate::io::AssetReader) to load assets from a byte source.
///
/// For a complementary version of this trait that can save assets, see [`AssetSaver`](crate::saver::AssetSaver).
pub trait AssetLoader: Send + Sync + 'static {
/// The top level [`Asset`] loaded by this [`AssetLoader`].
type Asset: Asset;
/// The settings type used by this [`AssetLoader`].
type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
/// The type of [error](`std::error::Error`) which could be encountered by this loader.
type Error: Into<Box<dyn core::error::Error + Send + Sync + 'static>>;
/// Asynchronously loads [`AssetLoader::Asset`] (and any other labeled assets) from the bytes provided by [`Reader`].
fn load(
&self,
reader: &mut dyn Reader,
settings: &Self::Settings,
load_context: &mut LoadContext,
) -> impl ConditionalSendFuture<Output = Result<Self::Asset, Self::Error>>;
/// Returns a list of extensions supported by this [`AssetLoader`], without the preceding dot.
/// Note that users of this [`AssetLoader`] may choose to load files with a non-matching extension.
fn extensions(&self) -> &[&str] {
&[]
}
}
/// Provides type-erased access to an [`AssetLoader`].
pub trait ErasedAssetLoader: Send + Sync + 'static {
/// Asynchronously loads the asset(s) from the bytes provided by [`Reader`].
fn load<'a>(
&'a self,
reader: &'a mut dyn Reader,
meta: &'a dyn AssetMetaDyn,
load_context: LoadContext<'a>,
) -> BoxedFuture<
'a,
Result<ErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>,
>;
/// Returns a list of extensions supported by this asset loader, without the preceding dot.
fn extensions(&self) -> &[&str];
/// Deserializes metadata from the input `meta` bytes into the appropriate type (erased as [`Box<dyn AssetMetaDyn>`]).
fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError>;
/// Returns the default meta value for the [`AssetLoader`] (erased as [`Box<dyn AssetMetaDyn>`]).
fn default_meta(&self) -> Box<dyn AssetMetaDyn>;
/// Returns the type name of the [`AssetLoader`].
fn type_name(&self) -> &'static str;
/// Returns the [`TypeId`] of the [`AssetLoader`].
fn type_id(&self) -> TypeId;
/// Returns the type name of the top-level [`Asset`] loaded by the [`AssetLoader`].
fn asset_type_name(&self) -> &'static str;
/// Returns the [`TypeId`] of the top-level [`Asset`] loaded by the [`AssetLoader`].
fn asset_type_id(&self) -> TypeId;
}
impl<L> ErasedAssetLoader for L
where
L: AssetLoader + Send + Sync,
{
/// Processes the asset in an asynchronous closure.
fn load<'a>(
&'a self,
reader: &'a mut dyn Reader,
meta: &'a dyn AssetMetaDyn,
mut load_context: LoadContext<'a>,
) -> BoxedFuture<
'a,
Result<ErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>,
> {
Box::pin(async move {
let settings = meta
.loader_settings()
.expect("Loader settings should exist")
.downcast_ref::<L::Settings>()
.expect("AssetLoader settings should match the loader type");
let asset = <L as AssetLoader>::load(self, reader, settings, &mut load_context)
.await
.map_err(Into::into)?;
Ok(load_context.finish(asset).into())
})
}
fn extensions(&self) -> &[&str] {
<L as AssetLoader>::extensions(self)
}
fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError> {
let meta = AssetMeta::<L, ()>::deserialize(meta)?;
Ok(Box::new(meta))
}
fn default_meta(&self) -> Box<dyn AssetMetaDyn> {
Box::new(AssetMeta::<L, ()>::new(crate::meta::AssetAction::Load {
loader: self.type_name().to_string(),
settings: L::Settings::default(),
}))
}
fn type_name(&self) -> &'static str {
core::any::type_name::<L>()
}
fn type_id(&self) -> TypeId {
TypeId::of::<L>()
}
fn asset_type_name(&self) -> &'static str {
core::any::type_name::<L::Asset>()
}
fn asset_type_id(&self) -> TypeId {
TypeId::of::<L::Asset>()
}
}
pub(crate) struct LabeledAsset {
pub(crate) asset: ErasedLoadedAsset,
pub(crate) handle: UntypedHandle,
}
/// The successful result of an [`AssetLoader::load`] call. This contains the loaded "root" asset and any other "labeled" assets produced
/// by the loader. It also holds the input [`AssetMeta`] (if it exists) and tracks dependencies:
/// * normal dependencies: dependencies that must be loaded as part of this asset load (ex: assets a given asset has handles to).
/// * Loader dependencies: dependencies whose actual asset values are used during the load process
pub struct LoadedAsset<A: Asset> {
pub(crate) value: A,
pub(crate) dependencies: HashSet<UntypedAssetId>,
pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
}
impl<A: Asset> LoadedAsset<A> {
/// Create a new loaded asset. This will use [`VisitAssetDependencies`](crate::VisitAssetDependencies) to populate `dependencies`.
pub fn new_with_dependencies(value: A) -> Self {
let mut dependencies = <HashSet<_>>::default();
value.visit_dependencies(&mut |id| {
dependencies.insert(id);
});
LoadedAsset {
value,
dependencies,
loader_dependencies: HashMap::default(),
labeled_assets: HashMap::default(),
}
}
/// Cast (and take ownership) of the [`Asset`] value of the given type.
pub fn take(self) -> A {
self.value
}
/// Retrieves a reference to the internal [`Asset`] type.
pub fn get(&self) -> &A {
&self.value
}
/// Returns the [`ErasedLoadedAsset`] for the given label, if it exists.
pub fn get_labeled(
&self,
label: impl Into<CowArc<'static, str>>,
) -> Option<&ErasedLoadedAsset> {
self.labeled_assets.get(&label.into()).map(|a| &a.asset)
}
/// Iterate over all labels for "labeled assets" in the loaded asset
pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
self.labeled_assets.keys().map(|s| &**s)
}
}
impl<A: Asset> From<A> for LoadedAsset<A> {
fn from(asset: A) -> Self {
LoadedAsset::new_with_dependencies(asset)
}
}
/// A "type erased / boxed" counterpart to [`LoadedAsset`]. This is used in places where the loaded type is not statically known.
pub struct ErasedLoadedAsset {
pub(crate) value: Box<dyn AssetContainer>,
pub(crate) dependencies: HashSet<UntypedAssetId>,
pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
}
impl<A: Asset> From<LoadedAsset<A>> for ErasedLoadedAsset {
fn from(asset: LoadedAsset<A>) -> Self {
ErasedLoadedAsset {
value: Box::new(asset.value),
dependencies: asset.dependencies,
loader_dependencies: asset.loader_dependencies,
labeled_assets: asset.labeled_assets,
}
}
}
impl ErasedLoadedAsset {
/// Cast (and take ownership) of the [`Asset`] value of the given type. This will return [`Some`] if
/// the stored type matches `A` and [`None`] if it does not.
pub fn take<A: Asset>(self) -> Option<A> {
self.value.downcast::<A>().map(|a| *a).ok()
}
/// Retrieves a reference to the internal [`Asset`] type, if it matches the type `A`. Otherwise returns [`None`].
pub fn get<A: Asset>(&self) -> Option<&A> {
self.value.downcast_ref::<A>()
}
/// Retrieves the [`TypeId`] of the stored [`Asset`] type.
pub fn asset_type_id(&self) -> TypeId {
(*self.value).type_id()
}
/// Retrieves the `type_name` of the stored [`Asset`] type.
pub fn asset_type_name(&self) -> &'static str {
self.value.asset_type_name()
}
/// Returns the [`ErasedLoadedAsset`] for the given label, if it exists.
pub fn get_labeled(
&self,
label: impl Into<CowArc<'static, str>>,
) -> Option<&ErasedLoadedAsset> {
self.labeled_assets.get(&label.into()).map(|a| &a.asset)
}
/// Iterate over all labels for "labeled assets" in the loaded asset
pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
self.labeled_assets.keys().map(|s| &**s)
}
/// Cast this loaded asset as the given type. If the type does not match,
/// the original type-erased asset is returned.
pub fn downcast<A: Asset>(mut self) -> Result<LoadedAsset<A>, ErasedLoadedAsset> {
match self.value.downcast::<A>() {
Ok(value) => Ok(LoadedAsset {
value: *value,
dependencies: self.dependencies,
loader_dependencies: self.loader_dependencies,
labeled_assets: self.labeled_assets,
}),
Err(value) => {
self.value = value;
Err(self)
}
}
}
}
/// A type erased container for an [`Asset`] value that is capable of inserting the [`Asset`] into a [`World`]'s [`Assets`] collection.
pub trait AssetContainer: Downcast + Any + Send + Sync + 'static {
fn insert(self: Box<Self>, id: UntypedAssetId, world: &mut World);
fn asset_type_name(&self) -> &'static str;
}
impl_downcast!(AssetContainer);
impl<A: Asset> AssetContainer for A {
fn insert(self: Box<Self>, id: UntypedAssetId, world: &mut World) {
world.resource_mut::<Assets<A>>().insert(id.typed(), *self);
}
fn asset_type_name(&self) -> &'static str {
core::any::type_name::<A>()
}
}
/// An error that occurs when attempting to call [`NestedLoader::load`] which
/// is configured to work [immediately].
///
/// [`NestedLoader::load`]: crate::NestedLoader::load
/// [immediately]: crate::Immediate
#[derive(Error, Debug)]
pub enum LoadDirectError {
#[error("Requested to load an asset path ({0:?}) with a subasset, but this is unsupported. See issue #18291")]
RequestedSubasset(AssetPath<'static>),
#[error("Failed to load dependency {dependency:?} {error}")]
LoadError {
dependency: AssetPath<'static>,
error: AssetLoadError,
},
}
/// An error that occurs while deserializing [`AssetMeta`].
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum DeserializeMetaError {
#[error("Failed to deserialize asset meta: {0:?}")]
DeserializeSettings(#[from] SpannedError),
#[error("Failed to deserialize minimal asset meta: {0:?}")]
DeserializeMinimal(SpannedError),
}
/// A context that provides access to assets in [`AssetLoader`]s, tracks dependencies, and collects asset load state.
///
/// Any asset state accessed by [`LoadContext`] will be tracked and stored for use in dependency events and asset preprocessing.
pub struct LoadContext<'a> {
pub(crate) asset_server: &'a AssetServer,
pub(crate) should_load_dependencies: bool,
populate_hashes: bool,
asset_path: AssetPath<'static>,
pub(crate) dependencies: HashSet<UntypedAssetId>,
/// Direct dependencies used by this loader.
pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
}
impl<'a> LoadContext<'a> {
/// Creates a new [`LoadContext`] instance.
pub(crate) fn new(
asset_server: &'a AssetServer,
asset_path: AssetPath<'static>,
should_load_dependencies: bool,
populate_hashes: bool,
) -> Self {
Self {
asset_server,
asset_path,
populate_hashes,
should_load_dependencies,
dependencies: HashSet::default(),
loader_dependencies: HashMap::default(),
labeled_assets: HashMap::default(),
}
}
/// Begins a new labeled asset load. Use the returned [`LoadContext`] to load
/// dependencies for the new asset and call [`LoadContext::finish`] to finalize the asset load.
/// When finished, make sure you call [`LoadContext::add_labeled_asset`] to add the results back to the parent
/// context.
/// Prefer [`LoadContext::labeled_asset_scope`] when possible, which will automatically add
/// the labeled [`LoadContext`] back to the parent context.
/// [`LoadContext::begin_labeled_asset`] exists largely to enable parallel asset loading.
///
/// See [`AssetPath`] for more on labeled assets.
///
/// ```no_run
/// # use bevy_asset::{Asset, LoadContext};
/// # use bevy_reflect::TypePath;
/// # #[derive(Asset, TypePath, Default)]
/// # struct Image;
/// # let load_context: LoadContext = panic!();
/// let mut handles = Vec::new();
/// for i in 0..2 {
/// let mut labeled = load_context.begin_labeled_asset();
/// handles.push(std::thread::spawn(move || {
/// (i.to_string(), labeled.finish(Image::default()))
/// }));
/// }
///
/// for handle in handles {
/// let (label, loaded_asset) = handle.join().unwrap();
/// load_context.add_loaded_labeled_asset(label, loaded_asset);
/// }
/// ```
pub fn begin_labeled_asset(&self) -> LoadContext {
LoadContext::new(
self.asset_server,
self.asset_path.clone(),
self.should_load_dependencies,
self.populate_hashes,
)
}
/// Creates a new [`LoadContext`] for the given `label`. The `load` function is responsible for loading an [`Asset`] of
/// type `A`. `load` will be called immediately and the result will be used to finalize the [`LoadContext`], resulting in a new
/// [`LoadedAsset`], which is registered under the `label` label.
///
/// This exists to remove the need to manually call [`LoadContext::begin_labeled_asset`] and then manually register the
/// result with [`LoadContext::add_labeled_asset`].
///
/// See [`AssetPath`] for more on labeled assets.
pub fn labeled_asset_scope<A: Asset>(
&mut self,
label: String,
load: impl FnOnce(&mut LoadContext) -> A,
) -> Handle<A> {
let mut context = self.begin_labeled_asset();
let asset = load(&mut context);
let loaded_asset = context.finish(asset);
self.add_loaded_labeled_asset(label, loaded_asset)
}
/// This will add the given `asset` as a "labeled [`Asset`]" with the `label` label.
///
/// # Warning
///
/// This will not assign dependencies to the given `asset`. If adding an asset
/// with dependencies generated from calls such as [`LoadContext::load`], use
/// [`LoadContext::labeled_asset_scope`] or [`LoadContext::begin_labeled_asset`] to generate a
/// new [`LoadContext`] to track the dependencies for the labeled asset.
///
/// See [`AssetPath`] for more on labeled assets.
pub fn add_labeled_asset<A: Asset>(&mut self, label: String, asset: A) -> Handle<A> {
self.labeled_asset_scope(label, |_| asset)
}
/// Add a [`LoadedAsset`] that is a "labeled sub asset" of the root path of this load context.
/// This can be used in combination with [`LoadContext::begin_labeled_asset`] to parallelize
/// sub asset loading.
///
/// See [`AssetPath`] for more on labeled assets.
pub fn add_loaded_labeled_asset<A: Asset>(
&mut self,
label: impl Into<CowArc<'static, str>>,
loaded_asset: LoadedAsset<A>,
) -> Handle<A> {
let label = label.into();
let loaded_asset: ErasedLoadedAsset = loaded_asset.into();
let labeled_path = self.asset_path.clone().with_label(label.clone());
let handle = self
.asset_server
.get_or_create_path_handle(labeled_path, None);
self.labeled_assets.insert(
label,
LabeledAsset {
asset: loaded_asset,
handle: handle.clone().untyped(),
},
);
handle
}
/// Returns `true` if an asset with the label `label` exists in this context.
///
/// See [`AssetPath`] for more on labeled assets.
pub fn has_labeled_asset<'b>(&self, label: impl Into<CowArc<'b, str>>) -> bool {
let path = self.asset_path.clone().with_label(label.into());
!self.asset_server.get_handles_untyped(&path).is_empty()
}
/// "Finishes" this context by populating the final [`Asset`] value.
pub fn finish<A: Asset>(self, value: A) -> LoadedAsset<A> {
LoadedAsset {
value,
dependencies: self.dependencies,
loader_dependencies: self.loader_dependencies,
labeled_assets: self.labeled_assets,
}
}
/// Gets the source path for this load context.
pub fn path(&self) -> &Path {
self.asset_path.path()
}
/// Gets the source asset path for this load context.
pub fn asset_path(&self) -> &AssetPath<'static> {
&self.asset_path
}
/// Reads the asset at the given path and returns its bytes
pub async fn read_asset_bytes<'b, 'c>(
&'b mut self,
path: impl Into<AssetPath<'c>>,
) -> Result<Vec<u8>, ReadAssetBytesError> {
let path = path.into();
let source = self.asset_server.get_source(path.source())?;
let asset_reader = match self.asset_server.mode() {
AssetServerMode::Unprocessed => source.reader(),
AssetServerMode::Processed => source.processed_reader()?,
};
let mut reader = asset_reader.read(path.path()).await?;
let hash = if self.populate_hashes {
// NOTE: ensure meta is read while the asset bytes reader is still active to ensure transactionality
// See `ProcessorGatedReader` for more info
let meta_bytes = asset_reader.read_meta_bytes(path.path()).await?;
let minimal: ProcessedInfoMinimal = ron::de::from_bytes(&meta_bytes)
.map_err(DeserializeMetaError::DeserializeMinimal)?;
let processed_info = minimal
.processed_info
.ok_or(ReadAssetBytesError::MissingAssetHash)?;
processed_info.full_hash
} else {
Default::default()
};
let mut bytes = Vec::new();
reader
.read_to_end(&mut bytes)
.await
.map_err(|source| ReadAssetBytesError::Io {
path: path.path().to_path_buf(),
source,
})?;
self.loader_dependencies.insert(path.clone_owned(), hash);
Ok(bytes)
}
/// Returns a handle to an asset of type `A` with the label `label`. This [`LoadContext`] must produce an asset of the
/// given type and the given label or the dependencies of this asset will never be considered "fully loaded". However you
/// can call this method before _or_ after adding the labeled asset.
pub fn get_label_handle<'b, A: Asset>(
&mut self,
label: impl Into<CowArc<'b, str>>,
) -> Handle<A> {
let path = self.asset_path.clone().with_label(label);
let handle = self.asset_server.get_or_create_path_handle::<A>(path, None);
self.dependencies.insert(handle.id().untyped());
handle
}
pub(crate) async fn load_direct_internal(
&mut self,
path: AssetPath<'static>,
meta: &dyn AssetMetaDyn,
loader: &dyn ErasedAssetLoader,
reader: &mut dyn Reader,
) -> Result<ErasedLoadedAsset, LoadDirectError> {
let loaded_asset = self
.asset_server
.load_with_meta_loader_and_reader(
&path,
meta,
loader,
reader,
false,
self.populate_hashes,
)
.await
.map_err(|error| LoadDirectError::LoadError {
dependency: path.clone(),
error,
})?;
let info = meta.processed_info().as_ref();
let hash = info.map(|i| i.full_hash).unwrap_or_default();
self.loader_dependencies.insert(path, hash);
Ok(loaded_asset)
}
/// Create a builder for loading nested assets in this context.
#[must_use]
pub fn loader(&mut self) -> NestedLoader<'a, '_, StaticTyped, Deferred> {
NestedLoader::new(self)
}
/// Retrieves a handle for the asset at the given path and adds that path as a dependency of the asset.
/// If the current context is a normal [`AssetServer::load`], an actual asset load will be kicked off immediately, which ensures the load happens
/// as soon as possible.
/// "Normal loads" kicked from within a normal Bevy App will generally configure the context to kick off loads immediately.
/// If the current context is configured to not load dependencies automatically (ex: [`AssetProcessor`](crate::processor::AssetProcessor)),
/// a load will not be kicked off automatically. It is then the calling context's responsibility to begin a load if necessary.
///
/// If you need to override asset settings, asset type, or load directly, please see [`LoadContext::loader`].
pub fn load<'b, A: Asset>(&mut self, path: impl Into<AssetPath<'b>>) -> Handle<A> {
self.loader().load(path)
}
}
/// An error produced when calling [`LoadContext::read_asset_bytes`]
#[derive(Error, Debug)]
pub enum ReadAssetBytesError {
#[error(transparent)]
DeserializeMetaError(#[from] DeserializeMetaError),
#[error(transparent)]
AssetReaderError(#[from] AssetReaderError),
#[error(transparent)]
MissingAssetSourceError(#[from] MissingAssetSourceError),
#[error(transparent)]
MissingProcessedAssetReaderError(#[from] MissingProcessedAssetReaderError),
/// Encountered an I/O error while loading an asset.
#[error("Encountered an io error while loading asset at `{}`: {source}", path.display())]
Io {
path: PathBuf,
source: std::io::Error,
},
#[error("The LoadContext for this read_asset_bytes call requires hash metadata, but it was not provided. This is likely an internal implementation error.")]
MissingAssetHash,
}

509
vendor/bevy_asset/src/loader_builders.rs vendored Normal file
View File

@@ -0,0 +1,509 @@
//! Implementations of the builder-pattern used for loading dependent assets via
//! [`LoadContext::loader`].
use crate::{
io::Reader,
meta::{meta_transform_settings, AssetMetaDyn, MetaTransform, Settings},
Asset, AssetLoadError, AssetPath, ErasedAssetLoader, ErasedLoadedAsset, Handle, LoadContext,
LoadDirectError, LoadedAsset, LoadedUntypedAsset, UntypedHandle,
};
use alloc::{borrow::ToOwned, boxed::Box, sync::Arc};
use core::any::TypeId;
// Utility type for handling the sources of reader references
enum ReaderRef<'a> {
Borrowed(&'a mut dyn Reader),
Boxed(Box<dyn Reader + 'a>),
}
impl ReaderRef<'_> {
pub fn as_mut(&mut self) -> &mut dyn Reader {
match self {
ReaderRef::Borrowed(r) => &mut **r,
ReaderRef::Boxed(b) => &mut **b,
}
}
}
/// A builder for loading nested assets inside a [`LoadContext`].
///
/// # Loader state
///
/// The type parameters `T` and `M` determine how this will load assets:
/// - `T`: the typing of this loader. How do we know what type of asset to load?
///
/// See [`StaticTyped`] (the default), [`DynamicTyped`], and [`UnknownTyped`].
///
/// - `M`: the load mode. Do we want to load this asset right now (in which case
/// you will have to `await` the operation), or do we just want a [`Handle`],
/// and leave the actual asset loading to later?
///
/// See [`Deferred`] (the default) and [`Immediate`].
///
/// When configuring this builder, you can freely switch between these modes
/// via functions like [`deferred`] and [`immediate`].
///
/// ## Typing
///
/// To inform the loader of what type of asset to load:
/// - in [`StaticTyped`]: statically providing a type parameter `A: Asset` to
/// [`load`].
///
/// This is the simplest way to get a [`Handle<A>`] to the loaded asset, as
/// long as you know the type of `A` at compile time.
///
/// - in [`DynamicTyped`]: providing the [`TypeId`] of the asset at runtime.
///
/// If you know the type ID of the asset at runtime, but not at compile time,
/// use [`with_dynamic_type`] followed by [`load`] to start loading an asset
/// of that type. This lets you get an [`UntypedHandle`] (via [`Deferred`]),
/// or a [`ErasedLoadedAsset`] (via [`Immediate`]).
///
/// - in [`UnknownTyped`]: loading either a type-erased version of the asset
/// ([`ErasedLoadedAsset`]), or a handle *to a handle* of the actual asset
/// ([`LoadedUntypedAsset`]).
///
/// If you have no idea what type of asset you will be loading (not even at
/// runtime with a [`TypeId`]), use this.
///
/// ## Load mode
///
/// To inform the loader how you want to load the asset:
/// - in [`Deferred`]: when you request to load the asset, you get a [`Handle`]
/// for it, but the actual loading won't be completed until later.
///
/// Use this if you only need a [`Handle`] or [`UntypedHandle`].
///
/// - in [`Immediate`]: the load request will load the asset right then and
/// there, waiting until the asset is fully loaded and giving you access to
/// it.
///
/// Note that this requires you to `await` a future, so you must be in an
/// async context to use direct loading. In an asset loader, you will be in
/// an async context.
///
/// Use this if you need the *value* of another asset in order to load the
/// current asset. For example, if you are deriving a new asset from the
/// referenced asset, or you are building a collection of assets. This will
/// add the path of the asset as a "load dependency".
///
/// If the current loader is used in a [`Process`] "asset preprocessor",
/// such as a [`LoadTransformAndSave`] preprocessor, changing a "load
/// dependency" will result in re-processing of the asset.
///
/// # Load kickoff
///
/// If the current context is a normal [`AssetServer::load`], an actual asset
/// load will be kicked off immediately, which ensures the load happens as soon
/// as possible. "Normal loads" kicked from within a normal Bevy App will
/// generally configure the context to kick off loads immediately.
///
/// If the current context is configured to not load dependencies automatically
/// (ex: [`AssetProcessor`]), a load will not be kicked off automatically. It is
/// then the calling context's responsibility to begin a load if necessary.
///
/// # Lifetimes
///
/// - `ctx`: the lifetime of the associated [`AssetServer`](crate::AssetServer) reference
/// - `builder`: the lifetime of the temporary builder structs
///
/// [`deferred`]: Self::deferred
/// [`immediate`]: Self::immediate
/// [`load`]: Self::load
/// [`with_dynamic_type`]: Self::with_dynamic_type
/// [`AssetServer::load`]: crate::AssetServer::load
/// [`AssetProcessor`]: crate::processor::AssetProcessor
/// [`Process`]: crate::processor::Process
/// [`LoadTransformAndSave`]: crate::processor::LoadTransformAndSave
pub struct NestedLoader<'ctx, 'builder, T, M> {
load_context: &'builder mut LoadContext<'ctx>,
meta_transform: Option<MetaTransform>,
typing: T,
mode: M,
}
mod sealed {
pub trait Typing {}
pub trait Mode {}
}
/// [`NestedLoader`] will be provided the type of asset as a type parameter on
/// [`load`].
///
/// [`load`]: NestedLoader::load
pub struct StaticTyped(());
impl sealed::Typing for StaticTyped {}
/// [`NestedLoader`] has been configured with info on what type of asset to load
/// at runtime.
pub struct DynamicTyped {
asset_type_id: TypeId,
}
impl sealed::Typing for DynamicTyped {}
/// [`NestedLoader`] does not know what type of asset it will be loading.
pub struct UnknownTyped(());
impl sealed::Typing for UnknownTyped {}
/// [`NestedLoader`] will create and return asset handles immediately, but only
/// actually load the asset later.
pub struct Deferred(());
impl sealed::Mode for Deferred {}
/// [`NestedLoader`] will immediately load an asset when requested.
pub struct Immediate<'builder, 'reader> {
reader: Option<&'builder mut (dyn Reader + 'reader)>,
}
impl sealed::Mode for Immediate<'_, '_> {}
// common to all states
impl<'ctx, 'builder> NestedLoader<'ctx, 'builder, StaticTyped, Deferred> {
pub(crate) fn new(load_context: &'builder mut LoadContext<'ctx>) -> Self {
NestedLoader {
load_context,
meta_transform: None,
typing: StaticTyped(()),
mode: Deferred(()),
}
}
}
impl<'ctx, 'builder, T: sealed::Typing, M: sealed::Mode> NestedLoader<'ctx, 'builder, T, M> {
fn with_transform(
mut self,
transform: impl Fn(&mut dyn AssetMetaDyn) + Send + Sync + 'static,
) -> Self {
if let Some(prev_transform) = self.meta_transform {
self.meta_transform = Some(Box::new(move |meta| {
prev_transform(meta);
transform(meta);
}));
} else {
self.meta_transform = Some(Box::new(transform));
}
self
}
/// Configure the settings used to load the asset.
///
/// If the settings type `S` does not match the settings expected by `A`'s asset loader, an error will be printed to the log
/// and the asset load will fail.
#[must_use]
pub fn with_settings<S: Settings>(
self,
settings: impl Fn(&mut S) + Send + Sync + 'static,
) -> Self {
self.with_transform(move |meta| meta_transform_settings(meta, &settings))
}
// convert between `T`s
/// When [`load`]ing, you must pass in the asset type as a type parameter
/// statically.
///
/// If you don't know the type statically (at compile time), consider
/// [`with_dynamic_type`] or [`with_unknown_type`].
///
/// [`load`]: Self::load
/// [`with_dynamic_type`]: Self::with_dynamic_type
/// [`with_unknown_type`]: Self::with_unknown_type
#[must_use]
pub fn with_static_type(self) -> NestedLoader<'ctx, 'builder, StaticTyped, M> {
NestedLoader {
load_context: self.load_context,
meta_transform: self.meta_transform,
typing: StaticTyped(()),
mode: self.mode,
}
}
/// When [`load`]ing, the loader will attempt to load an asset with the
/// given [`TypeId`].
///
/// [`load`]: Self::load
#[must_use]
pub fn with_dynamic_type(
self,
asset_type_id: TypeId,
) -> NestedLoader<'ctx, 'builder, DynamicTyped, M> {
NestedLoader {
load_context: self.load_context,
meta_transform: self.meta_transform,
typing: DynamicTyped { asset_type_id },
mode: self.mode,
}
}
/// When [`load`]ing, we will infer what type of asset to load from
/// metadata.
///
/// [`load`]: Self::load
#[must_use]
pub fn with_unknown_type(self) -> NestedLoader<'ctx, 'builder, UnknownTyped, M> {
NestedLoader {
load_context: self.load_context,
meta_transform: self.meta_transform,
typing: UnknownTyped(()),
mode: self.mode,
}
}
// convert between `M`s
/// When [`load`]ing, create only asset handles, rather than returning the
/// actual asset.
///
/// [`load`]: Self::load
pub fn deferred(self) -> NestedLoader<'ctx, 'builder, T, Deferred> {
NestedLoader {
load_context: self.load_context,
meta_transform: self.meta_transform,
typing: self.typing,
mode: Deferred(()),
}
}
/// The [`load`] call itself will load an asset, rather than scheduling the
/// loading to happen later.
///
/// This gives you access to the loaded asset, but requires you to be in an
/// async context, and be able to `await` the resulting future.
///
/// [`load`]: Self::load
#[must_use]
pub fn immediate<'c>(self) -> NestedLoader<'ctx, 'builder, T, Immediate<'builder, 'c>> {
NestedLoader {
load_context: self.load_context,
meta_transform: self.meta_transform,
typing: self.typing,
mode: Immediate { reader: None },
}
}
}
// deferred loading logic
impl NestedLoader<'_, '_, StaticTyped, Deferred> {
/// Retrieves a handle for the asset at the given path and adds that path as
/// a dependency of this asset.
///
/// This requires you to know the type of asset statically.
/// - If you have runtime info for what type of asset you're loading (e.g. a
/// [`TypeId`]), use [`with_dynamic_type`].
/// - If you do not know at all what type of asset you're loading, use
/// [`with_unknown_type`].
///
/// [`with_dynamic_type`]: Self::with_dynamic_type
/// [`with_unknown_type`]: Self::with_unknown_type
pub fn load<'c, A: Asset>(self, path: impl Into<AssetPath<'c>>) -> Handle<A> {
let path = path.into().to_owned();
let handle = if self.load_context.should_load_dependencies {
self.load_context.asset_server.load_with_meta_transform(
path,
self.meta_transform,
(),
true,
)
} else {
self.load_context
.asset_server
.get_or_create_path_handle(path, None)
};
self.load_context.dependencies.insert(handle.id().untyped());
handle
}
}
impl NestedLoader<'_, '_, DynamicTyped, Deferred> {
/// Retrieves a handle for the asset at the given path and adds that path as
/// a dependency of this asset.
///
/// This requires you to pass in the asset type ID into
/// [`with_dynamic_type`].
///
/// [`with_dynamic_type`]: Self::with_dynamic_type
pub fn load<'p>(self, path: impl Into<AssetPath<'p>>) -> UntypedHandle {
let path = path.into().to_owned();
let handle = if self.load_context.should_load_dependencies {
self.load_context
.asset_server
.load_erased_with_meta_transform(
path,
self.typing.asset_type_id,
self.meta_transform,
(),
)
} else {
self.load_context
.asset_server
.get_or_create_path_handle_erased(
path,
self.typing.asset_type_id,
self.meta_transform,
)
};
self.load_context.dependencies.insert(handle.id());
handle
}
}
impl NestedLoader<'_, '_, UnknownTyped, Deferred> {
/// Retrieves a handle for the asset at the given path and adds that path as
/// a dependency of this asset.
///
/// This will infer the asset type from metadata.
pub fn load<'p>(self, path: impl Into<AssetPath<'p>>) -> Handle<LoadedUntypedAsset> {
let path = path.into().to_owned();
let handle = if self.load_context.should_load_dependencies {
self.load_context
.asset_server
.load_unknown_type_with_meta_transform(path, self.meta_transform)
} else {
self.load_context
.asset_server
.get_or_create_path_handle(path, self.meta_transform)
};
self.load_context.dependencies.insert(handle.id().untyped());
handle
}
}
// immediate loading logic
impl<'builder, 'reader, T> NestedLoader<'_, '_, T, Immediate<'builder, 'reader>> {
/// Specify the reader to use to read the asset data.
#[must_use]
pub fn with_reader(mut self, reader: &'builder mut (dyn Reader + 'reader)) -> Self {
self.mode.reader = Some(reader);
self
}
async fn load_internal(
self,
path: &AssetPath<'static>,
asset_type_id: Option<TypeId>,
) -> Result<(Arc<dyn ErasedAssetLoader>, ErasedLoadedAsset), LoadDirectError> {
if path.label().is_some() {
return Err(LoadDirectError::RequestedSubasset(path.clone()));
}
let (mut meta, loader, mut reader) = if let Some(reader) = self.mode.reader {
let loader = if let Some(asset_type_id) = asset_type_id {
self.load_context
.asset_server
.get_asset_loader_with_asset_type_id(asset_type_id)
.await
.map_err(|error| LoadDirectError::LoadError {
dependency: path.clone(),
error: error.into(),
})?
} else {
self.load_context
.asset_server
.get_path_asset_loader(path)
.await
.map_err(|error| LoadDirectError::LoadError {
dependency: path.clone(),
error: error.into(),
})?
};
let meta = loader.default_meta();
(meta, loader, ReaderRef::Borrowed(reader))
} else {
let (meta, loader, reader) = self
.load_context
.asset_server
.get_meta_loader_and_reader(path, asset_type_id)
.await
.map_err(|error| LoadDirectError::LoadError {
dependency: path.clone(),
error,
})?;
(meta, loader, ReaderRef::Boxed(reader))
};
if let Some(meta_transform) = self.meta_transform {
meta_transform(&mut *meta);
}
let asset = self
.load_context
.load_direct_internal(path.clone(), meta.as_ref(), &*loader, reader.as_mut())
.await?;
Ok((loader, asset))
}
}
impl NestedLoader<'_, '_, StaticTyped, Immediate<'_, '_>> {
/// Attempts to load the asset at the given `path` immediately.
///
/// This requires you to know the type of asset statically.
/// - If you have runtime info for what type of asset you're loading (e.g. a
/// [`TypeId`]), use [`with_dynamic_type`].
/// - If you do not know at all what type of asset you're loading, use
/// [`with_unknown_type`].
///
/// [`with_dynamic_type`]: Self::with_dynamic_type
/// [`with_unknown_type`]: Self::with_unknown_type
pub async fn load<'p, A: Asset>(
self,
path: impl Into<AssetPath<'p>>,
) -> Result<LoadedAsset<A>, LoadDirectError> {
let path = path.into().into_owned();
self.load_internal(&path, Some(TypeId::of::<A>()))
.await
.and_then(move |(loader, untyped_asset)| {
untyped_asset
.downcast::<A>()
.map_err(|_| LoadDirectError::LoadError {
dependency: path.clone(),
error: AssetLoadError::RequestedHandleTypeMismatch {
path,
requested: TypeId::of::<A>(),
actual_asset_name: loader.asset_type_name(),
loader_name: loader.type_name(),
},
})
})
}
}
impl NestedLoader<'_, '_, DynamicTyped, Immediate<'_, '_>> {
/// Attempts to load the asset at the given `path` immediately.
///
/// This requires you to pass in the asset type ID into
/// [`with_dynamic_type`].
///
/// [`with_dynamic_type`]: Self::with_dynamic_type
pub async fn load<'p>(
self,
path: impl Into<AssetPath<'p>>,
) -> Result<ErasedLoadedAsset, LoadDirectError> {
let path = path.into().into_owned();
let asset_type_id = Some(self.typing.asset_type_id);
self.load_internal(&path, asset_type_id)
.await
.map(|(_, asset)| asset)
}
}
impl NestedLoader<'_, '_, UnknownTyped, Immediate<'_, '_>> {
/// Attempts to load the asset at the given `path` immediately.
///
/// This will infer the asset type from metadata.
pub async fn load<'p>(
self,
path: impl Into<AssetPath<'p>>,
) -> Result<ErasedLoadedAsset, LoadDirectError> {
let path = path.into().into_owned();
self.load_internal(&path, None)
.await
.map(|(_, asset)| asset)
}
}

262
vendor/bevy_asset/src/meta.rs vendored Normal file
View File

@@ -0,0 +1,262 @@
use alloc::{
boxed::Box,
string::{String, ToString},
vec::Vec,
};
use crate::{
loader::AssetLoader, processor::Process, Asset, AssetPath, DeserializeMetaError,
VisitAssetDependencies,
};
use downcast_rs::{impl_downcast, Downcast};
use ron::ser::PrettyConfig;
use serde::{Deserialize, Serialize};
use tracing::error;
pub const META_FORMAT_VERSION: &str = "1.0";
pub type MetaTransform = Box<dyn Fn(&mut dyn AssetMetaDyn) + Send + Sync>;
/// Asset metadata that informs how an [`Asset`] should be handled by the asset system.
///
/// `L` is the [`AssetLoader`] (if one is configured) for the [`AssetAction`]. This can be `()` if it is not required.
/// `P` is the [`Process`] processor, if one is configured for the [`AssetAction`]. This can be `()` if it is not required.
#[derive(Serialize, Deserialize)]
pub struct AssetMeta<L: AssetLoader, P: Process> {
/// The version of the meta format being used. This will change whenever a breaking change is made to
/// the meta format.
pub meta_format_version: String,
/// Information produced by the [`AssetProcessor`] _after_ processing this asset.
/// This will only exist alongside processed versions of assets. You should not manually set it in your asset source files.
///
/// [`AssetProcessor`]: crate::processor::AssetProcessor
#[serde(skip_serializing_if = "Option::is_none")]
pub processed_info: Option<ProcessedInfo>,
/// How to handle this asset in the asset system. See [`AssetAction`].
pub asset: AssetAction<L::Settings, P::Settings>,
}
impl<L: AssetLoader, P: Process> AssetMeta<L, P> {
pub fn new(asset: AssetAction<L::Settings, P::Settings>) -> Self {
Self {
meta_format_version: META_FORMAT_VERSION.to_string(),
processed_info: None,
asset,
}
}
/// Deserializes the given serialized byte representation of the asset meta.
pub fn deserialize(bytes: &[u8]) -> Result<Self, DeserializeMetaError> {
Ok(ron::de::from_bytes(bytes)?)
}
}
/// Configures how an asset source file should be handled by the asset system.
#[derive(Serialize, Deserialize)]
pub enum AssetAction<LoaderSettings, ProcessSettings> {
/// Load the asset with the given loader and settings
/// See [`AssetLoader`].
Load {
loader: String,
settings: LoaderSettings,
},
/// Process the asset with the given processor and settings.
/// See [`Process`] and [`AssetProcessor`].
///
/// [`AssetProcessor`]: crate::processor::AssetProcessor
Process {
processor: String,
settings: ProcessSettings,
},
/// Do nothing with the asset
Ignore,
}
/// Info produced by the [`AssetProcessor`] for a given processed asset. This is used to determine if an
/// asset source file (or its dependencies) has changed.
///
/// [`AssetProcessor`]: crate::processor::AssetProcessor
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
pub struct ProcessedInfo {
/// A hash of the asset bytes and the asset .meta data
pub hash: AssetHash,
/// A hash of the asset bytes, the asset .meta data, and the `full_hash` of every `process_dependency`
pub full_hash: AssetHash,
/// Information about the "process dependencies" used to process this asset.
pub process_dependencies: Vec<ProcessDependencyInfo>,
}
/// Information about a dependency used to process an asset. This is used to determine whether an asset's "process dependency"
/// has changed.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ProcessDependencyInfo {
pub full_hash: AssetHash,
pub path: AssetPath<'static>,
}
/// This is a minimal counterpart to [`AssetMeta`] that exists to speed up (or enable) serialization in cases where the whole [`AssetMeta`] isn't
/// necessary.
// PERF:
// Currently, this is used when retrieving asset loader and processor information (when the actual type is not known yet). This could probably
// be replaced (and made more efficient) by a custom deserializer that reads the loader/processor information _first_, then deserializes the contents
// using a type registry.
#[derive(Serialize, Deserialize)]
pub struct AssetMetaMinimal {
pub asset: AssetActionMinimal,
}
/// This is a minimal counterpart to [`AssetAction`] that exists to speed up (or enable) serialization in cases where the whole [`AssetAction`]
/// isn't necessary.
#[derive(Serialize, Deserialize)]
pub enum AssetActionMinimal {
Load { loader: String },
Process { processor: String },
Ignore,
}
/// This is a minimal counterpart to [`ProcessedInfo`] that exists to speed up serialization in cases where the whole [`ProcessedInfo`] isn't
/// necessary.
#[derive(Serialize, Deserialize)]
pub struct ProcessedInfoMinimal {
pub processed_info: Option<ProcessedInfo>,
}
/// A dynamic type-erased counterpart to [`AssetMeta`] that enables passing around and interacting with [`AssetMeta`] without knowing
/// its type.
pub trait AssetMetaDyn: Downcast + Send + Sync {
/// Returns a reference to the [`AssetLoader`] settings, if they exist.
fn loader_settings(&self) -> Option<&dyn Settings>;
/// Returns a mutable reference to the [`AssetLoader`] settings, if they exist.
fn loader_settings_mut(&mut self) -> Option<&mut dyn Settings>;
/// Serializes the internal [`AssetMeta`].
fn serialize(&self) -> Vec<u8>;
/// Returns a reference to the [`ProcessedInfo`] if it exists.
fn processed_info(&self) -> &Option<ProcessedInfo>;
/// Returns a mutable reference to the [`ProcessedInfo`] if it exists.
fn processed_info_mut(&mut self) -> &mut Option<ProcessedInfo>;
}
impl<L: AssetLoader, P: Process> AssetMetaDyn for AssetMeta<L, P> {
fn loader_settings(&self) -> Option<&dyn Settings> {
if let AssetAction::Load { settings, .. } = &self.asset {
Some(settings)
} else {
None
}
}
fn loader_settings_mut(&mut self) -> Option<&mut dyn Settings> {
if let AssetAction::Load { settings, .. } = &mut self.asset {
Some(settings)
} else {
None
}
}
fn serialize(&self) -> Vec<u8> {
ron::ser::to_string_pretty(&self, PrettyConfig::default())
.expect("type is convertible to ron")
.into_bytes()
}
fn processed_info(&self) -> &Option<ProcessedInfo> {
&self.processed_info
}
fn processed_info_mut(&mut self) -> &mut Option<ProcessedInfo> {
&mut self.processed_info
}
}
impl_downcast!(AssetMetaDyn);
/// Settings used by the asset system, such as by [`AssetLoader`], [`Process`], and [`AssetSaver`]
///
/// [`AssetSaver`]: crate::saver::AssetSaver
pub trait Settings: Downcast + Send + Sync + 'static {}
impl<T: 'static> Settings for T where T: Send + Sync {}
impl_downcast!(Settings);
/// The () processor should never be called. This implementation exists to make the meta format nicer to work with.
impl Process for () {
type Settings = ();
type OutputLoader = ();
async fn process(
&self,
_context: &mut bevy_asset::processor::ProcessContext<'_>,
_meta: AssetMeta<(), Self>,
_writer: &mut bevy_asset::io::Writer,
) -> Result<(), bevy_asset::processor::ProcessError> {
unreachable!()
}
}
impl Asset for () {}
impl VisitAssetDependencies for () {
fn visit_dependencies(&self, _visit: &mut impl FnMut(bevy_asset::UntypedAssetId)) {
unreachable!()
}
}
/// The () loader should never be called. This implementation exists to make the meta format nicer to work with.
impl AssetLoader for () {
type Asset = ();
type Settings = ();
type Error = std::io::Error;
async fn load(
&self,
_reader: &mut dyn crate::io::Reader,
_settings: &Self::Settings,
_load_context: &mut crate::LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
unreachable!();
}
fn extensions(&self) -> &[&str] {
unreachable!();
}
}
pub(crate) fn meta_transform_settings<S: Settings>(
meta: &mut dyn AssetMetaDyn,
settings: &(impl Fn(&mut S) + Send + Sync + 'static),
) {
if let Some(loader_settings) = meta.loader_settings_mut() {
if let Some(loader_settings) = loader_settings.downcast_mut::<S>() {
settings(loader_settings);
} else {
error!(
"Configured settings type {} does not match AssetLoader settings type",
core::any::type_name::<S>(),
);
}
}
}
pub(crate) fn loader_settings_meta_transform<S: Settings>(
settings: impl Fn(&mut S) + Send + Sync + 'static,
) -> MetaTransform {
Box::new(move |meta| meta_transform_settings(meta, &settings))
}
pub type AssetHash = [u8; 32];
/// NOTE: changing the hashing logic here is a _breaking change_ that requires a [`META_FORMAT_VERSION`] bump.
pub(crate) fn get_asset_hash(meta_bytes: &[u8], asset_bytes: &[u8]) -> AssetHash {
let mut hasher = blake3::Hasher::new();
hasher.update(meta_bytes);
hasher.update(asset_bytes);
*hasher.finalize().as_bytes()
}
/// NOTE: changing the hashing logic here is a _breaking change_ that requires a [`META_FORMAT_VERSION`] bump.
pub(crate) fn get_full_asset_hash(
asset_hash: AssetHash,
dependency_hashes: impl Iterator<Item = AssetHash>,
) -> AssetHash {
let mut hasher = blake3::Hasher::new();
hasher.update(&asset_hash);
for hash in dependency_hashes {
hasher.update(&hash);
}
*hasher.finalize().as_bytes()
}

1029
vendor/bevy_asset/src/path.rs vendored Normal file

File diff suppressed because it is too large Load Diff

223
vendor/bevy_asset/src/processor/log.rs vendored Normal file
View File

@@ -0,0 +1,223 @@
use crate::AssetPath;
use alloc::{
format,
string::{String, ToString},
vec::Vec,
};
use async_fs::File;
use bevy_platform::collections::HashSet;
use futures_lite::{AsyncReadExt, AsyncWriteExt};
use std::path::PathBuf;
use thiserror::Error;
use tracing::error;
/// An in-memory representation of a single [`ProcessorTransactionLog`] entry.
#[derive(Debug)]
pub(crate) enum LogEntry {
BeginProcessing(AssetPath<'static>),
EndProcessing(AssetPath<'static>),
UnrecoverableError,
}
/// A "write ahead" logger that helps ensure asset importing is transactional.
///
/// Prior to processing an asset, we write to the log to indicate it has started
/// After processing an asset, we write to the log to indicate it has finished.
/// On startup, the log can be read to determine if any transactions were incomplete.
// TODO: this should be a trait
pub struct ProcessorTransactionLog {
log_file: File,
}
/// An error that occurs when reading from the [`ProcessorTransactionLog`] fails.
#[derive(Error, Debug)]
pub enum ReadLogError {
/// An invalid log line was encountered, consisting of the contained string.
#[error("Encountered an invalid log line: '{0}'")]
InvalidLine(String),
/// A file-system-based error occurred while reading the log file.
#[error("Failed to read log file: {0}")]
Io(#[from] futures_io::Error),
}
/// An error that occurs when writing to the [`ProcessorTransactionLog`] fails.
#[derive(Error, Debug)]
#[error(
"Failed to write {log_entry:?} to the asset processor log. This is not recoverable. {error}"
)]
pub struct WriteLogError {
log_entry: LogEntry,
error: futures_io::Error,
}
/// An error that occurs when validating the [`ProcessorTransactionLog`] fails.
#[derive(Error, Debug)]
pub enum ValidateLogError {
/// An error that could not be recovered from. All assets will be reprocessed.
#[error("Encountered an unrecoverable error. All assets will be reprocessed.")]
UnrecoverableError,
/// A [`ReadLogError`].
#[error(transparent)]
ReadLogError(#[from] ReadLogError),
/// Duplicated process asset transactions occurred.
#[error("Encountered a duplicate process asset transaction: {0:?}")]
EntryErrors(Vec<LogEntryError>),
}
/// An error that occurs when validating individual [`ProcessorTransactionLog`] entries.
#[derive(Error, Debug)]
pub enum LogEntryError {
/// A duplicate process asset transaction occurred for the given asset path.
#[error("Encountered a duplicate process asset transaction: {0}")]
DuplicateTransaction(AssetPath<'static>),
/// A transaction was ended that never started for the given asset path.
#[error("A transaction was ended that never started {0}")]
EndedMissingTransaction(AssetPath<'static>),
/// An asset started processing but never finished at the given asset path.
#[error("An asset started processing but never finished: {0}")]
UnfinishedTransaction(AssetPath<'static>),
}
const LOG_PATH: &str = "imported_assets/log";
const ENTRY_BEGIN: &str = "Begin ";
const ENTRY_END: &str = "End ";
const UNRECOVERABLE_ERROR: &str = "UnrecoverableError";
impl ProcessorTransactionLog {
fn full_log_path() -> PathBuf {
#[cfg(not(target_arch = "wasm32"))]
let base_path = crate::io::file::get_base_path();
#[cfg(target_arch = "wasm32")]
let base_path = PathBuf::new();
base_path.join(LOG_PATH)
}
/// Create a new, fresh log file. This will delete the previous log file if it exists.
pub(crate) async fn new() -> Result<Self, futures_io::Error> {
let path = Self::full_log_path();
match async_fs::remove_file(&path).await {
Ok(_) => { /* successfully removed file */ }
Err(err) => {
// if the log file is not found, we assume we are starting in a fresh (or good) state
if err.kind() != futures_io::ErrorKind::NotFound {
error!("Failed to remove previous log file {}", err);
}
}
}
if let Some(parent_folder) = path.parent() {
async_fs::create_dir_all(parent_folder).await?;
}
Ok(Self {
log_file: File::create(path).await?,
})
}
pub(crate) async fn read() -> Result<Vec<LogEntry>, ReadLogError> {
let mut log_lines = Vec::new();
let mut file = match File::open(Self::full_log_path()).await {
Ok(file) => file,
Err(err) => {
if err.kind() == futures_io::ErrorKind::NotFound {
// if the log file doesn't exist, this is equivalent to an empty file
return Ok(log_lines);
}
return Err(err.into());
}
};
let mut string = String::new();
file.read_to_string(&mut string).await?;
for line in string.lines() {
if let Some(path_str) = line.strip_prefix(ENTRY_BEGIN) {
log_lines.push(LogEntry::BeginProcessing(
AssetPath::parse(path_str).into_owned(),
));
} else if let Some(path_str) = line.strip_prefix(ENTRY_END) {
log_lines.push(LogEntry::EndProcessing(
AssetPath::parse(path_str).into_owned(),
));
} else if line.is_empty() {
continue;
} else {
return Err(ReadLogError::InvalidLine(line.to_string()));
}
}
Ok(log_lines)
}
pub(crate) async fn validate() -> Result<(), ValidateLogError> {
let mut transactions: HashSet<AssetPath<'static>> = Default::default();
let mut errors: Vec<LogEntryError> = Vec::new();
let entries = Self::read().await?;
for entry in entries {
match entry {
LogEntry::BeginProcessing(path) => {
// There should never be duplicate "start transactions" in a log
// Every start should be followed by:
// * nothing (if there was an abrupt stop)
// * an End (if the transaction was completed)
if !transactions.insert(path.clone()) {
errors.push(LogEntryError::DuplicateTransaction(path));
}
}
LogEntry::EndProcessing(path) => {
if !transactions.remove(&path) {
errors.push(LogEntryError::EndedMissingTransaction(path));
}
}
LogEntry::UnrecoverableError => return Err(ValidateLogError::UnrecoverableError),
}
}
for transaction in transactions {
errors.push(LogEntryError::UnfinishedTransaction(transaction));
}
if !errors.is_empty() {
return Err(ValidateLogError::EntryErrors(errors));
}
Ok(())
}
/// Logs the start of an asset being processed. If this is not followed at some point in the log by a closing [`ProcessorTransactionLog::end_processing`],
/// in the next run of the processor the asset processing will be considered "incomplete" and it will be reprocessed.
pub(crate) async fn begin_processing(
&mut self,
path: &AssetPath<'_>,
) -> Result<(), WriteLogError> {
self.write(&format!("{ENTRY_BEGIN}{path}\n"))
.await
.map_err(|e| WriteLogError {
log_entry: LogEntry::BeginProcessing(path.clone_owned()),
error: e,
})
}
/// Logs the end of an asset being successfully processed. See [`ProcessorTransactionLog::begin_processing`].
pub(crate) async fn end_processing(
&mut self,
path: &AssetPath<'_>,
) -> Result<(), WriteLogError> {
self.write(&format!("{ENTRY_END}{path}\n"))
.await
.map_err(|e| WriteLogError {
log_entry: LogEntry::EndProcessing(path.clone_owned()),
error: e,
})
}
/// Logs an unrecoverable error. On the next run of the processor, all assets will be regenerated. This should only be used as a last resort.
/// Every call to this should be considered with scrutiny and ideally replaced with something more granular.
pub(crate) async fn unrecoverable(&mut self) -> Result<(), WriteLogError> {
self.write(UNRECOVERABLE_ERROR)
.await
.map_err(|e| WriteLogError {
log_entry: LogEntry::UnrecoverableError,
error: e,
})
}
async fn write(&mut self, line: &str) -> Result<(), futures_io::Error> {
self.log_file.write_all(line.as_bytes()).await?;
self.log_file.flush().await?;
Ok(())
}
}

1476
vendor/bevy_asset/src/processor/mod.rs vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,338 @@
use crate::{
io::{
AssetReaderError, AssetWriterError, MissingAssetWriterError,
MissingProcessedAssetReaderError, MissingProcessedAssetWriterError, SliceReader, Writer,
},
meta::{AssetAction, AssetMeta, AssetMetaDyn, ProcessDependencyInfo, ProcessedInfo, Settings},
processor::AssetProcessor,
saver::{AssetSaver, SavedAsset},
transformer::{AssetTransformer, IdentityAssetTransformer, TransformedAsset},
AssetLoadError, AssetLoader, AssetPath, DeserializeMetaError, ErasedLoadedAsset,
MissingAssetLoaderForExtensionError, MissingAssetLoaderForTypeNameError,
};
use alloc::{
borrow::ToOwned,
boxed::Box,
string::{String, ToString},
};
use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
use core::marker::PhantomData;
use serde::{Deserialize, Serialize};
use thiserror::Error;
/// Asset "processor" logic that reads input asset bytes (stored on [`ProcessContext`]), processes the value in some way,
/// and then writes the final processed bytes with [`Writer`]. The resulting bytes must be loadable with the given [`Process::OutputLoader`].
///
/// This is a "low level", maximally flexible interface. Most use cases are better served by the [`LoadTransformAndSave`] implementation
/// of [`Process`].
pub trait Process: Send + Sync + Sized + 'static {
/// The configuration / settings used to process the asset. This will be stored in the [`AssetMeta`] and is user-configurable per-asset.
type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
/// The [`AssetLoader`] that will be used to load the final processed asset.
type OutputLoader: AssetLoader;
/// Processes the asset stored on `context` in some way using the settings stored on `meta`. The results are written to `writer`. The
/// final written processed asset is loadable using [`Process::OutputLoader`]. This load will use the returned [`AssetLoader::Settings`].
fn process(
&self,
context: &mut ProcessContext,
meta: AssetMeta<(), Self>,
writer: &mut Writer,
) -> impl ConditionalSendFuture<
Output = Result<<Self::OutputLoader as AssetLoader>::Settings, ProcessError>,
>;
}
/// A flexible [`Process`] implementation that loads the source [`Asset`] using the `L` [`AssetLoader`], then transforms
/// the `L` asset into an `S` [`AssetSaver`] asset using the `T` [`AssetTransformer`], and lastly saves the asset using the `S` [`AssetSaver`].
///
/// When creating custom processors, it is generally recommended to use the [`LoadTransformAndSave`] [`Process`] implementation,
/// as it encourages you to separate your code into an [`AssetLoader`] capable of loading assets without processing enabled,
/// an [`AssetTransformer`] capable of converting from an `L` asset to an `S` asset, and
/// an [`AssetSaver`] that allows you save any `S` asset. However you can
/// also implement [`Process`] directly if [`LoadTransformAndSave`] feels limiting or unnecessary.
///
/// If your [`Process`] does not need to transform the [`Asset`], you can use [`IdentityAssetTransformer`] as `T`.
/// This will directly return the input [`Asset`], allowing your [`Process`] to directly load and then save an [`Asset`].
/// However, this pattern should only be used for cases such as file format conversion.
/// Otherwise, consider refactoring your [`AssetLoader`] and [`AssetSaver`] to isolate the transformation step into an explicit [`AssetTransformer`].
///
/// This uses [`LoadTransformAndSaveSettings`] to configure the processor.
///
/// [`Asset`]: crate::Asset
pub struct LoadTransformAndSave<
L: AssetLoader,
T: AssetTransformer<AssetInput = L::Asset>,
S: AssetSaver<Asset = T::AssetOutput>,
> {
transformer: T,
saver: S,
marker: PhantomData<fn() -> L>,
}
impl<L: AssetLoader, S: AssetSaver<Asset = L::Asset>> From<S>
for LoadTransformAndSave<L, IdentityAssetTransformer<L::Asset>, S>
{
fn from(value: S) -> Self {
LoadTransformAndSave {
transformer: IdentityAssetTransformer::new(),
saver: value,
marker: PhantomData,
}
}
}
/// Settings for the [`LoadTransformAndSave`] [`Process::Settings`] implementation.
///
/// `LoaderSettings` corresponds to [`AssetLoader::Settings`], `TransformerSettings` corresponds to [`AssetTransformer::Settings`],
/// and `SaverSettings` corresponds to [`AssetSaver::Settings`].
#[derive(Serialize, Deserialize, Default)]
pub struct LoadTransformAndSaveSettings<LoaderSettings, TransformerSettings, SaverSettings> {
/// The [`AssetLoader::Settings`] for [`LoadTransformAndSave`].
pub loader_settings: LoaderSettings,
/// The [`AssetTransformer::Settings`] for [`LoadTransformAndSave`].
pub transformer_settings: TransformerSettings,
/// The [`AssetSaver::Settings`] for [`LoadTransformAndSave`].
pub saver_settings: SaverSettings,
}
impl<
L: AssetLoader,
T: AssetTransformer<AssetInput = L::Asset>,
S: AssetSaver<Asset = T::AssetOutput>,
> LoadTransformAndSave<L, T, S>
{
pub fn new(transformer: T, saver: S) -> Self {
LoadTransformAndSave {
transformer,
saver,
marker: PhantomData,
}
}
}
/// An error that is encountered during [`Process::process`].
#[derive(Error, Debug)]
pub enum ProcessError {
#[error(transparent)]
MissingAssetLoaderForExtension(#[from] MissingAssetLoaderForExtensionError),
#[error(transparent)]
MissingAssetLoaderForTypeName(#[from] MissingAssetLoaderForTypeNameError),
#[error("The processor '{0}' does not exist")]
#[from(ignore)]
MissingProcessor(String),
#[error("Encountered an AssetReader error for '{path}': {err}")]
#[from(ignore)]
AssetReaderError {
path: AssetPath<'static>,
err: AssetReaderError,
},
#[error("Encountered an AssetWriter error for '{path}': {err}")]
#[from(ignore)]
AssetWriterError {
path: AssetPath<'static>,
err: AssetWriterError,
},
#[error(transparent)]
MissingAssetWriterError(#[from] MissingAssetWriterError),
#[error(transparent)]
MissingProcessedAssetReaderError(#[from] MissingProcessedAssetReaderError),
#[error(transparent)]
MissingProcessedAssetWriterError(#[from] MissingProcessedAssetWriterError),
#[error("Failed to read asset metadata for {path}: {err}")]
#[from(ignore)]
ReadAssetMetaError {
path: AssetPath<'static>,
err: AssetReaderError,
},
#[error(transparent)]
DeserializeMetaError(#[from] DeserializeMetaError),
#[error(transparent)]
AssetLoadError(#[from] AssetLoadError),
#[error("The wrong meta type was passed into a processor. This is probably an internal implementation error.")]
WrongMetaType,
#[error("Encountered an error while saving the asset: {0}")]
#[from(ignore)]
AssetSaveError(Box<dyn core::error::Error + Send + Sync + 'static>),
#[error("Encountered an error while transforming the asset: {0}")]
#[from(ignore)]
AssetTransformError(Box<dyn core::error::Error + Send + Sync + 'static>),
#[error("Assets without extensions are not supported.")]
ExtensionRequired,
}
impl<Loader, Transformer, Saver> Process for LoadTransformAndSave<Loader, Transformer, Saver>
where
Loader: AssetLoader,
Transformer: AssetTransformer<AssetInput = Loader::Asset>,
Saver: AssetSaver<Asset = Transformer::AssetOutput>,
{
type Settings =
LoadTransformAndSaveSettings<Loader::Settings, Transformer::Settings, Saver::Settings>;
type OutputLoader = Saver::OutputLoader;
async fn process(
&self,
context: &mut ProcessContext<'_>,
meta: AssetMeta<(), Self>,
writer: &mut Writer,
) -> Result<<Self::OutputLoader as AssetLoader>::Settings, ProcessError> {
let AssetAction::Process { settings, .. } = meta.asset else {
return Err(ProcessError::WrongMetaType);
};
let loader_meta = AssetMeta::<Loader, ()>::new(AssetAction::Load {
loader: core::any::type_name::<Loader>().to_string(),
settings: settings.loader_settings,
});
let pre_transformed_asset = TransformedAsset::<Loader::Asset>::from_loaded(
context.load_source_asset(loader_meta).await?,
)
.unwrap();
let post_transformed_asset = self
.transformer
.transform(pre_transformed_asset, &settings.transformer_settings)
.await
.map_err(|err| ProcessError::AssetTransformError(err.into()))?;
let saved_asset =
SavedAsset::<Transformer::AssetOutput>::from_transformed(&post_transformed_asset);
let output_settings = self
.saver
.save(writer, saved_asset, &settings.saver_settings)
.await
.map_err(|error| ProcessError::AssetSaveError(error.into()))?;
Ok(output_settings)
}
}
/// A type-erased variant of [`Process`] that enables interacting with processor implementations without knowing
/// their type.
pub trait ErasedProcessor: Send + Sync {
/// Type-erased variant of [`Process::process`].
fn process<'a>(
&'a self,
context: &'a mut ProcessContext,
meta: Box<dyn AssetMetaDyn>,
writer: &'a mut Writer,
) -> BoxedFuture<'a, Result<Box<dyn AssetMetaDyn>, ProcessError>>;
/// Deserialized `meta` as type-erased [`AssetMeta`], operating under the assumption that it matches the meta
/// for the underlying [`Process`] impl.
fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError>;
/// Returns the default type-erased [`AssetMeta`] for the underlying [`Process`] impl.
fn default_meta(&self) -> Box<dyn AssetMetaDyn>;
}
impl<P: Process> ErasedProcessor for P {
fn process<'a>(
&'a self,
context: &'a mut ProcessContext,
meta: Box<dyn AssetMetaDyn>,
writer: &'a mut Writer,
) -> BoxedFuture<'a, Result<Box<dyn AssetMetaDyn>, ProcessError>> {
Box::pin(async move {
let meta = meta
.downcast::<AssetMeta<(), P>>()
.map_err(|_e| ProcessError::WrongMetaType)?;
let loader_settings = <P as Process>::process(self, context, *meta, writer).await?;
let output_meta: Box<dyn AssetMetaDyn> =
Box::new(AssetMeta::<P::OutputLoader, ()>::new(AssetAction::Load {
loader: core::any::type_name::<P::OutputLoader>().to_string(),
settings: loader_settings,
}));
Ok(output_meta)
})
}
fn deserialize_meta(&self, meta: &[u8]) -> Result<Box<dyn AssetMetaDyn>, DeserializeMetaError> {
let meta: AssetMeta<(), P> = ron::de::from_bytes(meta)?;
Ok(Box::new(meta))
}
fn default_meta(&self) -> Box<dyn AssetMetaDyn> {
Box::new(AssetMeta::<(), P>::new(AssetAction::Process {
processor: core::any::type_name::<P>().to_string(),
settings: P::Settings::default(),
}))
}
}
/// Provides scoped data access to the [`AssetProcessor`].
/// This must only expose processor data that is represented in the asset's hash.
pub struct ProcessContext<'a> {
/// The "new" processed info for the final processed asset. It is [`ProcessContext`]'s
/// job to populate `process_dependencies` with any asset dependencies used to process
/// this asset (ex: loading an asset value from the [`AssetServer`] of the [`AssetProcessor`])
///
/// DO NOT CHANGE ANY VALUES HERE OTHER THAN APPENDING TO `process_dependencies`
///
/// Do not expose this publicly as it would be too easily to invalidate state.
///
/// [`AssetServer`]: crate::server::AssetServer
pub(crate) new_processed_info: &'a mut ProcessedInfo,
/// This exists to expose access to asset values (via the [`AssetServer`]).
///
/// ANY ASSET VALUE THAT IS ACCESSED SHOULD BE ADDED TO `new_processed_info.process_dependencies`
///
/// Do not expose this publicly as it would be too easily to invalidate state by forgetting to update
/// `process_dependencies`.
///
/// [`AssetServer`]: crate::server::AssetServer
processor: &'a AssetProcessor,
path: &'a AssetPath<'static>,
asset_bytes: &'a [u8],
}
impl<'a> ProcessContext<'a> {
pub(crate) fn new(
processor: &'a AssetProcessor,
path: &'a AssetPath<'static>,
asset_bytes: &'a [u8],
new_processed_info: &'a mut ProcessedInfo,
) -> Self {
Self {
processor,
path,
asset_bytes,
new_processed_info,
}
}
/// Load the source asset using the `L` [`AssetLoader`] and the passed in `meta` config.
/// This will take the "load dependencies" (asset values used when loading with `L`]) and
/// register them as "process dependencies" because they are asset values required to process the
/// current asset.
pub async fn load_source_asset<L: AssetLoader>(
&mut self,
meta: AssetMeta<L, ()>,
) -> Result<ErasedLoadedAsset, AssetLoadError> {
let server = &self.processor.server;
let loader_name = core::any::type_name::<L>();
let loader = server.get_asset_loader_with_type_name(loader_name).await?;
let mut reader = SliceReader::new(self.asset_bytes);
let loaded_asset = server
.load_with_meta_loader_and_reader(self.path, &meta, &*loader, &mut reader, false, true)
.await?;
for (path, full_hash) in &loaded_asset.loader_dependencies {
self.new_processed_info
.process_dependencies
.push(ProcessDependencyInfo {
full_hash: *full_hash,
path: path.to_owned(),
});
}
Ok(loaded_asset)
}
/// The path of the asset being processed.
#[inline]
pub fn path(&self) -> &AssetPath<'static> {
self.path
}
/// The source bytes of the asset being processed.
#[inline]
pub fn asset_bytes(&self) -> &[u8] {
self.asset_bytes
}
}

309
vendor/bevy_asset/src/reflect.rs vendored Normal file
View File

@@ -0,0 +1,309 @@
use alloc::boxed::Box;
use core::any::{Any, TypeId};
use bevy_ecs::world::{unsafe_world_cell::UnsafeWorldCell, World};
use bevy_reflect::{FromReflect, FromType, PartialReflect, Reflect};
use crate::{Asset, AssetId, Assets, Handle, UntypedAssetId, UntypedHandle};
/// Type data for the [`TypeRegistry`](bevy_reflect::TypeRegistry) used to operate on reflected [`Asset`]s.
///
/// This type provides similar methods to [`Assets<T>`] like [`get`](ReflectAsset::get),
/// [`add`](ReflectAsset::add) and [`remove`](ReflectAsset::remove), but can be used in situations where you don't know which asset type `T` you want
/// until runtime.
///
/// [`ReflectAsset`] can be obtained via [`TypeRegistration::data`](bevy_reflect::TypeRegistration::data) if the asset was registered using [`register_asset_reflect`](crate::AssetApp::register_asset_reflect).
#[derive(Clone)]
pub struct ReflectAsset {
handle_type_id: TypeId,
assets_resource_type_id: TypeId,
get: fn(&World, UntypedHandle) -> Option<&dyn Reflect>,
// SAFETY:
// - may only be called with an [`UnsafeWorldCell`] which can be used to access the corresponding `Assets<T>` resource mutably
// - may only be used to access **at most one** access at once
get_unchecked_mut: unsafe fn(UnsafeWorldCell<'_>, UntypedHandle) -> Option<&mut dyn Reflect>,
add: fn(&mut World, &dyn PartialReflect) -> UntypedHandle,
insert: fn(&mut World, UntypedHandle, &dyn PartialReflect),
len: fn(&World) -> usize,
ids: for<'w> fn(&'w World) -> Box<dyn Iterator<Item = UntypedAssetId> + 'w>,
remove: fn(&mut World, UntypedHandle) -> Option<Box<dyn Reflect>>,
}
impl ReflectAsset {
/// The [`TypeId`] of the [`Handle<T>`] for this asset
pub fn handle_type_id(&self) -> TypeId {
self.handle_type_id
}
/// The [`TypeId`] of the [`Assets<T>`] resource
pub fn assets_resource_type_id(&self) -> TypeId {
self.assets_resource_type_id
}
/// Equivalent of [`Assets::get`]
pub fn get<'w>(&self, world: &'w World, handle: UntypedHandle) -> Option<&'w dyn Reflect> {
(self.get)(world, handle)
}
/// Equivalent of [`Assets::get_mut`]
pub fn get_mut<'w>(
&self,
world: &'w mut World,
handle: UntypedHandle,
) -> Option<&'w mut dyn Reflect> {
// SAFETY: unique world access
#[expect(
unsafe_code,
reason = "Use of unsafe `Self::get_unchecked_mut()` function."
)]
unsafe {
(self.get_unchecked_mut)(world.as_unsafe_world_cell(), handle)
}
}
/// Equivalent of [`Assets::get_mut`], but works with an [`UnsafeWorldCell`].
///
/// Only use this method when you have ensured that you are the *only* one with access to the [`Assets`] resource of the asset type.
/// Furthermore, this does *not* allow you to have look up two distinct handles,
/// you can only have at most one alive at the same time.
/// This means that this is *not allowed*:
/// ```no_run
/// # use bevy_asset::{ReflectAsset, UntypedHandle};
/// # use bevy_ecs::prelude::World;
/// # let reflect_asset: ReflectAsset = unimplemented!();
/// # let mut world: World = unimplemented!();
/// # let handle_1: UntypedHandle = unimplemented!();
/// # let handle_2: UntypedHandle = unimplemented!();
/// let unsafe_world_cell = world.as_unsafe_world_cell();
/// let a = unsafe { reflect_asset.get_unchecked_mut(unsafe_world_cell, handle_1).unwrap() };
/// let b = unsafe { reflect_asset.get_unchecked_mut(unsafe_world_cell, handle_2).unwrap() };
/// // ^ not allowed, two mutable references through the same asset resource, even though the
/// // handles are distinct
///
/// println!("a = {a:?}, b = {b:?}");
/// ```
///
/// # Safety
/// This method does not prevent you from having two mutable pointers to the same data,
/// violating Rust's aliasing rules. To avoid this:
/// * Only call this method if you know that the [`UnsafeWorldCell`] may be used to access the corresponding `Assets<T>`
/// * Don't call this method more than once in the same scope.
#[expect(
unsafe_code,
reason = "This function calls unsafe code and has safety requirements."
)]
pub unsafe fn get_unchecked_mut<'w>(
&self,
world: UnsafeWorldCell<'w>,
handle: UntypedHandle,
) -> Option<&'w mut dyn Reflect> {
// SAFETY: requirements are deferred to the caller
unsafe { (self.get_unchecked_mut)(world, handle) }
}
/// Equivalent of [`Assets::add`]
pub fn add(&self, world: &mut World, value: &dyn PartialReflect) -> UntypedHandle {
(self.add)(world, value)
}
/// Equivalent of [`Assets::insert`]
pub fn insert(&self, world: &mut World, handle: UntypedHandle, value: &dyn PartialReflect) {
(self.insert)(world, handle, value);
}
/// Equivalent of [`Assets::remove`]
pub fn remove(&self, world: &mut World, handle: UntypedHandle) -> Option<Box<dyn Reflect>> {
(self.remove)(world, handle)
}
/// Equivalent of [`Assets::len`]
pub fn len(&self, world: &World) -> usize {
(self.len)(world)
}
/// Equivalent of [`Assets::is_empty`]
pub fn is_empty(&self, world: &World) -> bool {
self.len(world) == 0
}
/// Equivalent of [`Assets::ids`]
pub fn ids<'w>(&self, world: &'w World) -> impl Iterator<Item = UntypedAssetId> + 'w {
(self.ids)(world)
}
}
impl<A: Asset + FromReflect> FromType<A> for ReflectAsset {
fn from_type() -> Self {
ReflectAsset {
handle_type_id: TypeId::of::<Handle<A>>(),
assets_resource_type_id: TypeId::of::<Assets<A>>(),
get: |world, handle| {
let assets = world.resource::<Assets<A>>();
let asset = assets.get(&handle.typed_debug_checked());
asset.map(|asset| asset as &dyn Reflect)
},
get_unchecked_mut: |world, handle| {
// SAFETY: `get_unchecked_mut` must be called with `UnsafeWorldCell` having access to `Assets<A>`,
// and must ensure to only have at most one reference to it live at all times.
#[expect(unsafe_code, reason = "Uses `UnsafeWorldCell::get_resource_mut()`.")]
let assets = unsafe { world.get_resource_mut::<Assets<A>>().unwrap().into_inner() };
let asset = assets.get_mut(&handle.typed_debug_checked());
asset.map(|asset| asset as &mut dyn Reflect)
},
add: |world, value| {
let mut assets = world.resource_mut::<Assets<A>>();
let value: A = FromReflect::from_reflect(value)
.expect("could not call `FromReflect::from_reflect` in `ReflectAsset::add`");
assets.add(value).untyped()
},
insert: |world, handle, value| {
let mut assets = world.resource_mut::<Assets<A>>();
let value: A = FromReflect::from_reflect(value)
.expect("could not call `FromReflect::from_reflect` in `ReflectAsset::set`");
assets.insert(&handle.typed_debug_checked(), value);
},
len: |world| {
let assets = world.resource::<Assets<A>>();
assets.len()
},
ids: |world| {
let assets = world.resource::<Assets<A>>();
Box::new(assets.ids().map(AssetId::untyped))
},
remove: |world, handle| {
let mut assets = world.resource_mut::<Assets<A>>();
let value = assets.remove(&handle.typed_debug_checked());
value.map(|value| Box::new(value) as Box<dyn Reflect>)
},
}
}
}
/// Reflect type data struct relating a [`Handle<T>`] back to the `T` asset type.
///
/// Say you want to look up the asset values of a list of handles when you have access to their `&dyn Reflect` form.
/// Assets can be looked up in the world using [`ReflectAsset`], but how do you determine which [`ReflectAsset`] to use when
/// only looking at the handle? [`ReflectHandle`] is stored in the type registry on each `Handle<T>` type, so you can use [`ReflectHandle::asset_type_id`] to look up
/// the [`ReflectAsset`] type data on the corresponding `T` asset type:
///
///
/// ```no_run
/// # use bevy_reflect::{TypeRegistry, prelude::*};
/// # use bevy_ecs::prelude::*;
/// use bevy_asset::{ReflectHandle, ReflectAsset};
///
/// # let world: &World = unimplemented!();
/// # let type_registry: TypeRegistry = unimplemented!();
/// let handles: Vec<&dyn Reflect> = unimplemented!();
/// for handle in handles {
/// let reflect_handle = type_registry.get_type_data::<ReflectHandle>(handle.type_id()).unwrap();
/// let reflect_asset = type_registry.get_type_data::<ReflectAsset>(reflect_handle.asset_type_id()).unwrap();
///
/// let handle = reflect_handle.downcast_handle_untyped(handle.as_any()).unwrap();
/// let value = reflect_asset.get(world, handle).unwrap();
/// println!("{value:?}");
/// }
/// ```
#[derive(Clone)]
pub struct ReflectHandle {
asset_type_id: TypeId,
downcast_handle_untyped: fn(&dyn Any) -> Option<UntypedHandle>,
typed: fn(UntypedHandle) -> Box<dyn Reflect>,
}
impl ReflectHandle {
/// The [`TypeId`] of the asset
pub fn asset_type_id(&self) -> TypeId {
self.asset_type_id
}
/// A way to go from a [`Handle<T>`] in a `dyn Any` to a [`UntypedHandle`]
pub fn downcast_handle_untyped(&self, handle: &dyn Any) -> Option<UntypedHandle> {
(self.downcast_handle_untyped)(handle)
}
/// A way to go from a [`UntypedHandle`] to a [`Handle<T>`] in a `Box<dyn Reflect>`.
/// Equivalent of [`UntypedHandle::typed`].
pub fn typed(&self, handle: UntypedHandle) -> Box<dyn Reflect> {
(self.typed)(handle)
}
}
impl<A: Asset> FromType<Handle<A>> for ReflectHandle {
fn from_type() -> Self {
ReflectHandle {
asset_type_id: TypeId::of::<A>(),
downcast_handle_untyped: |handle: &dyn Any| {
handle
.downcast_ref::<Handle<A>>()
.map(|h| h.clone().untyped())
},
typed: |handle: UntypedHandle| Box::new(handle.typed_debug_checked::<A>()),
}
}
}
#[cfg(test)]
mod tests {
use alloc::{string::String, vec::Vec};
use core::any::TypeId;
use crate::{Asset, AssetApp, AssetPlugin, ReflectAsset, UntypedHandle};
use bevy_app::App;
use bevy_ecs::reflect::AppTypeRegistry;
use bevy_reflect::Reflect;
#[derive(Asset, Reflect)]
struct AssetType {
field: String,
}
#[test]
fn test_reflect_asset_operations() {
let mut app = App::new();
app.add_plugins(AssetPlugin::default())
.init_asset::<AssetType>()
.register_asset_reflect::<AssetType>();
let reflect_asset = {
let type_registry = app.world().resource::<AppTypeRegistry>();
let type_registry = type_registry.read();
type_registry
.get_type_data::<ReflectAsset>(TypeId::of::<AssetType>())
.unwrap()
.clone()
};
let value = AssetType {
field: "test".into(),
};
let handle = reflect_asset.add(app.world_mut(), &value);
// struct is a reserved keyword, so we can't use it here
let strukt = reflect_asset
.get_mut(app.world_mut(), handle)
.unwrap()
.reflect_mut()
.as_struct()
.unwrap();
strukt
.field_mut("field")
.unwrap()
.apply(&String::from("edited"));
assert_eq!(reflect_asset.len(app.world()), 1);
let ids: Vec<_> = reflect_asset.ids(app.world()).collect();
assert_eq!(ids.len(), 1);
let fetched_handle = UntypedHandle::Weak(ids[0]);
let asset = reflect_asset
.get(app.world(), fetched_handle.clone_weak())
.unwrap();
assert_eq!(asset.downcast_ref::<AssetType>().unwrap().field, "edited");
reflect_asset
.remove(app.world_mut(), fetched_handle)
.unwrap();
assert_eq!(reflect_asset.len(app.world()), 0);
}
}

51
vendor/bevy_asset/src/render_asset.rs vendored Normal file
View File

@@ -0,0 +1,51 @@
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
use serde::{Deserialize, Serialize};
bitflags::bitflags! {
/// Defines where the asset will be used.
///
/// If an asset is set to the `RENDER_WORLD` but not the `MAIN_WORLD`, the asset will be
/// unloaded from the asset server once it's been extracted and prepared in the render world.
///
/// Unloading the asset saves on memory, as for most cases it is no longer necessary to keep
/// it in RAM once it's been uploaded to the GPU's VRAM. However, this means you can no longer
/// access the asset from the CPU (via the `Assets<T>` resource) once unloaded (without re-loading it).
///
/// If you never need access to the asset from the CPU past the first frame it's loaded on,
/// or only need very infrequent access, then set this to `RENDER_WORLD`. Otherwise, set this to
/// `RENDER_WORLD | MAIN_WORLD`.
///
/// If you have an asset that doesn't actually need to end up in the render world, like an Image
/// that will be decoded into another Image asset, use `MAIN_WORLD` only.
///
/// ## Platform-specific
///
/// On Wasm, it is not possible for now to free reserved memory. To control memory usage, load assets
/// in sequence and unload one before loading the next. See this
/// [discussion about memory management](https://github.com/WebAssembly/design/issues/1397) for more
/// details.
#[repr(transparent)]
#[derive(Serialize, Deserialize, Hash, Clone, Copy, PartialEq, Eq, Debug, Reflect)]
#[reflect(opaque)]
#[reflect(Serialize, Deserialize, Hash, Clone, PartialEq, Debug)]
pub struct RenderAssetUsages: u8 {
/// The bit flag for the main world.
const MAIN_WORLD = 1 << 0;
/// The bit flag for the render world.
const RENDER_WORLD = 1 << 1;
}
}
impl Default for RenderAssetUsages {
/// Returns the default render asset usage flags:
/// `RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD`
///
/// This default configuration ensures the asset persists in the main world, even after being prepared for rendering.
///
/// If your asset does not change, consider using `RenderAssetUsages::RENDER_WORLD` exclusively. This will cause
/// the asset to be unloaded from the main world once it has been prepared for rendering. If the asset does not need
/// to reach the render world at all, use `RenderAssetUsages::MAIN_WORLD` exclusively.
fn default() -> Self {
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD
}
}

167
vendor/bevy_asset/src/saver.rs vendored Normal file
View File

@@ -0,0 +1,167 @@
use crate::{
io::Writer, meta::Settings, transformer::TransformedAsset, Asset, AssetLoader,
ErasedLoadedAsset, Handle, LabeledAsset, UntypedHandle,
};
use alloc::boxed::Box;
use atomicow::CowArc;
use bevy_platform::collections::HashMap;
use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
use core::{borrow::Borrow, hash::Hash, ops::Deref};
use serde::{Deserialize, Serialize};
/// Saves an [`Asset`] of a given [`AssetSaver::Asset`] type. [`AssetSaver::OutputLoader`] will then be used to load the saved asset
/// in the final deployed application. The saver should produce asset bytes in a format that [`AssetSaver::OutputLoader`] can read.
///
/// This trait is generally used in concert with [`AssetWriter`](crate::io::AssetWriter) to write assets as bytes.
///
/// For a complementary version of this trait that can load assets, see [`AssetLoader`].
pub trait AssetSaver: Send + Sync + 'static {
/// The top level [`Asset`] saved by this [`AssetSaver`].
type Asset: Asset;
/// The settings type used by this [`AssetSaver`].
type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
/// The type of [`AssetLoader`] used to load this [`Asset`]
type OutputLoader: AssetLoader;
/// The type of [error](`std::error::Error`) which could be encountered by this saver.
type Error: Into<Box<dyn core::error::Error + Send + Sync + 'static>>;
/// Saves the given runtime [`Asset`] by writing it to a byte format using `writer`. The passed in `settings` can influence how the
/// `asset` is saved.
fn save(
&self,
writer: &mut Writer,
asset: SavedAsset<'_, Self::Asset>,
settings: &Self::Settings,
) -> impl ConditionalSendFuture<
Output = Result<<Self::OutputLoader as AssetLoader>::Settings, Self::Error>,
>;
}
/// A type-erased dynamic variant of [`AssetSaver`] that allows callers to save assets without knowing the actual type of the [`AssetSaver`].
pub trait ErasedAssetSaver: Send + Sync + 'static {
/// Saves the given runtime [`ErasedLoadedAsset`] by writing it to a byte format using `writer`. The passed in `settings` can influence how the
/// `asset` is saved.
fn save<'a>(
&'a self,
writer: &'a mut Writer,
asset: &'a ErasedLoadedAsset,
settings: &'a dyn Settings,
) -> BoxedFuture<'a, Result<(), Box<dyn core::error::Error + Send + Sync + 'static>>>;
/// The type name of the [`AssetSaver`].
fn type_name(&self) -> &'static str;
}
impl<S: AssetSaver> ErasedAssetSaver for S {
fn save<'a>(
&'a self,
writer: &'a mut Writer,
asset: &'a ErasedLoadedAsset,
settings: &'a dyn Settings,
) -> BoxedFuture<'a, Result<(), Box<dyn core::error::Error + Send + Sync + 'static>>> {
Box::pin(async move {
let settings = settings
.downcast_ref::<S::Settings>()
.expect("AssetLoader settings should match the loader type");
let saved_asset = SavedAsset::<S::Asset>::from_loaded(asset).unwrap();
if let Err(err) = self.save(writer, saved_asset, settings).await {
return Err(err.into());
}
Ok(())
})
}
fn type_name(&self) -> &'static str {
core::any::type_name::<S>()
}
}
/// An [`Asset`] (and any labeled "sub assets") intended to be saved.
pub struct SavedAsset<'a, A: Asset> {
value: &'a A,
labeled_assets: &'a HashMap<CowArc<'static, str>, LabeledAsset>,
}
impl<'a, A: Asset> Deref for SavedAsset<'a, A> {
type Target = A;
fn deref(&self) -> &Self::Target {
self.value
}
}
impl<'a, A: Asset> SavedAsset<'a, A> {
/// Creates a new [`SavedAsset`] from `asset` if its internal value matches `A`.
pub fn from_loaded(asset: &'a ErasedLoadedAsset) -> Option<Self> {
let value = asset.value.downcast_ref::<A>()?;
Some(SavedAsset {
value,
labeled_assets: &asset.labeled_assets,
})
}
/// Creates a new [`SavedAsset`] from the a [`TransformedAsset`]
pub fn from_transformed(asset: &'a TransformedAsset<A>) -> Self {
Self {
value: &asset.value,
labeled_assets: &asset.labeled_assets,
}
}
/// Retrieves the value of this asset.
#[inline]
pub fn get(&self) -> &'a A {
self.value
}
/// Returns the labeled asset, if it exists and matches this type.
pub fn get_labeled<B: Asset, Q>(&self, label: &Q) -> Option<SavedAsset<B>>
where
CowArc<'static, str>: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
let labeled = self.labeled_assets.get(label)?;
let value = labeled.asset.value.downcast_ref::<B>()?;
Some(SavedAsset {
value,
labeled_assets: &labeled.asset.labeled_assets,
})
}
/// Returns the type-erased labeled asset, if it exists and matches this type.
pub fn get_erased_labeled<Q>(&self, label: &Q) -> Option<&ErasedLoadedAsset>
where
CowArc<'static, str>: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
let labeled = self.labeled_assets.get(label)?;
Some(&labeled.asset)
}
/// Returns the [`UntypedHandle`] of the labeled asset with the provided 'label', if it exists.
pub fn get_untyped_handle<Q>(&self, label: &Q) -> Option<UntypedHandle>
where
CowArc<'static, str>: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
let labeled = self.labeled_assets.get(label)?;
Some(labeled.handle.clone())
}
/// Returns the [`Handle`] of the labeled asset with the provided 'label', if it exists and is an asset of type `B`
pub fn get_handle<Q, B: Asset>(&self, label: &Q) -> Option<Handle<B>>
where
CowArc<'static, str>: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
let labeled = self.labeled_assets.get(label)?;
if let Ok(handle) = labeled.handle.clone().try_typed::<B>() {
return Some(handle);
}
None
}
/// Iterate over all labels for "labeled assets" in the loaded asset
pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
self.labeled_assets.keys().map(|s| &**s)
}
}

789
vendor/bevy_asset/src/server/info.rs vendored Normal file
View File

@@ -0,0 +1,789 @@
use crate::{
meta::{AssetHash, MetaTransform},
Asset, AssetHandleProvider, AssetLoadError, AssetPath, DependencyLoadState, ErasedLoadedAsset,
Handle, InternalAssetEvent, LoadState, RecursiveDependencyLoadState, StrongHandle,
UntypedAssetId, UntypedHandle,
};
use alloc::{
borrow::ToOwned,
boxed::Box,
sync::{Arc, Weak},
vec::Vec,
};
use bevy_ecs::world::World;
use bevy_platform::collections::{hash_map::Entry, HashMap, HashSet};
use bevy_tasks::Task;
use bevy_utils::TypeIdMap;
use core::{any::TypeId, task::Waker};
use crossbeam_channel::Sender;
use either::Either;
use thiserror::Error;
use tracing::warn;
#[derive(Debug)]
pub(crate) struct AssetInfo {
weak_handle: Weak<StrongHandle>,
pub(crate) path: Option<AssetPath<'static>>,
pub(crate) load_state: LoadState,
pub(crate) dep_load_state: DependencyLoadState,
pub(crate) rec_dep_load_state: RecursiveDependencyLoadState,
loading_dependencies: HashSet<UntypedAssetId>,
failed_dependencies: HashSet<UntypedAssetId>,
loading_rec_dependencies: HashSet<UntypedAssetId>,
failed_rec_dependencies: HashSet<UntypedAssetId>,
dependents_waiting_on_load: HashSet<UntypedAssetId>,
dependents_waiting_on_recursive_dep_load: HashSet<UntypedAssetId>,
/// The asset paths required to load this asset. Hashes will only be set for processed assets.
/// This is set using the value from [`LoadedAsset`].
/// This will only be populated if [`AssetInfos::watching_for_changes`] is set to `true` to
/// save memory.
///
/// [`LoadedAsset`]: crate::loader::LoadedAsset
loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
/// The number of handle drops to skip for this asset.
/// See usage (and comments) in `get_or_create_path_handle` for context.
handle_drops_to_skip: usize,
/// List of tasks waiting for this asset to complete loading
pub(crate) waiting_tasks: Vec<Waker>,
}
impl AssetInfo {
fn new(weak_handle: Weak<StrongHandle>, path: Option<AssetPath<'static>>) -> Self {
Self {
weak_handle,
path,
load_state: LoadState::NotLoaded,
dep_load_state: DependencyLoadState::NotLoaded,
rec_dep_load_state: RecursiveDependencyLoadState::NotLoaded,
loading_dependencies: HashSet::default(),
failed_dependencies: HashSet::default(),
loading_rec_dependencies: HashSet::default(),
failed_rec_dependencies: HashSet::default(),
loader_dependencies: HashMap::default(),
dependents_waiting_on_load: HashSet::default(),
dependents_waiting_on_recursive_dep_load: HashSet::default(),
handle_drops_to_skip: 0,
waiting_tasks: Vec::new(),
}
}
}
#[derive(Default)]
pub(crate) struct AssetInfos {
path_to_id: HashMap<AssetPath<'static>, TypeIdMap<UntypedAssetId>>,
infos: HashMap<UntypedAssetId, AssetInfo>,
/// If set to `true`, this informs [`AssetInfos`] to track data relevant to watching for changes (such as `load_dependents`)
/// This should only be set at startup.
pub(crate) watching_for_changes: bool,
/// Tracks assets that depend on the "key" asset path inside their asset loaders ("loader dependencies")
/// This should only be set when watching for changes to avoid unnecessary work.
pub(crate) loader_dependents: HashMap<AssetPath<'static>, HashSet<AssetPath<'static>>>,
/// Tracks living labeled assets for a given source asset.
/// This should only be set when watching for changes to avoid unnecessary work.
pub(crate) living_labeled_assets: HashMap<AssetPath<'static>, HashSet<Box<str>>>,
pub(crate) handle_providers: TypeIdMap<AssetHandleProvider>,
pub(crate) dependency_loaded_event_sender: TypeIdMap<fn(&mut World, UntypedAssetId)>,
pub(crate) dependency_failed_event_sender:
TypeIdMap<fn(&mut World, UntypedAssetId, AssetPath<'static>, AssetLoadError)>,
pub(crate) pending_tasks: HashMap<UntypedAssetId, Task<()>>,
}
impl core::fmt::Debug for AssetInfos {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("AssetInfos")
.field("path_to_id", &self.path_to_id)
.field("infos", &self.infos)
.finish()
}
}
impl AssetInfos {
pub(crate) fn create_loading_handle_untyped(
&mut self,
type_id: TypeId,
type_name: &'static str,
) -> UntypedHandle {
unwrap_with_context(
Self::create_handle_internal(
&mut self.infos,
&self.handle_providers,
&mut self.living_labeled_assets,
self.watching_for_changes,
type_id,
None,
None,
true,
),
Either::Left(type_name),
)
.unwrap()
}
fn create_handle_internal(
infos: &mut HashMap<UntypedAssetId, AssetInfo>,
handle_providers: &TypeIdMap<AssetHandleProvider>,
living_labeled_assets: &mut HashMap<AssetPath<'static>, HashSet<Box<str>>>,
watching_for_changes: bool,
type_id: TypeId,
path: Option<AssetPath<'static>>,
meta_transform: Option<MetaTransform>,
loading: bool,
) -> Result<UntypedHandle, GetOrCreateHandleInternalError> {
let provider = handle_providers
.get(&type_id)
.ok_or(MissingHandleProviderError(type_id))?;
if watching_for_changes {
if let Some(path) = &path {
let mut without_label = path.to_owned();
if let Some(label) = without_label.take_label() {
let labels = living_labeled_assets.entry(without_label).or_default();
labels.insert(label.as_ref().into());
}
}
}
let handle = provider.reserve_handle_internal(true, path.clone(), meta_transform);
let mut info = AssetInfo::new(Arc::downgrade(&handle), path);
if loading {
info.load_state = LoadState::Loading;
info.dep_load_state = DependencyLoadState::Loading;
info.rec_dep_load_state = RecursiveDependencyLoadState::Loading;
}
infos.insert(handle.id, info);
Ok(UntypedHandle::Strong(handle))
}
pub(crate) fn get_or_create_path_handle<A: Asset>(
&mut self,
path: AssetPath<'static>,
loading_mode: HandleLoadingMode,
meta_transform: Option<MetaTransform>,
) -> (Handle<A>, bool) {
let result = self.get_or_create_path_handle_internal(
path,
Some(TypeId::of::<A>()),
loading_mode,
meta_transform,
);
// it is ok to unwrap because TypeId was specified above
let (handle, should_load) =
unwrap_with_context(result, Either::Left(core::any::type_name::<A>())).unwrap();
(handle.typed_unchecked(), should_load)
}
pub(crate) fn get_or_create_path_handle_erased(
&mut self,
path: AssetPath<'static>,
type_id: TypeId,
type_name: Option<&str>,
loading_mode: HandleLoadingMode,
meta_transform: Option<MetaTransform>,
) -> (UntypedHandle, bool) {
let result = self.get_or_create_path_handle_internal(
path,
Some(type_id),
loading_mode,
meta_transform,
);
let type_info = match type_name {
Some(type_name) => Either::Left(type_name),
None => Either::Right(type_id),
};
unwrap_with_context(result, type_info)
.expect("type should be correct since the `TypeId` is specified above")
}
/// Retrieves asset tracking data, or creates it if it doesn't exist.
/// Returns true if an asset load should be kicked off
pub(crate) fn get_or_create_path_handle_internal(
&mut self,
path: AssetPath<'static>,
type_id: Option<TypeId>,
loading_mode: HandleLoadingMode,
meta_transform: Option<MetaTransform>,
) -> Result<(UntypedHandle, bool), GetOrCreateHandleInternalError> {
let handles = self.path_to_id.entry(path.clone()).or_default();
let type_id = type_id
.or_else(|| {
// If a TypeId is not provided, we may be able to infer it if only a single entry exists
if handles.len() == 1 {
Some(*handles.keys().next().unwrap())
} else {
None
}
})
.ok_or(GetOrCreateHandleInternalError::HandleMissingButTypeIdNotSpecified)?;
match handles.entry(type_id) {
Entry::Occupied(entry) => {
let id = *entry.get();
// if there is a path_to_id entry, info always exists
let info = self.infos.get_mut(&id).unwrap();
let mut should_load = false;
if loading_mode == HandleLoadingMode::Force
|| (loading_mode == HandleLoadingMode::Request
&& matches!(info.load_state, LoadState::NotLoaded | LoadState::Failed(_)))
{
info.load_state = LoadState::Loading;
info.dep_load_state = DependencyLoadState::Loading;
info.rec_dep_load_state = RecursiveDependencyLoadState::Loading;
should_load = true;
}
if let Some(strong_handle) = info.weak_handle.upgrade() {
// If we can upgrade the handle, there is at least one live handle right now,
// The asset load has already kicked off (and maybe completed), so we can just
// return a strong handle
Ok((UntypedHandle::Strong(strong_handle), should_load))
} else {
// Asset meta exists, but all live handles were dropped. This means the `track_assets` system
// hasn't been run yet to remove the current asset
// (note that this is guaranteed to be transactional with the `track_assets` system because
// because it locks the AssetInfos collection)
// We must create a new strong handle for the existing id and ensure that the drop of the old
// strong handle doesn't remove the asset from the Assets collection
info.handle_drops_to_skip += 1;
let provider = self
.handle_providers
.get(&type_id)
.ok_or(MissingHandleProviderError(type_id))?;
let handle =
provider.get_handle(id.internal(), true, Some(path), meta_transform);
info.weak_handle = Arc::downgrade(&handle);
Ok((UntypedHandle::Strong(handle), should_load))
}
}
// The entry does not exist, so this is a "fresh" asset load. We must create a new handle
Entry::Vacant(entry) => {
let should_load = match loading_mode {
HandleLoadingMode::NotLoading => false,
HandleLoadingMode::Request | HandleLoadingMode::Force => true,
};
let handle = Self::create_handle_internal(
&mut self.infos,
&self.handle_providers,
&mut self.living_labeled_assets,
self.watching_for_changes,
type_id,
Some(path),
meta_transform,
should_load,
)?;
entry.insert(handle.id());
Ok((handle, should_load))
}
}
}
pub(crate) fn get(&self, id: UntypedAssetId) -> Option<&AssetInfo> {
self.infos.get(&id)
}
pub(crate) fn contains_key(&self, id: UntypedAssetId) -> bool {
self.infos.contains_key(&id)
}
pub(crate) fn get_mut(&mut self, id: UntypedAssetId) -> Option<&mut AssetInfo> {
self.infos.get_mut(&id)
}
pub(crate) fn get_path_and_type_id_handle(
&self,
path: &AssetPath<'_>,
type_id: TypeId,
) -> Option<UntypedHandle> {
let id = self.path_to_id.get(path)?.get(&type_id)?;
self.get_id_handle(*id)
}
pub(crate) fn get_path_ids<'a>(
&'a self,
path: &'a AssetPath<'_>,
) -> impl Iterator<Item = UntypedAssetId> + 'a {
/// Concrete type to allow returning an `impl Iterator` even if `self.path_to_id.get(&path)` is `None`
enum HandlesByPathIterator<T> {
None,
Some(T),
}
impl<T> Iterator for HandlesByPathIterator<T>
where
T: Iterator<Item = UntypedAssetId>,
{
type Item = UntypedAssetId;
fn next(&mut self) -> Option<Self::Item> {
match self {
HandlesByPathIterator::None => None,
HandlesByPathIterator::Some(iter) => iter.next(),
}
}
}
if let Some(type_id_to_id) = self.path_to_id.get(path) {
HandlesByPathIterator::Some(type_id_to_id.values().copied())
} else {
HandlesByPathIterator::None
}
}
pub(crate) fn get_path_handles<'a>(
&'a self,
path: &'a AssetPath<'_>,
) -> impl Iterator<Item = UntypedHandle> + 'a {
self.get_path_ids(path)
.filter_map(|id| self.get_id_handle(id))
}
pub(crate) fn get_id_handle(&self, id: UntypedAssetId) -> Option<UntypedHandle> {
let info = self.infos.get(&id)?;
let strong_handle = info.weak_handle.upgrade()?;
Some(UntypedHandle::Strong(strong_handle))
}
/// Returns `true` if the asset this path points to is still alive
pub(crate) fn is_path_alive<'a>(&self, path: impl Into<AssetPath<'a>>) -> bool {
self.get_path_ids(&path.into())
.filter_map(|id| self.infos.get(&id))
.any(|info| info.weak_handle.strong_count() > 0)
}
/// Returns `true` if the asset at this path should be reloaded
pub(crate) fn should_reload(&self, path: &AssetPath) -> bool {
if self.is_path_alive(path) {
return true;
}
if let Some(living) = self.living_labeled_assets.get(path) {
!living.is_empty()
} else {
false
}
}
/// Returns `true` if the asset should be removed from the collection.
pub(crate) fn process_handle_drop(&mut self, id: UntypedAssetId) -> bool {
Self::process_handle_drop_internal(
&mut self.infos,
&mut self.path_to_id,
&mut self.loader_dependents,
&mut self.living_labeled_assets,
&mut self.pending_tasks,
self.watching_for_changes,
id,
)
}
/// Updates [`AssetInfo`] / load state for an asset that has finished loading (and relevant dependencies / dependents).
pub(crate) fn process_asset_load(
&mut self,
loaded_asset_id: UntypedAssetId,
loaded_asset: ErasedLoadedAsset,
world: &mut World,
sender: &Sender<InternalAssetEvent>,
) {
// Check whether the handle has been dropped since the asset was loaded.
if !self.infos.contains_key(&loaded_asset_id) {
return;
}
loaded_asset.value.insert(loaded_asset_id, world);
let mut loading_deps = loaded_asset.dependencies;
let mut failed_deps = <HashSet<_>>::default();
let mut dep_error = None;
let mut loading_rec_deps = loading_deps.clone();
let mut failed_rec_deps = <HashSet<_>>::default();
let mut rec_dep_error = None;
loading_deps.retain(|dep_id| {
if let Some(dep_info) = self.get_mut(*dep_id) {
match dep_info.rec_dep_load_state {
RecursiveDependencyLoadState::Loading
| RecursiveDependencyLoadState::NotLoaded => {
// If dependency is loading, wait for it.
dep_info
.dependents_waiting_on_recursive_dep_load
.insert(loaded_asset_id);
}
RecursiveDependencyLoadState::Loaded => {
// If dependency is loaded, reduce our count by one
loading_rec_deps.remove(dep_id);
}
RecursiveDependencyLoadState::Failed(ref error) => {
if rec_dep_error.is_none() {
rec_dep_error = Some(error.clone());
}
failed_rec_deps.insert(*dep_id);
loading_rec_deps.remove(dep_id);
}
}
match dep_info.load_state {
LoadState::NotLoaded | LoadState::Loading => {
// If dependency is loading, wait for it.
dep_info.dependents_waiting_on_load.insert(loaded_asset_id);
true
}
LoadState::Loaded => {
// If dependency is loaded, reduce our count by one
false
}
LoadState::Failed(ref error) => {
if dep_error.is_none() {
dep_error = Some(error.clone());
}
failed_deps.insert(*dep_id);
false
}
}
} else {
// the dependency id does not exist, which implies it was manually removed or never existed in the first place
warn!(
"Dependency {} from asset {} is unknown. This asset's dependency load status will not switch to 'Loaded' until the unknown dependency is loaded.",
dep_id, loaded_asset_id
);
true
}
});
let dep_load_state = match (loading_deps.len(), failed_deps.len()) {
(0, 0) => DependencyLoadState::Loaded,
(_loading, 0) => DependencyLoadState::Loading,
(_loading, _failed) => DependencyLoadState::Failed(dep_error.unwrap()),
};
let rec_dep_load_state = match (loading_rec_deps.len(), failed_rec_deps.len()) {
(0, 0) => {
sender
.send(InternalAssetEvent::LoadedWithDependencies {
id: loaded_asset_id,
})
.unwrap();
RecursiveDependencyLoadState::Loaded
}
(_loading, 0) => RecursiveDependencyLoadState::Loading,
(_loading, _failed) => RecursiveDependencyLoadState::Failed(rec_dep_error.unwrap()),
};
let (dependents_waiting_on_load, dependents_waiting_on_rec_load) = {
let watching_for_changes = self.watching_for_changes;
// if watching for changes, track reverse loader dependencies for hot reloading
if watching_for_changes {
let info = self
.infos
.get(&loaded_asset_id)
.expect("Asset info should always exist at this point");
if let Some(asset_path) = &info.path {
for loader_dependency in loaded_asset.loader_dependencies.keys() {
let dependents = self
.loader_dependents
.entry(loader_dependency.clone())
.or_default();
dependents.insert(asset_path.clone());
}
}
}
let info = self
.get_mut(loaded_asset_id)
.expect("Asset info should always exist at this point");
info.loading_dependencies = loading_deps;
info.failed_dependencies = failed_deps;
info.loading_rec_dependencies = loading_rec_deps;
info.failed_rec_dependencies = failed_rec_deps;
info.load_state = LoadState::Loaded;
info.dep_load_state = dep_load_state;
info.rec_dep_load_state = rec_dep_load_state.clone();
if watching_for_changes {
info.loader_dependencies = loaded_asset.loader_dependencies;
}
let dependents_waiting_on_rec_load =
if rec_dep_load_state.is_loaded() || rec_dep_load_state.is_failed() {
Some(core::mem::take(
&mut info.dependents_waiting_on_recursive_dep_load,
))
} else {
None
};
(
core::mem::take(&mut info.dependents_waiting_on_load),
dependents_waiting_on_rec_load,
)
};
for id in dependents_waiting_on_load {
if let Some(info) = self.get_mut(id) {
info.loading_dependencies.remove(&loaded_asset_id);
if info.loading_dependencies.is_empty() && !info.dep_load_state.is_failed() {
// send dependencies loaded event
info.dep_load_state = DependencyLoadState::Loaded;
}
}
}
if let Some(dependents_waiting_on_rec_load) = dependents_waiting_on_rec_load {
match rec_dep_load_state {
RecursiveDependencyLoadState::Loaded => {
for dep_id in dependents_waiting_on_rec_load {
Self::propagate_loaded_state(self, loaded_asset_id, dep_id, sender);
}
}
RecursiveDependencyLoadState::Failed(ref error) => {
for dep_id in dependents_waiting_on_rec_load {
Self::propagate_failed_state(self, loaded_asset_id, dep_id, error);
}
}
RecursiveDependencyLoadState::Loading | RecursiveDependencyLoadState::NotLoaded => {
// dependents_waiting_on_rec_load should be None in this case
unreachable!("`Loading` and `NotLoaded` state should never be propagated.")
}
}
}
}
/// Recursively propagates loaded state up the dependency tree.
fn propagate_loaded_state(
infos: &mut AssetInfos,
loaded_id: UntypedAssetId,
waiting_id: UntypedAssetId,
sender: &Sender<InternalAssetEvent>,
) {
let dependents_waiting_on_rec_load = if let Some(info) = infos.get_mut(waiting_id) {
info.loading_rec_dependencies.remove(&loaded_id);
if info.loading_rec_dependencies.is_empty() && info.failed_rec_dependencies.is_empty() {
info.rec_dep_load_state = RecursiveDependencyLoadState::Loaded;
if info.load_state.is_loaded() {
sender
.send(InternalAssetEvent::LoadedWithDependencies { id: waiting_id })
.unwrap();
}
Some(core::mem::take(
&mut info.dependents_waiting_on_recursive_dep_load,
))
} else {
None
}
} else {
None
};
if let Some(dependents_waiting_on_rec_load) = dependents_waiting_on_rec_load {
for dep_id in dependents_waiting_on_rec_load {
Self::propagate_loaded_state(infos, waiting_id, dep_id, sender);
}
}
}
/// Recursively propagates failed state up the dependency tree
fn propagate_failed_state(
infos: &mut AssetInfos,
failed_id: UntypedAssetId,
waiting_id: UntypedAssetId,
error: &Arc<AssetLoadError>,
) {
let dependents_waiting_on_rec_load = if let Some(info) = infos.get_mut(waiting_id) {
info.loading_rec_dependencies.remove(&failed_id);
info.failed_rec_dependencies.insert(failed_id);
info.rec_dep_load_state = RecursiveDependencyLoadState::Failed(error.clone());
Some(core::mem::take(
&mut info.dependents_waiting_on_recursive_dep_load,
))
} else {
None
};
if let Some(dependents_waiting_on_rec_load) = dependents_waiting_on_rec_load {
for dep_id in dependents_waiting_on_rec_load {
Self::propagate_failed_state(infos, waiting_id, dep_id, error);
}
}
}
pub(crate) fn process_asset_fail(&mut self, failed_id: UntypedAssetId, error: AssetLoadError) {
// Check whether the handle has been dropped since the asset was loaded.
if !self.infos.contains_key(&failed_id) {
return;
}
let error = Arc::new(error);
let (dependents_waiting_on_load, dependents_waiting_on_rec_load) = {
let Some(info) = self.get_mut(failed_id) else {
// The asset was already dropped.
return;
};
info.load_state = LoadState::Failed(error.clone());
info.dep_load_state = DependencyLoadState::Failed(error.clone());
info.rec_dep_load_state = RecursiveDependencyLoadState::Failed(error.clone());
for waker in info.waiting_tasks.drain(..) {
waker.wake();
}
(
core::mem::take(&mut info.dependents_waiting_on_load),
core::mem::take(&mut info.dependents_waiting_on_recursive_dep_load),
)
};
for waiting_id in dependents_waiting_on_load {
if let Some(info) = self.get_mut(waiting_id) {
info.loading_dependencies.remove(&failed_id);
info.failed_dependencies.insert(failed_id);
// don't overwrite DependencyLoadState if already failed to preserve first error
if !info.dep_load_state.is_failed() {
info.dep_load_state = DependencyLoadState::Failed(error.clone());
}
}
}
for waiting_id in dependents_waiting_on_rec_load {
Self::propagate_failed_state(self, failed_id, waiting_id, &error);
}
}
fn remove_dependents_and_labels(
info: &AssetInfo,
loader_dependents: &mut HashMap<AssetPath<'static>, HashSet<AssetPath<'static>>>,
path: &AssetPath<'static>,
living_labeled_assets: &mut HashMap<AssetPath<'static>, HashSet<Box<str>>>,
) {
for loader_dependency in info.loader_dependencies.keys() {
if let Some(dependents) = loader_dependents.get_mut(loader_dependency) {
dependents.remove(path);
}
}
let Some(label) = path.label() else {
return;
};
let mut without_label = path.to_owned();
without_label.remove_label();
let Entry::Occupied(mut entry) = living_labeled_assets.entry(without_label) else {
return;
};
entry.get_mut().remove(label);
if entry.get().is_empty() {
entry.remove();
}
}
fn process_handle_drop_internal(
infos: &mut HashMap<UntypedAssetId, AssetInfo>,
path_to_id: &mut HashMap<AssetPath<'static>, TypeIdMap<UntypedAssetId>>,
loader_dependents: &mut HashMap<AssetPath<'static>, HashSet<AssetPath<'static>>>,
living_labeled_assets: &mut HashMap<AssetPath<'static>, HashSet<Box<str>>>,
pending_tasks: &mut HashMap<UntypedAssetId, Task<()>>,
watching_for_changes: bool,
id: UntypedAssetId,
) -> bool {
let Entry::Occupied(mut entry) = infos.entry(id) else {
// Either the asset was already dropped, it doesn't exist, or it isn't managed by the asset server
// None of these cases should result in a removal from the Assets collection
return false;
};
if entry.get_mut().handle_drops_to_skip > 0 {
entry.get_mut().handle_drops_to_skip -= 1;
return false;
}
pending_tasks.remove(&id);
let type_id = entry.key().type_id();
let info = entry.remove();
let Some(path) = &info.path else {
return true;
};
if watching_for_changes {
Self::remove_dependents_and_labels(
&info,
loader_dependents,
path,
living_labeled_assets,
);
}
if let Some(map) = path_to_id.get_mut(path) {
map.remove(&type_id);
if map.is_empty() {
path_to_id.remove(path);
}
};
true
}
/// Consumes all current handle drop events. This will update information in [`AssetInfos`], but it
/// will not affect [`Assets`] storages. For normal use cases, prefer `Assets::track_assets()`
/// This should only be called if `Assets` storage isn't being used (such as in [`AssetProcessor`](crate::processor::AssetProcessor))
///
/// [`Assets`]: crate::Assets
pub(crate) fn consume_handle_drop_events(&mut self) {
for provider in self.handle_providers.values() {
while let Ok(drop_event) = provider.drop_receiver.try_recv() {
let id = drop_event.id;
if drop_event.asset_server_managed {
Self::process_handle_drop_internal(
&mut self.infos,
&mut self.path_to_id,
&mut self.loader_dependents,
&mut self.living_labeled_assets,
&mut self.pending_tasks,
self.watching_for_changes,
id.untyped(provider.type_id),
);
}
}
}
}
}
/// Determines how a handle should be initialized
#[derive(Copy, Clone, PartialEq, Eq)]
pub(crate) enum HandleLoadingMode {
/// The handle is for an asset that isn't loading/loaded yet.
NotLoading,
/// The handle is for an asset that is being _requested_ to load (if it isn't already loading)
Request,
/// The handle is for an asset that is being forced to load (even if it has already loaded)
Force,
}
#[derive(Error, Debug)]
#[error("Cannot allocate a handle because no handle provider exists for asset type {0:?}")]
pub struct MissingHandleProviderError(TypeId);
/// An error encountered during [`AssetInfos::get_or_create_path_handle_internal`].
#[derive(Error, Debug)]
pub(crate) enum GetOrCreateHandleInternalError {
#[error(transparent)]
MissingHandleProviderError(#[from] MissingHandleProviderError),
#[error("Handle does not exist but TypeId was not specified.")]
HandleMissingButTypeIdNotSpecified,
}
pub(crate) fn unwrap_with_context<T>(
result: Result<T, GetOrCreateHandleInternalError>,
type_info: Either<&str, TypeId>,
) -> Option<T> {
match result {
Ok(value) => Some(value),
Err(GetOrCreateHandleInternalError::HandleMissingButTypeIdNotSpecified) => None,
Err(GetOrCreateHandleInternalError::MissingHandleProviderError(_)) => match type_info {
Either::Left(type_name) => {
panic!("Cannot allocate an Asset Handle of type '{type_name}' because the asset type has not been initialized. \
Make sure you have called `app.init_asset::<{type_name}>()`");
}
Either::Right(type_id) => {
panic!("Cannot allocate an AssetHandle of type '{type_id:?}' because the asset type has not been initialized. \
Make sure you have called `app.init_asset::<(actual asset type)>()`")
}
},
}
}

860
vendor/bevy_asset/src/server/loaders.rs vendored Normal file
View File

@@ -0,0 +1,860 @@
use crate::{
loader::{AssetLoader, ErasedAssetLoader},
path::AssetPath,
};
use alloc::{boxed::Box, sync::Arc, vec::Vec};
use async_broadcast::RecvError;
use bevy_platform::collections::HashMap;
use bevy_tasks::IoTaskPool;
use bevy_utils::TypeIdMap;
use core::any::TypeId;
use thiserror::Error;
use tracing::warn;
#[cfg(feature = "trace")]
use {
alloc::string::ToString,
bevy_tasks::ConditionalSendFuture,
tracing::{info_span, instrument::Instrument},
};
#[derive(Default)]
pub(crate) struct AssetLoaders {
loaders: Vec<MaybeAssetLoader>,
type_id_to_loaders: TypeIdMap<Vec<usize>>,
extension_to_loaders: HashMap<Box<str>, Vec<usize>>,
type_name_to_loader: HashMap<&'static str, usize>,
preregistered_loaders: HashMap<&'static str, usize>,
}
impl AssetLoaders {
/// Get the [`AssetLoader`] stored at the specific index
fn get_by_index(&self, index: usize) -> Option<MaybeAssetLoader> {
self.loaders.get(index).cloned()
}
/// Registers a new [`AssetLoader`]. [`AssetLoader`]s must be registered before they can be used.
pub(crate) fn push<L: AssetLoader>(&mut self, loader: L) {
let type_name = core::any::type_name::<L>();
let loader_asset_type = TypeId::of::<L::Asset>();
let loader_asset_type_name = core::any::type_name::<L::Asset>();
#[cfg(feature = "trace")]
let loader = InstrumentedAssetLoader(loader);
let loader = Arc::new(loader);
let (loader_index, is_new) =
if let Some(index) = self.preregistered_loaders.remove(type_name) {
(index, false)
} else {
(self.loaders.len(), true)
};
if is_new {
let existing_loaders_for_type_id = self.type_id_to_loaders.get(&loader_asset_type);
let mut duplicate_extensions = Vec::new();
for extension in AssetLoader::extensions(&*loader) {
let list = self
.extension_to_loaders
.entry((*extension).into())
.or_default();
if !list.is_empty() {
if let Some(existing_loaders_for_type_id) = existing_loaders_for_type_id {
if list
.iter()
.any(|index| existing_loaders_for_type_id.contains(index))
{
duplicate_extensions.push(extension);
}
}
}
list.push(loader_index);
}
if !duplicate_extensions.is_empty() {
warn!("Duplicate AssetLoader registered for Asset type `{loader_asset_type_name}` with extensions `{duplicate_extensions:?}`. \
Loader must be specified in a .meta file in order to load assets of this type with these extensions.");
}
self.type_name_to_loader.insert(type_name, loader_index);
self.type_id_to_loaders
.entry(loader_asset_type)
.or_default()
.push(loader_index);
self.loaders.push(MaybeAssetLoader::Ready(loader));
} else {
let maybe_loader = core::mem::replace(
self.loaders.get_mut(loader_index).unwrap(),
MaybeAssetLoader::Ready(loader.clone()),
);
match maybe_loader {
MaybeAssetLoader::Ready(_) => unreachable!(),
MaybeAssetLoader::Pending { sender, .. } => {
IoTaskPool::get()
.spawn(async move {
let _ = sender.broadcast(loader).await;
})
.detach();
}
}
}
}
/// Pre-register an [`AssetLoader`] that will later be added.
///
/// Assets loaded with matching extensions will be blocked until the
/// real loader is added.
pub(crate) fn reserve<L: AssetLoader>(&mut self, extensions: &[&str]) {
let loader_asset_type = TypeId::of::<L::Asset>();
let loader_asset_type_name = core::any::type_name::<L::Asset>();
let type_name = core::any::type_name::<L>();
let loader_index = self.loaders.len();
self.preregistered_loaders.insert(type_name, loader_index);
self.type_name_to_loader.insert(type_name, loader_index);
let existing_loaders_for_type_id = self.type_id_to_loaders.get(&loader_asset_type);
let mut duplicate_extensions = Vec::new();
for extension in extensions {
let list = self
.extension_to_loaders
.entry((*extension).into())
.or_default();
if !list.is_empty() {
if let Some(existing_loaders_for_type_id) = existing_loaders_for_type_id {
if list
.iter()
.any(|index| existing_loaders_for_type_id.contains(index))
{
duplicate_extensions.push(extension);
}
}
}
list.push(loader_index);
}
if !duplicate_extensions.is_empty() {
warn!("Duplicate AssetLoader preregistered for Asset type `{loader_asset_type_name}` with extensions `{duplicate_extensions:?}`. \
Loader must be specified in a .meta file in order to load assets of this type with these extensions.");
}
self.type_id_to_loaders
.entry(loader_asset_type)
.or_default()
.push(loader_index);
let (mut sender, receiver) = async_broadcast::broadcast(1);
sender.set_overflow(true);
self.loaders
.push(MaybeAssetLoader::Pending { sender, receiver });
}
/// Get the [`AssetLoader`] by name
pub(crate) fn get_by_name(&self, name: &str) -> Option<MaybeAssetLoader> {
let index = self.type_name_to_loader.get(name).copied()?;
self.get_by_index(index)
}
/// Find an [`AssetLoader`] based on provided search criteria
pub(crate) fn find(
&self,
type_name: Option<&str>,
asset_type_id: Option<TypeId>,
extension: Option<&str>,
asset_path: Option<&AssetPath<'_>>,
) -> Option<MaybeAssetLoader> {
// If provided the type name of the loader, return that immediately
if let Some(type_name) = type_name {
return self.get_by_name(type_name);
}
// The presence of a label will affect loader choice
let label = asset_path.as_ref().and_then(|path| path.label());
// Try by asset type
let candidates = if let Some(type_id) = asset_type_id {
if label.is_none() {
Some(self.type_id_to_loaders.get(&type_id)?)
} else {
None
}
} else {
None
};
if let Some(candidates) = candidates {
if candidates.is_empty() {
return None;
} else if candidates.len() == 1 {
let index = candidates.first().copied().unwrap();
return self.get_by_index(index);
}
}
// Asset type is insufficient, use extension information
let try_extension = |extension| {
if let Some(indices) = self.extension_to_loaders.get(extension) {
if let Some(candidates) = candidates {
if candidates.is_empty() {
indices.last()
} else {
indices
.iter()
.rev()
.find(|index| candidates.contains(index))
}
} else {
indices.last()
}
} else {
None
}
};
// Try the provided extension
if let Some(extension) = extension {
if let Some(&index) = try_extension(extension) {
return self.get_by_index(index);
}
}
// Try extracting the extension from the path
if let Some(full_extension) = asset_path.and_then(AssetPath::get_full_extension) {
if let Some(&index) = try_extension(full_extension.as_str()) {
return self.get_by_index(index);
}
// Try secondary extensions from the path
for extension in AssetPath::iter_secondary_extensions(&full_extension) {
if let Some(&index) = try_extension(extension) {
return self.get_by_index(index);
}
}
}
// Fallback if no resolution step was conclusive
match candidates?
.last()
.copied()
.and_then(|index| self.get_by_index(index))
{
Some(loader) => {
warn!(
"Multiple AssetLoaders found for Asset: {:?}; Path: {:?}; Extension: {:?}",
asset_type_id, asset_path, extension
);
Some(loader)
}
None => {
warn!(
"No AssetLoader found for Asset: {:?}; Path: {:?}; Extension: {:?}",
asset_type_id, asset_path, extension
);
None
}
}
}
/// Get the [`AssetLoader`] for a given asset type
pub(crate) fn get_by_type(&self, type_id: TypeId) -> Option<MaybeAssetLoader> {
let index = self.type_id_to_loaders.get(&type_id)?.last().copied()?;
self.get_by_index(index)
}
/// Get the [`AssetLoader`] for a given extension
pub(crate) fn get_by_extension(&self, extension: &str) -> Option<MaybeAssetLoader> {
let index = self.extension_to_loaders.get(extension)?.last().copied()?;
self.get_by_index(index)
}
/// Get the [`AssetLoader`] for a given path
pub(crate) fn get_by_path(&self, path: &AssetPath<'_>) -> Option<MaybeAssetLoader> {
let extension = path.get_full_extension()?;
let result = core::iter::once(extension.as_str())
.chain(AssetPath::iter_secondary_extensions(&extension))
.filter_map(|extension| self.extension_to_loaders.get(extension)?.last().copied())
.find_map(|index| self.get_by_index(index))?;
Some(result)
}
}
#[derive(Error, Debug, Clone)]
pub(crate) enum GetLoaderError {
#[error(transparent)]
CouldNotResolve(#[from] RecvError),
}
#[derive(Clone)]
pub(crate) enum MaybeAssetLoader {
Ready(Arc<dyn ErasedAssetLoader>),
Pending {
sender: async_broadcast::Sender<Arc<dyn ErasedAssetLoader>>,
receiver: async_broadcast::Receiver<Arc<dyn ErasedAssetLoader>>,
},
}
impl MaybeAssetLoader {
pub(crate) async fn get(self) -> Result<Arc<dyn ErasedAssetLoader>, GetLoaderError> {
match self {
MaybeAssetLoader::Ready(loader) => Ok(loader),
MaybeAssetLoader::Pending { mut receiver, .. } => Ok(receiver.recv().await?),
}
}
}
#[cfg(feature = "trace")]
struct InstrumentedAssetLoader<T>(T);
#[cfg(feature = "trace")]
impl<T: AssetLoader> AssetLoader for InstrumentedAssetLoader<T> {
type Asset = T::Asset;
type Settings = T::Settings;
type Error = T::Error;
fn load(
&self,
reader: &mut dyn crate::io::Reader,
settings: &Self::Settings,
load_context: &mut crate::LoadContext,
) -> impl ConditionalSendFuture<Output = Result<Self::Asset, Self::Error>> {
let span = info_span!(
"asset loading",
loader = core::any::type_name::<T>(),
asset = load_context.asset_path().to_string(),
);
self.0.load(reader, settings, load_context).instrument(span)
}
fn extensions(&self) -> &[&str] {
self.0.extensions()
}
}
#[cfg(test)]
mod tests {
use alloc::{format, string::String};
use core::marker::PhantomData;
use std::{
path::Path,
sync::mpsc::{channel, Receiver, Sender},
};
use bevy_reflect::TypePath;
use bevy_tasks::block_on;
use crate::Asset;
use super::*;
#[derive(Asset, TypePath, Debug)]
struct A;
#[derive(Asset, TypePath, Debug)]
struct B;
#[derive(Asset, TypePath, Debug)]
struct C;
struct Loader<A: Asset, const N: usize, const E: usize> {
sender: Sender<()>,
_phantom: PhantomData<A>,
}
impl<T: Asset, const N: usize, const E: usize> Loader<T, N, E> {
fn new() -> (Self, Receiver<()>) {
let (tx, rx) = channel();
let loader = Self {
sender: tx,
_phantom: PhantomData,
};
(loader, rx)
}
}
impl<T: Asset, const N: usize, const E: usize> AssetLoader for Loader<T, N, E> {
type Asset = T;
type Settings = ();
type Error = String;
async fn load(
&self,
_: &mut dyn crate::io::Reader,
_: &Self::Settings,
_: &mut crate::LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
self.sender.send(()).unwrap();
Err(format!(
"Loaded {}:{}",
core::any::type_name::<Self::Asset>(),
N
))
}
fn extensions(&self) -> &[&str] {
self.sender.send(()).unwrap();
match E {
1 => &["a"],
2 => &["b"],
3 => &["c"],
4 => &["d"],
_ => &[],
}
}
}
/// Basic framework for creating, storing, loading, and checking an [`AssetLoader`] inside an [`AssetLoaders`]
#[test]
fn basic() {
let mut loaders = AssetLoaders::default();
let (loader, rx) = Loader::<A, 1, 0>::new();
assert!(rx.try_recv().is_err());
loaders.push(loader);
assert!(rx.try_recv().is_ok());
assert!(rx.try_recv().is_err());
let loader = block_on(
loaders
.get_by_name(core::any::type_name::<Loader<A, 1, 0>>())
.unwrap()
.get(),
)
.unwrap();
loader.extensions();
assert!(rx.try_recv().is_ok());
assert!(rx.try_recv().is_err());
}
/// Ensure that if multiple loaders have different types but no extensions, they can be found
#[test]
fn type_resolution() {
let mut loaders = AssetLoaders::default();
let (loader_a1, rx_a1) = Loader::<A, 1, 0>::new();
let (loader_b1, rx_b1) = Loader::<B, 1, 0>::new();
let (loader_c1, rx_c1) = Loader::<C, 1, 0>::new();
loaders.push(loader_a1);
loaders.push(loader_b1);
loaders.push(loader_c1);
assert!(rx_a1.try_recv().is_ok());
assert!(rx_b1.try_recv().is_ok());
assert!(rx_c1.try_recv().is_ok());
let loader = block_on(loaders.get_by_type(TypeId::of::<A>()).unwrap().get()).unwrap();
loader.extensions();
assert!(rx_a1.try_recv().is_ok());
assert!(rx_b1.try_recv().is_err());
assert!(rx_c1.try_recv().is_err());
let loader = block_on(loaders.get_by_type(TypeId::of::<B>()).unwrap().get()).unwrap();
loader.extensions();
assert!(rx_a1.try_recv().is_err());
assert!(rx_b1.try_recv().is_ok());
assert!(rx_c1.try_recv().is_err());
let loader = block_on(loaders.get_by_type(TypeId::of::<C>()).unwrap().get()).unwrap();
loader.extensions();
assert!(rx_a1.try_recv().is_err());
assert!(rx_b1.try_recv().is_err());
assert!(rx_c1.try_recv().is_ok());
}
/// Ensure that the last loader added is selected
#[test]
fn type_resolution_shadow() {
let mut loaders = AssetLoaders::default();
let (loader_a1, rx_a1) = Loader::<A, 1, 0>::new();
let (loader_a2, rx_a2) = Loader::<A, 2, 0>::new();
let (loader_a3, rx_a3) = Loader::<A, 3, 0>::new();
loaders.push(loader_a1);
loaders.push(loader_a2);
loaders.push(loader_a3);
assert!(rx_a1.try_recv().is_ok());
assert!(rx_a2.try_recv().is_ok());
assert!(rx_a3.try_recv().is_ok());
let loader = block_on(loaders.get_by_type(TypeId::of::<A>()).unwrap().get()).unwrap();
loader.extensions();
assert!(rx_a1.try_recv().is_err());
assert!(rx_a2.try_recv().is_err());
assert!(rx_a3.try_recv().is_ok());
}
/// Ensure that if multiple loaders have like types but differing extensions, they can be found
#[test]
fn extension_resolution() {
let mut loaders = AssetLoaders::default();
let (loader_a1, rx_a1) = Loader::<A, 1, 1>::new();
let (loader_b1, rx_b1) = Loader::<A, 1, 2>::new();
let (loader_c1, rx_c1) = Loader::<A, 1, 3>::new();
loaders.push(loader_a1);
loaders.push(loader_b1);
loaders.push(loader_c1);
assert!(rx_a1.try_recv().is_ok());
assert!(rx_b1.try_recv().is_ok());
assert!(rx_c1.try_recv().is_ok());
let loader = block_on(loaders.get_by_extension("a").unwrap().get()).unwrap();
loader.extensions();
assert!(rx_a1.try_recv().is_ok());
assert!(rx_b1.try_recv().is_err());
assert!(rx_c1.try_recv().is_err());
let loader = block_on(loaders.get_by_extension("b").unwrap().get()).unwrap();
loader.extensions();
assert!(rx_a1.try_recv().is_err());
assert!(rx_b1.try_recv().is_ok());
assert!(rx_c1.try_recv().is_err());
let loader = block_on(loaders.get_by_extension("c").unwrap().get()).unwrap();
loader.extensions();
assert!(rx_a1.try_recv().is_err());
assert!(rx_b1.try_recv().is_err());
assert!(rx_c1.try_recv().is_ok());
}
/// Ensure that if multiple loaders have like types but differing extensions, they can be found
#[test]
fn path_resolution() {
let mut loaders = AssetLoaders::default();
let (loader_a1, rx_a1) = Loader::<A, 1, 1>::new();
let (loader_b1, rx_b1) = Loader::<A, 1, 2>::new();
let (loader_c1, rx_c1) = Loader::<A, 1, 3>::new();
loaders.push(loader_a1);
loaders.push(loader_b1);
loaders.push(loader_c1);
assert!(rx_a1.try_recv().is_ok());
assert!(rx_b1.try_recv().is_ok());
assert!(rx_c1.try_recv().is_ok());
let path = AssetPath::from_path(Path::new("asset.a"));
let loader = block_on(loaders.get_by_path(&path).unwrap().get()).unwrap();
loader.extensions();
assert!(rx_a1.try_recv().is_ok());
assert!(rx_b1.try_recv().is_err());
assert!(rx_c1.try_recv().is_err());
let path = AssetPath::from_path(Path::new("asset.b"));
let loader = block_on(loaders.get_by_path(&path).unwrap().get()).unwrap();
loader.extensions();
assert!(rx_a1.try_recv().is_err());
assert!(rx_b1.try_recv().is_ok());
assert!(rx_c1.try_recv().is_err());
let path = AssetPath::from_path(Path::new("asset.c"));
let loader = block_on(loaders.get_by_path(&path).unwrap().get()).unwrap();
loader.extensions();
assert!(rx_a1.try_recv().is_err());
assert!(rx_b1.try_recv().is_err());
assert!(rx_c1.try_recv().is_ok());
}
/// Full resolution algorithm
#[test]
fn total_resolution() {
let mut loaders = AssetLoaders::default();
let (loader_a1_a, rx_a1_a) = Loader::<A, 1, 1>::new();
let (loader_b1_b, rx_b1_b) = Loader::<B, 1, 2>::new();
let (loader_c1_a, rx_c1_a) = Loader::<C, 1, 1>::new();
let (loader_c1_b, rx_c1_b) = Loader::<C, 1, 2>::new();
let (loader_c1_c, rx_c1_c) = Loader::<C, 1, 3>::new();
loaders.push(loader_a1_a);
loaders.push(loader_b1_b);
loaders.push(loader_c1_a);
loaders.push(loader_c1_b);
loaders.push(loader_c1_c);
assert!(rx_a1_a.try_recv().is_ok());
assert!(rx_b1_b.try_recv().is_ok());
assert!(rx_c1_a.try_recv().is_ok());
assert!(rx_c1_b.try_recv().is_ok());
assert!(rx_c1_c.try_recv().is_ok());
// Type and Extension agree
let loader = block_on(
loaders
.find(
None,
Some(TypeId::of::<A>()),
None,
Some(&AssetPath::from_path(Path::new("asset.a"))),
)
.unwrap()
.get(),
)
.unwrap();
loader.extensions();
assert!(rx_a1_a.try_recv().is_ok());
assert!(rx_b1_b.try_recv().is_err());
assert!(rx_c1_a.try_recv().is_err());
assert!(rx_c1_b.try_recv().is_err());
assert!(rx_c1_c.try_recv().is_err());
let loader = block_on(
loaders
.find(
None,
Some(TypeId::of::<B>()),
None,
Some(&AssetPath::from_path(Path::new("asset.b"))),
)
.unwrap()
.get(),
)
.unwrap();
loader.extensions();
assert!(rx_a1_a.try_recv().is_err());
assert!(rx_b1_b.try_recv().is_ok());
assert!(rx_c1_a.try_recv().is_err());
assert!(rx_c1_b.try_recv().is_err());
assert!(rx_c1_c.try_recv().is_err());
let loader = block_on(
loaders
.find(
None,
Some(TypeId::of::<C>()),
None,
Some(&AssetPath::from_path(Path::new("asset.c"))),
)
.unwrap()
.get(),
)
.unwrap();
loader.extensions();
assert!(rx_a1_a.try_recv().is_err());
assert!(rx_b1_b.try_recv().is_err());
assert!(rx_c1_a.try_recv().is_err());
assert!(rx_c1_b.try_recv().is_err());
assert!(rx_c1_c.try_recv().is_ok());
// Type should override Extension
let loader = block_on(
loaders
.find(
None,
Some(TypeId::of::<C>()),
None,
Some(&AssetPath::from_path(Path::new("asset.a"))),
)
.unwrap()
.get(),
)
.unwrap();
loader.extensions();
assert!(rx_a1_a.try_recv().is_err());
assert!(rx_b1_b.try_recv().is_err());
assert!(rx_c1_a.try_recv().is_ok());
assert!(rx_c1_b.try_recv().is_err());
assert!(rx_c1_c.try_recv().is_err());
let loader = block_on(
loaders
.find(
None,
Some(TypeId::of::<C>()),
None,
Some(&AssetPath::from_path(Path::new("asset.b"))),
)
.unwrap()
.get(),
)
.unwrap();
loader.extensions();
assert!(rx_a1_a.try_recv().is_err());
assert!(rx_b1_b.try_recv().is_err());
assert!(rx_c1_a.try_recv().is_err());
assert!(rx_c1_b.try_recv().is_ok());
assert!(rx_c1_c.try_recv().is_err());
// Type should override bad / missing extension
let loader = block_on(
loaders
.find(
None,
Some(TypeId::of::<A>()),
None,
Some(&AssetPath::from_path(Path::new("asset.x"))),
)
.unwrap()
.get(),
)
.unwrap();
loader.extensions();
assert!(rx_a1_a.try_recv().is_ok());
assert!(rx_b1_b.try_recv().is_err());
assert!(rx_c1_a.try_recv().is_err());
assert!(rx_c1_b.try_recv().is_err());
assert!(rx_c1_c.try_recv().is_err());
let loader = block_on(
loaders
.find(
None,
Some(TypeId::of::<A>()),
None,
Some(&AssetPath::from_path(Path::new("asset"))),
)
.unwrap()
.get(),
)
.unwrap();
loader.extensions();
assert!(rx_a1_a.try_recv().is_ok());
assert!(rx_b1_b.try_recv().is_err());
assert!(rx_c1_a.try_recv().is_err());
assert!(rx_c1_b.try_recv().is_err());
assert!(rx_c1_c.try_recv().is_err());
}
/// Ensure that if there is a complete ambiguity in [`AssetLoader`] to use, prefer most recently registered by asset type.
#[test]
fn ambiguity_resolution() {
let mut loaders = AssetLoaders::default();
let (loader_a1_a, rx_a1_a) = Loader::<A, 1, 1>::new();
let (loader_a2_a, rx_a2_a) = Loader::<A, 2, 1>::new();
let (loader_a3_a, rx_a3_a) = Loader::<A, 3, 1>::new();
loaders.push(loader_a1_a);
loaders.push(loader_a2_a);
loaders.push(loader_a3_a);
assert!(rx_a1_a.try_recv().is_ok());
assert!(rx_a2_a.try_recv().is_ok());
assert!(rx_a3_a.try_recv().is_ok());
let loader = block_on(
loaders
.find(
None,
Some(TypeId::of::<A>()),
None,
Some(&AssetPath::from_path(Path::new("asset.a"))),
)
.unwrap()
.get(),
)
.unwrap();
loader.extensions();
assert!(rx_a1_a.try_recv().is_err());
assert!(rx_a2_a.try_recv().is_err());
assert!(rx_a3_a.try_recv().is_ok());
let loader = block_on(
loaders
.find(
None,
Some(TypeId::of::<A>()),
None,
Some(&AssetPath::from_path(Path::new("asset.x"))),
)
.unwrap()
.get(),
)
.unwrap();
loader.extensions();
assert!(rx_a1_a.try_recv().is_err());
assert!(rx_a2_a.try_recv().is_err());
assert!(rx_a3_a.try_recv().is_ok());
let loader = block_on(
loaders
.find(
None,
Some(TypeId::of::<A>()),
None,
Some(&AssetPath::from_path(Path::new("asset"))),
)
.unwrap()
.get(),
)
.unwrap();
loader.extensions();
assert!(rx_a1_a.try_recv().is_err());
assert!(rx_a2_a.try_recv().is_err());
assert!(rx_a3_a.try_recv().is_ok());
}
}

2042
vendor/bevy_asset/src/server/mod.rs vendored Normal file

File diff suppressed because it is too large Load Diff

284
vendor/bevy_asset/src/transformer.rs vendored Normal file
View File

@@ -0,0 +1,284 @@
use crate::{meta::Settings, Asset, ErasedLoadedAsset, Handle, LabeledAsset, UntypedHandle};
use alloc::boxed::Box;
use atomicow::CowArc;
use bevy_platform::collections::HashMap;
use bevy_tasks::ConditionalSendFuture;
use core::{
borrow::Borrow,
convert::Infallible,
hash::Hash,
marker::PhantomData,
ops::{Deref, DerefMut},
};
use serde::{Deserialize, Serialize};
/// Transforms an [`Asset`] of a given [`AssetTransformer::AssetInput`] type to an [`Asset`] of [`AssetTransformer::AssetOutput`] type.
///
/// This trait is commonly used in association with [`LoadTransformAndSave`](crate::processor::LoadTransformAndSave) to accomplish common asset pipeline workflows.
pub trait AssetTransformer: Send + Sync + 'static {
/// The [`Asset`] type which this [`AssetTransformer`] takes as and input.
type AssetInput: Asset;
/// The [`Asset`] type which this [`AssetTransformer`] outputs.
type AssetOutput: Asset;
/// The settings type used by this [`AssetTransformer`].
type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
/// The type of [error](`std::error::Error`) which could be encountered by this transformer.
type Error: Into<Box<dyn core::error::Error + Send + Sync + 'static>>;
/// Transforms the given [`TransformedAsset`] to [`AssetTransformer::AssetOutput`].
/// The [`TransformedAsset`]'s `labeled_assets` can be altered to add new Labeled Sub-Assets
/// The passed in `settings` can influence how the `asset` is transformed
fn transform<'a>(
&'a self,
asset: TransformedAsset<Self::AssetInput>,
settings: &'a Self::Settings,
) -> impl ConditionalSendFuture<Output = Result<TransformedAsset<Self::AssetOutput>, Self::Error>>;
}
/// An [`Asset`] (and any "sub assets") intended to be transformed
pub struct TransformedAsset<A: Asset> {
pub(crate) value: A,
pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
}
impl<A: Asset> Deref for TransformedAsset<A> {
type Target = A;
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl<A: Asset> DerefMut for TransformedAsset<A> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.value
}
}
impl<A: Asset> TransformedAsset<A> {
/// Creates a new [`TransformedAsset`] from `asset` if its internal value matches `A`.
pub fn from_loaded(asset: ErasedLoadedAsset) -> Option<Self> {
if let Ok(value) = asset.value.downcast::<A>() {
return Some(TransformedAsset {
value: *value,
labeled_assets: asset.labeled_assets,
});
}
None
}
/// Creates a new [`TransformedAsset`] from `asset`, transferring the `labeled_assets` from this [`TransformedAsset`] to the new one
pub fn replace_asset<B: Asset>(self, asset: B) -> TransformedAsset<B> {
TransformedAsset {
value: asset,
labeled_assets: self.labeled_assets,
}
}
/// Takes the labeled assets from `labeled_source` and places them in this [`TransformedAsset`]
pub fn take_labeled_assets<B: Asset>(&mut self, labeled_source: TransformedAsset<B>) {
self.labeled_assets = labeled_source.labeled_assets;
}
/// Retrieves the value of this asset.
#[inline]
pub fn get(&self) -> &A {
&self.value
}
/// Mutably retrieves the value of this asset.
#[inline]
pub fn get_mut(&mut self) -> &mut A {
&mut self.value
}
/// Returns the labeled asset, if it exists and matches this type.
pub fn get_labeled<B: Asset, Q>(&mut self, label: &Q) -> Option<TransformedSubAsset<B>>
where
CowArc<'static, str>: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
let labeled = self.labeled_assets.get_mut(label)?;
let value = labeled.asset.value.downcast_mut::<B>()?;
Some(TransformedSubAsset {
value,
labeled_assets: &mut labeled.asset.labeled_assets,
})
}
/// Returns the type-erased labeled asset, if it exists and matches this type.
pub fn get_erased_labeled<Q>(&self, label: &Q) -> Option<&ErasedLoadedAsset>
where
CowArc<'static, str>: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
let labeled = self.labeled_assets.get(label)?;
Some(&labeled.asset)
}
/// Returns the [`UntypedHandle`] of the labeled asset with the provided 'label', if it exists.
pub fn get_untyped_handle<Q>(&self, label: &Q) -> Option<UntypedHandle>
where
CowArc<'static, str>: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
let labeled = self.labeled_assets.get(label)?;
Some(labeled.handle.clone())
}
/// Returns the [`Handle`] of the labeled asset with the provided 'label', if it exists and is an asset of type `B`
pub fn get_handle<Q, B: Asset>(&self, label: &Q) -> Option<Handle<B>>
where
CowArc<'static, str>: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
let labeled = self.labeled_assets.get(label)?;
if let Ok(handle) = labeled.handle.clone().try_typed::<B>() {
return Some(handle);
}
None
}
/// Adds `asset` as a labeled sub asset using `label` and `handle`
pub fn insert_labeled(
&mut self,
label: impl Into<CowArc<'static, str>>,
handle: impl Into<UntypedHandle>,
asset: impl Into<ErasedLoadedAsset>,
) {
let labeled = LabeledAsset {
asset: asset.into(),
handle: handle.into(),
};
self.labeled_assets.insert(label.into(), labeled);
}
/// Iterate over all labels for "labeled assets" in the loaded asset
pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
self.labeled_assets.keys().map(|s| &**s)
}
}
/// A labeled sub-asset of [`TransformedAsset`]
pub struct TransformedSubAsset<'a, A: Asset> {
value: &'a mut A,
labeled_assets: &'a mut HashMap<CowArc<'static, str>, LabeledAsset>,
}
impl<'a, A: Asset> Deref for TransformedSubAsset<'a, A> {
type Target = A;
fn deref(&self) -> &Self::Target {
self.value
}
}
impl<'a, A: Asset> DerefMut for TransformedSubAsset<'a, A> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.value
}
}
impl<'a, A: Asset> TransformedSubAsset<'a, A> {
/// Creates a new [`TransformedSubAsset`] from `asset` if its internal value matches `A`.
pub fn from_loaded(asset: &'a mut ErasedLoadedAsset) -> Option<Self> {
let value = asset.value.downcast_mut::<A>()?;
Some(TransformedSubAsset {
value,
labeled_assets: &mut asset.labeled_assets,
})
}
/// Retrieves the value of this asset.
#[inline]
pub fn get(&self) -> &A {
self.value
}
/// Mutably retrieves the value of this asset.
#[inline]
pub fn get_mut(&mut self) -> &mut A {
self.value
}
/// Returns the labeled asset, if it exists and matches this type.
pub fn get_labeled<B: Asset, Q>(&mut self, label: &Q) -> Option<TransformedSubAsset<B>>
where
CowArc<'static, str>: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
let labeled = self.labeled_assets.get_mut(label)?;
let value = labeled.asset.value.downcast_mut::<B>()?;
Some(TransformedSubAsset {
value,
labeled_assets: &mut labeled.asset.labeled_assets,
})
}
/// Returns the type-erased labeled asset, if it exists and matches this type.
pub fn get_erased_labeled<Q>(&self, label: &Q) -> Option<&ErasedLoadedAsset>
where
CowArc<'static, str>: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
let labeled = self.labeled_assets.get(label)?;
Some(&labeled.asset)
}
/// Returns the [`UntypedHandle`] of the labeled asset with the provided 'label', if it exists.
pub fn get_untyped_handle<Q>(&self, label: &Q) -> Option<UntypedHandle>
where
CowArc<'static, str>: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
let labeled = self.labeled_assets.get(label)?;
Some(labeled.handle.clone())
}
/// Returns the [`Handle`] of the labeled asset with the provided 'label', if it exists and is an asset of type `B`
pub fn get_handle<Q, B: Asset>(&self, label: &Q) -> Option<Handle<B>>
where
CowArc<'static, str>: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
let labeled = self.labeled_assets.get(label)?;
if let Ok(handle) = labeled.handle.clone().try_typed::<B>() {
return Some(handle);
}
None
}
/// Adds `asset` as a labeled sub asset using `label` and `handle`
pub fn insert_labeled(
&mut self,
label: impl Into<CowArc<'static, str>>,
handle: impl Into<UntypedHandle>,
asset: impl Into<ErasedLoadedAsset>,
) {
let labeled = LabeledAsset {
asset: asset.into(),
handle: handle.into(),
};
self.labeled_assets.insert(label.into(), labeled);
}
/// Iterate over all labels for "labeled assets" in the loaded asset
pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
self.labeled_assets.keys().map(|s| &**s)
}
}
/// An identity [`AssetTransformer`] which infallibly returns the input [`Asset`] on transformation.]
pub struct IdentityAssetTransformer<A: Asset> {
_phantom: PhantomData<fn(A) -> A>,
}
impl<A: Asset> IdentityAssetTransformer<A> {
/// Creates a new [`IdentityAssetTransformer`] with the correct internal [`PhantomData`] field.
pub const fn new() -> Self {
Self {
_phantom: PhantomData,
}
}
}
impl<A: Asset> Default for IdentityAssetTransformer<A> {
fn default() -> Self {
Self::new()
}
}
impl<A: Asset> AssetTransformer for IdentityAssetTransformer<A> {
type AssetInput = A;
type AssetOutput = A;
type Settings = ();
type Error = Infallible;
async fn transform<'a>(
&'a self,
asset: TransformedAsset<Self::AssetInput>,
_settings: &'a Self::Settings,
) -> Result<TransformedAsset<Self::AssetOutput>, Self::Error> {
Ok(asset)
}
}