Skip to content

Commit 6314f50

Browse files
States derive macro (#7535)
# Objective Implementing `States` manually is repetitive, so let's not. One thing I'm unsure of is whether the macro import statement is in the right place.
1 parent 6d399bf commit 6314f50

File tree

6 files changed

+57
-52
lines changed

6 files changed

+57
-52
lines changed

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ extern crate proc_macro;
33
mod component;
44
mod fetch;
55
mod set;
6+
mod states;
67

78
use crate::{fetch::derive_world_query_impl, set::derive_set};
89
use bevy_macro_utils::{derive_boxed_label, get_named_struct_fields, BevyManifest};
@@ -558,3 +559,8 @@ pub fn derive_resource(input: TokenStream) -> TokenStream {
558559
pub fn derive_component(input: TokenStream) -> TokenStream {
559560
component::derive_component(input)
560561
}
562+
563+
#[proc_macro_derive(States)]
564+
pub fn derive_states(input: TokenStream) -> TokenStream {
565+
states::derive_states(input)
566+
}

crates/bevy_ecs/macros/src/states.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use proc_macro::{Span, TokenStream};
2+
use quote::{format_ident, quote};
3+
use syn::{parse_macro_input, Data::Enum, DeriveInput};
4+
5+
use crate::bevy_ecs_path;
6+
7+
pub fn derive_states(input: TokenStream) -> TokenStream {
8+
let ast = parse_macro_input!(input as DeriveInput);
9+
let error = || {
10+
syn::Error::new(
11+
Span::call_site().into(),
12+
"derive(States) only supports fieldless enums",
13+
)
14+
.into_compile_error()
15+
.into()
16+
};
17+
let Enum(enumeration) = ast.data else {
18+
return error();
19+
};
20+
if enumeration.variants.iter().any(|v| !v.fields.is_empty()) {
21+
return error();
22+
}
23+
24+
let generics = ast.generics;
25+
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
26+
27+
let mut trait_path = bevy_ecs_path();
28+
trait_path.segments.push(format_ident!("schedule").into());
29+
trait_path.segments.push(format_ident!("States").into());
30+
let struct_name = &ast.ident;
31+
let idents = enumeration.variants.iter().map(|v| &v.ident);
32+
let len = idents.len();
33+
34+
quote! {
35+
impl #impl_generics #trait_path for #struct_name #ty_generics #where_clause {
36+
type Iter = std::array::IntoIter<Self, #len>;
37+
38+
fn variants() -> Self::Iter {
39+
[#(Self::#idents,)*].into_iter()
40+
}
41+
}
42+
}
43+
.into()
44+
}

crates/bevy_ecs/src/schedule/state.rs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use crate::schedule::{ScheduleLabel, SystemSet};
77
use crate::system::Resource;
88
use crate::world::World;
99

10+
pub use bevy_ecs_macros::States;
11+
1012
/// Types that can define world-wide states in a finite-state machine.
1113
///
1214
/// The [`Default`] trait defines the starting state.
@@ -25,22 +27,14 @@ use crate::world::World;
2527
/// ```rust
2628
/// use bevy_ecs::prelude::States;
2729
///
28-
/// #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
30+
/// #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)]
2931
/// enum GameState {
3032
/// #[default]
3133
/// MainMenu,
3234
/// SettingsMenu,
3335
/// InGame,
3436
/// }
3537
///
36-
/// impl States for GameState {
37-
/// type Iter = std::array::IntoIter<GameState, 3>;
38-
///
39-
/// fn variants() -> Self::Iter {
40-
/// [GameState::MainMenu, GameState::SettingsMenu, GameState::InGame].into_iter()
41-
/// }
42-
/// }
43-
///
4438
/// ```
4539
pub trait States: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug + Default {
4640
type Iter: Iterator<Item = Self>;

examples/ecs/state.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,13 @@ fn main() {
2525
.run();
2626
}
2727

28-
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash)]
28+
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, States)]
2929
enum AppState {
3030
#[default]
3131
Menu,
3232
InGame,
3333
}
3434

35-
impl States for AppState {
36-
type Iter = std::array::IntoIter<AppState, 2>;
37-
38-
fn variants() -> Self::Iter {
39-
[AppState::Menu, AppState::InGame].into_iter()
40-
}
41-
}
42-
4335
#[derive(Resource)]
4436
struct MenuData {
4537
button_entity: Entity,

examples/games/alien_cake_addict.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,13 @@ use std::f32::consts::PI;
55
use bevy::prelude::*;
66
use rand::Rng;
77

8-
#[derive(Clone, Eq, PartialEq, Debug, Hash, Default)]
8+
#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, States)]
99
enum GameState {
1010
#[default]
1111
Playing,
1212
GameOver,
1313
}
1414

15-
impl States for GameState {
16-
type Iter = std::array::IntoIter<GameState, 2>;
17-
18-
fn variants() -> Self::Iter {
19-
[GameState::Playing, GameState::GameOver].into_iter()
20-
}
21-
}
22-
2315
#[derive(Resource)]
2416
struct BonusSpawnTimer(Timer);
2517

examples/games/game_menu.rs

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,14 @@ use bevy::prelude::*;
77
const TEXT_COLOR: Color = Color::rgb(0.9, 0.9, 0.9);
88

99
// Enum that will be used as a global state for the game
10-
#[derive(Clone, Copy, Default, Eq, PartialEq, Debug, Hash)]
10+
#[derive(Clone, Copy, Default, Eq, PartialEq, Debug, Hash, States)]
1111
enum GameState {
1212
#[default]
1313
Splash,
1414
Menu,
1515
Game,
1616
}
1717

18-
impl States for GameState {
19-
type Iter = std::array::IntoIter<GameState, 3>;
20-
21-
fn variants() -> Self::Iter {
22-
[GameState::Splash, GameState::Menu, GameState::Game].into_iter()
23-
}
24-
}
25-
2618
// One of the two settings that can be set through the menu. It will be a resource in the app
2719
#[derive(Resource, Debug, Component, PartialEq, Eq, Clone, Copy)]
2820
enum DisplayQuality {
@@ -312,7 +304,7 @@ mod menu {
312304
}
313305

314306
// State used for the current menu screen
315-
#[derive(Clone, Copy, Default, Eq, PartialEq, Debug, Hash)]
307+
#[derive(Clone, Copy, Default, Eq, PartialEq, Debug, Hash, States)]
316308
enum MenuState {
317309
Main,
318310
Settings,
@@ -322,21 +314,6 @@ mod menu {
322314
Disabled,
323315
}
324316

325-
impl States for MenuState {
326-
type Iter = std::array::IntoIter<MenuState, 5>;
327-
328-
fn variants() -> Self::Iter {
329-
[
330-
MenuState::Main,
331-
MenuState::Settings,
332-
MenuState::SettingsDisplay,
333-
MenuState::SettingsSound,
334-
MenuState::Disabled,
335-
]
336-
.into_iter()
337-
}
338-
}
339-
340317
// Tag component used to tag entities added on the main menu screen
341318
#[derive(Component)]
342319
struct OnMainMenuScreen;

0 commit comments

Comments
 (0)