Skip to content

Commit 39091c0

Browse files
committed
derive World
1 parent e6afc94 commit 39091c0

File tree

4 files changed

+166
-3
lines changed

4 files changed

+166
-3
lines changed

crates/stecs-derive/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ mod get;
77
mod optic;
88
mod query;
99
mod split;
10+
mod world;
11+
12+
#[proc_macro_derive(World, attributes(world))]
13+
pub fn derive_world(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
14+
let input: syn::DeriveInput = syn::parse_macro_input!(input);
15+
match world::WorldOpts::from_derive_input(&input) {
16+
Ok(input) => input.derive().into(),
17+
Err(e) => e.write_errors().into(),
18+
}
19+
}
1020

1121
#[proc_macro_derive(SplitFields, attributes(split))]
1222
pub fn derive_split_fields(input: proc_macro::TokenStream) -> proc_macro::TokenStream {

crates/stecs-derive/src/world.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
use crate::syn;
2+
3+
use darling::{ast, FromDeriveInput, FromField};
4+
use proc_macro2::TokenStream;
5+
use quote::{quote, ToTokens};
6+
7+
#[derive(FromDeriveInput)]
8+
#[darling(supports(struct_named), attributes(world))]
9+
pub struct WorldOpts {
10+
ident: syn::Ident,
11+
// vis: syn::Visibility,
12+
data: ast::Data<(), FieldOpts>,
13+
// generics: syn::Generics,
14+
}
15+
16+
#[derive(FromField)]
17+
#[darling(attributes(split))]
18+
struct FieldOpts {
19+
ident: Option<syn::Ident>,
20+
// ty: syn::Type,
21+
// groups: Option<Vec<()>>,
22+
}
23+
24+
struct Struct {
25+
name: syn::Ident,
26+
// visibility: syn::Visibility,
27+
fields: Vec<Field>,
28+
// generics: syn::Generics,
29+
}
30+
31+
struct Field {
32+
name: syn::Ident,
33+
// ty: syn::Type,
34+
// groups: Vec<()>,
35+
}
36+
37+
#[derive(thiserror::Error, Debug)]
38+
enum ParseError {
39+
#[error("not a struct")]
40+
NotAStruct,
41+
#[error("field has no name")]
42+
NamelessField,
43+
}
44+
45+
impl TryFrom<WorldOpts> for Struct {
46+
type Error = ParseError;
47+
48+
fn try_from(value: WorldOpts) -> Result<Self, Self::Error> {
49+
let fields = value
50+
.data
51+
.take_struct()
52+
.ok_or(ParseError::NotAStruct)?
53+
.fields;
54+
let fields = fields
55+
.into_iter()
56+
.map(|field| {
57+
let name = field.ident.ok_or(ParseError::NamelessField)?;
58+
Ok(Field {
59+
name,
60+
// ty: field.ty
61+
})
62+
})
63+
.collect::<Result<Vec<Field>, ParseError>>()?;
64+
Ok(Self {
65+
name: value.ident,
66+
// visibility: value.vis,
67+
fields,
68+
// generics: value.generics,
69+
})
70+
}
71+
}
72+
73+
impl WorldOpts {
74+
pub fn derive(self) -> TokenStream {
75+
let query = Struct::try_from(self).unwrap_or_else(|err| panic!("{err}"));
76+
query.derive()
77+
}
78+
}
79+
80+
impl Struct {
81+
fn derive(self) -> TokenStream {
82+
let world = self.name;
83+
84+
// impl Default for World
85+
let default = {
86+
let fields = self.fields.iter().map(|field| {
87+
let name = &field.name;
88+
quote! { #name: ::std::default::Default::default(), }
89+
});
90+
91+
quote! {
92+
impl ::std::default::Default for #world {
93+
fn default() -> Self {
94+
Self {
95+
#(#fields)*
96+
}
97+
}
98+
}
99+
}
100+
};
101+
102+
let all_fields = self.fields.iter().map(|field| &field.name);
103+
let query_all = generate_query(quote! { query_all }, all_fields);
104+
105+
quote! {
106+
#default
107+
#query_all
108+
}
109+
}
110+
}
111+
112+
// macro_rules! query_all {
113+
// ($world:expr, $args:tt) => {
114+
// query!([$world.players, $world.enemies, $world.particles], $args)
115+
// };
116+
// }
117+
fn generate_query(
118+
name: impl ToTokens,
119+
fields: impl IntoIterator<Item = impl ToTokens>,
120+
) -> TokenStream {
121+
let fields = fields.into_iter().map(|field| quote! { $world.#field });
122+
quote! {
123+
macro_rules! #name {
124+
($world:expr, $args:tt) => {
125+
query!([#(#fields),*], $args)
126+
}
127+
}
128+
}
129+
}

examples/world.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,27 @@
11
use stecs::prelude::*;
22

3-
// #[derive(World)]
3+
#[derive(World)]
44
pub struct World {
5+
#[world(groups = ["actor"])]
56
pub players: StructOf<Dense<Player>>,
7+
#[world(groups = ["actor"])]
68
pub enemies: StructOf<Dense<Enemy>>,
9+
#[world(groups = [])]
710
pub particles: StructOf<Dense<Particle>>,
811
}
912

13+
// macro_rules! query_all {
14+
// ($world:expr, $args:tt) => {
15+
// query!([$world.players, $world.enemies, $world.particles], $args)
16+
// };
17+
// }
18+
19+
// macro_rules! actors {
20+
// ($world:expr) => {
21+
// [$world.players, $world.enemies]
22+
// };
23+
// }
24+
1025
#[derive(SplitFields)]
1126
pub struct Position {
1227
pub x: f32,
@@ -43,4 +58,10 @@ pub struct Enemy {
4358
pub ai: EnemyAi,
4459
}
4560

46-
fn main() {}
61+
fn main() {
62+
let world = World::default();
63+
64+
for position in query_all!(world, (&position.x)) {
65+
println!("entity at position: {}", position);
66+
}
67+
}

src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@
178178
///
179179
pub use stecs_derive::SplitFields;
180180

181+
// TODO: docs
182+
pub use stecs_derive::World;
183+
181184
/// Get components of a specific entity.
182185
///
183186
/// Syntax is identical to [`query!`], with an additional `id` argument right after the archetype.
@@ -291,6 +294,6 @@ pub mod prelude {
291294
archetype::{Archetype, StructOf},
292295
get, query,
293296
storage::{IdGenerator, Storage},
294-
SplitFields,
297+
SplitFields, World,
295298
};
296299
}

0 commit comments

Comments
 (0)