Skip to content

Implementation of EngineBitfield trait #524

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
merged 1 commit into from
Dec 5, 2023
Merged
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 godot-codegen/src/codegen_special_cases.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ fn is_type_excluded(ty: &str, ctx: &mut Context) -> bool {
None => false,
Some(class) => is_class_excluded(class.as_str()),
},
RustTy::EngineBitfield {
surrounding_class, ..
} => match surrounding_class.as_ref() {
None => false,
Some(class) => is_class_excluded(class.as_str()),
},
RustTy::EngineClass { inner_class, .. } => is_class_excluded(&inner_class.to_string()),
}
}
Expand Down
9 changes: 9 additions & 0 deletions godot-codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,14 @@ enum RustTy {
surrounding_class: Option<String>,
},

/// `module::Bitfield`
EngineBitfield {
tokens: TokenStream,
/// `None` for globals
#[allow(dead_code)] // only read in minimal config
surrounding_class: Option<String>,
},

/// `Gd<Node>`
EngineClass {
/// Tokens with full `Gd<T>`
Expand Down Expand Up @@ -219,6 +227,7 @@ impl ToTokens for RustTy {
} => quote! { *mut #inner }.to_tokens(tokens),
RustTy::EngineArray { tokens: path, .. } => path.to_tokens(tokens),
RustTy::EngineEnum { tokens: path, .. } => path.to_tokens(tokens),
RustTy::EngineBitfield { tokens: path, .. } => path.to_tokens(tokens),
RustTy::EngineClass { tokens: path, .. } => path.to_tokens(tokens),
}
}
Expand Down
164 changes: 104 additions & 60 deletions godot-codegen/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,37 @@ pub fn make_enum_definition(enum_: &Enum) -> TokenStream {
unique_ords.sort();
unique_ords.dedup();

let bitfield_ops = if enum_.is_bitfield {
let tokens = quote! {
let mut derives = vec!["Copy", "Clone", "Eq", "PartialEq", "Hash", "Debug"];

if enum_.is_bitfield {
derives.push("Default");
}

let derives = derives.into_iter().map(ident);

let index_enum_impl = if enum_.is_bitfield {
// Bitfields don't implement IndexEnum.
TokenStream::new()
} else {
// Enums implement IndexEnum only if they are "index-like" (see docs).
if let Some(enum_max) = try_count_index_enum(enum_) {
quote! {
impl crate::obj::IndexEnum for #enum_name {
const ENUMERATOR_COUNT: usize = #enum_max;
}
}
} else {
TokenStream::new()
}
};

let bitfield_ops;
let self_as_trait;
let engine_impl;
let enum_ord_type;

if enum_.is_bitfield {
bitfield_ops = quote! {
// impl #enum_name {
// pub const UNSET: Self = Self { ord: 0 };
// }
Expand All @@ -254,97 +283,85 @@ pub fn make_enum_definition(enum_: &Enum) -> TokenStream {
}
}
};
enum_ord_type = quote! { u64 };
self_as_trait = quote! { <Self as crate::obj::EngineBitfield> };
engine_impl = quote! {
impl crate::obj::EngineBitfield for #enum_name {
fn try_from_ord(ord: u64) -> Option<Self> {
Some(Self { ord })
}

Some(tokens)
} else {
None
};

let try_from_ord = if enum_.is_bitfield {
quote! {
fn try_from_ord(ord: i32) -> Option<Self> {
Some(Self { ord })
fn ord(self) -> u64 {
self.ord
}
}
}
};
} else {
quote! {
fn try_from_ord(ord: i32) -> Option<Self> {
match ord {
#( ord @ #unique_ords )|* => Some(Self { ord }),
_ => None,
bitfield_ops = TokenStream::new();
enum_ord_type = quote! { i32 };
self_as_trait = quote! { <Self as crate::obj::EngineEnum> };
engine_impl = quote! {
impl crate::obj::EngineEnum for #enum_name {
// fn try_from_ord(ord: i32) -> Option<Self> {
// match ord {
// #(
// #matches
// )*
// _ => None,
// }
// }

fn try_from_ord(ord: i32) -> Option<Self> {
match ord {
#( ord @ #unique_ords )|* => Some(Self { ord }),
_ => None,
}
}
}
}
};

let mut derives = vec!["Copy", "Clone", "Eq", "PartialEq", "Debug", "Hash"];

if enum_.is_bitfield {
derives.push("Default");
}

let index_enum_impl = if let Some(enum_max) = try_count_index_enum(enum_) {
quote! {
impl crate::obj::IndexEnum for #enum_name {
const ENUMERATOR_COUNT: usize = #enum_max;
fn ord(self) -> i32 {
self.ord
}
}
}
} else {
TokenStream::new()
};
};

let derives = derives.into_iter().map(ident);

// Enumerator ordinal stored as i32, since that's enough to hold all current values and the default repr in C++.
// Public interface is i64 though, for consistency (and possibly forward compatibility?).
// TODO maybe generalize GodotFfi over EngineEnum trait
// Bitfield ordinals are stored as u64. See also: https://github.com/godotengine/godot-cpp/pull/1320
quote! {
#[repr(transparent)]
#[derive(#( #derives ),*)]
pub struct #enum_name {
ord: i32
ord: #enum_ord_type
}
impl #enum_name {
#(
#enumerators
)*
}
impl crate::obj::EngineEnum for #enum_name {
// fn try_from_ord(ord: i32) -> Option<Self> {
// match ord {
// #(
// #matches
// )*
// _ => None,
// }
// }

