Skip to content
Snippets Groups Projects
Verified Commit 0ce86f7e authored by Louis's avatar Louis :fire:
Browse files

Add event_system for generating all the scaffolding for network compatible event broadcasting

parent d9d86899
No related branches found
No related tags found
No related merge requests found
......@@ -4,6 +4,7 @@
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
......
......@@ -22,16 +22,16 @@ syn = "2.0"
[dev-dependencies]
test-case = "3.3.1"
serde = { version = "1.0", features = ["derive"]}
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
anyhow = "1.0"
micro_bevy_world_utils = "0.4.0"
[dev-dependencies.bevy]
version = "0.13"
default-features = false
features = [
"bevy_asset",
"bevy_sprite",
"bevy_core_pipeline",
"bevy_asset",
"bevy_sprite",
"bevy_core_pipeline",
]
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()
.map(
|fl @ Field {
ident,
ty,
mutability,
..
}| {
let f = Field {
ident: ident.clone(),
ty: ty.clone(),
vis: parse_quote!(pub),
..fl.clone()
}
.to_token_stream();
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
}
}
}
}
mod components;
pub use components::event_system;
#![allow(non_camel_case_types)]
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
......@@ -38,6 +40,8 @@ fq!(ImportBevyPrelude => use ::bevy::prelude::*);
fq!(BevyApp => ::bevy::app::App);
fq!(BevyPlugin => ::bevy::app::Plugin);
fq!(BevyUpdate => ::bevy::app::Update);
fq!(BevyWorld => ::bevy::ecs::world::World);
fq!(BevyEvent => ::bevy::ecs::event::Event);
fq!(BevyRes => ::bevy::ecs::system::Res);
fq!(BevyResMut => ::bevy::ecs::system::ResMut);
fq!(BevyEventReader => ::bevy::ecs::event::EventReader);
......@@ -60,5 +64,7 @@ fq!(BevyAddAsset => ::bevy::asset::AssetApp);
fq!(BevyAsyncRead => ::bevy::asset::AsyncReadExt);
fq!(BevyAssetReader => ::bevy::asset::io::Reader);
fq!(send_event => ::micro_bevy_world_utils::send_event);
#[cfg(feature = "kayak")]
fq!(KayakWidget => ::kayak_ui::prelude::Widget);
......@@ -99,6 +99,7 @@ pub(crate) mod fqpath;
pub(crate) mod utils;
pub(crate) mod asset_system;
pub(crate) mod event_system;
pub(crate) mod json_loader;
#[cfg(feature = "kayak")]
pub(crate) mod kayak;
......@@ -286,6 +287,27 @@ pub fn asset_system(_: TokenStream, input: TokenStream) -> TokenStream {
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)]
......
use micro_games_macros::event_system;
use std::sync::Mutex;
#[test]
fn event_system_correctly_generates_and_dispatches_events() {
use bevy::prelude::*;
let mut app = App::new();
#[event_system]
enum MyEvents {
Wait { source: Entity },
Log { source: Entity, mesasge: String },
}
/// A hatch to allow us to assert that the system has actually run, so we don't miss an
/// assertion
#[derive(Resource)]
struct HasRun(bool);
app.insert_resource(HasRun(false));
app.add_plugins(MyEventsPlugin);
app.add_systems(
Update,
|mut has_run: ResMut<HasRun>, mut events: EventReader<WaitEvent>| {
has_run.0 = true;
let event_length = events.len();
assert_eq!(event_length, 1);
},
);
dispatch_my_events(
&mut app.world,
MyEvents::Wait(WaitEvent {
source: Entity::from_raw(0),
}),
);
app.update();
assert!(app.world.resource::<HasRun>().0);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment