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 upAdd asset hot reloading #445
Conversation
torkleyy
added
project: assets
type: feature
labels
Oct 22, 2017
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Oct 22, 2017
Member
So as @Rhuagh pointed out, we need some sort of notification system, but I think it's better to do that in a follow-up.
|
So as @Rhuagh pointed out, we need some sort of notification system, but I think it's better to do that in a follow-up. |
torkleyy
added
the
status: ready
label
Oct 22, 2017
torkleyy
requested review from
Xaeroxe and
Rhuagh
Oct 22, 2017
| struct Ron; | ||
| -impl Format<MeshAsset> for Ron { | ||
| +impl SimpleFormat<MeshAsset> for Ron { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| @@ -25,6 +26,7 @@ impl Loader { | ||
| { | ||
| Loader { | ||
| directory: Arc::new(Directory::new(directory)), | ||
| + hot_reload: true, // TODO: allow disabling |
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
Oct 23, 2017
Member
No, I have to add configs to this; the param is just true to try it out.
torkleyy
Oct 23, 2017
Member
No, I have to add configs to this; the param is just true to try it out.
omni-viral
requested changes
Oct 23, 2017
I like the idea.
But I have concerns about overhead when you don't use the feature.
And I see it's hard to make all this feature-gated.
| - ) -> Result<TextureData, BoxedErr> { | ||
| - imagefmt::bmp::read(&mut Cursor::new(source.load(&name)?), ColFmt::RGBA) | ||
| + fn import(&self, bytes: Vec<u8>, options: TextureMetadata) -> Result<TextureData, BoxedErr> { | ||
| + imagefmt::bmp::read(&mut Cursor::new(bytes), ColFmt::RGBA) |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
omni-viral
Oct 23, 2017
Member
It's better to read BMP file directly into gpu visible memory.
But this can be addressed later. Just put a note, please.
omni-viral
Oct 23, 2017
Member
It's better to read BMP file directly into gpu visible memory.
But this can be addressed later. Just put a note, please.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| fn import( | ||
| &self, | ||
| name: String, | ||
| source: Arc<Source>, | ||
| options: Self::Options, | ||
| - ) -> Result<A::Data, BoxedErr>; | ||
| + create_reload: bool, | ||
| + ) -> Result<(A::Data, Option<Box<Reload<A>>>), BoxedErr>; |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
omni-viral
Oct 23, 2017
Member
This result type looks sophisticated.
Maybe it's worth to make a structure.
omni-viral
Oct 23, 2017
Member
This result type looks sophisticated.
Maybe it's worth to make a structure.
| + source, | ||
| + options, | ||
| + .. | ||
| + } = this; |
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.
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.
| + /// The reload structure has metadata which allows the asset management | ||
| + /// to reload assets if necessary (for hot reloading). | ||
| + /// You should only create this if `create_reload` is `true`. | ||
| + /// Also, the parameter is just a request, which means you can also return `None`. | ||
| fn import( | ||
| &self, | ||
| name: String, |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
omni-viral
Oct 23, 2017
Member
name and options should be passed by reference to avoid unnecessary cloning.
omni-viral
Oct 23, 2017
Member
name and options should be passed by reference to avoid unnecessary cloning.
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
Oct 23, 2017
Member
I'm aware of the fact that an allocation is not desirable, but we can't point to the stack in a multi-threaded system.
torkleyy
Oct 23, 2017
Member
I'm aware of the fact that an allocation is not desirable, but we can't point to the stack in a multi-threaded system.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
omni-viral
Oct 23, 2017
Member
I don't follow. If you use those only locally in function (or nested function calls). It's perfectly ok to pass them as references. And if you need to save them for future-use (In returned Reload) you clone value and store it.
omni-viral
Oct 23, 2017
Member
I don't follow. If you use those only locally in function (or nested function calls). It's perfectly ok to pass them as references. And if you need to save them for future-use (In returned Reload) you clone value and store it.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Oct 23, 2017
Member
Please point me to the exact location where I could just pass a reference.
torkleyy
Oct 23, 2017
•
Member
Please point me to the exact location where I could just pass a reference.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
omni-viral
Oct 23, 2017
Member
name and options in Format::import and options in SimpleFormat.
And then in impl Reload for SingleFile you can reuse SingleFile value instead of creating new one
omni-viral
Oct 23, 2017
Member
name and options in Format::import and options in SimpleFormat.
And then in impl Reload for SingleFile you can reuse SingleFile value instead of creating new one
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + .. | ||
| + } = this; | ||
| + | ||
| + format.import(path, source, options, true) |
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
Oct 23, 2017
Member
No, imagine a GLTF file which points to other files; in case the GLTF file gets reloaded, new files might have been added which we also want to track.
torkleyy
Oct 23, 2017
Member
No, imagine a GLTF file which points to other files; in case the GLTF file gets reloaded, new files might have been added which we also want to track.
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.
| + /// Returns the asset name. | ||
| + fn name(&self) -> String; | ||
| + /// Returns the format name. | ||
| + fn format(&self) -> String; |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
omni-viral
Oct 23, 2017
Member
All formats has it's name as associated &'static str. So you can contain and return &'static str.
omni-viral
Oct 23, 2017
Member
All formats has it's name as associated &'static str. So you can contain and return &'static str.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Oct 23, 2017
Member
I had this as &'static str first, but changed it back for an unknown reason. I'll try to change this later.
torkleyy
Oct 23, 2017
Member
I had this as &'static str first, but changed it back for an unknown reason. I'll try to change this later.
| @@ -30,9 +33,11 @@ impl Allocator { | ||
| pub struct AssetStorage<A: Asset> { | ||
| assets: VecStorage<A>, | ||
| bitset: BitSet, | ||
| - handles: Vec<Handle<A>>, | ||
| + handles: Vec<WeakHandle<A>>, |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
omni-viral
Oct 23, 2017
Member
No need for using Weak inside AssetStorage
- Upgrading
Weakhas more overhead than cloningArc. There is possibly infinite loop with CAS. - You get nothing from
Weakas you store simpleu32and you don't have to drop it asap to free some resource. No memory get freed until you drop lastWeak. - And then you have to allocate new
Arcafter you finally dropWeak.
Using WeakHandle outside of AssetStorage may make some sense though.
omni-viral
Oct 23, 2017
Member
No need for using Weak inside AssetStorage
- Upgrading
Weakhas more overhead than cloningArc. There is possibly infinite loop with CAS. - You get nothing from
Weakas you store simpleu32and you don't have to drop it asap to free some resource. No memory get freed until you drop lastWeak. - And then you have to allocate new
Arcafter you finally dropWeak.
Using WeakHandle outside of AssetStorage may make some sense though.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Oct 23, 2017
Member
The biggest reason I used WeakHandle here is for consistency, since the AssetStorage does not in fact own a handle (logically). And for reloads it is indeed necessary.
torkleyy
Oct 23, 2017
Member
The biggest reason I used WeakHandle here is for consistency, since the AssetStorage does not in fact own a handle (logically). And for reloads it is indeed necessary.
| - while let Some(i) = self.handles.iter().position(Handle::is_unused) { | ||
| - let old = self.handles.swap_remove(i); | ||
| - let id = i as u32; | ||
| + while let Some(i) = self.handles.iter().position(Handle::is_unique) { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
omni-viral
Oct 23, 2017
Member
You shouldn't search from start each time.
Try this.
let mut iter = self.handles.iter();
let mut i = 0;
while let Some(next) = iter.by_ref().position(Handle::is_unique) {
i += next;
omni-viral
Oct 23, 2017
Member
You shouldn't search from start each time.
Try this.
let mut iter = self.handles.iter();
let mut i = 0;
while let Some(next) = iter.by_ref().position(Handle::is_unique) {
i += next;
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Oct 23, 2017
Member
Sorry, I was being stupid during refactoring.
I'm not sure your solution works, but I'll change this. Anyway, I plan on changing that cleanup code as to make it more efficient.
torkleyy
Oct 23, 2017
•
Member
Sorry, I was being stupid during refactoring.
I'm not sure your solution works, but I'll change this. Anyway, I plan on changing that cleanup code as to make it more efficient.
| - self.unused_handles.push(old); | ||
| + | ||
| + // Can't reuse old handle here, because otherwise weak handles would still be valid. | ||
| + // TODO: maybe just store u32? |
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.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Oct 23, 2017
Member
I think what you're saying is that because I changed it back to Handle, I can reuse the handles again?
I don't think we'd want that, because:
- Handle A gets allocated
- Handle A has a
downgradedversion in a cache - Handle A gets dropped
- Handle A gets added to the
unused_handlesqueue - Another allocation is required;
unused_handlesgives us the "old" handle A - The cached value we stored before (using
WeakHandle) is now valid again
That's why I added that comment and recreated the handle. Did a make a mistake in my reasoning?
torkleyy
Oct 23, 2017
Member
I think what you're saying is that because I changed it back to Handle, I can reuse the handles again?
I don't think we'd want that, because:
- Handle A gets allocated
- Handle A has a
downgradedversion in a cache - Handle A gets dropped
- Handle A gets added to the
unused_handlesqueue - Another allocation is required;
unused_handlesgives us the "old" handle A - The cached value we stored before (using
WeakHandle) is now valid again
That's why I added that comment and recreated the handle. Did a make a mistake in my reasoning?
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + } | ||
| + | ||
| + // Reload every seconds | ||
| + // TODO: more configuration |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
omni-viral
Oct 23, 2017
Member
You can add more details to TODO.
Configure timeout.
Configure automatic or only manual with special method in AssetStorage.
omni-viral
Oct 23, 2017
Member
You can add more details to TODO.
Configure timeout.
Configure automatic or only manual with special method in AssetStorage.
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.
| - pub format: String, | ||
| - pub handle: Handle<A>, | ||
| - pub name: String, | ||
| +pub enum Processed<A: Asset> { |
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
Oct 23, 2017
Member
I think I misunderstood your question. Yes, Processed needs to be pub, because Loader creates such a structure. It's not exported at crate root, though.
torkleyy
Oct 23, 2017
•
Member
I think I misunderstood your question. Yes, Processed needs to be pub, because Loader creates such a structure. It's not exported at crate root, though.
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
Oct 23, 2017
Member
There's no need for that and besides, I think that syntax is horrible. I try not to use it where possible.
torkleyy
Oct 23, 2017
Member
There's no need for that and besides, I think that syntax is horrible. I try not to use it where possible.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Xaeroxe
Oct 23, 2017
Member
Why? It's very straightforward and obvious. You don't need to look at other files to figure out it's not actually pub, and pub(crate) permits easy re-use of internal structures.
Xaeroxe
Oct 23, 2017
Member
Why? It's very straightforward and obvious. You don't need to look at other files to figure out it's not actually pub, and pub(crate) permits easy re-use of internal structures.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
jojolepro
Oct 23, 2017
Collaborator
So as @Rhuagh pointed out, we need some sort of notification system, but I think it's better to do that in a follow-up.
Events??? :P
Events??? :P |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
@jojolepro Not really, more like watching a directory. |
torkleyy
requested a review
from
Rhuagh
Oct 23, 2017
| + /// The frame after calling this, all changed assets will be reloaded. | ||
| + pub fn trigger(&mut self) { | ||
| + let counter = match self.inner { | ||
| + HotReloadStrategyInner::Trigger { counter } => counter.checked_add(1).unwrap_or(0), |
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.
| + pub fn trigger(&mut self) { | ||
| + let counter = match self.inner { | ||
| + HotReloadStrategyInner::Trigger { counter } => counter.checked_add(1).unwrap_or(0), | ||
| + _ => 0, |
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.
| + source, | ||
| + options, | ||
| + .. | ||
| + } = this; |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + /// The reload structure has metadata which allows the asset management | ||
| + /// to reload assets if necessary (for hot reloading). | ||
| + /// You should only create this if `create_reload` is `true`. | ||
| + /// Also, the parameter is just a request, which means you can also return `None`. | ||
| fn import( | ||
| &self, | ||
| name: String, |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
omni-viral
Oct 23, 2017
Member
name and options in Format::import and options in SimpleFormat.
And then in impl Reload for SingleFile you can reuse SingleFile value instead of creating new one
omni-viral
Oct 23, 2017
Member
name and options in Format::import and options in SimpleFormat.
And then in impl Reload for SingleFile you can reuse SingleFile value instead of creating new one
| - self.unused_handles.push(old); | ||
| + | ||
| + // Can't reuse old handle here, because otherwise weak handles would still be valid. | ||
| + // TODO: maybe just store u32? |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + fn hot_reload(&mut self, pool: &ThreadPool) { | ||
| + let elapsed = self.last_reload.elapsed().as_secs(); | ||
| + | ||
| + if elapsed >= 1 { |
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
reviewed
Oct 23, 2017
Looks pretty good, although I'd be overall much happier if we provided a hot reload strategy that uses file system events to trigger hot reloading. Would probably help a lot for games with lots of file assets.
| +/// This is a simplified version of `Format`, which doesn't give you as much as freedom, | ||
| +/// but in return is simpler to implement. | ||
| +/// All `SimpleFormat` types automatically implement `Format`. | ||
| +/// This format assumes that the asset name is the full path and also the only file. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Xaeroxe
Oct 23, 2017
Member
Minor grammar nit: Maybe this should end with "and the asset is only contained within one file."
Xaeroxe
Oct 23, 2017
Member
Minor grammar nit: Maybe this should end with "and the asset is only contained within one file."
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| - pub format: String, | ||
| - pub handle: Handle<A>, | ||
| - pub name: String, | ||
| +pub enum Processed<A: Asset> { |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Xaeroxe
Oct 23, 2017
Member
Why? It's very straightforward and obvious. You don't need to look at other files to figure out it's not actually pub, and pub(crate) permits easy re-use of internal structures.
Xaeroxe
Oct 23, 2017
Member
Why? It's very straightforward and obvious. You don't need to look at other files to figure out it's not actually pub, and pub(crate) permits easy re-use of internal structures.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
Okay, all rebased, fixed, addressed and ready for a final review ;) |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
torkleyy
Oct 23, 2017
Member
I'd be overall much happier if we provided a hot reload strategy that uses file system events to trigger hot reloading.
Me too, but I don't want to implement everything in one PR.
Me too, but I don't want to implement everything in one PR. |
| + } | ||
| +} | ||
| + | ||
| +/// This is a simplified version of `Format`, which doesn't give you as much as freedom, |
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.
| + /// If set to `true`, this `Loader` will ask formats to | ||
| + /// generate "reload instructions" which *allow* reloading. | ||
| + /// Calling `set_hot_reload(true)` does not actually enable | ||
| + /// hot reloading; this is controlled by the `HotReloadStrategy` |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| + } | ||
| + | ||
| + /// No periodical hot-reloading is performed. | ||
| + /// Instead, you can `trigger` it to check for changed assets |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Rhuagh
Oct 23, 2017
Member
This documentation seems to conflict with the documentation on trigger below.
Rhuagh
Oct 23, 2017
Member
This documentation seems to conflict with the documentation on trigger below.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
| -#[cfg(test)] | ||
| -#[cfg_attr(test, macro_use)] | ||
| -extern crate quickcheck; | ||
| +//#[cfg(test)] |
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
Oct 23, 2017
Member
There's not a single usage in the whole crate, I'm not sure what @Aceeri did here.
torkleyy
Oct 23, 2017
Member
There's not a single usage in the whole crate, I'm not sure what @Aceeri did here.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Rhuagh
Oct 23, 2017
Member
Ah, seems he removed all the tests using it. Remove it altogether then I'd say.
Rhuagh
Oct 23, 2017
Member
Ah, seems he removed all the tests using it. Remove it altogether then I'd say.
| Never, | ||
| } | ||
| +/// System for updating `HotReloadStrategy`. | ||
| +/// **NOTE:** You have to add this after all asset processing systems. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
bot
added a commit
that referenced
this pull request
Oct 24, 2017
torkleyy
added some commits
Oct 22, 2017
torkleyy
requested review from
Rhuagh and
omni-viral
Oct 24, 2017
Xaeroxe
approved these changes
Oct 24, 2017
•
Looks great! I'm glad the frame numbers were helpful.
Pro tip: There is no frame 0, as the number is incremented at the beginning of the frame rather than the end, so if you need a special value in the future 0 works just as well as MAX.
torkleyy
assigned
Rhuagh
Oct 24, 2017
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
Assigning @Rhuagh to merge in case of an approval. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
bors r+ |
bot
added a commit
that referenced
this pull request
Oct 24, 2017
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
bors
bot
merged commit 51ae37f
into
amethyst:develop
Oct 24, 2017
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
Yay! No more rebasing! :P |
torkleyy commentedOct 22, 2017
•
edited
Edited 1 time
-
torkleyy
edited Oct 22, 2017 (most recent)
Arc<rayon::ThreadPool>(or short:core::ThreadPool)WeakHandlesFixes #266
Problems
If a file has been written only partially and the asset storage tries to load it, it fails :( I'm not sure if there's a check we can doFixed by using fallback