Skip to content
Snippets Groups Projects
lib.rs 8.27 KiB
Newer Older
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;
#[cfg(feature = "kayak")]
pub(crate) mod kayak;
pub(crate) mod std_traits;

/// 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;
/// # use bevy::prelude::Resource;
/// # use bevy::reflect::{TypePath, TypeUuid};
/// # 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, TypeUuid, Serialize, Deserialize)]
/// #[loader(extension = "satt", uuid = "00000000-0000-0000-0000-000000000000")]
/// #[uuid = "00000000-0000-0000-0000-000000000001"]
/// 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;
/// # use bevy::prelude::Resource;
/// # use bevy::reflect::{TypePath, TypeUuid};
/// # use serde::{Deserialize, Serialize};
/// # use micro_games_macros::JsonLoader;
///
/// #[derive(JsonLoader, TypePath, TypeUuid, Serialize, Deserialize)]
/// #[loader(
///     extension = "asset.json", uuid = "00000000-0000-0000-0000-000000000000",
///     storage = inner_module::SimpleAssetLocator,
///     asset_name = some_asset_prop, index_name = set_of_assets
/// )]
/// #[uuid = "00000000-0000-0000-0000-000000000001"]
/// 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};
/// use micro_games_macros::asset_system;
///
/// #[asset_system]
/// pub struct AssetHandles {
///   image: Image,
///   #[skip]
///   spritesheet: TextureAtlas
/// }
///
/// 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::{EventWriter, Event, Image, TextureAtlas, Assets, ResMut, Vec2, Handle};
/// 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<TextureAtlas>>)]
/// pub struct AssetHandles {
///   image: Image,
///   #[skip]
///   spritesheet: TextureAtlas
/// }
///
/// impl <'w>AssetHandlesLoader<'w> {
///   pub fn load_spritesheet(&mut self, path: String, name: String) -> Handle<TextureAtlas> {
///     let image_handle = self.load_image(path, name.clone());
///     let sheet = TextureAtlas::new_empty(image_handle, Vec2::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
}

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

#[proc_macro_derive(Widget)]
#[cfg(feature = "kayak")]
pub fn derive_kayak_wigdet(input: TokenStream) -> TokenStream {
	let input = parse_macro_input!(input as DeriveInput);
	kayak::derive_widget(input).into()
}
Louis's avatar
Louis committed

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