Repository: bevyengine/bevy
File: crates/bevy_asset/src/assets.rs (Rust)

Code before:
    use bevy_ecs::prelude::*;
    use bevy_reflect::TypePath;
    use std::sync::atomic::{AtomicU32, Ordering};
    use std::sync::Arc;

    #[test]
    fn asset_index_round_trip() {
        let asset_index = AssetIndex {
            generation: 42,
            index: 1337,
        };
        let roundtripped = AssetIndex::from_bits(asset_index.to_bits());
        assert_eq!(asset_index, roundtripped);
    }

    #[test]
    fn assets_mut_change_detection() {
        #[derive(Asset, TypePath)]
        struct MyAsset {
            value: u32,
        }

        let mut app = App::new();
        app.add_plugins(AssetPlugin::default());
        app.init_asset::<MyAsset>();

        let mut assets = app.world_mut().resource_mut::<Assets<MyAsset>>();
        let my_asset_handle = assets.add(MyAsset { value: 0 });
        let my_asset_value = Arc::new(AtomicU32::new(0));
        let my_asset_change_counter = Arc::new(AtomicU32::new(0));

        app.add_systems(Update, {
            let my_asset_value = my_asset_value.clone();
            let my_asset_id = my_asset_handle.id();

            move |mut assets: ResMut<Assets<MyAsset>>| {
                let mut asset = assets.get_mut(my_asset_id.clone()).unwrap();
                let value = my_asset_value.load(Ordering::Relaxed);

                if asset.value != value {
                    asset.value = value;
                }
            }
        });
        app.add_systems(Update, {
            let my_asset_change_counter = my_asset_change_counter.clone();
            let my_asset_id = my_asset_handle.id();

            move |mut message_reader: MessageReader<AssetEvent<MyAsset>>| {
                for event in message_reader.read() {
                    if event.is_modified(my_asset_id) {

Review comment (refactor):
Thanks for adding a test! I think this is heading in the right direction but I think we can simplify this test a bit. 

Instead of sharing state inside and outside the app using atomics wrapped in refcells, how about we aggregate things in a Resource that we can directly get out of the app in our test sequence? There is also no need for atomics as nothing is supposed to happen in parallel in this test. Although, the current way you've setup the systems is actually not well defined as the order in which they are executed is not specified. You probably want to move the system that reads from the MessageReader to the PostUpdate schedule for example. 

The interaction of your systems with the asset value is also quite hard to follow, I think it could all just be a bit more straightforward and more clearly named.

Let me know what you think :)

Code after:
        index: AssetIndex,
        current_generation: u32,
    },
    #[error("AssetIndex {index:?} has been removed")]
    Removed { index: AssetIndex },
}

#[cfg(test)]
mod test {
    use crate::tests::create_app;
    use crate::{Asset, AssetApp, AssetEvent, AssetIndex, Assets};
    use bevy_ecs::prelude::Messages;
    use bevy_reflect::TypePath;

    #[test]
    fn asset_index_round_trip() {
        let asset_index = AssetIndex {
            generation: 42,
            index: 1337,
        };
        let roundtripped = AssetIndex::from_bits(asset_index.to_bits());
        assert_eq!(asset_index, roundtripped);
    }

    #[test]
    fn assets_mut_change_detection() {
        #[derive(Asset, TypePath, Default)]
        struct TestAsset {
            value: u32,
        }

        let mut app = create_app().0;
        app.init_asset::<TestAsset>();

        let mut assets = app.world_mut().resource_mut::<Assets<TestAsset>>();
        let my_asset_handle = assets.add(TestAsset::default());
        let my_asset_id = my_asset_handle.id();

        // check a few times just in case there are some unexpected leftover events from previous runs
        for _ in 0..3 {
            // check that modifying the asset value triggers an event
            {
                let mut assets = app.world_mut().resource_mut::<Assets<TestAsset>>();
                let mut asset = assets.get_mut(my_asset_id).unwrap();
                asset.value += 1;
            }

            app.update();

            let modified_count = app
                .world_mut()