Skip to content
Snippets Groups Projects
lib.rs 14.7 KiB
Newer Older
//! 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
Louis's avatar
Louis committed
//! 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]
Louis's avatar
Louis committed
//! #[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,
//! }
//!
Louis's avatar
Louis committed
//! #[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)
Louis's avatar
Louis committed
//!     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 json_loader;
pub(crate) mod std_traits;
Louis's avatar
Louis committed
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;
Louis's avatar
Louis committed
/// # 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>>,
/// }
///
Louis's avatar
Louis committed
/// #[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;
Louis's avatar
Louis committed
/// # use bevy::reflect::{TypePath};
/// # use serde::{Deserialize, Serialize};
/// # use micro_games_macros::JsonLoader;
///
Louis's avatar
Louis committed
/// #[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
Louis's avatar
Louis committed
/// use bevy::prelude::{Image, TextureAtlas, TextureAtlasLayout};
/// use micro_games_macros::asset_system;
///
/// #[asset_system]
/// pub struct AssetHandles {
///     image: Image,
///     #[skip]
Louis's avatar
Louis committed
///     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
Louis's avatar
Louis committed
/// 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>)]
Louis's avatar
Louis committed
/// #[loader_property(pub sheets: ResMut<'w, Assets<TextureAtlasLayout>>)]
/// pub struct AssetHandles {
///     image: Image,
///     #[skip]
Louis's avatar
Louis committed
///     spritesheet: TextureAtlasLayout,
/// }
///
/// impl<'w> AssetHandlesLoader<'w> {
Louis's avatar
Louis committed
///     pub fn load_spritesheet(&mut self, path: String, name: String) -> Handle<TextureAtlasLayout> {
///         let image_handle = self.load_image(path, name.clone());
Louis's avatar
Louis committed
///         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()
Louis's avatar
Louis committed
}

///
/// ```rust
/// use bevy::prelude::{Entity, EventWriter, IVec2};
/// use micro_games_macros::event_system;
///
/// #[event_system]
/// enum ActionEvent {
Louis's avatar
Louis committed
///     Wait { source: Entity },
///     Move { source: Entity, to: IVec2 }
/// }
///
/// pub fn emit_wait_event(mut event_writer: EventWriter<WaitEvent>) {
Louis's avatar
Louis committed
///     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()
Louis's avatar
Louis committed
}
Louis's avatar
Louis committed

/// 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
}