Skip to content

Commit 66e9a01

Browse files
authored
Merge pull request #1232 from godot-rust/feature/enum-iteration
Access all enum/bitfield values programmatically
2 parents 1ab1d8e + e5006b1 commit 66e9a01

File tree

16 files changed

+463
-21
lines changed

16 files changed

+463
-21
lines changed

.github/composite/godot-itest/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ runs:
176176
# since it's not available on Windows, use taskkill in that case.
177177
# * exit: the terminated process would return 143, but this is more explicit and future-proof
178178
#
179-
# --disallow-focus: fail if #[itest(focus)] is encountered, to prevent running only a few tests for full CI
179+
# --disallow-focus: fail if #[itest (focus)] is encountered, to prevent running only a few tests for full CI
180180
run: |
181181
cd itest/godot
182182
echo "OUTCOME=itest" >> $GITHUB_ENV

godot-codegen/src/generator/enums.rs

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use crate::models::domain::{Enum, Enumerator, EnumeratorValue, RustTy};
1313
use crate::special_cases;
1414
use proc_macro2::TokenStream;
1515
use quote::{quote, ToTokens};
16+
use std::collections::HashSet;
1617

1718
pub fn make_enums(enums: &[Enum], cfg_attributes: &TokenStream) -> TokenStream {
1819
let definitions = enums.iter().map(make_enum_definition);
@@ -141,7 +142,7 @@ pub fn make_enum_definition_with(
141142
///
142143
/// Returns `None` if `enum_` isn't an indexable enum.
143144
fn make_enum_index_impl(enum_: &Enum) -> Option<TokenStream> {
144-
let enum_max = enum_.find_index_enum_max()?;
145+
let enum_max = enum_.max_index?; // Do nothing if enum isn't sequential with a MAX constant.
145146
let name = &enum_.name;
146147

147148
Some(quote! {
@@ -226,6 +227,8 @@ fn make_enum_engine_trait_impl(enum_: &Enum, enum_bitmask: Option<&RustTy>) -> T
226227
if enum_.is_bitfield {
227228
// Bitfields: u64, assume any combination is valid.
228229

230+
let constants_function = make_all_constants_function(enum_);
231+
229232
quote! {
230233
// We may want to add this in the future.
231234
//
@@ -241,6 +244,8 @@ fn make_enum_engine_trait_impl(enum_: &Enum, enum_bitmask: Option<&RustTy>) -> T
241244
fn ord(self) -> u64 {
242245
self.ord
243246
}
247+
248+
#constants_function
244249
}
245250
}
246251
} else if enum_.is_exhaustive {
@@ -262,6 +267,7 @@ fn make_enum_engine_trait_impl(enum_: &Enum, enum_bitmask: Option<&RustTy>) -> T
262267
});
263268

264269
let str_functions = make_enum_str_functions(enum_);
270+
let values_and_constants_functions = make_enum_values_and_constants_functions(enum_);
265271

266272
quote! {
267273
impl #engine_trait for #name {
@@ -277,6 +283,7 @@ fn make_enum_engine_trait_impl(enum_: &Enum, enum_bitmask: Option<&RustTy>) -> T
277283
}
278284

279285
#str_functions
286+
#values_and_constants_functions
280287
}
281288
}
282289
} else {
@@ -288,6 +295,7 @@ fn make_enum_engine_trait_impl(enum_: &Enum, enum_bitmask: Option<&RustTy>) -> T
288295

289296
let unique_ords = enum_.unique_ords().expect("self is an enum");
290297
let str_functions = make_enum_str_functions(enum_);
298+
let values_and_constants_functions = make_enum_values_and_constants_functions(enum_);
291299

292300
// We can technically check against all possible mask values, remove each mask, and then verify it's a valid base-enum value.
293301
// However, this is not forward compatible: if a new mask is added in a future API version, it wouldn't be removed, and the
@@ -317,6 +325,65 @@ fn make_enum_engine_trait_impl(enum_: &Enum, enum_bitmask: Option<&RustTy>) -> T
317325
}
318326