#try_from_ord
#engine_impl

#bitfield_ops

fn ord(self) -> i32 {
self.ord
}
}
#index_enum_impl

impl crate::builtin::meta::GodotConvert for #enum_name {
type Via = i32;
type Via = #enum_ord_type;
}

impl crate::builtin::meta::ToGodot for #enum_name {
fn to_godot(&self) -> Self::Via {
<Self as crate::obj::EngineEnum>::ord(*self)
#self_as_trait::ord(*self)
}
}

impl crate::builtin::meta::FromGodot for #enum_name {
fn try_from_godot(via: Self::Via) -> std::result::Result<Self, crate::builtin::meta::ConvertError> {
<Self as crate::obj::EngineEnum>::try_from_ord(via)
#self_as_trait::try_from_ord(via)
.ok_or_else(|| crate::builtin::meta::FromGodotError::InvalidEnum.into_error(via))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add some docs to FromGodotError::InvalidEnum mentioning very briefly that this also includes bitfields?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, yeah, makes sense.

Although I'm not quite sure on how we should phrase this, other than: /// InvalidEnum is also used by bitfields. 😅

}
}

#bitfield_ops
}
}

Expand Down Expand Up @@ -670,11 +687,28 @@ fn to_rust_type_uncached(full_ty: &GodotTy, ctx: &mut Context) -> RustTy {
};
}

