assets_manager 0.13.9

Conveniently load, cache, and reload external resources
Documentation
use crate::{
    Asset, AssetCache, BoxedError, SharedString,
    source::{DirEntry, FileSystem},
    tests::{X, Y},
};
use std::{
    fs::{self, File},
    io::{self, Write},
    path::Path,
    sync::Arc,
};

fn sleep() {
    std::thread::sleep(std::time::Duration::from_millis(50));
}

type Res = Result<(), Box<dyn std::error::Error>>;

fn write_i32(path: &Path, n: i32) -> io::Result<()> {
    log::debug!("Write {n} at {path:?}");
    let mut file = File::create(path)?;
    write!(file, "{n}")
}

macro_rules! test_scenario {
    (
        name: $name:ident,
        type: $load:ty,
        id: $id:literal,
        $(not_loaded: $not_loaded:ty,)?
    ) => {
        #[test]
        fn $name() -> Res {
            let _ = env_logger::try_init();

            std::fs::create_dir_all("assets/test/hot_asset/")?;

            let id = concat!("test.hot_asset.", $id);
            let cache = AssetCache::new("assets")?;

            let old = 42;
            let new = -1;

            let source = cache.downcast_raw_source::<FileSystem>().unwrap();
            let path = source.path_of(DirEntry::File(id, "x"));
            write_i32(&path, old)?;
            sleep();

            let asset = cache.load::<$load>(id)?;
            let mut watcher = asset.reload_watcher();
            assert_eq!(asset.read().0, old);
            assert!(!watcher.reloaded());

            write_i32(&path, new)?;
            sleep();
            assert_eq!(asset.read().0, new);
            assert!(watcher.reloaded());
            assert!(!watcher.reloaded());
            $( assert!(!cache.contains::<$not_loaded>(id)); )?

            write_i32(&path, old)?;
            sleep();
            assert_eq!(asset.read().0, old);
            assert!(watcher.reloaded());
            assert!(!watcher.reloaded());
            $( assert!(!cache.contains::<$not_loaded>(id)); )?

            Ok(())
        }
    };
}

test_scenario! {
    name: reload_asset,
    type: X,
    id: "a",
}

test_scenario! {
    name: reload_compound,
    type: Y,
    id: "c",
}

test_scenario! {
    name: reload_arc_compound,
    type: Arc<Y>,
    id: "f",
    not_loaded: Y,
}

test_scenario! {
    name: reload_arc_asset,
    type: Arc<X>,
    id: "g",
    not_loaded: X,
}

#[test]
fn directory() -> Result<(), BoxedError> {
    let _ = env_logger::try_init();

    let _ = std::fs::remove_dir_all("assets/test/hot_dir/");
    std::fs::create_dir_all("assets/test/hot_dir/")?;
    write_i32("assets/test/hot_dir/a.x".as_ref(), 1)?;

    let cache = AssetCache::new("assets")?;

    let dir = cache.load_dir::<X>("test.hot_dir")?;
    let mut watcher = dir.reload_watcher();
    assert!(!watcher.reloaded());

    assert_eq!(dir.read().ids().collect::<Vec<_>>(), ["test.hot_dir.a"]);

    write_i32("assets/test/hot_dir/a.x".as_ref(), 1)?;
    sleep();
    assert!(!watcher.reloaded());

    write_i32("assets/test/hot_dir/b.x".as_ref(), 1)?;
    sleep();
    assert_eq!(
        dir.read().ids().collect::<Vec<_>>(),
        ["test.hot_dir.a", "test.hot_dir.b"]
    );

    assert!(watcher.reloaded());

    std::fs::remove_file("assets/test/hot_dir/b.x")?;
    sleep();
    assert_eq!(dir.read().ids().collect::<Vec<_>>(), ["test.hot_dir.a"]);
    assert!(watcher.reloaded());

    std::fs::remove_file("assets/test/hot_dir/a.x")?;
    sleep();
    assert_eq!(dir.read().ids().collect::<Vec<_>>().len(), 0);
    assert!(watcher.reloaded());

    Ok(())
}

#[test]
fn multi_threading() {
    let _ = env_logger::try_init();

    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    struct MyAsset {
        a: i32,
        b: i32,
    }

    impl Asset for MyAsset {
        fn load(cache: &AssetCache, id: &SharedString) -> Result<Self, BoxedError> {
            let recorder = crate::hot_reloading::Recorder::current();

            let (a, b) = std::thread::scope(|s| {
                let a = s.spawn(|| recorder.install(|| cache.load_expect::<X>(&format!("{id}.a"))));
                let b = s.spawn(|| cache.load_expect::<X>(&format!("{id}.b")));

                (a.join().unwrap().read().0, b.join().unwrap().read().0)
            });

            Ok(MyAsset { a, b })
        }
    }

    fs::create_dir_all("assets/test/mt/").unwrap();
    write_i32("assets/test/mt/a.x".as_ref(), 1).unwrap();
    write_i32("assets/test/mt/b.x".as_ref(), 2).unwrap();

    let cache = AssetCache::new("assets").unwrap();

    let asset = cache.load_expect::<MyAsset>("test.mt");
    assert_eq!(asset.copied(), MyAsset { a: 1, b: 2 });

    write_i32("assets/test/mt/a.x".as_ref(), 3).unwrap();
    sleep();

    assert_eq!(asset.copied(), MyAsset { a: 3, b: 2 });

    write_i32("assets/test/mt/b.x".as_ref(), 4).unwrap();
    sleep();

    assert_eq!(asset.copied(), MyAsset { a: 3, b: 2 });
}

#[test]
fn parent() {
    let _ = env_logger::try_init();

    fs::create_dir_all("assets/test/hot_child/").unwrap();
    write_i32("assets/test/hot_child/a.x".as_ref(), 1).unwrap();
    write_i32("assets/test/hot_child/b.x".as_ref(), 2).unwrap();

    let root = AssetCache::new("assets").unwrap();
    let child = root.make_child();

    let a_root = root.load_expect::<X>("test.hot_child.a");
    let a = child.load_expect::<Y>("test.hot_child.a");
    let b = child.load_expect::<X>("test.hot_child.b");

    assert_eq!(a.read().0, 1);
    assert_eq!(b.read().0, 2);

    write_i32("assets/test/hot_child/b.x".as_ref(), 4).unwrap();
    sleep();

    assert_eq!(b.read().0, 4);

    write_i32("assets/test/hot_child/a.x".as_ref(), 3).unwrap();
    sleep();

    assert_eq!(a.read().0, 3);

    drop(child);

    write_i32("assets/test/hot_child/a.x".as_ref(), 5).unwrap();
    sleep();
    assert_eq!(a_root.read().0, 5);
}