Reputation: 770
I'm reworking my rust ecs lib, and I'm re-writing the component iterator.
The main idea is to be able to iterate over any tuples of components, mutably or not, with invocation of my iteration macro like so:
for (pos: &mut Position, vel: &Velocity) in component_iterator!(compnents; mut Position, Velocity) {
Position += Velocity;
}
where Position
and Velocity are structs
.
The way I'm parsing the macro input is with recursive calls, that re order the input and allows me generate code one component at the time.
The macro will generate a struct, implement the Iterator
trait on it and return an instance of it. the issue comes from the macro implementing the Iterator trait.
Warning, lots of code coming in, I'll try to keep it short : Here is one of the four conditions I am parsing :
macro_rules! impl_iterator_inner {
// we are adding a non terminal non mutable component.
(
impl<'a> Iterator for MacroGeneratedComponentIterator<'a> {
type Item = ($($item_out:tt)*);
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.current_component {
($($match_out:tt)*)
MacroGeneratedComponentsEnum::EndOfIterator => {
let result = ($($result_out:tt)*);
self.current_entity += 1;
self.current_component = macro_generated_reset();
return Some(result);
}
}
}
}
}, $comp:ident, $($rest:tt)+
) => {
paste::paste! {
impl_iterator_inner!(
impl<'a> Iterator for MacroGeneratedComponentIterator<'a> {
type Item = ($($item_out)* &'a $comp,);
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.current_component {
(
$($match_out)*
MacroGeneratedComponentsEnum::$comp => {
while {
let elem = &self.[<$comp:snake>].peek()?;
if elem.index > self.current_entity {
self.current_entity = elem.index; // update the current entity
self.current_component = macro_generated_reset();
false
} else if elem.index == self.current_entity {
match self.active_entities.get(self.current_entity)? {
true => {
self.current_entity += 1; // current entity is inactive, go to next one
self.current_component = macro_generated_reset();
}
false => self.current_component = macro_generated_return_next(self.current_component),
}
false // stop iteration
} else { true /* keep iterating */ }
} {
self.[<$comp:snake>].next();
}
}
)
MacroGeneratedComponentsEnum::EndOfIterator => {
let result = (
(
$($result_out)*
&self.[<$comp:snake>].next()?.elem,
)
);
self.current_entity += 1;
self.current_component = macro_generated_reset();
return Some(result);
}
}
}
}
}, $($rest)+
);
}
};
}
in parameter, I'll take the whole impl, the next component and rest. Based on the current component, I will call the macro again with only the rest, adding to the impl the code for the current component. I have more of these, that treat mut $comp:ident
for mutable components, and the terminaison cases, where I return the whole impl.
I need these recursive calls, because I can't find another way to properly treat the mut Component
input, and the code generation is done at different places, and the macros have to be hygienic.
Now, to my problem :
When I'm trying this on a single component, I'm only reading the terminaison case (which is very similar to the shown case, and with no rest in the params) it works fine. But the moment I have a single recursive call, I'm getting an error :
"expected value, found module self
" at every instance of the self
keyword in my macro invocation. I though you could put anything in macro invocations, as long as it was parsed ?
I don't understand it as I'm generating a whole impl, so the &mut self
is properly defined in the function ?
The issue can be explored more in detail here : https://github.com/VirgileHenry/Foundry (warning, lot of code in progress and refactor, so some examples won't work.)
What is going on here ?
EDIT :
Here is a minimal example reproducing the problem : I'm using recursive macro calls to generate an impl for a struct, and it throws the same error : https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=369340ee70847e631eef219883938827
Upvotes: 1
Views: 713
Reputation: 23329
This is due to macro hygiene. The self
value inside the macro is not accessible from the outside, and conversely the self
value from the caller is not accessible from inside the macro. You can work around it by capturing self
in a macro argument:
macro_rules! impl_macro {
(
impl MyTrait for MyStruct {
fn draw(&mut $self:ident) {
($($out:tt)*)
}
}, $to_draw:expr, $($rest:tt)+
) => {
impl_macro!(
impl MyTrait for MyStruct {
fn draw(&mut $self) {
(
$($out)*
print!("self data : {}", $self.data);
println!("{:?}", $to_draw);
)
}
}, $($rest)+
);
};
(
impl MyTrait for MyStruct {
fn draw(&mut $self:ident) {
($($out:tt)*)
}
}, $to_draw:expr
) => {
impl MyTrait for MyStruct {
fn draw(&mut $self) {
$($out)*
print!("self data : {}", $self.data);
println!("{:?}", $to_draw);
}
}
};
}
struct MyStruct {
pub data: u8,
}
trait MyTrait {
fn draw(&mut self);
}
fn main() {
impl_macro!(
impl MyTrait for MyStruct {
fn draw(&mut self) {
()
}
}, "first draw", "second", 12
);
let mut st = MyStruct { data: 0 };
st.draw();
}
See also How to call methods on self in macros?
Upvotes: 2