let qualified_enum = ty
.strip_prefix("enum::")
.or_else(|| ty.strip_prefix("bitfield::"));
if let Some(bitfield) = ty.strip_prefix("bitfield::") {
return if let Some((class, enum_)) = bitfield.split_once('.') {
// Class-local bitfield.
let module = ModName::from_godot(class);
let bitfield_ty = make_enum_name(enum_);

RustTy::EngineBitfield {
tokens: quote! { crate::engine::#module::#bitfield_ty},
surrounding_class: Some(class.to_string()),
}
} else {
// Global bitfield.
let bitfield_ty = make_enum_name(bitfield);

RustTy::EngineBitfield {
tokens: quote! { crate::engine::global::#bitfield_ty },
surrounding_class: None,
}
};
}

if let Some(qualified_enum) = qualified_enum {
if let Some(qualified_enum) = ty.strip_prefix("enum::") {
return if let Some((class, enum_)) = qualified_enum.split_once('.') {
// Class-local enum
let module = ModName::from_godot(class);
Expand Down Expand Up @@ -799,6 +833,7 @@ fn to_rust_expr_inner(expr: &str, ty: &RustTy, is_inner: bool) -> TokenStream {
if let Ok(num) = expr.parse::<i64>() {
let lit = Literal::i64_unsuffixed(num);
return match ty {
RustTy::EngineBitfield { .. } => quote! { crate::obj::EngineBitfield::from_ord(#lit) },
RustTy::EngineEnum { .. } => quote! { crate::obj::EngineEnum::from_ord(#lit) },
RustTy::BuiltinIdent(ident) if ident == "Variant" => quote! { Variant::from(#lit) },
RustTy::BuiltinIdent(ident)
Expand Down Expand Up @@ -948,6 +983,12 @@ fn gdscript_to_rust_expr() {
};
let ty_enum = Some(&ty_enum);

let ty_bitfield = RustTy::EngineBitfield {
tokens: quote! { SomeEnum },
surrounding_class: None,
};
let ty_bitfield = Some(&ty_bitfield);

let ty_variant = RustTy::BuiltinIdent(ident("Variant"));
let ty_variant = Some(&ty_variant);

Expand Down Expand Up @@ -997,6 +1038,9 @@ fn gdscript_to_rust_expr() {
// enum (from int)
("7", ty_enum, quote! { crate::obj::EngineEnum::from_ord(7) }),

// bitfield (from int)
("7", ty_bitfield, quote! { crate::obj::EngineBitfield::from_ord(7) }),

// Variant (from int)
("8", ty_variant, quote! { Variant::from(8) }),

Expand Down
1 change: 1 addition & 0 deletions godot-core/src/builtin/meta/godot_convert/convert_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ pub(crate) enum FromGodotError {
expected: array_inner::TypeInfo,
got: array_inner::TypeInfo,
},
/// InvalidEnum is also used by bitfields.
InvalidEnum,
ZeroInstanceId,
}
Expand Down
2 changes: 2 additions & 0 deletions godot-core/src/builtin/meta/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ pub struct PropertyInfo {
impl PropertyInfo {
/// Converts to the FFI type. Keep this object allocated while using that!
pub fn property_sys(&self) -> sys::GDExtensionPropertyInfo {
use crate::obj::EngineBitfield as _;
use crate::obj::EngineEnum as _;

sys::GDExtensionPropertyInfo {
Expand All @@ -261,6 +262,7 @@ impl PropertyInfo {
}

pub fn empty_sys() -> sys::GDExtensionPropertyInfo {
use crate::obj::EngineBitfield as _;
use crate::obj::EngineEnum as _;

sys::GDExtensionPropertyInfo {
Expand Down
2 changes: 1 addition & 1 deletion godot-core/src/builtin/meta/registration/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ impl ClassMethodInfo {
}

pub fn register_extension_class_method(&self) {
use crate::obj::EngineEnum as _;
use crate::obj::EngineBitfield as _;

let (return_value_info, return_value_metadata) = match &self.return_value {
Some(info) => (Some(&info.info), info.metadata),
Expand Down
13 changes: 13 additions & 0 deletions godot-core/src/obj/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,19 @@ pub trait EngineEnum: Copy {
}
}

/// Auto-implemented for all engine-provided bitfields.
pub trait EngineBitfield: Copy {
fn try_from_ord(ord: u64) -> Option<Self>;

/// Ordinal value of the bit flag, as specified in Godot.
fn ord(self) -> u64;

fn from_ord(ord: u64) -> Self {
Self::try_from_ord(ord)
.unwrap_or_else(|| panic!("ordinal {ord} does not map to any valid bit flag"))
}
}

/// Trait for enums that can be used as indices in arrays.
///
/// The conditions for a Godot enum to be "index-like" are:
Expand Down
1 change: 1 addition & 0 deletions godot/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ pub mod prelude {

// Make trait methods available
pub use super::engine::NodeExt as _;
pub use super::obj::EngineBitfield as _;
pub use super::obj::EngineEnum as _;
pub use super::obj::UserClass as _; // new_gd(), alloc_gd()
pub use super::obj::WithBaseField as _; // to_gd()
Expand Down