Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign upCore asset management #244
Conversation
torkleyy
referenced this pull request
Jun 13, 2017
Closed
Restructuring of the asset management #195
ebkalderon
requested changes
Jun 13, 2017
•
@torkleyy This is a fantastic start to the new asset management system! I really like the API overall, though there are some nitpicks and questions to resolve.
Also, would you mind explaining how this PR would interact with the recently-merged project module? It already specifies a project directory and parses files, so merging this PR as-is would result in a lot of duplicate code. Wouldn't a text configuration file also be considered an asset? I would think so, since the values might change on disk and be hot reloaded at runtime. I guess amethyst_assets would end up replacing the project module eventually?
Speaking of, do you have any plans to include hot reloading of modified assets eventually? Futures would be perfect for this use case!
Phew, sorry for the wall of text. Great work so far!
| + type Location; | ||
| + | ||
| + /// Loads the bytes given a category, id and extension of the asset. | ||
| + fn load(&self, category: &str, id: &str, ext: &str) -> Result<Vec<u8>, Self::Error>; |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ebkalderon
Jun 13, 2017
Member
I'd recommend using AsRef here for ergonomics' sake, like this:
fn load<S: AsRef<str>>(&self, category: S, id: S, ext: S) -> Result<Vec<u8>, Self::Error>;
ebkalderon
Jun 13, 2017
Member
I'd recommend using AsRef here for ergonomics' sake, like this:
fn load<S: AsRef<str>>(&self, category: S, id: S, ext: S) -> Result<Vec<u8>, Self::Error>;
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
vitvakatu
Jun 14, 2017
Contributor
In this case, every S (category, id and ext) must be the same type. I'm not sure it's okay
vitvakatu
Jun 14, 2017
Contributor
In this case, every S (category, id and ext) must be the same type. I'm not sure it's okay
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Jun 14, 2017
Member
Hold on, there's no need to use a type parameter here, it's never called by the user.
torkleyy
Jun 14, 2017
Member
Hold on, there's no need to use a type parameter here, it's never called by the user.
| +impl Loader { | ||
| + /// Creates a new asset loader, initializing the directory store with the | ||
| + /// given path. | ||
| + pub fn new(alloc: &Allocator, directory: PathBuf, pool: Arc<ThreadPool>) -> Self { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ebkalderon
Jun 13, 2017
Member
It might be more ergonomic to use Into<PathBuf> here, like this:
pub fn new<P: Into<PathBuf>>(alloc: &Allocator, dir: P, pool: Arc<ThreadPool>) -> Self {
ebkalderon
Jun 13, 2017
Member
It might be more ergonomic to use Into<PathBuf> here, like this:
pub fn new<P: Into<PathBuf>>(alloc: &Allocator, dir: P, pool: Arc<ThreadPool>) -> Self {
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + pub fn new(name: String, ext: &'static str, store: StoreId) -> Self { | ||
| + AssetSpec { | ||
| + ext, | ||
| + name: name, |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ebkalderon
Jun 13, 2017
Member
Why was the field name specified explicitly here but not for the other two?
ebkalderon
Jun 13, 2017
Member
Why was the field name specified explicitly here but not for the other two?
| + let pool = Arc::new(ThreadPool::new(cfg).expect("Invalid config")); | ||
| + | ||
| + let alloc = Allocator::new(); | ||
| + let mut loader = Loader::new(&alloc, "examples/assets".into(), pool); |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ebkalderon
Jun 13, 2017
Member
If Into<PathBuf> is used for Loader::new(), this .into() will no longer be needed.
ebkalderon
Jun 13, 2017
Member
If Into<PathBuf> is used for Loader::new(), this .into() will no longer be needed.
| @@ -0,0 +1,15 @@ | ||
| +[package] | ||
| +name = "amethyst-assets" |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ebkalderon
Jun 13, 2017
Member
Please use an underscore instead of a hyphen to match the convention of the other crates.
ebkalderon
Jun 13, 2017
Member
Please use an underscore instead of a hyphen to match the convention of the other crates.
| +use std::path::PathBuf; | ||
| +use std::sync::atomic::{AtomicUsize, Ordering}; | ||
| + | ||
| +/// An `Allocator`, holding a counter for producing unique ideas for the stores. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Jun 14, 2017
Member
torkleyy
Jun 14, 2017
Member
| +futures = "0.1.14" | ||
| +parking_lot = "0.4.4" | ||
| +rayon = { version = "0.7", features = ["unstable"] } | ||
| + |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ebkalderon
Jun 13, 2017
Member
Don't forget to specify documentation, homepage, repository, categories, keywords, travis-ci, and appveyor. Also, the trailing newline isn't needed, I think.
ebkalderon
Jun 13, 2017
•
Member
Don't forget to specify documentation, homepage, repository, categories, keywords, travis-ci, and appveyor. Also, the trailing newline isn't needed, I think.
| +[dependencies] | ||
| +fnv = "1" | ||
| +futures = "0.1.14" | ||
| +parking_lot = "0.4.4" |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Aceeri
Jun 14, 2017
Member
Id personally prefer it if we did since there is no guarantee a dependency couldnt accidentally break something. Moreso it is just an honor code right now.
Aceeri
Jun 14, 2017
Member
Id personally prefer it if we did since there is no guarantee a dependency couldnt accidentally break something. Moreso it is just an honor code right now.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Jun 14, 2017
Member
I basically agree with what @Aceeri said: I prefer to just specify the latest patch version, since I don't know whether or not I'm using features of a patch, so it could work for me and on CI, but the dependency version isn't correct.
torkleyy
Jun 14, 2017
Member
I basically agree with what @Aceeri said: I prefer to just specify the latest patch version, since I don't know whether or not I'm using features of a patch, so it could work for me and on CI, but the dependency version isn't correct.
| @@ -0,0 +1,201 @@ | ||
| + Apache License |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ebkalderon
Jun 13, 2017
Member
If amethyst_assets contains its own README.md and license files, should the other crates contain their own copies as well? Not against it; just wanted to know what you think we should do going forward.
ebkalderon
Jun 13, 2017
Member
If amethyst_assets contains its own README.md and license files, should the other crates contain their own copies as well? Not against it; just wanted to know what you think we should do going forward.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Jun 14, 2017
Member
I'm not sure; AFAIK licenses should be distributed with the source code. Now, a cargo package contains the source code, but not the top-level license. That's the reason I put it in there, just to not cause any trouble. But I personally don't care whether or not there's a license in these crates.
torkleyy
Jun 14, 2017
Member
I'm not sure; AFAIK licenses should be distributed with the source code. Now, a cargo package contains the source code, but not the top-level license. That's the reason I put it in there, just to not cause any trouble. But I personally don't care whether or not there's a license in these crates.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ebkalderon
Jun 15, 2017
Member
Looking at gfx and Servo as an example, the subcrates do not each contain licenses, presumably because the license files found at the repository root apply to all source code in the tree. But then again, IANAL.
ebkalderon
Jun 15, 2017
Member
Looking at gfx and Servo as an example, the subcrates do not each contain licenses, presumably because the license files found at the repository root apply to all source code in the tree. But then again, IANAL.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| @@ -9,6 +9,11 @@ The format is based on [Keep a Changelog][kc], and this project adheres to | ||
| ## [Unreleased] | ||
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ebkalderon
Jun 13, 2017
Member
I don't think this trailing newline should be here, strictly speaking, according to KeepAChangelog.
ebkalderon
Jun 13, 2017
Member
I don't think this trailing newline should be here, strictly speaking, according to KeepAChangelog.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Jun 14, 2017
Member
Wow, thanks for the detailed review! I'm actually still resolving some questions myself. And sorry for the weird API, I did a lot of refactoring so not everything has been optimized for usage yet.
|
Wow, thanks for the detailed review! I'm actually still resolving some questions myself. And sorry for the weird API, I did a lot of refactoring so not everything has been optimized for usage yet. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Jun 14, 2017
Member
I guess amethyst_assets would end up replacing the project module eventually?
Yes, it could be made an asset then, but the config! macro would remain.
Yes, it could be made an asset then, but the |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Jun 14, 2017
Member
Speaking of, do you have any plans to include hot reloading of modified assets eventually?
That would be a great feature! But there is so much else to figure out. This is much harder than I thought from a design point of view.
That would be a great feature! But there is so much else to figure out. This is much harder than I thought from a design point of view. |
torkleyy
added some commits
Jun 13, 2017
| + let name = name.into(); | ||
| + | ||
| + let store_id = storage.store_id(); | ||
| + let spec = AssetSpec::new(name.clone(), F::extension(), store_id); |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
omni-viral
Jun 14, 2017
Member
I'd like to avoid allocations (String::clone) in case of getting cached asset
omni-viral
Jun 14, 2017
Member
I'd like to avoid allocations (String::clone) in case of getting cached asset
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| +/// methods for loading | ||
| +pub trait Store { | ||
| + type Error: Error + 'static; | ||
| + type Location; |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Jun 14, 2017
Member
Thanks for so much feedback so far! I'll continue development soon; no need to review in that time, I may have already fixed it. I'll tell you when it's ready!
|
Thanks for so much feedback so far! I'll continue development soon; no need to review in that time, I may have already fixed it. I'll tell you when it's ready! |
ebkalderon
reviewed
Jun 15, 2017
Keep up the good work, @torkleyy. Just added a few more comments and questions.
| +use AssetSpec; | ||
| + | ||
| +/// Error type returned when loading an asset. | ||
| +/// Includes the `AssetSpec` and the error (`LoadErrorPure`). |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ebkalderon
Jun 15, 2017
Member
Thanks for addressing my concerns, @torkleyy. Don't forget to correct this doc comment!
ebkalderon
Jun 15, 2017
Member
Thanks for addressing my concerns, @torkleyy. Don't forget to correct this doc comment!
| + .map_err(LoadError::StorageError)?; | ||
| + let data = format.import(bytes).map_err(LoadError::FormatError)?; | ||
| + let a = Asset::from_data(data, context) | ||
| + .map_err(LoadError::AssetError)?; |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ebkalderon
Jun 15, 2017
Member
To avoid these .map_err()s everywhere, I would recommend implementing From<Whatever> for LoadError.
ebkalderon
Jun 15, 2017
Member
To avoid these .map_err()s everywhere, I would recommend implementing From<Whatever> for LoadError.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Jun 16, 2017
Member
That's not possible; I didn't define the error type I'm getting an instance of here, it's an associated type implementing Error.
torkleyy
Jun 16, 2017
Member
That's not possible; I didn't define the error type I'm getting an instance of here, it's an associated type implementing Error.
| +/// Used as a placeholder for associated error types if | ||
| +/// something cannot fail. | ||
| +#[derive(Debug)] | ||
| +pub enum NoError {} |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ebkalderon
Jun 15, 2017
Member
I don't have access to a computer right now, but could the usage of this placeholder error be viably replaced with ()?
ebkalderon
Jun 15, 2017
Member
I don't have access to a computer right now, but could the usage of this placeholder error be viably replaced with ()?
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
brendanzab
Jul 13, 2017
It's not really a phantom type. It's a void or 'bottom' type - ie. an enum with no variants. The neat thing about it is that it is impossible to create at run time, unlike () which can be created exactly one time - ie. using (). If you used () you would have to sprinkle around unreacable!()s in a bunch of places, where as with an empty enum the compiler will allow you to return from an impossible-to-call function by doing match impossible {}.
brendanzab
Jul 13, 2017
It's not really a phantom type. It's a void or 'bottom' type - ie. an enum with no variants. The neat thing about it is that it is impossible to create at run time, unlike () which can be created exactly one time - ie. using (). If you used () you would have to sprinkle around unreacable!()s in a bunch of places, where as with an empty enum the compiler will allow you to return from an impossible-to-call function by doing match impossible {}.
| + | ||
| + /// Notifies about an asset load. This is can be used to cache the asset. | ||
| + /// To return a cached asset, see the `cached` function. | ||
| + fn asset_loaded(_context: &Self::Context, _spec: AssetSpec, _asset: &Self) {} |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ebkalderon
Jun 15, 2017
Member
I don't quite understand from the doc comment what you mean by "notifies about an asset load." Notifies whom? What do you mean by an asset load (from disk, from Store, from a cache)?
Also, if the point of this method is to cache an asset, perhaps you could rename this method to something like cache() and then rename the method below to retrieve()?
ebkalderon
Jun 15, 2017
Member
I don't quite understand from the doc comment what you mean by "notifies about an asset load." Notifies whom? What do you mean by an asset load (from disk, from Store, from a cache)?
Also, if the point of this method is to cache an asset, perhaps you could rename this method to something like cache() and then rename the method below to retrieve()?
| @@ -0,0 +1,201 @@ | ||
| + Apache License |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ebkalderon
Jun 15, 2017
Member
Looking at gfx and Servo as an example, the subcrates do not each contain licenses, presumably because the license files found at the repository root apply to all source code in the tree. But then again, IANAL.
ebkalderon
Jun 15, 2017
Member
Looking at gfx and Servo as an example, the subcrates do not each contain licenses, presumably because the license files found at the repository root apply to all source code in the tree. But then again, IANAL.
| + | ||
| +impl AssetSpec { | ||
| + /// Creates a new asset specifier from the given parameters. | ||
| + pub fn new(name: String, ext: &'static str, store: StoreId) -> Self { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ebkalderon
Jun 15, 2017
Member
Would you be able to make this an Into<String> as well? I think I missed this on the last review.
ebkalderon
Jun 15, 2017
Member
Would you be able to make this an Into<String> as well? I think I missed this on the last review.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Jun 16, 2017
Member
It's only used in the code of this crate, so I don't think it's necessary.
torkleyy
Jun 16, 2017
Member
It's only used in the code of this crate, so I don't think it's necessary.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| +{ | ||
| + /// Creates a new `Cache` and initializes it with the default values. | ||
| + pub fn new() -> Self { | ||
| + Default::default() |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ebkalderon
Jun 15, 2017
Member
Shouldn't you #[derive(Default)] and then replace this line with Self::default()? Or would that complicate things with T if T doesn't implement Default?
ebkalderon
Jun 15, 2017
Member
Shouldn't you #[derive(Default)] and then replace this line with Self::default()? Or would that complicate things with T if T doesn't implement Default?
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Jun 16, 2017
Member
Self::defaultandDefault::defaultare the same here- derive doesn't work here because of exactly that reason
torkleyy
Jun 16, 2017
Member
Self::defaultandDefault::defaultare the same here- derive doesn't work here because of exactly that reason
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Xaeroxe
Jun 16, 2017
Member
So it looks like some of my work might end up having a merge conflict with this, I'm specifically working on key rebinding. I'd like to make the key rebinding PR sometime between 2017-06-17 & 2017-06-18. I'm thinking that the "default" keybinding should probably be an asset, with the active player rebinding file being a config file, similar to our display config files.
This PR kind of looks like it's in the thick of iteration, so I don't think it'll be merged by the time the key rebinding functionality is ready. Should I just integrate that default file with the existing asset system? I could also make a PR to the branch that is hosting this PR to give it the same functionality I'd be giving the old asset management system.
|
So it looks like some of my work might end up having a merge conflict with this, I'm specifically working on key rebinding. I'd like to make the key rebinding PR sometime between 2017-06-17 & 2017-06-18. I'm thinking that the "default" keybinding should probably be an asset, with the active player rebinding file being a config file, similar to our display config files. This PR kind of looks like it's in the thick of iteration, so I don't think it'll be merged by the time the key rebinding functionality is ready. Should I just integrate that default file with the existing asset system? I could also make a PR to the branch that is hosting this PR to give it the same functionality I'd be giving the old asset management system. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Jun 17, 2017
Member
@Xaeroxe I'm not sure because I don't know how much this PR will change. I don't expect it to change too much though, I guess the main part I'll change is the Loader, so I think you should be able to base your key bindings on the Format and Asset traits.
|
@Xaeroxe I'm not sure because I don't know how much this PR will change. I don't expect it to change too much though, I guess the main part I'll change is the |
nchashch
self-requested a review
Jun 19, 2017
Xaeroxe
reviewed
Jun 21, 2017
Looks great! Very robust and re-usable code here. I had a few nits and chimed in on some existing conversation. Thanks for your work!
| + } | ||
| + | ||
| + /// Inserts an asset, locking the internal `RwLock` to get write access to the hash map. | ||
| + pub fn insert(&self, spec: AssetSpec, asset: T) { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Xaeroxe
Jun 21, 2017
Member
If a value exists for the given key in a HashMap the insert operation will return the value it replaced. It might be helpful to have this function do the same, so that users of this function can identify when they did a redundant insert, and modify their code to not do those redundant inserts. It's certainly not necessary, it's just helpful for debugging.
Xaeroxe
Jun 21, 2017
Member
If a value exists for the given key in a HashMap the insert operation will return the value it replaced. It might be helpful to have this function do the same, so that users of this function can identify when they did a redundant insert, and modify their code to not do those redundant inserts. It's certainly not necessary, it's just helpful for debugging.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Jun 22, 2017
Member
Well, why would somebody check the value? The insert will only be called if there hasn't been a cached version.
torkleyy
Jun 22, 2017
Member
Well, why would somebody check the value? The insert will only be called if there hasn't been a cached version.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Xaeroxe
Jun 22, 2017
Member
I guess the "somebody" would be us, in the event we ever have to modify how this function is called.
Xaeroxe
Jun 22, 2017
Member
I guess the "somebody" would be us, in the event we ever have to modify how this function is called.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + | ||
| + /// Retrieves an asset, locking the internal `RwLock` to get read access to the hash map. | ||
| + /// In case this asset has been inserted previously, it will be cloned and returned. | ||
| + /// Otherwise, you'll `None`. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Xaeroxe
Jun 21, 2017
Member
Doc comment is slightly bad english, should be "Otherwise, you'll receive None.
Xaeroxe
Jun 21, 2017
Member
Doc comment is slightly bad english, should be "Otherwise, you'll receive None.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + /// * `"png"` | ||
| + /// * `"obj"` | ||
| + /// * `"wav"` | ||
| + fn extension() -> &'static str; |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Xaeroxe
Jun 21, 2017
Member
Personally I think we shouldn't support files with no extension. Not all formats/files have header data and extensions are a nice way to make some sane assumptions about the underlying data.
Xaeroxe
Jun 21, 2017
Member
Personally I think we shouldn't support files with no extension. Not all formats/files have header data and extensions are a nice way to make some sane assumptions about the underlying data.
| + fn extension() -> &'static str; | ||
| + | ||
| + /// Imports the given bytes and produces asset data. | ||
| + fn import(&self, bytes: Vec<u8>) -> Result<Self::Data, Self::Error>; |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Xaeroxe
Jun 21, 2017
Member
Some in-memory asset data structures (I might even argue most) won't need to store the source data contained on disk. So it'd be nice to pass in a slice here and let the implementation clone the data if it needs to. Having to clone the vec when one was already allocated is an unfortunate downside to this but I personally think it's the correct approach.
Xaeroxe
Jun 21, 2017
Member
Some in-memory asset data structures (I might even argue most) won't need to store the source data contained on disk. So it'd be nice to pass in a slice here and let the implementation clone the data if it needs to. Having to clone the vec when one was already allocated is an unfortunate downside to this but I personally think it's the correct approach.
| + | ||
| + fn description(&self) -> &str { | ||
| + match *self { | ||
| + LoadError::AssetError(_) => "Failed to load asset", |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Xaeroxe
Jun 21, 2017
Member
Failed to do thing type error messages have been very frustrating for me to debug in the past, due to the lack of additional information. Perhaps we could also append the description() for the inner value? That would help this a bit.
Xaeroxe
Jun 21, 2017
Member
Failed to do thing type error messages have been very frustrating for me to debug in the past, due to the lack of additional information. Perhaps we could also append the description() for the inner value? That would help this a bit.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Aceeri
Jun 21, 2017
Member
The error description method is extremely unhelpful. Normal use cases would just use a Debug implementation because here you need a reference to a string, which makes it impossible to create a new string other than one stored inside the structure or is a static string.
Aceeri
Jun 21, 2017
Member
The error description method is extremely unhelpful. Normal use cases would just use a Debug implementation because here you need a reference to a string, which makes it impossible to create a new string other than one stored inside the structure or is a static string.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Xaeroxe
Jun 21, 2017
Member
Yeah you're right. These thoughts started occurring to me after I made my comment of course :P
Xaeroxe
Jun 21, 2017
Member
Yeah you're right. These thoughts started occurring to me after I made my comment of course :P
| + { | ||
| + let context = self.contexts | ||
| + .get(&TypeId::of::<A>()) | ||
| + .expect("Assets need to be registered"); |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Xaeroxe
Jun 21, 2017
Member
I feel like panic is a good thing to do here, mainly because if a problem causes code to be fundamentally broken it's important to have high visibility on it in debugging.
Xaeroxe
Jun 21, 2017
Member
I feel like panic is a good thing to do here, mainly because if a problem causes code to be fundamentally broken it's important to have high visibility on it in debugging.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Aceeri
Jun 21, 2017
Member
No idea why, but I can't comment on:
I feel like panic is a good thing to do here, mainly because if a problem causes code to be fundamentally broken it's important to have high visibility on it in debugging.
Anyways, expect does panic, it is basically a match of:
match result {
Ok(ok) => ok,
Err(_) => panic!(msg),
}|
No idea why, but I can't comment on:
Anyways, match result {
Ok(ok) => ok,
Err(_) => panic!(msg),
} |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Xaeroxe
Jun 21, 2017
Member
@Aceeri yeah that comment is out of context thanks to GitHub. I was responding to @ebkalderon.
|
@Aceeri yeah that comment is out of context thanks to GitHub. I was responding to @ebkalderon. |
bot
added a commit
to slide-rs/specs
that referenced
this pull request
Jul 2, 2017
torkleyy
added this to the 0.5.0 milestone
Jul 4, 2017
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
Blocked on rayon-rs/rayon#344 |
| + | ||
| + let closure = move || load_asset::<A, F, _, S>(&*context, &format, name, &*storage); | ||
| + | ||
| + AssetFuture(thread_pool.spawn_future_async(lazy(closure))) |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
lschmierer
Jul 12, 2017
Member
Mayber return a Stream for hot-reloading support later?
For noe we can simply use
futures::stream::futures_ordered
http://alexcrichton.com/futures-rs/futures/stream/fn.futures_ordered.html
lschmierer
Jul 12, 2017
Member
Mayber return a Stream for hot-reloading support later?
For noe we can simply use
futures::stream::futures_ordered
http://alexcrichton.com/futures-rs/futures/stream/fn.futures_ordered.html
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Jul 12, 2017
Member
Interesting idea..I have to think about it. Just keep in mind that hot-reloading is more a feature for development, so it's not necessary for the final game.
torkleyy
Jul 12, 2017
Member
Interesting idea..I have to think about it. Just keep in mind that hot-reloading is more a feature for development, so it's not necessary for the final game.
torkleyy
added some commits
Jul 12, 2017
| +/// Represents a future value of an asset. This future may be | ||
| +/// added to the ECS world, where the responsible system can poll it and merge | ||
| +/// it into the persistent storage once it is `Ready`. | ||
| +pub struct AssetFuture<A, E> { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Jul 12, 2017
Member
So there is something wrong about AssetFuture. Calling Future::wait on it just blocks forever...
Fixed now.
torkleyy
Jul 12, 2017
•
Member
So there is something wrong about AssetFuture. Calling Future::wait on it just blocks forever...
Fixed now.
torkleyy
added some commits
Jul 12, 2017
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Jul 12, 2017
Member
Okay, as discussed with @Xaeroxe, this PR should be merged after the core functionality is implemented, which it is. Stuff like hot reloading will be implemented later.
So this PR is ready for review now.
|
Okay, as discussed with @Xaeroxe, this PR should be merged after the core functionality is implemented, which it is. Stuff like hot reloading will be implemented later. So this PR is ready for review now. |
torkleyy
requested review from
ebkalderon and
Xaeroxe
Jul 12, 2017
Xaeroxe
reviewed
Jul 12, 2017
Looks good to me for the most part, I had a few nits on the internals but I like the API. Thanks!
| + | ||
| +impl Display for NoError { | ||
| + fn fmt(&self, _: &mut Formatter) -> FmtResult { | ||
| + match *self {} |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Xaeroxe
Jul 12, 2017
Member
This is a bizarre syntactic construct. I'm kind of surprised it even compiles. This appears to return () in a function which should be returning FmtResult. According to rust-lang/rust#24590 this appears to be undefined behavior. Is there any way we can improve upon this? Return some kind of default useless value maybe? This might be a pain point in the future.
Xaeroxe
Jul 12, 2017
Member
This is a bizarre syntactic construct. I'm kind of surprised it even compiles. This appears to return () in a function which should be returning FmtResult. According to rust-lang/rust#24590 this appears to be undefined behavior. Is there any way we can improve upon this? Return some kind of default useless value maybe? This might be a pain point in the future.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Jul 12, 2017
Member
I could not find the issue saying it's undefined behavior. I'd actually see it's very clearly defined and makes sense - NoError can never be instantiated, and this empty match perfectly reflects that.
torkleyy
Jul 12, 2017
Member
I could not find the issue saying it's undefined behavior. I'd actually see it's very clearly defined and makes sense - NoError can never be instantiated, and this empty match perfectly reflects that.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Jul 12, 2017
Member
The undefined behavior is still instantiating NoError, not the empty match.
torkleyy
Jul 12, 2017
•
Member
The undefined behavior is still instantiating NoError, not the empty match.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Xaeroxe
Jul 12, 2017
Member
I still think we should change it primarily because this could in future Rust versions lead to E0308. This statement appears to return (), not FmtResult. I feel something like Ok(()) is much less likely to break in the future.
Xaeroxe
Jul 12, 2017
Member
I still think we should change it primarily because this could in future Rust versions lead to E0308. This statement appears to return (), not FmtResult. I feel something like Ok(()) is much less likely to break in the future.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Xaeroxe
Jul 12, 2017
Member
Basically I'm pretty sure the only reason this compiles is because the compiler optimizes out this implementation in the final binary. In my opinion that's a bug in rustc and we shouldn't rely on bugs in our compiler.
Xaeroxe
Jul 12, 2017
Member
Basically I'm pretty sure the only reason this compiles is because the compiler optimizes out this implementation in the final binary. In my opinion that's a bug in rustc and we shouldn't rely on bugs in our compiler.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
brendanzab
Jul 13, 2017
The reason it compiles is because NoError has no variants, and thus can never be created. This means that it is impossible for fmt to ever be called! You do have to figure out a way to return a value from the function though. To do this you use the funny looking match on no cases. In the future we will have the never type, ! (under the #[feature(never_type)] flag). This basically gives us:
enum ! {}I think it will impl a bunch of core traits for cases like this. Using a NoError type in the interim seems like a reasonable thing to do though though.
See https://github.com/rust-lang/rfcs/blob/master/text/1216-bang-type.md
Putting unreachable!() is completely unnecessary, because the code can never be compiled. This is what a great type system allows you to do!
brendanzab
Jul 13, 2017
•
The reason it compiles is because NoError has no variants, and thus can never be created. This means that it is impossible for fmt to ever be called! You do have to figure out a way to return a value from the function though. To do this you use the funny looking match on no cases. In the future we will have the never type, ! (under the #[feature(never_type)] flag). This basically gives us:
enum ! {}I think it will impl a bunch of core traits for cases like this. Using a NoError type in the interim seems like a reasonable thing to do though though.
See https://github.com/rust-lang/rfcs/blob/master/text/1216-bang-type.md
Putting unreachable!() is completely unnecessary, because the code can never be compiled. This is what a great type system allows you to do!
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
brendanzab
Jul 13, 2017
Think about it. Say if I have:
enum Impossible {}
fn foo(impossible Impossible) -> Vec<String> {
// ...
}How do I ever call foo?
let vec = foo(Impossible::/*ugghhhhh*/);Nothing you ever write, will ever satisfy the compiler. Therefore the compiler will allow you to do crazy things like summoning a vector from nothing, because it knows the function can never be called:
fn foo(impossible Impossible) -> Vec<String> {
match impossible {}
}This is a super useful tool when using other folk's API's that take type parameters, or associated types, like the futures crate. It allows you to tell the compiler at the type level that, 'this future will always succeed'. Eg.
fn no_unwrapping_wait<F: Future<Error = Impossible>>(f: F) -> F::Item {
match f.wait() {
Ok(item) => item,
}
}Note that there is no Err case, because that is impossible to get to, and is hence ruled out by the compiler.
brendanzab
Jul 13, 2017
•
Think about it. Say if I have:
enum Impossible {}
fn foo(impossible Impossible) -> Vec<String> {
// ...
}How do I ever call foo?
let vec = foo(Impossible::/*ugghhhhh*/);Nothing you ever write, will ever satisfy the compiler. Therefore the compiler will allow you to do crazy things like summoning a vector from nothing, because it knows the function can never be called:
fn foo(impossible Impossible) -> Vec<String> {
match impossible {}
}This is a super useful tool when using other folk's API's that take type parameters, or associated types, like the futures crate. It allows you to tell the compiler at the type level that, 'this future will always succeed'. Eg.
fn no_unwrapping_wait<F: Future<Error = Impossible>>(f: F) -> F::Item {
match f.wait() {
Ok(item) => item,
}
}Note that there is no Err case, because that is impossible to get to, and is hence ruled out by the compiler.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Xaeroxe
Jul 13, 2017
Member
Yeah that was more or less the conclusion I came to below. I'm content with this solution.
Xaeroxe
Jul 13, 2017
Member
Yeah that was more or less the conclusion I came to below. I'm content with this solution.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
brendanzab
Jul 13, 2017
Yeah, sorry, missed your update!
brendanzab
Jul 13, 2017
Yeah, sorry, missed your update!
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Aceeri
Jul 13, 2017
Member
Was thinking for some reason that the enum could be instantiated similar to a zero-sized struct.
Aceeri
Jul 13, 2017
•
Member
Was thinking for some reason that the enum could be instantiated similar to a zero-sized struct.
| + | ||
| +impl Error for NoError { | ||
| + fn description(&self) -> &str { | ||
| + match *self {} |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + } | ||
| +} | ||
| + | ||
| +struct AssetFutureInner<A, E> { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Xaeroxe
requested changes
Jul 12, 2017
•
I'm leaving my review status as "Request changes" until the empty match statements I commented on earlier are replaced. I am otherwise very eager to merge this.
Xaeroxe
approved these changes
Jul 13, 2017
So @torkleyy wrote this: https://www.reddit.com/r/rust/comments/6mwkqk/empty_matches/ and the comments on here actually make a pretty convincing case so I'm going to drop my complaint from before.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Xaeroxe
Jul 13, 2017
Member
Requesting review from @ebkalderon and @nchashch. I'd like to prioritize getting this merged as it's blocking rather a lot of work.
|
Requesting review from @ebkalderon and @nchashch. I'd like to prioritize getting this merged as it's blocking rather a lot of work. |
| + fn from_data(data: Self::Data, context: &Self::Context) -> Result<Self, Self::Error>; | ||
| + | ||
| + /// Notifies about an asset load. This is can be used to cache the asset. | ||
| + /// To return a cached asset, see the `cached` function. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
lschmierer
Jul 13, 2017
Member
As the crate name is amethyst_assets, the folder should probably called amethyst_assets, too.
Like amethyst_renderer on @ebkalderon's renderer PR.
|
As the crate name is Like |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
At least some tetsts would be nice. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Jul 13, 2017
Member
@lschmierer I know that, but I thought that at this stage we would like to proceed a bit faster because changes are rather big atm. But I agree - I'll definitely write some before we release a new version!
|
@lschmierer I know that, but I thought that at this stage we would like to proceed a bit faster because changes are rather big atm. But I agree - I'll definitely write some before we release a new version! |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Jul 13, 2017
Member
As the crate name is amethyst_assets, the folder should probably called amethyst_assets, too.
But why prefix everything with amethyst_? I mean that's already clear from the repository the folder is in, isn't it?
OTOTH, consistency would be nice. So maybe @ebkalderon could rename the folder to just renderer?
But why prefix everything with OTOTH, consistency would be nice. So maybe @ebkalderon could rename the folder to just |
Aceeri
requested changes
Jul 14, 2017
Looks pretty nice to me, just a couple nitpicks about documentation and a couple of questions.
| + let pool = Arc::new(ThreadPool::new(cfg).expect("Invalid config")); | ||
| + | ||
| + let alloc = Allocator::new(); | ||
| + let mut loader = Loader::new(&alloc, "examples/assets", pool); |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Aceeri
Jul 14, 2017
Member
Might be better to do
format!("{}/examples/assets", env!("CARGO_MANIFEST_DIR"));here so running the example doesn't require building from the crate root.
Aceeri
Jul 14, 2017
Member
Might be better to do
format!("{}/examples/assets", env!("CARGO_MANIFEST_DIR"));here so running the example doesn't require building from the crate root.
| +/// A specifier for an asset, uniquely identifying it by | ||
| +/// | ||
| +/// * the extension (the format it was provided in) | ||
| +/// * it's name |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + pub ext: &'static str, | ||
| + /// The name of this asset. | ||
| + pub name: String, | ||
| + /// The storage id, uniquely identifying the storage it's been loaded from. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + fn extension() -> &'static str; | ||
| + | ||
| + /// Reads the given bytes and produces asset data. | ||
| + fn parse(&self, bytes: Vec<u8>) -> Result<Self::Data, Self::Error>; |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Aceeri
Jul 14, 2017
Member
I feel like there was a discussion about this earlier, but I can't find it. Would it be better for this to take in a &'a [u8]? I'd assume that this wouldn't be modifying the bytes passed in at least.
Aceeri
Jul 14, 2017
Member
I feel like there was a discussion about this earlier, but I can't find it. Would it be better for this to take in a &'a [u8]? I'd assume that this wouldn't be modifying the bytes passed in at least.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Xaeroxe
Jul 14, 2017
Member
There was a discussion on this but Github marked it outdated due to other changes.
No it would not be better to take a slice, primarily because the bytes would be dropped after this function is called and for some very simple asset types the bytes themselves might be part of the asset, so passing in a Vec here allows us to save an allocation in some instances, and is harmless in others.
Xaeroxe
Jul 14, 2017
Member
There was a discussion on this but Github marked it outdated due to other changes.
No it would not be better to take a slice, primarily because the bytes would be dropped after this function is called and for some very simple asset types the bytes themselves might be part of the asset, so passing in a Vec here allows us to save an allocation in some instances, and is harmless in others.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ebkalderon
Jul 19, 2017
Member
Do you plan to make this and the other similar methods accept a stream instead of a Vec before merging?
ebkalderon
Jul 19, 2017
Member
Do you plan to make this and the other similar methods accept a stream instead of a Vec before merging?
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + } | ||
| + } | ||
| + | ||
| + pub fn add_store<I, S>(&mut self, name: I, store: S) |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Jul 14, 2017
Member
Oh yeah, probably, but I'd like every item in the crate to have docs. Should enable that lint.
torkleyy
Jul 14, 2017
Member
Oh yeah, probably, but I'd like every item in the crate to have docs. Should enable that lint.
| + .insert(TypeId::of::<A>(), Box::new(Arc::new(context))); | ||
| + } | ||
| + | ||
| + /// Like `load_from`, but doesn't ask the cache for a |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + /// | ||
| + /// # Panics | ||
| + /// | ||
| + /// Panics if the asset wasn't registered. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Aceeri
Jul 14, 2017
Member
Is this ideal? Are assets statically registered similar to components/resources in specs or can they be unregistered and such?
Aceeri
Jul 14, 2017
Member
Is this ideal? Are assets statically registered similar to components/resources in specs or can they be unregistered and such?
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Xaeroxe
Jul 14, 2017
Member
Assets can't be unregistered and with this system are intended to be statically registered, so I think this is fine.
Xaeroxe
Jul 14, 2017
Member
Assets can't be unregistered and with this system are intended to be statically registered, so I think this is fine.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Jul 14, 2017
Member
Yeah it's fine, not ideal, however. The problem is that registering can only work mutably, but loading an asset should be immutable. So they cannot be auto-registered or smth like that.
torkleyy
Jul 14, 2017
Member
Yeah it's fine, not ideal, however. The problem is that registering can only work mutably, but loading an asset should be immutable. So they cannot be auto-registered or smth like that.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Aceeri
Jul 15, 2017
Member
Is it just an unregistered asset format? Or do you also need to register each asset themselves for it to not panic?
Aceeri
Jul 15, 2017
Member
Is it just an unregistered asset format? Or do you also need to register each asset themselves for it to not panic?
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Xaeroxe
Jul 15, 2017
Member
You must register asset formats before loading an asset of that format. You are not required to register the individual assets only the formats which they take.
Xaeroxe
Jul 15, 2017
Member
You must register asset formats before loading an asset of that format. You are not required to register the individual assets only the formats which they take.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Jul 15, 2017
Member
You register every type that implements Asset (in case you want to load it at some point).
torkleyy
Jul 15, 2017
Member
You register every type that implements Asset (in case you want to load it at some point).
| + } | ||
| +} | ||
| + | ||
| +pub trait AnyStore: Send + Sync + 'static { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + | ||
| +/// Like `load_asset`, but loads the asset on a worker thread and returns | ||
| +/// an `AssetFuture` immediately. | ||
| +pub fn reload_asset_future<A, F, N, S> |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Aceeri
Jul 14, 2017
Member
Should these reload methods specify the difference between what they do and just the regular load methods?
Aceeri
Jul 14, 2017
Member
Should these reload methods specify the difference between what they do and just the regular load methods?
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
added some commits
Jul 16, 2017
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Jul 16, 2017
Member
Okay, so I see there are still things left (like streams for the storages, hot reloading, implementation of asset formats, ...), but I think we can still merge this for now. That way @Xaeroxe can already build audio stuff, and @ebkalderon rendering assets. If there are critical issues to fix before merging, feel free to push to my branch, because I won't have access to my computer for the next few days.
|
Okay, so I see there are still things left (like streams for the storages, hot reloading, implementation of asset formats, ...), but I think we can still merge this for now. That way @Xaeroxe can already build audio stuff, and @ebkalderon rendering assets. If there are critical issues to fix before merging, feel free to push to my branch, because I won't have access to my computer for the next few days. |
ebkalderon
requested changes
Jul 19, 2017
Terrific job @torkleyy! Thank you for addressing pretty much all the concerns raised. Almost ready to merge, I think, though there are a few final nitpicks left to address.
| + pub ext: &'static str, | ||
| + /// The name of this asset. | ||
| + pub name: String, | ||
| + /// The storage id, uniquely identifying the storage it's been loaded from. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ebkalderon
Jul 19, 2017
Member
This comment sounds a tad awkward. Perhaps it could read instead:
/// Unique identifier indicating the Storage from which the asset was loaded.
ebkalderon
Jul 19, 2017
Member
This comment sounds a tad awkward. Perhaps it could read instead:
/// Unique identifier indicating the Storage from which the asset was loaded.
| + fn extension() -> &'static str; | ||
| + | ||
| + /// Reads the given bytes and produces asset data. | ||
| + fn parse(&self, bytes: Vec<u8>) -> Result<Self::Data, Self::Error>; |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ebkalderon
Jul 19, 2017
Member
Do you plan to make this and the other similar methods accept a stream instead of a Vec before merging?
ebkalderon
Jul 19, 2017
Member
Do you plan to make this and the other similar methods accept a stream instead of a Vec before merging?
| +description = """ | ||
| +Asynchronous asset management for games. | ||
| +""" | ||
| + |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ebkalderon
Jul 19, 2017
Member
To keep amethyst_assets consistent with other crates, please don't forget to add:
license = "MIT/Apache-2.0"
keywords = ["game", "asset", "resource", "management", "amethyst"]
categories = ["filesystem"]
documentation = "https://www.amethyst.rs/doc/master/amethyst"
homepage = "https://www.amethyst.rs/"
repository = "https://github.com/amethyst/amethyst"
[badges]
appveyor = { repository = "amethyst/amethyst", branch = "develop" }
travis-ci = { repository = "amethyst/amethyst" }
ebkalderon
Jul 19, 2017
Member
To keep amethyst_assets consistent with other crates, please don't forget to add:
license = "MIT/Apache-2.0"
keywords = ["game", "asset", "resource", "management", "amethyst"]
categories = ["filesystem"]
documentation = "https://www.amethyst.rs/doc/master/amethyst"
homepage = "https://www.amethyst.rs/"
repository = "https://github.com/amethyst/amethyst"
[badges]
appveyor = { repository = "amethyst/amethyst", branch = "develop" }
travis-ci = { repository = "amethyst/amethyst" }| @@ -75,4 +75,4 @@ name = "assets" | ||
| path = "examples/05_assets/main.rs" | ||
| [workspace] | ||
| -members = ["src/renderer"] | ||
| +members = ["assets", "src/renderer"] |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
ebkalderon
Jul 19, 2017
Member
I would prefer to name this folder amethyst_assets simply to differentiate subcrates from the book and src directories. It may look a little verbose, but at least users won't be misled into thinking there are real game assets in that folder, as @lschmierer pointed out in the Gitter chat a while ago.
ebkalderon
Jul 19, 2017
Member
I would prefer to name this folder amethyst_assets simply to differentiate subcrates from the book and src directories. It may look a little verbose, but at least users won't be misled into thinking there are real game assets in that folder, as @lschmierer pointed out in the Gitter chat a while ago.
torkleyy commentedJun 13, 2017
README.mdThis PR contains the core functionality for asset management. This means that e.g. formats or special syntax are to be done.
It also does not contain any tests yet. I'm creating this PR to get some feedback early on.
Replaces #195