Skip to main content

runtime_load/
runtime_load.rs

1//! Runtime load: prove the `.hnb` [`AssetLoader`] works end to end.
2//!
3//! Serves the built-in demo graph as a `.hnb` file from an *in-memory* asset
4//! source (nothing touches disk), then loads it through the Bevy
5//! [`AssetServer`] — exercising [`EffectGraphLoader`] exactly as a game would
6//! when loading unbaked graphs during development — and bakes the loaded graph
7//! in-process into a `bevy_hanabi` [`EffectAsset`].
8//!
9//! ```sh
10//! cargo run -p hanabi_effect_graph --example runtime_load
11//! ```
12//!
13//! [`AssetLoader`]: bevy::asset::AssetLoader
14//! [`EffectGraphLoader`]: hanabi_effect_graph::EffectGraphLoader
15//! [`EffectAsset`]: bevy_hanabi::EffectAsset
16
17use std::path::Path;
18
19use bevy::{
20    asset::{
21        AssetApp,
22        io::{
23            AssetSourceBuilder, AssetSourceId,
24            memory::{Dir, MemoryAssetReader},
25        },
26    },
27    prelude::*,
28};
29use hanabi_effect_graph::{
30    EffectGraphPlugin, bake, demo,
31    model::{EffectGraphAsset, FORMAT_VERSION},
32    modifier_registry::ModifierRegistryPlugin,
33    to_ron_string,
34};
35
36fn main() {
37    // Serve a `.hnb` file from an in-memory directory so the `AssetServer` can
38    // resolve it through a custom source — no filesystem involved.
39    let staged = EffectGraphAsset {
40        version: FORMAT_VERSION,
41        graph: demo::demo_graph(),
42        layout: None,
43    };
44    let ron = to_ron_string(&staged).expect("serialize EffectGraphAsset");
45    let dir = Dir::default();
46    dir.insert_asset(Path::new("effect.hnb"), ron.into_bytes());
47
48    let mut app = App::new();
49    // The custom source must be registered before `AssetPlugin` reads it.
50    app.register_asset_source(
51        AssetSourceId::Default,
52        AssetSourceBuilder::new(move || Box::new(MemoryAssetReader { root: dir.clone() })),
53    )
54    .add_plugins((
55        MinimalPlugins,
56        AssetPlugin::default(),
57        // Registers the asset type + `.hnb` loader, and the modifier types the
58        // bake resolves by type path.
59        EffectGraphPlugin,
60        ModifierRegistryPlugin,
61    ));
62
63    let handle: Handle<EffectGraphAsset> = app.world().resource::<AssetServer>().load("effect.hnb");
64
65    // Pump the app until the async load resolves (or give up).
66    let mut frames = 0;
67    loop {
68        app.update();
69        match app.world().resource::<AssetServer>().load_state(&handle) {
70            bevy::asset::LoadState::Loaded => break,
71            bevy::asset::LoadState::Failed(error) => {
72                eprintln!("failed to load effect.hnb: {error}");
73                std::process::exit(1);
74            }
75            _ => {}
76        }
77        frames += 1;
78        if frames > 1000 {
79            eprintln!("timed out waiting for effect.hnb to load");
80            std::process::exit(1);
81        }
82    }
83
84    let world = app.world();
85    let loaded = world
86        .resource::<Assets<EffectGraphAsset>>()
87        .get(&handle)
88        .expect("loaded EffectGraphAsset");
89    println!("loaded effect.hnb via AssetServer in {frames} frame(s)");
90
91    let registry = world.resource::<AppTypeRegistry>().read();
92    match bake::bake(&loaded.graph, &registry) {
93        Ok(effect) => {
94            println!("baked '{}' in-process:", effect.name);
95            println!("  capacity:         {}", effect.capacity());
96            println!("  init modifiers:   {}", effect.init_modifiers().count());
97            println!("  update modifiers: {}", effect.update_modifiers().count());
98            println!("  render modifiers: {}", effect.render_modifiers().count());
99        }
100        Err(errors) => {
101            eprintln!("bake failed:");
102            for e in errors {
103                eprintln!("  - {e:?}");
104            }
105            std::process::exit(1);
106        }
107    }
108}