-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Fix handles being split by asset_server_managed.
#22261
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
4e1973e to
dd9e260
Compare
…hould not remove the asset.
dd9e260 to
d54f347
Compare
kristoff3r
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, the logic in track_assets felt a bit complicated but I couldn't find a way to simplify it.
| /// Returns true if the asset's entry was removed. This is not the same as removing the asset | ||
| /// itself - an asset which has not been inserted may still have its entry removed. | ||
| pub(crate) fn try_remove_dropped(&mut self, index: AssetIndex) -> bool { | ||
| use bevy_platform::collections::hash_map::Entry; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see why this import needs to be inside this function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file defines a type Entry, so the hashmap Entry collides with it. Scoping it here sidesteps that issue.
|
|
||
| assets.remove_dropped(drop_event.index.index); | ||
| for index in maybe_drop_indices.drain() { | ||
| if !assets.try_remove_dropped(index) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might be wrong here, but isn't this technically a race condition? There are a bunch of lock-shaped things here, the &mut Assets<A> (which prevents get_strong_handle from being called during track_assets) and the write guard on the infos (which prevents access to the server's handle provider list, as well as load_* calls), but none of those actually prevent new HandleEvent::New(_) messages being sent or get_handle from being called to create a new strong handle.
I'm not sure if this is an actual problem: I don't think this is currently possible because the HandleProvider/Sender isn't exposed beyond the pub(crate) scope.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup that's exactly correct, the only thing preventing us from a race condition here is the fact that we have exclusive access to Assets and are locking the asset server - there's no other way to get a new handle to an existing asset. The only other way to send a HandleEvent::new is with AssetHandleProvider::reserve_handle, but that only gives you handles to a new asset, so we can't have accidentally dropped it yet.
I will have added a comment here to explain that. The existing comment about the lock being transactional is insufficient.
janis-bhm
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be nice to find a way to get rid of the weak handle reference in the asset info and use the Asset<A>s new "strong count"; the weak handle is still used in is_path_alive and get_index_handle for checking liveness and getting the asset's handle, which will fail if the original handle was dropped and a new handle was created by the handle provider.
The modified test below fails in this PR:
Details
#[test]
fn asset_is_live_after_reacquired() {
let (mut app, dir) = create_app();
dir.insert_asset_text(Path::new("dep.cool.ron"), SIMPLE_TEXT);
app.init_asset::<CoolText>()
.init_asset::<SubText>()
.register_asset_loader(CoolTextLoader);
let asset_server = app.world().resource::<AssetServer>().clone();
let handle = asset_server.load::<CoolText>("dep.cool.ron");
run_app_until(&mut app, |_| asset_server.is_loaded(&handle).then_some(()));
// Dropping the handle and then reacquiring it through a load should **not** result in the
// asset being dropped. Note: we won't reload the asset in any case since we only try to
// drop on the next update.
let new_handle = app
.world_mut()
.resource_mut::<Assets<CoolText>>()
.get_strong_handle(handle.id())
.expect("should yield strong handle");
drop(handle);
app.update();
assert!(app
.world()
.resource::<Assets<CoolText>>()
.contains(&new_handle));
let asset_server = app.world().resource::<AssetServer>().clone();
assert!(asset_server.read_infos().is_path_alive("dep.cool.ron"));
let index =
ErasedAssetIndex::try_from(new_handle.id().untyped()).expect("should yield index");
assert!(asset_server.read_infos().get_index_handle(index).is_some());
}…cely with the asset server.
… to avoid creating our own iterator.
48d3192 to
80857f1
Compare
80857f1 to
c53fe87
Compare
|
@janis-bhm That was a good catch! I didn't know these methods existed. Now when you call @kristoff3r I've added some not entirely trivial changes, so I'd like another review just in case. |
Objective
Assets::get_strong_handlehas different behaviour to one fromAssetServer::load. #20651.Asset<A>/AssetServerhandles (#20651, #20776) #20874.Solution
Assets::track_assetsnow increments when a "new" message is received, and decrements when a "drop" message is received. The asset entry is dropped if the counter is at 0 after processing all messages.asset_server_managedflag, since we can just lookup the index to see if it's present.handle_drops_to_skipcounter since now we count all new handles, not just those inget_strong_handle.Testing