Conversation
b6c4ed7 to
6f72525
Compare
4638d29 to
c547158
Compare
|
Just rebased onto main. |
BenjaminBrienen
left a comment
There was a problem hiding this comment.
Very clean code and easy to follow!
alice-i-cecile
left a comment
There was a problem hiding this comment.
The code is really well-made: commented, clearly engineered, great use of enums. Can you add an example demonstrating how (and especially why) a user might use this API?
Once that's in place, I'm on board and am willing to merge.
|
Left some thoughts in the issue: #15346 (comment) |
7228729 to
3f7c624
Compare
This currently buys us nothing, but it's a nice stepping stone to making `AssetPresence` have a locked variant.
This variant is still unused.
Now assets can be "locked" into an `Arc`.
This will make it more performant to try unlocking all the locked assets in the "happy case" that few assets are locked.
This way, if you lock an asset, you just have to drop its `Arc` and wait a frame for the asset to become mutable again (as opposed to requiring a system to figure out which assets can be unlocked and trying to unlock just those). The price is constant polling whenever an asset is locked. We have to repeatedly try unlocking an asset until it is actually ready to be unlocked.
This makes it easier to just unwrap a mutable borrow by just doing `.as_mut().unwrap()`.
…main world. For simplicity, we just clone the assets. In theory, we could have an enum to store the RenderAssets as either a locked Arc or an Unlocked asset, but I'd rather just avoid that branch.
This is actually a totally fine case, we just need a borrow to be able to clone the internal Arc's.
3f7c624 to
d333659
Compare
|
I rebased (#15281 had a big blast radius), and also included a new example to show off how you can use asset locking. I still need to figure out how to implement |
d333659 to
c65eb32
Compare
c65eb32 to
167aad4
Compare
This makes working with asset locking much more palatable, as users don't need to explicitly handle the locking case in most situations.
This is likely the common case for users, so we should have our examples use this function.
|
Ok, I've implemented One thing that's kinda rough here is that |
bushrat011899
left a comment
There was a problem hiding this comment.
I think this is a very interesting concept to add to Assets and opens up further discussion around other systems. I've left some notes with possible future work, but I think as-is this is good to merge.
| /// asset is cloned and replaces the existing asset (the previously locked asset is still | ||
| /// readable by holders of its [`Arc`]). | ||
| #[inline] | ||
| pub fn get_mut_or_clone(&mut self, id: impl Into<AssetId<A>>) -> Option<&mut A> { |
There was a problem hiding this comment.
Future PR: a more fundamental version of the function could be
pub fn get_mut_or_replace_with(
&mut self,
id: impl Into<AssetId<A>>,
f: impl FnOnce(Option<&A>) -> A
) -> Option<&mut A>;Which would allow the user to decide how to replace a locked asset, remove the A: Clone requirement, and handling the Missing case.
|
|
||
| /// Locks the current asset, which prevents the asset from being modified | ||
| /// until it is unlocked. Returns [`None`] only if [`AssetPresence::None`]. | ||
| pub fn lock(&mut self) -> Option<Arc<A>> { |
There was a problem hiding this comment.
Future PR: since the only way to lock an Asset is via this method, using #[track_caller] and std::panic::Location::caller(), we could store a Vec<Location> in the Locked variant, allowing the try_unlock method to return an error indicating where the Asset has been locked from. I believe the #[track_caller] attribute would need to be added to all the methods that forward to lock as well (e.g., Assets::lock)
I imagine we'd want such a thing in debug builds only, since it adds more weight to the AssetPresence type, but I imagine it would be quite helpful for diagnosing possibly frustrating deadlocks.
There was a problem hiding this comment.
This is an interesting idea, but it's not clear to me how big a win this is. For one, the list of lock locations can only grow (until an asset is finally unlocked). I suppose if we assume locking is infrequent and short-lived this isn't a big deal, but I can totally see an asset being locked for a long time and constantly getting more and more lock locations. For another, that only tracks the initial lock location, but you're free to clone the Arc as much as you want. I suspect the more dangerous situation is something storing the Arc without knowing it's a locked asset and blocking mutations that way (and there's no real way to solve this AFAICT).
Regardless, this definitely seems worthy of a discussion and I could be convinced!
There was a problem hiding this comment.
Yeah it's definitely not perfect, but it could help with at least giving the user a starting point for debugging how an asset deadlock started. Potentially, we could even wrap the returned Arc to give it better debugging functionality. Storing a HashMap of Location's on the heap and counting down each location as the wrapped Arc is dropped, and counting up as the Arc is cloned.
Definitely half-baked and up for debate, and absolutely out of scope for this PR!
|
Going to wait on @cart to take another look at this design: he had opinions in the linked thread and I don't feel comfortable merging this without him :) |
|
Has merge conflicts. Still awaiting response from @cart. |
Objective
Solution
Testing
bevy_asset.Showcase
The primary new parts of the API are:
Assets::lock- Locks the asset if it existsAssets::try_unlock- Attempts to unlock the asset if there are no more outstanding handles (otherwise the asset remains locked).Migration Guide
Assets::get_or_insert_withmay now return an error if the asset is locked.Assets::get_mutmay now return an error if the asset is locked (in addition to if the asset is missing). Consider usingAssets::get_mut_or_cloneif your type isCloneto avoid handling locking.Assets::removeandAssets::remove_untrackednow return anAssetPresence<A>. This requires you to handle the case where an asset is locked.Assets::iter_mutnow returns an(AssetId<A>, AssetPresenceMut<'a, A>)instead of(AssetId<A>, &'a mut A). This allows you to handle the locked case.ReflectAsset::get_mutandReflectAsset::get_unchecked_mutmay now return an error if the asset is locked (in addition to if the asset is missing).ReflectAsset::removenow returns aReflectedAssetPresenceto store both the locked and unlocked assets.