mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
Copy on Write AssetPaths (#9729)
# Objective The `AssetServer` and `AssetProcessor` do a lot of `AssetPath` cloning (across many threads). To store the path on the handle, to store paths in dependency lists, to pass an owned path to the offloaded thread, to pass a path to the LoadContext, etc , etc. Cloning multiple string allocations multiple times like this will add up. It is worth optimizing this. Referenced in #9714 ## Solution Added a new `CowArc<T>` type to `bevy_util`, which behaves a lot like `Cow<T>`, but the Owned variant is an `Arc<T>`. Use this in place of `Cow<str>` and `Cow<Path>` on `AssetPath`. --- ## Changelog - `AssetPath` now internally uses `CowArc`, making clone operations much cheaper - `AssetPath` now serializes as `AssetPath("some_path.extension#Label")` instead of as `AssetPath { path: "some_path.extension", label: Some("Label) }` ## Migration Guide ```rust // Old AssetPath::new("logo.png", None); // New AssetPath::new("logo.png"); // Old AssetPath::new("scene.gltf", Some("Mesh0"); // New AssetPath::new("scene.gltf").with_label("Mesh0"); ``` `AssetPath` now serializes as `AssetPath("some_path.extension#Label")` instead of as `AssetPath { path: "some_path.extension", label: Some("Label) }` --------- Co-authored-by: Pascal Hertleif <killercup@gmail.com>
This commit is contained in:
parent
1980ac88f1
commit
17edf4f7c7
11 changed files with 400 additions and 163 deletions
|
@ -36,7 +36,7 @@ impl ProcessorGatedReader {
|
|||
) -> Result<RwLockReadGuardArc<()>, AssetReaderError> {
|
||||
let infos = self.processor_data.asset_infos.read().await;
|
||||
let info = infos
|
||||
.get(&AssetPath::new(path.to_owned(), None))
|
||||
.get(&AssetPath::from_path(path.to_path_buf()))
|
||||
.ok_or_else(|| AssetReaderError::NotFound(path.to_owned()))?;
|
||||
Ok(info.file_transaction_lock.read_arc().await)
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::{
|
|||
Asset, AssetLoadError, AssetServer, Assets, Handle, UntypedAssetId, UntypedHandle,
|
||||
};
|
||||
use bevy_ecs::world::World;
|
||||
use bevy_utils::{BoxedFuture, HashMap, HashSet};
|
||||
use bevy_utils::{BoxedFuture, CowArc, HashMap, HashSet};
|
||||
use downcast_rs::{impl_downcast, Downcast};
|
||||
use futures_lite::AsyncReadExt;
|
||||
use ron::error::SpannedError;
|
||||
|
@ -143,7 +143,7 @@ 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<String, LabeledAsset>,
|
||||
pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
|
||||
pub(crate) meta: Option<Box<dyn AssetMetaDyn>>,
|
||||
}
|
||||
|
||||
|
@ -175,7 +175,7 @@ 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<String, LabeledAsset>,
|
||||
pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
|
||||
pub(crate) meta: Option<Box<dyn AssetMetaDyn>>,
|
||||
}
|
||||
|
||||
|
@ -214,13 +214,16 @@ impl ErasedLoadedAsset {
|
|||
}
|
||||
|
||||
/// Returns the [`ErasedLoadedAsset`] for the given label, if it exists.
|
||||
pub fn get_labeled(&self, label: &str) -> Option<&ErasedLoadedAsset> {
|
||||
self.labeled_assets.get(label).map(|a| &a.asset)
|
||||
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.as_str())
|
||||
self.labeled_assets.keys().map(|s| &**s)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -269,7 +272,7 @@ pub struct LoadContext<'a> {
|
|||
dependencies: HashSet<UntypedAssetId>,
|
||||
/// Direct dependencies used by this loader.
|
||||
loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
|
||||
labeled_assets: HashMap<String, LabeledAsset>,
|
||||
labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
|
||||
}
|
||||
|
||||
impl<'a> LoadContext<'a> {
|
||||
|
@ -362,9 +365,10 @@ impl<'a> LoadContext<'a> {
|
|||
/// See [`AssetPath`] for more on labeled assets.
|
||||
pub fn add_loaded_labeled_asset<A: Asset>(
|
||||
&mut self,
|
||||
label: String,
|
||||
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.with_label(label.clone());
|
||||
let handle = self
|
||||
|
@ -383,9 +387,9 @@ impl<'a> LoadContext<'a> {
|
|||
/// 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(&self, label: &str) -> bool {
|
||||
let path = self.asset_path.with_label(label);
|
||||
self.asset_server.get_handle_untyped(path).is_some()
|
||||
pub fn has_labeled_asset<'b>(&self, label: impl Into<CowArc<'b, str>>) -> bool {
|
||||
let path = self.asset_path.with_label(label.into());
|
||||
self.asset_server.get_handle_untyped(&path).is_some()
|
||||
}
|
||||
|
||||
/// "Finishes" this context by populating the final [`Asset`] value (and the erased [`AssetMeta`] value, if it exists).
|
||||
|
@ -406,7 +410,7 @@ impl<'a> LoadContext<'a> {
|
|||
}
|
||||
|
||||
/// Gets the source asset path for this load context.
|
||||
pub fn asset_path(&self) -> &AssetPath {
|
||||
pub fn asset_path(&self) -> &AssetPath<'static> {
|
||||
&self.asset_path
|
||||
}
|
||||
|
||||
|
@ -432,7 +436,7 @@ impl<'a> LoadContext<'a> {
|
|||
let mut bytes = Vec::new();
|
||||
reader.read_to_end(&mut bytes).await?;
|
||||
self.loader_dependencies
|
||||
.insert(AssetPath::new(path.to_owned(), None), hash);
|
||||
.insert(AssetPath::from_path(path.to_owned()), hash);
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
|
@ -461,14 +465,12 @@ impl<'a> LoadContext<'a> {
|
|||
path: impl Into<AssetPath<'b>>,
|
||||
settings: impl Fn(&mut S) + Send + Sync + 'static,
|
||||
) -> Handle<A> {
|
||||
let path = path.into().to_owned();
|
||||
let path = path.into();
|
||||
let handle = if self.should_load_dependencies {
|
||||
self.asset_server.load_with_settings(path.clone(), settings)
|
||||
} else {
|
||||
self.asset_server.get_or_create_path_handle(
|
||||
path.clone(),
|
||||
Some(loader_settings_meta_transform(settings)),
|
||||
)
|
||||
self.asset_server
|
||||
.get_or_create_path_handle(path, Some(loader_settings_meta_transform(settings)))
|
||||
};
|
||||
self.dependencies.insert(handle.id().untyped());
|
||||
handle
|
||||
|
@ -477,8 +479,11 @@ impl<'a> LoadContext<'a> {
|
|||
/// 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<A: Asset>(&mut self, label: &str) -> Handle<A> {
|
||||
let path = self.asset_path.with_label(label).to_owned();
|
||||
pub fn get_label_handle<'b, A: Asset>(
|
||||
&mut self,
|
||||
label: impl Into<CowArc<'b, str>>,
|
||||
) -> Handle<A> {
|
||||
let path = self.asset_path.with_label(label);
|
||||
let handle = self.asset_server.get_or_create_path_handle::<A>(path, None);
|
||||
self.dependencies.insert(handle.id().untyped());
|
||||
handle
|
||||
|
@ -498,36 +503,37 @@ impl<'a> LoadContext<'a> {
|
|||
&mut self,
|
||||
path: impl Into<AssetPath<'b>>,
|
||||
) -> Result<ErasedLoadedAsset, LoadDirectError> {
|
||||
let path = path.into();
|
||||
let path = path.into().into_owned();
|
||||
let to_error = |e: AssetLoadError| -> LoadDirectError {
|
||||
LoadDirectError {
|
||||
dependency: path.to_owned(),
|
||||
dependency: path.clone(),
|
||||
error: e,
|
||||
}
|
||||
};
|
||||
let (meta, loader, mut reader) = self
|
||||
.asset_server
|
||||
.get_meta_loader_and_reader(&path)
|
||||
.await
|
||||
.map_err(to_error)?;
|
||||
let loaded_asset = self
|
||||
.asset_server
|
||||
.load_with_meta_loader_and_reader(
|
||||
&path,
|
||||
meta,
|
||||
&*loader,
|
||||
&mut *reader,
|
||||
false,
|
||||
self.populate_hashes,
|
||||
)
|
||||
.await
|
||||
.map_err(to_error)?;
|
||||
let loaded_asset = {
|
||||
let (meta, loader, mut reader) = self
|
||||
.asset_server
|
||||
.get_meta_loader_and_reader(&path)
|
||||
.await
|
||||
.map_err(to_error)?;
|
||||
self.asset_server
|
||||
.load_with_meta_loader_and_reader(
|
||||
&path,
|
||||
meta,
|
||||
&*loader,
|
||||
&mut *reader,
|
||||
false,
|
||||
self.populate_hashes,
|
||||
)
|
||||
.await
|
||||
.map_err(to_error)?
|
||||
};
|
||||
let info = loaded_asset
|
||||
.meta
|
||||
.as_ref()
|
||||
.and_then(|m| m.processed_info().as_ref());
|
||||
let hash = info.map(|i| i.full_hash).unwrap_or(Default::default());
|
||||
self.loader_dependencies.insert(path.to_owned(), hash);
|
||||
self.loader_dependencies.insert(path, hash);
|
||||
Ok(loaded_asset)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use bevy_utils::CowArc;
|
||||
use serde::{de::Visitor, ser::SerializeTupleStruct, Deserialize, Serialize};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fmt::{Debug, Display},
|
||||
hash::Hash,
|
||||
ops::Deref,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
|
@ -33,11 +34,17 @@ use std::{
|
|||
/// // This loads the `PlayerMesh` labeled asset from the `my_scene.scn` base asset.
|
||||
/// let mesh: Handle<Mesh> = asset_server.load("my_scene.scn#PlayerMesh");
|
||||
/// ```
|
||||
#[derive(Eq, PartialEq, Hash, Clone, Serialize, Deserialize, Reflect)]
|
||||
///
|
||||
/// [`AssetPath`] implements [`From`] for `&'static str`, `&'static Path`, and `&'a String`,
|
||||
/// which allows us to optimize the static cases.
|
||||
/// This means that the common case of `asset_server.load("my_scene.scn")` when it creates and
|
||||
/// clones internal owned [`AssetPaths`](AssetPath).
|
||||
/// This also means that you should use [`AssetPath::new`] in cases where `&str` is the explicit type.
|
||||
#[derive(Eq, PartialEq, Hash, Clone, Reflect)]
|
||||
#[reflect(Debug, PartialEq, Hash, Serialize, Deserialize)]
|
||||
pub struct AssetPath<'a> {
|
||||
pub path: Cow<'a, Path>,
|
||||
pub label: Option<Cow<'a, str>>,
|
||||
path: CowArc<'a, Path>,
|
||||
label: Option<CowArc<'a, str>>,
|
||||
}
|
||||
|
||||
impl<'a> Debug for AssetPath<'a> {
|
||||
|
@ -57,74 +64,95 @@ impl<'a> Display for AssetPath<'a> {
|
|||
}
|
||||
|
||||
impl<'a> AssetPath<'a> {
|
||||
/// Creates a new asset path using borrowed information.
|
||||
#[inline]
|
||||
pub fn new_ref(path: &'a Path, label: Option<&'a str>) -> AssetPath<'a> {
|
||||
AssetPath {
|
||||
path: Cow::Borrowed(path),
|
||||
label: label.map(Cow::Borrowed),
|
||||
/// Creates a new [`AssetPath`] from a string in the asset path format:
|
||||
/// * An asset at the root: `"scene.gltf"`
|
||||
/// * An asset nested in some folders: `"some/path/scene.gltf"`
|
||||
/// * An asset with a "label": `"some/path/scene.gltf#Mesh0"`
|
||||
///
|
||||
/// Prefer [`From<'static str>`] for static strings, as this will prevent allocations
|
||||
/// and reference counting for [`AssetPath::into_owned`].
|
||||
pub fn new(asset_path: &'a str) -> AssetPath<'a> {
|
||||
let (path, label) = Self::get_parts(asset_path);
|
||||
Self {
|
||||
path: CowArc::Borrowed(path),
|
||||
label: label.map(CowArc::Borrowed),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new asset path.
|
||||
fn get_parts(asset_path: &str) -> (&Path, Option<&str>) {
|
||||
let mut parts = asset_path.splitn(2, '#');
|
||||
let path = Path::new(parts.next().expect("Path must be set."));
|
||||
let label = parts.next();
|
||||
(path, label)
|
||||
}
|
||||
|
||||
/// Creates a new [`AssetPath`] from a [`Path`].
|
||||
#[inline]
|
||||
pub fn new(path: PathBuf, label: Option<String>) -> AssetPath<'a> {
|
||||
pub fn from_path(path: impl Into<CowArc<'a, Path>>) -> AssetPath<'a> {
|
||||
AssetPath {
|
||||
path: Cow::Owned(path),
|
||||
label: label.map(Cow::Owned),
|
||||
path: path.into(),
|
||||
label: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the "sub-asset label".
|
||||
#[inline]
|
||||
pub fn label(&self) -> Option<&str> {
|
||||
self.label.as_ref().map(|label| label.as_ref())
|
||||
self.label.as_deref()
|
||||
}
|
||||
|
||||
/// Gets the path to the asset in the "virtual filesystem".
|
||||
#[inline]
|
||||
pub fn path(&self) -> &Path {
|
||||
&self.path
|
||||
self.path.deref()
|
||||
}
|
||||
|
||||
/// Gets the path to the asset in the "virtual filesystem" without a label (if a label is currently set).
|
||||
#[inline]
|
||||
pub fn without_label(&self) -> AssetPath<'_> {
|
||||
AssetPath::new_ref(&self.path, None)
|
||||
Self {
|
||||
path: self.path.clone(),
|
||||
label: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes a "sub-asset label" from this [`AssetPath`] and returns it, if one was set.
|
||||
/// Removes a "sub-asset label" from this [`AssetPath`], if one was set.
|
||||
#[inline]
|
||||
pub fn remove_label(&mut self) -> Option<Cow<'a, str>> {
|
||||
pub fn remove_label(&mut self) {
|
||||
self.label = None;
|
||||
}
|
||||
|
||||
/// Takes the "sub-asset label" from this [`AssetPath`], if one was set.
|
||||
#[inline]
|
||||
pub fn take_label(&mut self) -> Option<CowArc<'a, str>> {
|
||||
self.label.take()
|
||||
}
|
||||
|
||||
/// Returns this asset path with the given label. This will replace the previous
|
||||
/// label if it exists.
|
||||
#[inline]
|
||||
pub fn with_label(&self, label: impl Into<Cow<'a, str>>) -> AssetPath<'a> {
|
||||
pub fn with_label(&self, label: impl Into<CowArc<'a, str>>) -> AssetPath<'a> {
|
||||
AssetPath {
|
||||
path: self.path.clone(),
|
||||
label: Some(label.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the borrowed path data to owned.
|
||||
#[inline]
|
||||
pub fn to_owned(&self) -> AssetPath<'static> {
|
||||
/// Converts this into an "owned" value. If internally a value is borrowed, it will be cloned into an "owned [`Arc`]".
|
||||
/// If it is already an "owned [`Arc`]", it will remain unchanged.
|
||||
///
|
||||
/// [`Arc`]: std::sync::Arc
|
||||
pub fn into_owned(self) -> AssetPath<'static> {
|
||||
AssetPath {
|
||||
path: Cow::Owned(self.path.to_path_buf()),
|
||||
label: self
|
||||
.label
|
||||
.as_ref()
|
||||
.map(|value| Cow::Owned(value.to_string())),
|
||||
path: self.path.into_owned(),
|
||||
label: self.label.map(|l| l.into_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the full extension (including multiple '.' values).
|
||||
/// Ex: Returns `"config.ron"` for `"my_asset.config.ron"`
|
||||
pub fn get_full_extension(&self) -> Option<String> {
|
||||
let file_name = self.path.file_name()?.to_str()?;
|
||||
let file_name = self.path().file_name()?.to_str()?;
|
||||
let index = file_name.find('.')?;
|
||||
let extension = file_name[index + 1..].to_lowercase();
|
||||
Some(extension)
|
||||
|
@ -141,47 +169,104 @@ impl<'a> AssetPath<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for AssetPath<'a> {
|
||||
fn from(asset_path: &'a str) -> Self {
|
||||
let mut parts = asset_path.splitn(2, '#');
|
||||
let path = Path::new(parts.next().expect("Path must be set."));
|
||||
let label = parts.next();
|
||||
impl From<&'static str> for AssetPath<'static> {
|
||||
#[inline]
|
||||
fn from(asset_path: &'static str) -> Self {
|
||||
let (path, label) = Self::get_parts(asset_path);
|
||||
AssetPath {
|
||||
path: Cow::Borrowed(path),
|
||||
label: label.map(Cow::Borrowed),
|
||||
path: CowArc::Static(path),
|
||||
label: label.map(CowArc::Static),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a String> for AssetPath<'a> {
|
||||
#[inline]
|
||||
fn from(asset_path: &'a String) -> Self {
|
||||
asset_path.as_str().into()
|
||||
AssetPath::new(asset_path.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Path> for AssetPath<'a> {
|
||||
fn from(path: &'a Path) -> Self {
|
||||
AssetPath {
|
||||
path: Cow::Borrowed(path),
|
||||
impl From<String> for AssetPath<'static> {
|
||||
#[inline]
|
||||
fn from(asset_path: String) -> Self {
|
||||
AssetPath::new(asset_path.as_str()).into_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static Path> for AssetPath<'static> {
|
||||
#[inline]
|
||||
fn from(path: &'static Path) -> Self {
|
||||
Self {
|
||||
path: CowArc::Static(path),
|
||||
label: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<PathBuf> for AssetPath<'a> {
|
||||
impl From<PathBuf> for AssetPath<'static> {
|
||||
#[inline]
|
||||
fn from(path: PathBuf) -> Self {
|
||||
AssetPath {
|
||||
path: Cow::Owned(path),
|
||||
Self {
|
||||
path: path.into(),
|
||||
label: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> From<&'a AssetPath<'b>> for AssetPath<'b> {
|
||||
fn from(value: &'a AssetPath<'b>) -> Self {
|
||||
value.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<AssetPath<'a>> for PathBuf {
|
||||
fn from(path: AssetPath<'a>) -> Self {
|
||||
match path.path {
|
||||
Cow::Borrowed(borrowed) => borrowed.to_owned(),
|
||||
Cow::Owned(owned) => owned,
|
||||
}
|
||||
fn from(value: AssetPath<'a>) -> Self {
|
||||
value.path().to_path_buf()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Serialize for AssetPath<'a> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut state = serializer.serialize_tuple_struct("AssetPath", 1)?;
|
||||
let string = self.to_string();
|
||||
state.serialize_field(&string)?;
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for AssetPath<'static> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_tuple_struct("AssetPath", 1, AssetPathVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
struct AssetPathVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for AssetPathVisitor {
|
||||
type Value = AssetPath<'static>;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("string AssetPath")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(AssetPath::new(v).into_owned())
|
||||
}
|
||||
|
||||
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(AssetPath::from(v))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -364,7 +364,7 @@ impl AssetProcessor {
|
|||
/// asset have finished, thanks to the `file_transaction_lock`.
|
||||
async fn handle_removed_asset(&self, path: PathBuf) {
|
||||
debug!("Removing processed {:?} because source was removed", path);
|
||||
let asset_path = AssetPath::new(path, None);
|
||||
let asset_path = AssetPath::from_path(path);
|
||||
let mut infos = self.data.asset_infos.write().await;
|
||||
if let Some(info) = infos.get(&asset_path) {
|
||||
// we must wait for uncontested write access to the asset source to ensure existing readers / writers
|
||||
|
@ -380,19 +380,19 @@ impl AssetProcessor {
|
|||
/// This will cause direct path dependencies to break.
|
||||
async fn handle_renamed_asset(&self, old: PathBuf, new: PathBuf) {
|
||||
let mut infos = self.data.asset_infos.write().await;
|
||||
let old_asset_path = AssetPath::new(old, None);
|
||||
let old_asset_path = AssetPath::from_path(old);
|
||||
if let Some(info) = infos.get(&old_asset_path) {
|
||||
// we must wait for uncontested write access to the asset source to ensure existing readers / writers
|
||||
// can finish their operations
|
||||
let _write_lock = info.file_transaction_lock.write();
|
||||
let old = &old_asset_path.path;
|
||||
let old = old_asset_path.path();
|
||||
self.destination_writer().rename(old, &new).await.unwrap();
|
||||
self.destination_writer()
|
||||
.rename_meta(old, &new)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
let new_asset_path = AssetPath::new(new.clone(), None);
|
||||
let new_asset_path = AssetPath::from_path(new);
|
||||
infos.rename(&old_asset_path, &new_asset_path).await;
|
||||
}
|
||||
|
||||
|
@ -531,11 +531,11 @@ impl AssetProcessor {
|
|||
.map_err(InitializeError::FailedToReadDestinationPaths)?;
|
||||
|
||||
for path in &source_paths {
|
||||
asset_infos.get_or_insert(AssetPath::new(path.to_owned(), None));
|
||||
asset_infos.get_or_insert(AssetPath::from_path(path.clone()));
|
||||
}
|
||||
|
||||
for path in &destination_paths {
|
||||
let asset_path = AssetPath::new(path.to_owned(), None);
|
||||
let asset_path = AssetPath::from_path(path.clone());
|
||||
let mut dependencies = Vec::new();
|
||||
if let Some(info) = asset_infos.get_mut(&asset_path) {
|
||||
match self.destination_reader().read_meta_bytes(path).await {
|
||||
|
@ -551,7 +551,7 @@ impl AssetProcessor {
|
|||
for process_dependency_info in
|
||||
&processed_info.process_dependencies
|
||||
{
|
||||
dependencies.push(process_dependency_info.path.to_owned());
|
||||
dependencies.push(process_dependency_info.path.clone());
|
||||
}
|
||||
}
|
||||
info.processed_info = minimal.processed_info;
|
||||
|
@ -573,7 +573,7 @@ impl AssetProcessor {
|
|||
}
|
||||
|
||||
for dependency in dependencies {
|
||||
asset_infos.add_dependant(&dependency, asset_path.to_owned());
|
||||
asset_infos.add_dependant(&dependency, asset_path.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -627,7 +627,7 @@ impl AssetProcessor {
|
|||
async fn process_asset(&self, path: &Path) {
|
||||
let result = self.process_asset_internal(path).await;
|
||||
let mut infos = self.data.asset_infos.write().await;
|
||||
let asset_path = AssetPath::new(path.to_owned(), None);
|
||||
let asset_path = AssetPath::from_path(path.to_owned());
|
||||
infos.finish_processing(asset_path, result).await;
|
||||
}
|
||||
|
||||
|
@ -635,7 +635,7 @@ impl AssetProcessor {
|
|||
if path.extension().is_none() {
|
||||
return Err(ProcessError::ExtensionRequired);
|
||||
}
|
||||
let asset_path = AssetPath::new(path.to_owned(), None);
|
||||
let asset_path = AssetPath::from_path(path.to_path_buf());
|
||||
// TODO: check if already processing to protect against duplicate hot-reload events
|
||||
debug!("Processing {:?}", path);
|
||||
let server = &self.server;
|
||||
|
@ -912,7 +912,7 @@ impl AssetProcessorData {
|
|||
self.wait_until_initialized().await;
|
||||
let mut receiver = {
|
||||
let infos = self.asset_infos.write().await;
|
||||
let info = infos.get(&AssetPath::new(path.to_owned(), None));
|
||||
let info = infos.get(&AssetPath::from_path(path.to_path_buf()));
|
||||
match info {
|
||||
Some(info) => match info.status {
|
||||
Some(result) => return result,
|
||||
|
@ -1067,7 +1067,7 @@ impl ProcessorAssetInfos {
|
|||
} else {
|
||||
let dependants = self
|
||||
.non_existent_dependants
|
||||
.entry(asset_path.to_owned())
|
||||
.entry(asset_path.clone())
|
||||
.or_default();
|
||||
dependants.insert(dependant);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{io::Writer, meta::Settings, Asset, ErasedLoadedAsset};
|
||||
use crate::{AssetLoader, LabeledAsset};
|
||||
use bevy_utils::{BoxedFuture, HashMap};
|
||||
use bevy_utils::{BoxedFuture, CowArc, HashMap};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::Deref;
|
||||
|
||||
|
@ -60,7 +60,7 @@ impl<S: AssetSaver> ErasedAssetSaver for 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<String, LabeledAsset>,
|
||||
labeled_assets: &'a HashMap<CowArc<'static, str>, LabeledAsset>,
|
||||
}
|
||||
|
||||
impl<'a, A: Asset> Deref for SavedAsset<'a, A> {
|
||||
|
@ -88,8 +88,11 @@ impl<'a, A: Asset> SavedAsset<'a, A> {
|
|||
}
|
||||
|
||||
/// Returns the labeled asset, if it exists and matches this type.
|
||||
pub fn get_labeled<B: Asset>(&self, label: &str) -> Option<SavedAsset<B>> {
|
||||
let labeled = self.labeled_assets.get(label)?;
|
||||
pub fn get_labeled<B: Asset>(
|
||||
&self,
|
||||
label: impl Into<CowArc<'static, str>>,
|
||||
) -> Option<SavedAsset<B>> {
|
||||
let labeled = self.labeled_assets.get(&label.into())?;
|
||||
let value = labeled.asset.value.downcast_ref::<B>()?;
|
||||
Some(SavedAsset {
|
||||
value,
|
||||
|
@ -98,13 +101,16 @@ impl<'a, A: Asset> SavedAsset<'a, A> {
|
|||
}
|
||||
|
||||
/// Returns the type-erased labeled asset, if it exists and matches this type.
|
||||
pub fn get_erased_labeled(&self, label: &str) -> Option<&ErasedLoadedAsset> {
|
||||
let labeled = self.labeled_assets.get(label)?;
|
||||
pub fn get_erased_labeled(
|
||||
&self,
|
||||
label: impl Into<CowArc<'static, str>>,
|
||||
) -> Option<&ErasedLoadedAsset> {
|
||||
let labeled = self.labeled_assets.get(&label.into())?;
|
||||
Some(&labeled.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.as_str())
|
||||
self.labeled_assets.keys().map(|s| &**s)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -257,8 +257,9 @@ impl AssetInfos {
|
|||
}
|
||||
|
||||
/// Returns `true` if this path has
|
||||
pub(crate) fn is_path_alive(&self, path: &AssetPath) -> bool {
|
||||
if let Some(id) = self.path_to_id.get(path) {
|
||||
pub(crate) fn is_path_alive<'a>(&self, path: impl Into<AssetPath<'a>>) -> bool {
|
||||
let path = path.into();
|
||||
if let Some(id) = self.path_to_id.get(&path) {
|
||||
if let Some(info) = self.infos.get(id) {
|
||||
return info.weak_handle.strong_count() > 0;
|
||||
}
|
||||
|
|
|
@ -201,8 +201,9 @@ impl AssetServer {
|
|||
/// Retrieves the default [`AssetLoader`] for the given path, if one can be found.
|
||||
pub async fn get_path_asset_loader<'a>(
|
||||
&self,
|
||||
path: &AssetPath<'a>,
|
||||
path: impl Into<AssetPath<'a>>,
|
||||
) -> Result<Arc<dyn ErasedAssetLoader>, MissingAssetLoaderForExtensionError> {
|
||||
let path = path.into();
|
||||
let full_extension =
|
||||
path.get_full_extension()
|
||||
.ok_or(MissingAssetLoaderForExtensionError {
|
||||
|
@ -252,27 +253,22 @@ impl AssetServer {
|
|||
path: impl Into<AssetPath<'a>>,
|
||||
meta_transform: Option<MetaTransform>,
|
||||
) -> Handle<A> {
|
||||
let path: AssetPath = path.into();
|
||||
let mut path = path.into().into_owned();
|
||||
let (handle, should_load) = self.data.infos.write().get_or_create_path_handle::<A>(
|
||||
path.to_owned(),
|
||||
path.clone(),
|
||||
HandleLoadingMode::Request,
|
||||
meta_transform,
|
||||
);
|
||||
|
||||
if should_load {
|
||||
let mut owned_handle = Some(handle.clone().untyped());
|
||||
let mut owned_path = path.to_owned();
|
||||
let server = self.clone();
|
||||
IoTaskPool::get()
|
||||
.spawn(async move {
|
||||
if owned_path.label().is_some() {
|
||||
owned_path.remove_label();
|
||||
if path.take_label().is_some() {
|
||||
owned_handle = None;
|
||||
}
|
||||
if let Err(err) = server
|
||||
.load_internal(owned_handle, owned_path, false, None)
|
||||
.await
|
||||
{
|
||||
if let Err(err) = server.load_internal(owned_handle, path, false, None).await {
|
||||
error!("{}", err);
|
||||
}
|
||||
})
|
||||
|
@ -287,19 +283,21 @@ impl AssetServer {
|
|||
&self,
|
||||
path: impl Into<AssetPath<'a>>,
|
||||
) -> Result<UntypedHandle, AssetLoadError> {
|
||||
self.load_internal(None, path.into(), false, None).await
|
||||
let path: AssetPath = path.into();
|
||||
self.load_internal(None, path, false, None).await
|
||||
}
|
||||
|
||||
async fn load_internal<'a>(
|
||||
&self,
|
||||
input_handle: Option<UntypedHandle>,
|
||||
mut path: AssetPath<'a>,
|
||||
path: AssetPath<'a>,
|
||||
force: bool,
|
||||
meta_transform: Option<MetaTransform>,
|
||||
) -> Result<UntypedHandle, AssetLoadError> {
|
||||
let owned_path = path.to_owned();
|
||||
let mut path = path.into_owned();
|
||||
let path_clone = path.clone();
|
||||
let (mut meta, loader, mut reader) = self
|
||||
.get_meta_loader_and_reader(&owned_path)
|
||||
.get_meta_loader_and_reader(&path_clone)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
// if there was an input handle, a "load" operation has already started, so we must produce a "failure" event, if
|
||||
|
@ -316,7 +314,7 @@ impl AssetServer {
|
|||
Some(handle) => {
|
||||
if !has_label && handle.type_id() != loader.asset_type_id() {
|
||||
return Err(AssetLoadError::RequestedHandleTypeMismatch {
|
||||
path: path.to_owned(),
|
||||
path: path.into_owned(),
|
||||
requested: handle.type_id(),
|
||||
actual_asset_name: loader.asset_type_name(),
|
||||
loader_name: loader.type_name(),
|
||||
|
@ -328,7 +326,7 @@ impl AssetServer {
|
|||
None => {
|
||||
let mut infos = self.data.infos.write();
|
||||
infos.get_or_create_path_handle_untyped(
|
||||
path.to_owned(),
|
||||
path.clone(),
|
||||
loader.asset_type_id(),
|
||||
loader.asset_type_name(),
|
||||
HandleLoadingMode::Request,
|
||||
|
@ -346,7 +344,7 @@ impl AssetServer {
|
|||
// We need to get the actual asset id
|
||||
let mut infos = self.data.infos.write();
|
||||
let (actual_handle, _) = infos.get_or_create_path_handle_untyped(
|
||||
path.to_owned(),
|
||||
path.clone(),
|
||||
loader.asset_type_id(),
|
||||
loader.asset_type_name(),
|
||||
// ignore current load state ... we kicked off this sub asset load because it needed to be loaded but
|
||||
|
@ -390,13 +388,12 @@ impl AssetServer {
|
|||
/// Kicks off a reload of the asset stored at the given path. This will only reload the asset if it currently loaded.
|
||||
pub fn reload<'a>(&self, path: impl Into<AssetPath<'a>>) {
|
||||
let server = self.clone();
|
||||
let path = path.into();
|
||||
let owned_path = path.to_owned();
|
||||
let path = path.into().into_owned();
|
||||
IoTaskPool::get()
|
||||
.spawn(async move {
|
||||
if server.data.infos.read().is_path_alive(&owned_path) {
|
||||
info!("Reloading {owned_path} because it has changed");
|
||||
if let Err(err) = server.load_internal(None, owned_path, true, None).await {
|
||||
if server.data.infos.read().is_path_alive(&path) {
|
||||
info!("Reloading {path} because it has changed");
|
||||
if let Err(err) = server.load_internal(None, path, true, None).await {
|
||||
error!("{}", err);
|
||||
}
|
||||
}
|
||||
|
@ -423,13 +420,13 @@ impl AssetServer {
|
|||
#[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
|
||||
pub(crate) fn load_asset_untyped(
|
||||
&self,
|
||||
path: Option<&AssetPath<'static>>,
|
||||
path: Option<AssetPath<'static>>,
|
||||
asset: impl Into<ErasedLoadedAsset>,
|
||||
) -> UntypedHandle {
|
||||
let loaded_asset = asset.into();
|
||||
let handle = if let Some(path) = path {
|
||||
let (handle, _) = self.data.infos.write().get_or_create_path_handle_untyped(
|
||||
path.clone(),
|
||||
path,
|
||||
loaded_asset.asset_type_id(),
|
||||
loaded_asset.asset_type_name(),
|
||||
HandleLoadingMode::NotLoading,
|
||||
|
@ -474,7 +471,7 @@ impl AssetServer {
|
|||
load_folder(&child_path, server, handles).await?;
|
||||
} else {
|
||||
let path = child_path.to_str().expect("Path should be a valid string.");
|
||||
match server.load_untyped_async(path).await {
|
||||
match server.load_untyped_async(AssetPath::new(path)).await {
|
||||
Ok(handle) => handles.push(handle),
|
||||
// skip assets that cannot be loaded
|
||||
Err(
|
||||
|
@ -583,10 +580,10 @@ impl AssetServer {
|
|||
}
|
||||
|
||||
/// Returns the path for the given `id`, if it has one.
|
||||
pub fn get_path(&self, id: impl Into<UntypedAssetId>) -> Option<AssetPath<'static>> {
|
||||
pub fn get_path(&self, id: impl Into<UntypedAssetId>) -> Option<AssetPath> {
|
||||
let infos = self.data.infos.read();
|
||||
let info = infos.get(id.into())?;
|
||||
Some(info.path.as_ref()?.to_owned())
|
||||
Some(info.path.as_ref()?.clone())
|
||||
}
|
||||
|
||||
/// Pre-register a loader that will later be added.
|
||||
|
@ -618,14 +615,18 @@ impl AssetServer {
|
|||
}
|
||||
|
||||
/// Retrieve a handle for the given path. This will create a handle (and [`AssetInfo`]) if it does not exist
|
||||
pub(crate) fn get_or_create_path_handle<A: Asset>(
|
||||
pub(crate) fn get_or_create_path_handle<'a, A: Asset>(
|
||||
&self,
|
||||
path: AssetPath<'static>,
|
||||
path: impl Into<AssetPath<'a>>,
|
||||
meta_transform: Option<MetaTransform>,
|
||||
) -> Handle<A> {
|
||||
let mut infos = self.data.infos.write();
|
||||
infos
|
||||
.get_or_create_path_handle::<A>(path, HandleLoadingMode::NotLoading, meta_transform)
|
||||
.get_or_create_path_handle::<A>(
|
||||
path.into().into_owned(),
|
||||
HandleLoadingMode::NotLoading,
|
||||
meta_transform,
|
||||
)
|
||||
.0
|
||||
}
|
||||
|
||||
|
@ -655,19 +656,19 @@ impl AssetServer {
|
|||
AssetActionMinimal::Load { loader } => loader,
|
||||
AssetActionMinimal::Process { .. } => {
|
||||
return Err(AssetLoadError::CannotLoadProcessedAsset {
|
||||
path: asset_path.to_owned(),
|
||||
path: asset_path.clone().into_owned(),
|
||||
})
|
||||
}
|
||||
AssetActionMinimal::Ignore => {
|
||||
return Err(AssetLoadError::CannotLoadIgnoredAsset {
|
||||
path: asset_path.to_owned(),
|
||||
path: asset_path.clone().into_owned(),
|
||||
})
|
||||
}
|
||||
};
|
||||
let loader = self.get_asset_loader_with_type_name(&loader_name).await?;
|
||||
let meta = loader.deserialize_meta(&meta_bytes).map_err(|e| {
|
||||
AssetLoadError::AssetLoaderError {
|
||||
path: asset_path.to_owned(),
|
||||
path: asset_path.clone().into_owned(),
|
||||
loader: loader.type_name(),
|
||||
error: AssetLoaderError::DeserializeMeta(e),
|
||||
}
|
||||
|
@ -693,16 +694,14 @@ impl AssetServer {
|
|||
load_dependencies: bool,
|
||||
populate_hashes: bool,
|
||||
) -> Result<ErasedLoadedAsset, AssetLoadError> {
|
||||
let load_context = LoadContext::new(
|
||||
self,
|
||||
asset_path.to_owned(),
|
||||
load_dependencies,
|
||||
populate_hashes,
|
||||
);
|
||||
// TODO: experiment with this
|
||||
let asset_path = asset_path.clone().into_owned();
|
||||
let load_context =
|
||||
LoadContext::new(self, asset_path.clone(), load_dependencies, populate_hashes);
|
||||
loader.load(reader, meta, load_context).await.map_err(|e| {
|
||||
AssetLoadError::AssetLoaderError {
|
||||
loader: loader.type_name(),
|
||||
path: asset_path.to_owned(),
|
||||
path: asset_path,
|
||||
error: e,
|
||||
}
|
||||
})
|
||||
|
@ -753,12 +752,9 @@ pub fn handle_internal_asset_events(world: &mut World) {
|
|||
// TODO: if the asset was processed and the processed file was changed, the first modified event
|
||||
// should be skipped?
|
||||
AssetSourceEvent::ModifiedAsset(path) | AssetSourceEvent::ModifiedMeta(path) => {
|
||||
queue_ancestors(
|
||||
&AssetPath::new_ref(&path, None),
|
||||
&infos,
|
||||
&mut paths_to_reload,
|
||||
);
|
||||
paths_to_reload.insert(path.into());
|
||||
let path = AssetPath::from_path(path);
|
||||
queue_ancestors(&path, &infos, &mut paths_to_reload);
|
||||
paths_to_reload.insert(path);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
144
crates/bevy_utils/src/cow_arc.rs
Normal file
144
crates/bevy_utils/src/cow_arc.rs
Normal file
|
@ -0,0 +1,144 @@
|
|||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
hash::Hash,
|
||||
ops::Deref,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
/// Much like a [`Cow`](std::borrow::Cow), but owned values are Arc-ed to make clones cheap. This should be used for values that
|
||||
/// are cloned for use across threads and change rarely (if ever).
|
||||
///
|
||||
/// This also makes an opinionated tradeoff by adding a [`CowArc::Static`] and implementing [`From<&'static T>`] instead of
|
||||
/// [`From<'a T>`]. This preserves the static context and prevents conversion to [`CowArc::Owned`] in cases where a reference
|
||||
/// is known to be static. This is an optimization that prevents allocations and atomic ref-counting.
|
||||
///
|
||||
/// This means that static references should prefer [`From::from`] or [`CowArc::Static`] and non-static references must
|
||||
/// use [`CowArc::Borrowed`].
|
||||
pub enum CowArc<'a, T: ?Sized + 'static> {
|
||||
/// A borrowed value
|
||||
Borrowed(&'a T),
|
||||
/// A static value reference. This exists to avoid conversion to [`CowArc::Owned`] in cases where a reference is
|
||||
/// known to be static. This is an optimization that prevents allocations and atomic ref-counting.
|
||||
Static(&'static T),
|
||||
/// An owned [`Arc`]-ed value
|
||||
Owned(Arc<T>),
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> Deref for CowArc<'a, T> {
|
||||
type Target = T;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
CowArc::Borrowed(v) | CowArc::Static(v) => v,
|
||||
CowArc::Owned(v) => v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> CowArc<'a, T>
|
||||
where
|
||||
&'a T: Into<Arc<T>>,
|
||||
{
|
||||
/// Converts this into an "owned" value. If internally a value is borrowed, it will be cloned into an "owned [`Arc`]".
|
||||
/// If it is already an "owned [`Arc`]", it will remain unchanged.
|
||||
#[inline]
|
||||
pub fn into_owned(self) -> CowArc<'static, T> {
|
||||
match self {
|
||||
CowArc::Borrowed(value) => CowArc::Owned(value.into()),
|
||||
CowArc::Static(value) => CowArc::Static(value),
|
||||
CowArc::Owned(value) => CowArc::Owned(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> Clone for CowArc<'a, T> {
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Self::Borrowed(value) => Self::Borrowed(value),
|
||||
Self::Static(value) => Self::Static(value),
|
||||
Self::Owned(value) => Self::Owned(value.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: PartialEq + ?Sized> PartialEq for CowArc<'a, T> {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.deref().eq(other.deref())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: PartialEq + ?Sized> Eq for CowArc<'a, T> {}
|
||||
|
||||
impl<'a, T: Hash + ?Sized> Hash for CowArc<'a, T> {
|
||||
#[inline]
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.deref().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Debug + ?Sized> Debug for CowArc<'a, T> {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Debug::fmt(self.deref(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Display + ?Sized> Display for CowArc<'a, T> {
|
||||
#[inline]
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Display::fmt(self.deref(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: PartialOrd + ?Sized> PartialOrd for CowArc<'a, T> {
|
||||
#[inline]
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
self.deref().partial_cmp(other.deref())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Ord + ?Sized> Ord for CowArc<'a, T> {
|
||||
#[inline]
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.deref().cmp(other.deref())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PathBuf> for CowArc<'static, Path> {
|
||||
#[inline]
|
||||
fn from(value: PathBuf) -> Self {
|
||||
CowArc::Owned(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for CowArc<'static, Path> {
|
||||
#[inline]
|
||||
fn from(value: &'static str) -> Self {
|
||||
CowArc::Static(Path::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for CowArc<'static, str> {
|
||||
#[inline]
|
||||
fn from(value: String) -> Self {
|
||||
CowArc::Owned(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a String> for CowArc<'a, str> {
|
||||
#[inline]
|
||||
fn from(value: &'a String) -> Self {
|
||||
CowArc::Borrowed(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> From<&'static T> for CowArc<'static, T> {
|
||||
#[inline]
|
||||
fn from(value: &'static T) -> Self {
|
||||
CowArc::Static(value)
|
||||
}
|
||||
}
|
|
@ -18,11 +18,13 @@ pub use short_names::get_short_name;
|
|||
pub mod synccell;
|
||||
pub mod syncunsafecell;
|
||||
|
||||
mod cow_arc;
|
||||
mod default;
|
||||
mod float_ord;
|
||||
|
||||
pub use ahash::{AHasher, RandomState};
|
||||
pub use bevy_utils_proc_macros::*;
|
||||
pub use cow_arc::*;
|
||||
pub use default::default;
|
||||
pub use float_ord::*;
|
||||
pub use hashbrown;
|
||||
|
|
|
@ -72,10 +72,7 @@ fn setup(
|
|||
// load 16 textures
|
||||
let textures: Vec<_> = TILE_ID
|
||||
.iter()
|
||||
.map(|id| {
|
||||
let path = format!("textures/rpg/tiles/generic-rpg-tile{id:0>2}.png");
|
||||
asset_server.load(&path)
|
||||
})
|
||||
.map(|id| asset_server.load(format!("textures/rpg/tiles/generic-rpg-tile{id:0>2}.png")))
|
||||
.collect();
|
||||
|
||||
// a cube with multiple textures
|
||||
|
|
|
@ -77,7 +77,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
info!("Loading {}", scene_path);
|
||||
let (file_path, scene_index) = parse_scene(scene_path);
|
||||
|
||||
commands.insert_resource(SceneHandle::new(asset_server.load(&file_path), scene_index));
|
||||
commands.insert_resource(SceneHandle::new(asset_server.load(file_path), scene_index));
|
||||
}
|
||||
|
||||
fn setup_scene_after_load(
|
||||
|
|
Loading…
Reference in a new issue