From e4f45c59415193361b62c4f6a08b88fe5c093213 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 17 Apr 2024 14:30:20 -0400 Subject: [PATCH 1/3] Write items_by_name_example --- examples/items_by_name.rs | 106 +++++++++++++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 2 deletions(-) diff --git a/examples/items_by_name.rs b/examples/items_by_name.rs index 5502e14..2665ae0 100644 --- a/examples/items_by_name.rs +++ b/examples/items_by_name.rs @@ -7,7 +7,109 @@ //! although even then it can be useful to be able to spawn objects by name for scripting purposes: //! creating effects, dynamically spawning enemies, etc. //! -//! This example shows how to use manifests to create a simple system for spawning objects by name, +//! This example shows how to use manifests to create a simple system for working with manifest entries by name, //! although the same principles can be used to manipulate manifests if using the [`MutableManifest`] trait as well. -fn main() {} +use bevy::{log::LogPlugin, prelude::*, utils::HashMap}; +use leafwing_manifest::{ + asset_state::SimpleAssetState, + identifier::Id, + manifest::{Manifest, ManifestFormat}, + plugin::{ManifestPlugin, RegisterManifest}, +}; +use serde::{Deserialize, Serialize}; + +// While you *can* simply use the name directly via the various name-based methods on the Manifest trait, +// it's generally a good idea to store constants for the names you're going to use when possible. +// This has three main benefits: +// 1. It makes it easier to refactor your code later, as you can change the name in one place and have it propagate everywhere. +// 2. It makes it easier to catch typos, as the compiler will catch any references to a name that doesn't exist. +// 3. It saves on recomputing the hash of the name every time you need it. +const SWORD: Id = Id::from_name("sword"); +const SHIELD: Id = Id::from_name("shield"); + +/// The data for as single item that might be held in the player's inventory. +#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[allow(dead_code)] // Properties are for demonstration purposes only. +struct Item { + name: String, + description: String, + value: i32, + weight: f32, + max_stack: u8, +} + +/// A data-driven manifest, which contains the canonical data for all the items in the game. +#[derive(Debug, Resource, Asset, TypePath, Serialize, Deserialize, PartialEq)] +struct ItemManifest { + items: HashMap, Item>, +} + +impl Manifest for ItemManifest { + type Item = Item; + type RawItem = Item; + type RawManifest = ItemManifest; + 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 { + Ok(raw_manifest) + } +} + +fn main() { + App::new() + .add_plugins((MinimalPlugins, AssetPlugin::default(), LogPlugin::default())) + .init_state::() + .add_plugins(ManifestPlugin::::default()) + .register_manifest::("items.ron") + .add_systems(OnEnter(SimpleAssetState::Ready), look_up_items_by_name) + .run(); +} + +/// This system reads the generated item manifest resource and prints out all the items. +fn look_up_items_by_name(item_manifest: Res) { + // Look up the items by name. + let sword = item_manifest.get(SWORD); + let shield = item_manifest.get(SHIELD); + + // Print out the items. + if let Some(sword) = sword { + println!("Found sword: {:?}", sword); + } else { + println!("Sword not found!"); + } + + if let Some(shield) = shield { + println!("Found shield: {:?}", shield); + } else { + println!("Shield not found!"); + } + + // We could also use the `get_by_name` method, which is a bit more concise, + // but doesn't provide the same level of type safety as using the `Id` directly. + // However, using these methods is the right choice when working with truly dynamic inputs: + // for example, when reading from a file or user input. + let sword = item_manifest.get_by_name("sword"); + let shield = item_manifest.get_by_name("shield"); + + if let Some(sword) = sword { + println!("Found sword by name: {:?}", sword); + } else { + println!("Sword not found by name!"); + } + + if let Some(shield) = shield { + println!("Found shield by name: {:?}", shield); + } else { + println!("Shield not found by name!"); + } +} From edfe47b5c970b941be5961418496adb6e3c746a4 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 17 Apr 2024 17:07:05 -0400 Subject: [PATCH 2/3] Swap to Borrow for better API ergonomics --- src/manifest.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/manifest.rs b/src/manifest.rs index cb1cdc7..660224f 100644 --- a/src/manifest.rs +++ b/src/manifest.rs @@ -1,4 +1,4 @@ -use std::error::Error; +use std::{borrow::Borrow, error::Error}; use bevy::{ asset::Asset, @@ -96,8 +96,8 @@ pub trait Manifest: Sized + Resource { /// /// Returns [`None`] if no item with the given name is found. #[must_use] - fn get_by_name(&self, name: impl AsRef) -> Option<&Self::Item> { - self.get(Id::from_name(name.as_ref())) + fn get_by_name(&self, name: impl Borrow) -> Option<&Self::Item> { + self.get(Id::from_name(name.borrow())) } } @@ -168,14 +168,14 @@ pub trait MutableManifest: Manifest { /// The item is given a unique identifier, which is returned. fn insert_by_name( &mut self, - name: impl AsRef, + name: impl Borrow, item: Self::Item, ) -> Result, ManifestModificationError> { - let id = Id::from_name(name.as_ref()); + let id = Id::from_name(name.borrow()); if self.get(id).is_some() { Err(ManifestModificationError::DuplicateName( - name.as_ref().to_string(), + name.borrow().to_string(), )) } else { self.insert(item) @@ -195,9 +195,9 @@ pub trait MutableManifest: Manifest { /// The item removed is returned, if it was found. fn remove_by_name( &mut self, - name: impl AsRef, + name: impl Borrow, ) -> Result, ManifestModificationError> { - self.remove(&Id::from_name(name.as_ref())) + self.remove(&Id::from_name(name.borrow())) } /// Gets a mutable reference to an item from the manifest by its unique identifier. @@ -210,8 +210,8 @@ pub trait MutableManifest: Manifest { /// /// Returns [`None`] if no item with the given name is found. #[must_use] - fn get_mut_by_name(&mut self, name: impl AsRef) -> Option<&mut Self::Item> { - self.get_mut(Id::from_name(name.as_ref())) + fn get_mut_by_name(&mut self, name: impl Borrow) -> Option<&mut Self::Item> { + self.get_mut(Id::from_name(name.borrow())) } } From 4d46e6b4ed91185528f670f9b0538d0f3561cbcc Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Wed, 17 Apr 2024 17:37:49 -0400 Subject: [PATCH 3/3] Call out that we're copying simple.rs --- examples/items_by_name.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/items_by_name.rs b/examples/items_by_name.rs index 2665ae0..b2eb351 100644 --- a/examples/items_by_name.rs +++ b/examples/items_by_name.rs @@ -9,6 +9,8 @@ //! //! This example shows how to use manifests to create a simple system for working with manifest entries by name, //! although the same principles can be used to manipulate manifests if using the [`MutableManifest`] trait as well. +//! +//! This code is largely copied from the `simple.rs` example: we're just adding constants and a new system to demonstrate the name-based lookups. use bevy::{log::LogPlugin, prelude::*, utils::HashMap}; use leafwing_manifest::{ @@ -28,7 +30,6 @@ use serde::{Deserialize, Serialize}; const SWORD: Id = Id::from_name("sword"); const SHIELD: Id = Id::from_name("shield"); -/// The data for as single item that might be held in the player's inventory. #[derive(Debug, Serialize, Deserialize, PartialEq)] #[allow(dead_code)] // Properties are for demonstration purposes only. struct Item { @@ -39,7 +40,6 @@ struct Item { max_stack: u8, } -/// A data-driven manifest, which contains the canonical data for all the items in the game. #[derive(Debug, Resource, Asset, TypePath, Serialize, Deserialize, PartialEq)] struct ItemManifest { items: HashMap, Item>,