Skip to main content

Crate bevy_track_asset

Crate bevy_track_asset 

Source
Expand description

§Bevy Track Asset

Utility for tracking asset reloads and propagating changes to dependent ECS data.

When working with assets in Bevy, it’s common to cache derived data that depends on an asset. For example, a resource that holds the output of a compute shader may need to be re-computed whenever an asset it depends on changes.

This crate provides a composable system, set_changed_on_asset_reload_system, along with the TrackAsset, AssetDependent, and TriggerChangeDetection traits, to automate this pattern: whenever a tracked asset is modified or finishes loading, all dependent ECS items (components, resources, etc.) are automatically marked as changed, so that downstream systems can react using Bevy’s standard change detection.

§Feature flags

DefaultFeatureDescription
Yesbevy_appEnables automatic scheduling with AssetTrackingPlugin and TrackAssetPlugin.
Yesbevy_logLogs asset change events and warnings about missing plugins in debug builds.

§Getting Started

Add an AssetTrackingPlugin to your Bevy app to automatically configure TrackAssetSystems in a schedule of your choice. Most users will want to handle changes in PostUpdate.

use bevy::prelude::*;
use bevy_track_asset::prelude::*;

fn main() {
    App::new()
        .add_plugins((
            DefaultPlugins,
            AssetTrackingPlugin::<PostUpdate>::default(),
        ));
}

§Declaring dependencies

Implement AssetDependent for your component, resource, or custom system param item, returning the AssetId of the asset it depends on:

use bevy::prelude::*;
use bevy_track_asset::AssetDependent;

#[derive(Asset, TypePath)]
struct MyShader { /* ... */ }

#[derive(Component, Resource, Default)]
struct ComputedValue { /* ... */ }

#[derive(Component, Resource)]
#[require(ComputedValue)]
struct ShaderUser {
    shader: Handle<MyShader>,
    // ...
}

impl AssetDependent<MyShader> for ShaderUser {
    fn asset_id(&self) -> AssetId<MyShader> {
        self.shader.id()
    }
}

§Tracking the asset

Add a TrackAssetPlugin for your asset type and a system param that can extract the dependent items (e.g. Query<&mut ShaderUser>). This will add a set_changed_on_asset_reload_system to your app, which listens for asset events and marks dependent items as changed when their asset is reloaded.

app.add_plugins((
    TrackAssetPlugin::<MyShader, Query<&mut ShaderUser>>::default(),
    TrackAssetPlugin::<MyShader, TrackResMut<ShaderUser>>::default(),
));

§Reacting to changes

Systems can handle changes with standard bevy change detection patterns.

use bevy::prelude::*;
use bevy_track_asset::prelude::*;

fn main() -> AppExit {
    App::new()
        // ...other plugins
        .add_systems(PostUpdate, handle_changed_system.in_set(TrackAssetSystems::Reload))
        .run()
}

fn handle_changed_system(
    mut query: Query<(&ShaderUser, &mut ComputedValue), Changed<ShaderUser>>,
    assets: Res<Assets<MyShader>>
) {
    for (user, mut value) in &mut query {
        // Re-derive cached data from the reloaded asset...
        let shader = assets.get(&user.shader).unwrap();
        *value = shader.compute(user);
    }
}

§Custom SystemParam

You can also use custom system params to track more complex dependencies, such as multiple components or resources that depend on the same asset type. Just ensure that the Item type of your system param implements IntoIterator over elements that are both AssetDependent and TriggerChangeDetection, and the change-tracking system will handle the rest.

The following example demonstrates a custom system parameter (UsingImage), that tracks dependencies of Image (UseImage – both a component and a resource), and marks them as changed when the image behind their Handle<Image> is loaded or reloaded.

use bevy::{
    ecs::{
        query::QueryIter,
        system::{SystemParam, lifetimeless::Write},
    },
    prelude::*,
};
use bevy_track_asset::prelude::*;

/// Depends on an `Image` asset, and will be marked as changed when the asset reloads.
#[derive(Component, Resource)]
struct UseImage(Handle<Image>);

/// Iterator item returned by the custom system param.
enum ImageDependency<'w> {
    QueryUsesImage(Mut<'w, UseImage>),
    GlobalUseImage(ResMut<'w, UseImage>),
}

impl AssetDependent<Image> for ImageDependency<'_> {
    #[inline]
    fn asset_id(&self) -> AssetId<Image> {
        match self {
            ImageDependency::QueryUsesImage(component) => component.0.id(),
            ImageDependency::GlobalUseImage(resource) => resource.0.id(),
        }
    }
}

// Required to mark items changed when the asset reloads.
impl TriggerChangeDetection for ImageDependency<'_> {
    #[inline]
    fn trigger_change(&mut self) {
        match self {
            ImageDependency::QueryUsesImage(component) => component.set_changed(),
            ImageDependency::GlobalUseImage(resource) => resource.set_changed(),
        }
    }
}

/// Example of a custom system param that
/// marks resources and components as changed
/// when their associated asset is modified.
#[derive(SystemParam)]
struct UsingImage<'w, 's> {
    query_uses_image: Query<'w, 's, Write<UseImage>>,
    global_use_image: ResMut<'w, UseImage>,
}

impl<'w, 's> IntoIterator for UsingImage<'w, 's> {
    type Item = ImageDependency<'w>;
    type IntoIter = std::iter::Chain<
        std::iter::Map<
            QueryIter<'w, 's, Write<UseImage>, ()>,
            fn(Mut<'w, UseImage>) -> ImageDependency<'w>,
        >,
        std::iter::Once<ImageDependency<'w>>,
    >;

    #[inline]
    fn into_iter(self) -> Self::IntoIter {
        self.query_uses_image
            .into_iter()
            .map(
                ImageDependency::QueryUsesImage
                    as fn(bevy::prelude::Mut<'w, UseImage>) -> ImageDependency<'w>,
            )
            .chain(std::iter::once(ImageDependency::GlobalUseImage(
                self.global_use_image,
            )))
    }
}

Check out the full example in examples/custom_param.rs for a working demonstration of this pattern.

Modules§

prelude

Structs§

AssetTrackingPlugin
Plugin that configures TrackAssetSystems in a schedule of your choice.
TrackAssetPlugin
Convenience plugin for tracking asset changes in the TrackAssetSystems::Watcher system set during PostUpdate.
TrackResMut
A wrapper for ResMut<T> that implements TrackAsset, for tracking assets in resources.

Enums§

TrackAssetSystems
System sets for asset tracking systems. With the bevy_app feature enabled, these are automatically configured by AssetTrackingPlugin. Without bevy_app, you need to configure these yourself.

Traits§

AssetDependent
Declares that an ECS item depends on a specific asset, and can provide its AssetId.
TrackAsset
A marker trait for SystemParams whose data depends on an asset and can trigger change detection when the asset is changed.
TriggerChangeDetection
Allows an ECS item to be flagged as needing to be re-derived from its associated asset.

Functions§

extract_asset_id
Extracts the AssetId from an AssetEvent if it represents a meaningful change.
set_changed_on_asset_reload_system
A system that marks ECS items as changed when the asset they depend on is modified or reloaded.