Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - States derive macro #7535

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions crates/bevy_ecs/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ extern crate proc_macro;
mod component;
mod fetch;
mod set;
mod states;

use crate::{fetch::derive_world_query_impl, set::derive_set};
use bevy_macro_utils::{derive_boxed_label, get_named_struct_fields, BevyManifest};
Expand Down Expand Up @@ -558,3 +559,8 @@ pub fn derive_resource(input: TokenStream) -> TokenStream {
pub fn derive_component(input: TokenStream) -> TokenStream {
component::derive_component(input)
}

#[proc_macro_derive(States)]
pub fn derive_states(input: TokenStream) -> TokenStream {
states::derive_states(input)
}
44 changes: 44 additions & 0 deletions crates/bevy_ecs/macros/src/states.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use proc_macro::{Span, TokenStream};
use quote::{format_ident, quote};
use syn::{parse_macro_input, Data::Enum, DeriveInput};

use crate::bevy_ecs_path;

pub fn derive_states(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let error = || {
syn::Error::new(
Span::call_site().into(),
"derive(States) only supports fieldless enums",
)
.into_compile_error()
.into()
};
let Enum(enumeration) = ast.data else {
return error();
};
if enumeration.variants.iter().any(|v| !v.fields.is_empty()) {
return error();
}

let generics = ast.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

let mut trait_path = bevy_ecs_path();
trait_path.segments.push(format_ident!("schedule").into());
trait_path.segments.push(format_ident!("States").into());
let struct_name = &ast.ident;
let idents = enumeration.variants.iter().map(|v| &v.ident);
let len = idents.len();

quote! {
impl #impl_generics #trait_path for #struct_name #ty_generics #where_clause {
type Iter = std::array::IntoIter<Self, #len>;

fn variants() -> Self::Iter {
[#(Self::#idents,)*].into_iter()
}
}
}
.into()
}
12 changes: 3 additions & 9 deletions crates/bevy_ecs/src/schedule/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use crate::schedule::{ScheduleLabel, SystemSet};
use crate::system::Resource;
use crate::world::World;

pub use bevy_ecs_macros::States;

/// Types that can define world-wide states in a finite-state machine.
///
/// The [`Default`] trait defines the starting state.
Expand All @@ -25,22 +27,14 @@ use crate::world::World;
/// ```rust
/// use bevy_ecs::prelude::States;
///
/// #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
/// #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)]
/// enum GameState {
/// #[default]
/// MainMenu,
/// SettingsMenu,
/// InGame,
/// }
///
/// impl States for GameState {
/// type Iter = std::array::IntoIter<GameState, 3>;
///
/// fn variants() -> Self::Iter {
/// [GameState::MainMenu, GameState::SettingsMenu, GameState::InGame].into_iter()
/// }
/// }
///
/// ```
pub trait States: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug + Default {
type Iter: Iterator<Item = Self>;
Expand Down
10 changes: 1 addition & 9 deletions examples/ecs/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,13 @@ fn main() {
.run();
}

#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash)]
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, States)]
enum AppState {
#[default]
Menu,
InGame,
}

impl States for AppState {
type Iter = std::array::IntoIter<AppState, 2>;

fn variants() -> Self::Iter {
[AppState::Menu, AppState::InGame].into_iter()
}
}

#[derive(Resource)]
struct MenuData {
button_entity: Entity,
Expand Down
10 changes: 1 addition & 9 deletions examples/games/alien_cake_addict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,13 @@ use std::f32::consts::PI;
use bevy::prelude::*;
use rand::Rng;

#[derive(Clone, Eq, PartialEq, Debug, Hash, Default)]
#[derive(Clone, Eq, PartialEq, Debug, Hash, Default, States)]
enum GameState {
#[default]
Playing,
GameOver,
}

impl States for GameState {
type Iter = std::array::IntoIter<GameState, 2>;

fn variants() -> Self::Iter {
[GameState::Playing, GameState::GameOver].into_iter()
}
}

#[derive(Resource)]
struct BonusSpawnTimer(Timer);

Expand Down
27 changes: 2 additions & 25 deletions examples/games/game_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,14 @@ use bevy::prelude::*;
const TEXT_COLOR: Color = Color::rgb(0.9, 0.9, 0.9);

// Enum that will be used as a global state for the game
#[derive(Clone, Copy, Default, Eq, PartialEq, Debug, Hash)]
#[derive(Clone, Copy, Default, Eq, PartialEq, Debug, Hash, States)]
enum GameState {
#[default]
Splash,
Menu,
Game,
}

impl States for GameState {
type Iter = std::array::IntoIter<GameState, 3>;

fn variants() -> Self::Iter {
[GameState::Splash, GameState::Menu, GameState::Game].into_iter()
}
}

// One of the two settings that can be set through the menu. It will be a resource in the app
#[derive(Resource, Debug, Component, PartialEq, Eq, Clone, Copy)]
enum DisplayQuality {
Expand Down Expand Up @@ -312,7 +304,7 @@ mod menu {
}

// State used for the current menu screen
#[derive(Clone, Copy, Default, Eq, PartialEq, Debug, Hash)]
#[derive(Clone, Copy, Default, Eq, PartialEq, Debug, Hash, States)]
enum MenuState {
Main,
Settings,
Expand All @@ -322,21 +314,6 @@ mod menu {
Disabled,
}

impl States for MenuState {
type Iter = std::array::IntoIter<MenuState, 5>;

fn variants() -> Self::Iter {
[
MenuState::Main,
MenuState::Settings,
MenuState::SettingsDisplay,
MenuState::SettingsSound,
MenuState::Disabled,
]
.into_iter()
}
}

// Tag component used to tag entities added on the main menu screen
#[derive(Component)]
struct OnMainMenuScreen;
Expand Down