Skip to content

Commit

Permalink
derive World
Browse files Browse the repository at this point in the history
  • Loading branch information
Nertsal committed Nov 19, 2024
1 parent e6afc94 commit 39091c0
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 3 deletions.
10 changes: 10 additions & 0 deletions crates/stecs-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
129 changes: 129 additions & 0 deletions crates/stecs-derive/src/world.rs
Original file line number Diff line number Diff line change
@@ -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<syn::Ident>,
// ty: syn::Type,
// groups: Option<Vec<()>>,
}

struct Struct {
name: syn::Ident,
// visibility: syn::Visibility,
fields: Vec<Field>,
// 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<WorldOpts> for Struct {
type Error = ParseError;

fn try_from(value: WorldOpts) -> Result<Self, Self::Error> {
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::<Result<Vec<Field>, 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<Item = impl ToTokens>,
) -> TokenStream {
let fields = fields.into_iter().map(|field| quote! { $world.#field });
quote! {
macro_rules! #name {
($world:expr, $args:tt) => {
query!([#(#fields),*], $args)
}
}
}
}
25 changes: 23 additions & 2 deletions examples/world.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
use stecs::prelude::*;

// #[derive(World)]
#[derive(World)]
pub struct World {
#[world(groups = ["actor"])]
pub players: StructOf<Dense<Player>>,
#[world(groups = ["actor"])]
pub enemies: StructOf<Dense<Enemy>>,
#[world(groups = [])]
pub particles: StructOf<Dense<Particle>>,
}

// 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,
Expand Down Expand Up @@ -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);
}
}
5 changes: 4 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -291,6 +294,6 @@ pub mod prelude {
archetype::{Archetype, StructOf},
get, query,
storage::{IdGenerator, Storage},
SplitFields,
SplitFields, World,
};
}

0 comments on commit 39091c0

Please sign in to comment.