Skip to content
Snippets Groups Projects
components.rs 3.24 KiB
Newer Older
use crate::fqpath::*;
use crate::utils::{ident_prefix, ident_suffix, snake_case_ident};
use proc_macro2::{Ident, TokenStream};
use quote::{quote, quote_spanned, ToTokens};
use syn::{
	parse_quote, spanned::Spanned, Attribute, Data, DataEnum, DataStruct, DeriveInput, Field, Meta,
	Variant, Visibility,
};

macro_rules! err_message {
    ($spannable: expr, $($tok:tt)*) => {
		return quote_spanned!(
			$spannable.span() =>
			compile_error!($($tok)*);
		)
	};
}

pub fn event_system(DeriveInput { data, ident, .. }: DeriveInput) -> TokenStream {
	let enum_data = match data {
		Data::Struct(d) => err_message!(
			d.struct_token,
			"Can't create an event system from an enum type"
		),
		Data::Enum(data_enum) => data_enum,
		Data::Union(u) => err_message!(
			u.union_token,
			"Can't create an asset system from a union type"
		),
	};

	let structs = define_structs(&enum_data);
	let root = define_root_enum(&ident, &enum_data);
	let helper = define_helper_fn(&ident, &enum_data);
	let plugin = define_plugin(&ident, &enum_data);

	quote! {
		#structs
		#root
		#helper
		#plugin
	}
}

fn define_structs(data: &DataEnum) -> TokenStream {
	data.variants
		.iter()
		.map(
			|Variant {
			     ident,
			     attrs,
			     fields,
			     ..
			 }| {
				let event_name = ident_suffix(ident, "Event");
				let fields: TokenStream = fields
					.iter()
Louis's avatar
Louis committed
					.map(|fl @ Field { ident, ty, .. }| {
						let f = Field {
							ident: ident.clone(),
							ty: ty.clone(),
							vis: parse_quote!(pub),
							..fl.clone()
						}
						.to_token_stream();
Louis's avatar
Louis committed
						quote!(#f,)
					})
					.collect();

				quote! {
					#[derive(#FQClone, #FQDebug, #BevyEvent, #FQSerialize, #FQDeserialize)]
					pub struct #event_name {
						#fields
					}
				}
			},
		)
		.collect()
}

fn define_root_enum(name: &Ident, data: &DataEnum) -> TokenStream {
	let variants: TokenStream = data
		.variants
		.iter()
		.map(|Variant { ident, .. }| {
			let event = ident_suffix(ident, "Event");
			quote!(#ident(#event),)
		})
		.collect();

	let conversions: TokenStream = data
		.variants
		.iter()
		.map(|Variant { ident, .. }| {
			let event = ident_suffix(ident, "Event");
			quote! {
				impl #FQFrom<#event> for #name {
					fn from(value: #event) -> Self {
						#name::#ident(value)
					}
				}
			}
		})
		.collect();

	quote! {
		#[derive(#FQDebug, #FQClone, #BevyEvent, #FQSerialize, #FQDeserialize)]
		pub enum #name {
			#variants
		}

		#conversions
	}
}

fn define_helper_fn(name: &Ident, data: &DataEnum) -> TokenStream {
	let variants: TokenStream = data
		.variants
		.iter()
		.map(|Variant { ident, .. }| quote! { #name::#ident(value) => #send_event(world, value), })
		.collect();

	let fn_name = ident_prefix(&snake_case_ident(name), "dispatch_");

	quote! {
		pub fn #fn_name(world: &mut #BevyWorld, event: #name) {
			match event {
				#variants
			}
		}
	}
}

fn define_plugin(name: &Ident, data: &DataEnum) -> TokenStream {
	let variants: TokenStream = data
		.variants
		.iter()
		.map(|Variant { ident, .. }| {
			let event = ident_suffix(ident, "Event");
			quote!(app.add_event::<#event>();)
		})
		.collect();

	let plugin_name = ident_suffix(name, "Plugin");

	quote! {
		pub struct #plugin_name;
		impl #BevyPlugin for #plugin_name {
			fn build(&self, app: &mut #BevyApp) {
				#variants
			}
		}
	}
}