diff --git a/crates/stecs-derive/src/lib.rs b/crates/stecs-derive/src/lib.rs index 60138a1..94ec834 100644 --- a/crates/stecs-derive/src/lib.rs +++ b/crates/stecs-derive/src/lib.rs @@ -7,6 +7,16 @@ mod get; mod optic; mod query; mod split; +mod world; + +#[proc_macro_derive(World, attributes(world))] +pub fn derive_world(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input: syn::DeriveInput = syn::parse_macro_input!(input); + match world::WorldOpts::from_derive_input(&input) { + Ok(input) => input.derive().into(), + Err(e) => e.write_errors().into(), + } +} #[proc_macro_derive(SplitFields, attributes(split))] pub fn derive_split_fields(input: proc_macro::TokenStream) -> proc_macro::TokenStream { diff --git a/crates/stecs-derive/src/world.rs b/crates/stecs-derive/src/world.rs new file mode 100644 index 0000000..b6bb31b --- /dev/null +++ b/crates/stecs-derive/src/world.rs @@ -0,0 +1,129 @@ +use crate::syn; + +use darling::{ast, FromDeriveInput, FromField}; +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; + +#[derive(FromDeriveInput)] +#[darling(supports(struct_named), attributes(world))] +pub struct WorldOpts { + ident: syn::Ident, + // vis: syn::Visibility, + data: ast::Data<(), FieldOpts>, + // generics: syn::Generics, +} + +#[derive(FromField)] +#[darling(attributes(split))] +struct FieldOpts { + ident: Option, + // ty: syn::Type, + // groups: Option>, +} + +struct Struct { + name: syn::Ident, + // visibility: syn::Visibility, + fields: Vec, + // generics: syn::Generics, +} + +struct Field { + name: syn::Ident, + // ty: syn::Type, + // groups: Vec<()>, +} + +#[derive(thiserror::Error, Debug)] +enum ParseError { + #[error("not a struct")] + NotAStruct, + #[error("field has no name")] + NamelessField, +} + +impl TryFrom for Struct { + type Error = ParseError; + + fn try_from(value: WorldOpts) -> Result { + let fields = value + .data + .take_struct() + .ok_or(ParseError::NotAStruct)? + .fields; + let fields = fields + .into_iter() + .map(|field| { + let name = field.ident.ok_or(ParseError::NamelessField)?; + Ok(Field { + name, + // ty: field.ty + }) + }) + .collect::, ParseError>>()?; + Ok(Self { + name: value.ident, + // visibility: value.vis, + fields, + // generics: value.generics, + }) + } +} + +impl WorldOpts { + pub fn derive(self) -> TokenStream { + let query = Struct::try_from(self).unwrap_or_else(|err| panic!("{err}")); + query.derive() + } +} + +impl Struct { + fn derive(self) -> TokenStream { + let world = self.name; + + // impl Default for World + let default = { + let fields = self.fields.iter().map(|field| { + let name = &field.name; + quote! { #name: ::std::default::Default::default(), } + }); + + quote! { + impl ::std::default::Default for #world { + fn default() -> Self { + Self { + #(#fields)* + } + } + } + } + }; + + let all_fields = self.fields.iter().map(|field| &field.name); + let query_all = generate_query(quote! { query_all }, all_fields); + + quote! { + #default + #query_all + } + } +} + +// macro_rules! query_all { +// ($world:expr, $args:tt) => { +// query!([$world.players, $world.enemies, $world.particles], $args) +// }; +// } +fn generate_query( + name: impl ToTokens, + fields: impl IntoIterator, +) -> TokenStream { + let fields = fields.into_iter().map(|field| quote! { $world.#field }); + quote! { + macro_rules! #name { + ($world:expr, $args:tt) => { + query!([#(#fields),*], $args) + } + } + } +} diff --git a/examples/world.rs b/examples/world.rs index 88efe4b..f404f05 100644 --- a/examples/world.rs +++ b/examples/world.rs @@ -1,12 +1,27 @@ use stecs::prelude::*; -// #[derive(World)] +#[derive(World)] pub struct World { + #[world(groups = ["actor"])] pub players: StructOf>, + #[world(groups = ["actor"])] pub enemies: StructOf>, + #[world(groups = [])] pub particles: StructOf>, } +// macro_rules! query_all { +// ($world:expr, $args:tt) => { +// query!([$world.players, $world.enemies, $world.particles], $args) +// }; +// } + +// macro_rules! actors { +// ($world:expr) => { +// [$world.players, $world.enemies] +// }; +// } + #[derive(SplitFields)] pub struct Position { pub x: f32, @@ -43,4 +58,10 @@ pub struct Enemy { pub ai: EnemyAi, } -fn main() {} +fn main() { + let world = World::default(); + + for position in query_all!(world, (&position.x)) { + println!("entity at position: {}", position); + } +} diff --git a/src/lib.rs b/src/lib.rs index 5b15048..15a9df1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -178,6 +178,9 @@ /// pub use stecs_derive::SplitFields; +// TODO: docs +pub use stecs_derive::World; + /// Get components of a specific entity. /// /// Syntax is identical to [`query!`], with an additional `id` argument right after the archetype. @@ -291,6 +294,6 @@ pub mod prelude { archetype::{Archetype, StructOf}, get, query, storage::{IdGenerator, Storage}, - SplitFields, + SplitFields, World, }; }