diff --git a/examples/custom_asset_lifecycles.rs b/examples/custom_asset_lifecycles.rs index 86e49ff..5a9e790 100644 --- a/examples/custom_asset_lifecycles.rs +++ b/examples/custom_asset_lifecycles.rs @@ -6,4 +6,176 @@ //! As this example demonstrates, you can bypass the [`ManifestPlugin`](leafwing_manifest::plugin::ManifestPlugin) entirely, and load your assets however you like, //! calling the publicly exposed methods yourself to replicate the work it does. -fn main() {} +use bevy::{asset::LoadState, prelude::*}; +use leafwing_manifest::manifest::Manifest; +use manifest_definition::{ItemManifest, RawItemManifest}; + +/// The core data structures and [`Manifest`] implementation is stolen directly from raw.rs: +/// it's not the focus of this example! +mod manifest_definition { + use std::path::PathBuf; + + use bevy::{prelude::*, utils::HashMap}; + use leafwing_manifest::{ + identifier::Id, + manifest::{Manifest, ManifestFormat}, + }; + use serde::{Deserialize, Serialize}; + + #[derive(Debug, PartialEq)] + #[allow(dead_code)] // Properties are for demonstration purposes only. + pub struct Item { + name: String, + description: String, + value: i32, + weight: f32, + max_stack: u8, + sprite: Handle, + } + + #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] + pub struct RawItem { + name: String, + description: String, + value: i32, + weight: f32, + max_stack: u8, + sprite: PathBuf, + } + + #[derive(Debug, Resource, PartialEq)] + pub struct ItemManifest { + items: HashMap, Item>, + } + + #[derive(Debug, Asset, TypePath, Serialize, Deserialize, PartialEq, Clone)] + pub struct RawItemManifest { + items: Vec, + } + + impl Manifest for ItemManifest { + type Item = Item; + type RawItem = RawItem; + type RawManifest = RawItemManifest; + type ConversionError = std::convert::Infallible; + + const FORMAT: ManifestFormat = ManifestFormat::Ron; + + fn get(&self, id: Id) -> Option<&Self::Item> { + self.items.get(&id) + } + + fn from_raw_manifest( + raw_manifest: Self::RawManifest, + world: &mut World, + ) -> Result { + let asset_server = world.resource::(); + + let items: HashMap<_, _> = raw_manifest + .items + .into_iter() + .map(|raw_item| { + let sprite_handle = asset_server.load(raw_item.sprite); + + let item = Item { + name: raw_item.name, + description: raw_item.description, + value: raw_item.value, + weight: raw_item.weight, + max_stack: raw_item.max_stack, + sprite: sprite_handle, + }; + + let id = Id::from_name(&item.name); + + (id, item) + }) + .collect(); + + Ok(ItemManifest { items }) + } + } +} + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_systems(PreUpdate, manage_manifests) + .run(); +} + +// The same basic workflow applies: +// 1. Start loading the raw manifest +// 2. Wait for the raw manifest to load +// 3. Convert it into a usable form +// 4. Store it as a resource +#[derive(Debug, PartialEq, Default)] +enum ManifestProgress { + #[default] + NotLoaded, + Loading, + Loaded, + Processed, +} + +/// Handles the entire lifecycle of the manifest. +/// +/// This is a tiny simple systems for simplicity: the core steps can be arranged however you'd like. +/// See the source code of the [`plugin`](leafwing_manifest::plugin) module for further inspiration. +fn manage_manifests( + mut progress: Local, + mut manifest_handle: Local>>, + mut commands: Commands, + asset_server: Res, + raw_manifest_assets: Res>, +) { + match *progress { + // Step 1: Start loading the raw manifest. + ManifestProgress::NotLoaded => { + // Load the raw manifest from disk + let handle = asset_server.load("raw_items.ron"); + *manifest_handle = Some(handle); + *progress = ManifestProgress::Loading; + } + // Step 2: Wait for the raw manifest to load. + ManifestProgress::Loading => { + // The handle is always created in the previous step, so this is safe. + let handle = manifest_handle.as_ref().unwrap(); + // Check if the asset is loaded + let load_state = asset_server.get_load_state(handle).unwrap(); + match load_state { + // We're safely loaded: timie to move on to the next step. + LoadState::Loaded => { + *progress = ManifestProgress::Loaded; + } + // We're still waiting for the asset to load + LoadState::NotLoaded | LoadState::Loading => (), + // Something went wrong: panic! + LoadState::Failed => { + panic!("Failed to load manifest"); + } + } + } + // Step 3: Process the raw manifest into a usable form. + // Step 4: Store the usable form as a resource. + ManifestProgress::Loaded => { + let raw_manifest = raw_manifest_assets + .get(manifest_handle.as_ref().unwrap()) + .unwrap() + // This process can be done without cloning, but it involves more sophisticated machinery. + .clone(); + + // We're deferring the actual work with commands to avoid blocking the whole world + // every time this system runs. + commands.add(|mut world: &mut World| { + let item_manifest = + ItemManifest::from_raw_manifest(raw_manifest, &mut world).unwrap(); + + world.insert_resource(item_manifest); + }); + *progress = ManifestProgress::Processed; + } + // All done: no more work to do! + ManifestProgress::Processed => (), + } +}