//! A collection of complimentary utility macros for building games //! //! Use the [asset_system] macro to create a set of types that manage loading assets, and then //! derive [JsonLoader] for any asset type to create a flexible resource loader that links in //! to the generated asset system //! //! ```rust //! use bevy::prelude::{App, DefaultPlugins, Image, Plugin, Res, ResMut, Resource, Assets, Asset, TextureAtlasLayout}; //! use bevy::reflect::{TypePath}; //! use micro_games_macros::{asset_system, JsonLoader}; //! use serde::{Deserialize, Serialize}; //! //! // We can customise the properties on the "asset loader" type that the //! // macro generates by adding "loader_property" annotations. The parameter //! // to this macro is simple any property declaration that could be added to //! // a regular type that derives SystemParam (including lifetimes) //! //! #[asset_system] //! #[loader_property(pub sheets: ResMut<'w, Assets<TextureAtlasLayout>>)] //! pub struct AssetHandles { //! // For non-JsonLoader assets, you just need to specify a name -> AssetType property. //! // The generated asset handles type will contain a hashmap of name -> Handle<AssetType> //! // under the property name specified. Loading functions such as load_asset_name will //! // be created on the loading type, where the first parameter is the path and the second //! // parameter is the asset name that will be used in the hashmap for later retrieval of //! // the asset //! images: Image, //! // To use an asset_system with a JsonLoader, you will need to add entries for both the //! // asset itself and its index type (generated by JsonLoader). Specific instances of //! // an asset are stored under the Item type (no suffix), keyed in the handle map by their //! // ID property. The index generated from each file (a map of ID -> Item) is stored under //! // the Index type (type name: AssetName + Index), keyed by the name provided when loading //! // the asset //! some_resource: MyCoolResource, //! // The property names generate load functions, but don't have to match the name of the //! // resource being loaded. Index & Item types for a JsonLoader resource can have their //! // names specified as part of the JsonLoader derive //! some_index_of_resources: MyCoolResourceIndex, //! } //! //! #[derive(JsonLoader, TypePath, Serialize, Deserialize, Asset)] //! #[loader(extension = "mcr", uuid = "00000000-0000-0000-0000-000000000000", //! asset_name = some_resource, index_name = some_index_of_resources)] //! pub struct MyCoolResource { //! // You must include exactly one of either a property named "id", or another property annotated //! // with the "asset_id" attribute. //! #[asset_id] //! some_identifier: String, //! foo: usize, //! bar: bool, //! } //! //! // The asset system generates a system param for scheduling the load of an asset. It uses bevy's //! // asset server, which makes the system compatible with plugins such as bevy_embedded_assets //! pub fn loading_system(mut loader: AssetHandlesLoader) { //! // The loader contains a number of functions for loading either individual assets //! // (two parameters, path and id), or a series of assets (a single parameter, a vector //! // of tuples each denoting path and id) //! loader.load_images("path/to/my_image.png", "my_image"); //! //! // JsonLoader assets require the use of the Index type to correctly load them, even for //! // files that only contain one object instance. The identifier (either id or asset_id) //! // will be used to add each instance of an asset to the Item type map //! loader.load_some_index_of_resources("path/to/my_asset.mcr", "my_asset"); //! } //! //! pub fn use_asset_system(assets: Res<AssetHandles>) { //! // JsonLoader assets can be accessed through the item type functions. These functions //! // provide a weak handle to the asset, meaning that the assets will usually unload //! // if removed from the AssetHandles type unless another strong handle was created externally //! let some_resource_handle = assets.some_resource("the_asset_id"); //! //! // The handle functions exactly match the name of the asset property in the originally //! // defined struct //! let image_handle = assets.images("my_image"); //! } //! //! pub struct AssetSystemPlugin; //! impl Plugin for AssetSystemPlugin { //! fn build(&self, app: &mut App) { //! app.init_resource::<AssetHandles>() //! // JsonLoader will create a plugin that wraps all of the functionality for a single //! // asset type, including loaders and hot reload support //! .add_plugins(MyCoolResourcePlugin); //! } //! } //! //! fn main() { //! let app = App::new() //! .add_plugins((DefaultPlugins, AssetSystemPlugin)); //! } //! ``` use proc_macro::TokenStream; use syn::{parse_macro_input, DeriveInput}; pub(crate) mod fqpath; pub(crate) mod utils; pub(crate) mod asset_system; pub(crate) mod event_system; pub(crate) mod json_loader; pub(crate) mod std_traits; pub(crate) mod tag_finder; /// Generate loader and handler implementations for keyed JSON resources. The asset must implement `bevy::asset::Asset`, as well as /// `serde::Deserialize` and `serde::Serialize` /// /// Container Attributes: /// /// - `loader(extension = "")`: *Required* - The file extension associated with this asset /// - `loader(uuid = "")`: *Required* - The uuid to use for the generated index asset. This _must_ be different to the UUID provided for the asset itself /// - `loader(storage = qualified::path::to::AssetStorage)`: The qualified path to an asset storage instance. Defaults to `AssetHandles` /// - `loader(asset_name = my_asset)`: The property of the asset storage used to store the asset. Defaults to the snake case version of the struct name & must be a valid identifier /// - `loader(index_name = my_asset_index)`: The property of the asset storage used to store an index of the asset types. Defaults to `asset_name`, suffixed with `_index` & must be a valid identifier /// /// Property Attributes: /// /// - `loader(asset_id)`: Label a property of the asset as the identifier, to be used as the key on which it will be matched. Must implement [std::fmt::Display]. /// If not present, will be assumed to be a property named "id". If no such property is present, and this attribute is not specified, that is considered an error /// /// ## Examples /// /// By default, it is assumed that each resource is keyed by a property named "id", that the /// storage /// /// ```rust /// # use std::collections::HashMap; /// # use bevy::asset::{Handle, Asset}; /// # use bevy::prelude::Resource; /// # use bevy::reflect::{TypePath}; /// # use serde::{Deserialize, Serialize}; /// # use micro_games_macros::JsonLoader; /// /// #[derive(Resource)] /// pub struct AssetHandles { /// simple_asset: HashMap<String, Handle<SimpleAsset>>, /// simple_asset_index: HashMap<String, Handle<SimpleAssetIndex>>, /// } /// /// #[derive(JsonLoader, TypePath, Serialize, Deserialize, Asset)] /// #[loader(extension = "satt", uuid = "00000000-0000-0000-0000-000000000000")] /// pub struct SimpleAsset { /// id: String, /// widget: usize, /// } /// /// fn main() { /// bevy::app::App::new() /// .add_plugins(bevy::prelude::DefaultPlugins) /// .add_plugins(SimpleAssetPlugin); /// } /// ``` /// /// Attributes can also be used to customise most of the default assumptions made by this derive: /// /// ```rust /// # use std::collections::HashMap; /// # use bevy::asset::{Handle, Asset}; /// # use bevy::prelude::Resource; /// # use bevy::reflect::{TypePath}; /// # use serde::{Deserialize, Serialize}; /// # use micro_games_macros::JsonLoader; /// /// #[derive(JsonLoader, TypePath, Serialize, Deserialize, Asset)] /// #[loader( /// extension = "asset.json", uuid = "00000000-0000-0000-0000-000000000000", /// storage = inner_module::SimpleAssetLocator, /// asset_name = some_asset_prop, index_name = set_of_assets /// )] /// pub struct MyAsset { /// /// The asset identifier needs to implement [std::fmt::Display] /// #[asset_id] /// uniq_ident: usize, /// } /// /// pub mod inner_module { /// # use bevy::prelude::{Handle, Resource}; /// # use std::collections::HashMap; /// /// #[derive(Resource)] /// pub struct SimpleAssetLocator { /// pub some_asset_prop: HashMap<String, Handle<super::MyAsset>>, /// pub set_of_assets: HashMap<String, Handle<super::MyAssetIndex>>, /// } /// } /// /// fn main() { /// bevy::app::App::new() /// .add_plugins(bevy::prelude::DefaultPlugins) /// .add_plugins(MyAssetPlugin); /// } /// ``` #[proc_macro_derive(JsonLoader, attributes(loader, asset_id))] pub fn json_loader(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); json_loader::json_loader(input).into() } /// Convert a mapping of name -> Asset type into an asset loading system /// /// ## Examples /// /// Convert a struct that represents mappings between names and asset types /// into an asset system to handle loading, and storing asset handles /// /// ```rust /// use bevy::prelude::{Image, Res, Resource}; /// use micro_games_macros::asset_system; /// /// #[asset_system] /// pub struct AssetHandles { /// my_asset: Image, /// } /// /// pub fn loading_system(mut loader: AssetHandlesLoader) { /// loader.load_my_asset("path/to/asset.png", "Asset"); /// } /// pub fn use_asset_system(assets: Res<AssetHandles>) { /// let handle = assets.my_asset("Asset"); /// } /// ``` /// /// Annotate any property with a `skip` attribute to omit it from the resulting loader /// /// ```rust /// use bevy::prelude::{Image, TextureAtlas, TextureAtlasLayout}; /// use micro_games_macros::asset_system; /// /// #[asset_system] /// pub struct AssetHandles { /// image: Image, /// #[skip] /// spritesheet: TextureAtlasLayout, /// } /// /// impl<'w> AssetHandlesLoader<'w> { /// pub fn load_spritesheet(&mut self, path: String, name: String) { /// let image_handle = self.load_image(path, name.clone()); /// // let sheet_handle = .. more code .. /// // self.spritesheet.insert(name, sheet_handle); /// } /// } /// ``` /// /// Include extra properties in the generated loader with one or more `loader_property` attributes. /// The included property must be a `SystemParam`, and has access to the `'w` lifetime /// /// ```rust /// use bevy::prelude::{Assets, Event, EventWriter, Handle, Image, ResMut, TextureAtlasLayout, UVec2}; /// use micro_games_macros::{asset_system, loader_property}; /// /// #[derive(Event)] /// pub struct LoadingEvent { /// pub event_id: usize, /// } /// /// #[asset_system] /// #[loader_property(pub load_events: EventWriter<'w, LoadingEvent>)] /// #[loader_property(pub sheets: ResMut<'w, Assets<TextureAtlasLayout>>)] /// pub struct AssetHandles { /// image: Image, /// #[skip] /// spritesheet: TextureAtlasLayout, /// } /// /// impl<'w> AssetHandlesLoader<'w> { /// pub fn load_spritesheet(&mut self, path: String, name: String) -> Handle<TextureAtlasLayout> { /// let image_handle = self.load_image(path, name.clone()); /// let sheet = TextureAtlasLayout::new_empty(UVec2::ZERO); /// let sheet_handle = self.sheets.add(sheet); /// /// self.storage /// .spritesheet /// .insert(name.clone(), sheet_handle.clone()); /// self.load_events.send(LoadingEvent { event_id: 123 }); /// /// sheet_handle /// } /// } /// ``` #[proc_macro_attribute] pub fn asset_system(_: TokenStream, input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); asset_system::asset_system(input).into() } /// /// ```rust /// use bevy::prelude::{Entity, EventWriter, IVec2}; /// use micro_games_macros::event_system; /// /// #[event_system] /// enum ActionEvent { /// Wait { source: Entity }, /// Move { source: Entity, to: IVec2 } /// } /// /// pub fn emit_wait_event(mut event_writer: EventWriter<WaitEvent>) { /// event_writer.send(WaitEvent { source: Entity::from_raw(0) }); /// } /// ``` #[proc_macro_attribute] pub fn event_system(_: TokenStream, input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); event_system::event_system(input).into() } /// Marker attribute, used exclusively by other proc macros #[proc_macro_attribute] #[doc(hidden)] pub fn skip(_: TokenStream, input: TokenStream) -> TokenStream { input } /// Marker attribute, used exclusively by other proc macros #[proc_macro_attribute] #[doc(hidden)] pub fn loader_property(_: TokenStream, input: TokenStream) -> TokenStream { input } /// Derive [std::convert::From] for single property tuple or named struct types /// /// ## Examples /// /// `FromInner` can derive `From` for single-property tuple structs /// /// ```rust /// use micro_games_macros::FromInner; /// /// #[derive(PartialEq, Eq, Debug, FromInner)] /// struct MyValue(i32); /// /// let value = 123i32.into(); /// /// assert_eq!(MyValue(123), value); /// assert_eq!(MyValue::from(123), value); /// ``` /// /// As well as single-property named structs /// /// /// ```rust /// use micro_games_macros::FromInner; /// /// #[derive(PartialEq, Eq, Debug, FromInner)] /// struct MyValue { /// foo_bar: usize, /// } /// /// let value = MyValue::from(2000); /// /// assert_eq!(value.foo_bar, 2000); /// assert_eq!(MyValue { foo_bar: 2000 }, value); /// assert_eq!(MyValue::from(2000), value); /// ``` #[proc_macro_derive(FromInner)] pub fn derive_from_inner(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); std_traits::from_inner::derive(input).into() } /// Create a TagFinder type, used for checking what tag components might contain /// /// ## Examples /// /// Spawns a number of entities, and then perform different behaviour based on tags /// /// ``` /// # use bevy::prelude::*; /// # use micro_games_macros::tag_finder; /// /// #[tag_finder] /// struct TagFinder { /// ally: Ally, /// enemy: Enemy, /// } /// /// pub fn spawn_entities(mut commands: Commands) { /// commands.spawn((Ally, SpatialBundle::default())); /// commands.spawn((Enemy, SpatialBundle::default())); /// commands.spawn((Enemy, SpatialBundle::default())); /// } /// /// pub fn my_checking_system(query: Query<(Entity, &Transform)>, tags: TagFinder) { /// for (e, t) in &query { /// if tags.is_ally(e) { /// println!("Celebrate"); /// } else if tags.is_enemy(e) { /// println!("Run away") /// } /// } /// } /// ``` #[proc_macro_attribute] pub fn tag_finder(_: TokenStream, input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); tag_finder::tag_finder(input).into() } /// Marker attribute, used exclusively by other proc macros #[proc_macro_attribute] #[doc(hidden)] pub fn sparse(_: TokenStream, input: TokenStream) -> TokenStream { input }