-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
Modifying an Assets dependencies should mark the Asset as modified #20575
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
…ied if a dependency of an Asset was modified.
Welcome, new contributor! Please make sure you've read our contributing guide and we look forward to reviewing your pull request shortly ✨ |
I haven't given this a thorough review yet, but my initial instinct is that using Modified to indicate that a dependency has changed is wrong. We should have a separate event for that. For a concrete example, #20430 has to work around an issue today where multiple scene Modified events are causing certain workflows to break. This could expand that to also include anything the scene depends on, which could be textures, meshes, etc. Lots of modified events! Perhaps I've misinterpreted and a more thorough review will convince me I'm wrong, but I thought I'd give an initial impression. |
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.
A couple quick comments without going too deep:
-
Thank you for caring about this. This is my all time #1 biggest pet peeve in Bevy and something our users are constantly stubbing their toes on.
-
I'm concerned about performance. I'd been assuming that we'd have to build a reverse dependency tree in order to index from the modified event to its dependents. Scanning all assets every time potentially is expensive. Think of the scenario where we are animating a bunch of materials on the CPU, that's a lot of modified events every frame.
We talked about this a bit on Discord, and realized that #11266 + relations to model dependencies would be extremely useful for this. |
fn visit_dependencies(&self, visit: &mut impl FnMut(UntypedAssetId)); | ||
fn visit_dependencies( | ||
&self, | ||
visit: &mut impl FnMut(UntypedAssetId) -> ControlFlow<UntypedAssetId>, |
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 think I'm understanding how this control flow works. Let's say you have an asset with:
struct MyAsset(#[dependency] Vec<Handle<Mesh>>);
If for whatever reason one of those meshes returns ControlFlow::Break (because it was modified this frame), the entire MyAsset
stops iterating, right?
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.
Right, it's short-circuiting searching dependencies, as soon as it finds a match, that's enough and we can stop looking.
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.
Ohh I understand now. This walk of the assets is allowed to early return because we give it a visit function that can return Break. The existing uses of VisitDependencies don't early return because its visit function never returns Break.
Thanks for this explanation!!
let untyped_asset_modified_events: HashSet<_> = | ||
untyped_asset_modified_events.read().map(|e| e.id).collect(); | ||
let mut modified_events = Vec::new(); | ||
for (id, asset) in assets.iter() { |
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.
Oh this literally iterates through every single asset and checking if it (directly or indirectly) depends on one of the modified assets. I think this is a dealbreaker for me - iterating through every asset seems like it'll be very slow.
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.
Yeah, this was my biggest concern too that I called out in the initial PR comment. But I figured I would get see what others thought.
Interesting. My motivation for this was for playing video - every bevy video implementation I found requires the user to modify the material they are using every frame so the material displays it's updated I was trying to find a way to automate/simplify that for the user, but perhaps this could be done in the video crate using an |
I prototyped something similar to this - So we can monitor Then for any dependents with I changed the You can see the changes here, I didn't do a PR since I'm not sure about this approach: |
I totally botched this, I modeled after the child/parent relationship stuff but we need a "child with multiple parents" (e.g. an Image dependency with multiple materials using it) not a "parent with multiple children". I'll see if I can rework it. |
Bevy ECS does not currently have many-to-many relations and we need many-to-many relations to describe this relationship. An image may be used by multiple materials, and a material may use multiple images. Neither direction supports the one-to-many we have today. We will have this one day, and with #11266, we'd just be able to delete a boatload of code. |
I wonder if this would fix #15081 |
Out of interest I continued beating this dead horse :) I think you can model many-to-many by duplicating the AssetId wrapper components. I have a So if Asset A has dependencies B and C, and Asset D also depends on B and C, we can model it with these entities:
Then we can monitor So it's modeling the asset dependencies with a parallel network of ECS entities with (duplicated) asset components and relationships. This seems to work in a limited case (I modified the Here's my implementation main...rectalogic:bevy:assetrelations |
Just doing a quick drive by review, but I agree with the other comments. This removes granularity from the system in a way that is not general purpose. Materials happen to have bind groups that care about dependencies, but there are plenty of other systems that don't want to rebuild just because a dependency changes (ex: scenes). In the general case, each asset is its own "thing" and the handles it contain are "pointers", not that "thing" itself. We can consider "dependency changed" events, but I don't think we can enable them by default for everything, as they would likely be too expensive. We could consider an opt-in subscriber system though. |
Objective
Fixes #20269 and #16159
The issue is when an
Asset
is modified and it is a dependency of anotherAsset
, that "parent"Asset
is not marked as modified. Specifically in the 2 issues above, anImage
asset is modified and it is a dependency ofStandardMaterial
(base_color_texture
) or aColorMaterial
(texture
) asset. When theImage
is modified, neither material is marked modified and so they do not rebuild their cachedBindGroup
and so the image rendered does not change.Solution
First, modify
VisitAssetDependencies::visit_dependencies
to allow early termination viaControlFlow
.Then, for every
Assets<A>
modification, also queue anUntypedAssetModifiedEvent
with theUntypedAssetId
. So we have an event queue of modified assets across all asset types. Then for each asset we check if any of its dependency assets are in that set of modified assets, and if so mark the asset itself as modified.This does mean that any time an asset in
Assets<A>
is modified, we will iterate all assets inAssets<A>
and visit each of their dependencies which is perhaps suboptimal.Testing
Here is some sample code that demonstrates the issue with both
StandardMaterial
andColorMaterial
and anImage
.Click to expand sample code