319327
#str_functions
328+
#values_and_constants_functions
329+
}
330+
}
331+
}
332+
}
333+
334+
/// Creates both the `values()` and `all_constants()` implementations for the enum.
335+
fn make_enum_values_and_constants_functions(enum_: &Enum) -> TokenStream {
336+
let name = &enum_.name;
337+
338+
let mut distinct_values = Vec::new();
339+
let mut seen_ordinals = HashSet::new();
340+
341+
for (index, enumerator) in enum_.enumerators.iter().enumerate() {
342+
let constant = &enumerator.name;
343+
let ordinal = &enumerator.value;
344+
345+
// values() contains value only if distinct (first time seen) and not MAX.
346+
if enum_.max_index != Some(index) && seen_ordinals.insert(ordinal.clone()) {
347+
distinct_values.push(quote! { #name::#constant });
348+
}
349+
}
350+
351+
let values_function = quote! {
352+
fn values() -> &'static [Self] {
353+
&[
354+
#( #distinct_values ),*
355+
]
356+
}
357+
};
358+
359+
let all_constants_function = make_all_constants_function(enum_);
360+
361+
quote! {
362+
#values_function
363+
#all_constants_function
364+
}
365+
}
366+
367+
/// Creates a shared `all_constants()` implementation for enums and bitfields.
368+
fn make_all_constants_function(enum_: &Enum) -> TokenStream {
369+
let name = &enum_.name;
370+
371+
let all_constants = enum_.enumerators.iter().map(|enumerator| {
372+
let ident = &enumerator.name;
373+
let rust_name = enumerator.name.to_string();
374+
let godot_name = enumerator.godot_name.to_string();
375+
376+
quote! {
377+
crate::meta::inspect::EnumConstant::new(#rust_name, #godot_name, #name::#ident)
378+
}
379+
});
380+
381+
quote! {
382+
fn all_constants() -> &'static [crate::meta::inspect::EnumConstant<#name>] {
383+
const {
384+
&[
385+
#( #all_constants ),*
386+
]
320387
}
321388
}
322389
}

godot-codegen/src/models/domain/enums.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ pub struct Enum {
2222
pub is_private: bool,
2323
pub is_exhaustive: bool,
2424
pub enumerators: Vec<Enumerator>,
25+
/// If the enum is sequential and has a `*_MAX` constant (Godot name), this is the index of it.
26+
pub max_index: Option<usize>,
2527
}
2628

2729
impl Enum {
@@ -73,15 +75,18 @@ impl Enum {
7375

7476
/// Returns the maximum index of an indexable enum.
7577
///
76-
/// Return `None` if `self` isn't an indexable enum. Meaning it is either a bitfield, or it is an enum that can't be used as an index.
77-
pub fn find_index_enum_max(&self) -> Option<usize> {
78-
if self.is_bitfield {
78+
/// Returns `None` if this is a bitfield, or an enum that isn't sequential with a `*_MAX` enumerator.
79+
pub fn find_index_enum_max_impl(
80+
is_bitfield: bool,
81+
enumerators: &[Enumerator],
82+
) -> Option<usize> {
83+
if is_bitfield {
7984
return None;
8085
}
8186

8287
// Sort by ordinal value. Allocates for every enum in the JSON, but should be OK (most enums are indexable).
8388
let enumerators = {
84-
let mut enumerators = self.enumerators.clone();
89+
let mut enumerators = enumerators.to_vec();
8590
enumerators.sort_by_key(|v| v.value.to_i64());
8691
enumerators
8792
};
@@ -124,7 +129,7 @@ pub struct Enumerator {
124129
pub value: EnumeratorValue,
125130
}
126131

127-
#[derive(Clone)]
132+
#[derive(Clone, Hash, Eq, PartialEq)]
128133
pub enum EnumeratorValue {
129134
Enum(i32),
130135
Bitfield(u64),

godot-codegen/src/models/domain_mapping.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -662,7 +662,7 @@ impl Enum {
662662
conv::make_enumerator_names(godot_class_name, &rust_enum_name, godot_enumerator_names)
663663
};
664664

665-
let enumerators = json_enum
665+
let enumerators: Vec<Enumerator> = json_enum
666666
.values
667667
.iter()
668668
.zip(rust_enumerator_names)
@@ -671,6 +671,8 @@ impl Enum {
671671
})
672672
.collect();
673673

674+
let max_index = Enum::find_index_enum_max_impl(is_bitfield, &enumerators);
675+
674676
Self {
675677
name: ident(&rust_enum_name),
676678
godot_name: godot_name.clone(),
@@ -679,6 +681,7 @@ impl Enum {
679681
is_private,
680682
is_exhaustive,
681683
enumerators,
684+
max_index,
682685
}
683686
}
684687
}

godot-core/src/builtin/vectors/vector_axis.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,26 @@ macro_rules! impl_vector_axis_enum {
5454
)+
5555
}
5656
}
57+
58+
fn values() -> &'static [Self] {
59+
// For vector axis enums, all values are distinct, so both are the same
60+
&[
61+
$( $AxisEnum::$axis, )+
62+
]
63+
}
64+
65+
fn all_constants() -> &'static [crate::meta::inspect::EnumConstant<$AxisEnum>] {
66+
use crate::meta::inspect::EnumConstant;
67+
const { &[
68+
$(
69+
EnumConstant::new(
70+
stringify!($axis),
71+
concat!("AXIS_", stringify!($axis)),
72+
$AxisEnum::$axis
73+
),
74+
)+
75+
] }
76+
}
5777
}
5878

5979
impl GodotConvert for $AxisEnum {

godot-core/src/meta/inspect.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright (c) godot-rust; Bromeon and contributors.
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6+
*/
7+
8+
//! Introspection metadata for Godot engine types.
9+
10+
/// Metadata for a single enum or bitfield constant.
11+
///
12+
/// Returned by [`EngineEnum::all_constants()`][crate::obj::EngineEnum::all_constants] and
13+
/// [`EngineBitfield::all_constants()`][crate::obj::EngineBitfield::all_constants].
14+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15+
pub struct EnumConstant<T: Copy + 'static> {
16+
rust_name: &'static str,
17+
godot_name: &'static str,
18+
value: T,
19+
}
20+
21+
impl<T> EnumConstant<T>
22+
where
23+
T: Copy + Eq + PartialEq + 'static,
24+
{
25+
/// Creates a new enum constant metadata entry.
26+
pub(crate) const fn new(rust_name: &'static str, godot_name: &'static str, value: T) -> Self {
27+
Self {
28+
rust_name,
29+
godot_name,
30+
value,
31+
}
32+
}
33+
34+
/// Rust name of the constant, usually without prefix (e.g. `"ESCAPE"` for `Key::ESCAPE`).
35+
///
36+
/// For enums, this is the value returned by [`EngineEnum::as_str()`](crate::obj::EngineEnum::as_str()) **if the value is unique.**
37+
/// If multiple enum values share the same ordinal, then this function will return each one separately, while `as_str()` will return the
38+
/// first one.
39+
pub const fn rust_name(&self) -> &'static str {
40+
self.rust_name
41+
}
42+
43+
/// Godot constant name (e.g. `"KEY_ESCAPE"` for `Key::ESCAPE`).
44+
pub const fn godot_name(&self) -> &'static str {
45+
self.godot_name
46+
}
47+
48+
/// The Rust value itself.
49+
///
50+
/// Use `value().ord()` to get the ordinal value.
51+
pub const fn value(&self) -> T {
52+
self.value
53+
}
54+
}

godot-core/src/meta/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ mod uniform_object_deref;
5757
pub(crate) mod sealed;
5858

5959
pub mod error;
60+
pub mod inspect;
6061

6162
pub use args::*;
6263
pub use class_name::ClassName;

0 commit comments

Comments
 (0)