From ef66d926d6aeb5554e5e3eadbf35de79a286e964 Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Sat, 13 Jan 2024 19:59:58 +0100 Subject: [PATCH 01/10] Small tweaks to generated symbols --- godot-codegen/src/conv/name_conversions.rs | 2 ++ godot-codegen/src/tests.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/godot-codegen/src/conv/name_conversions.rs b/godot-codegen/src/conv/name_conversions.rs index 0ba9f79ef..b4c7a212d 100644 --- a/godot-codegen/src/conv/name_conversions.rs +++ b/godot-codegen/src/conv/name_conversions.rs @@ -44,6 +44,7 @@ pub fn to_snake_case(class_name: &str) -> String { .replace("GDExtension", "Gdextension") .replace("VSync", "Vsync") .replace("SDFGIY", "SdfgiY") + .replace("ENet", "Enet") .to_snake_case() } @@ -220,6 +221,7 @@ fn reduce_hardcoded_prefix( // Inconsistency with varying prefixes. (Some("RenderingServer" | "Mesh"), "ArrayFormat") => &["ARRAY_FORMAT_", "ARRAY_"], (Some("AudioServer"), "SpeakerMode") => &["SPEAKER_MODE_", "SPEAKER_"], + (Some("ENetConnection"), "HostStatistic") => &["HOST_"], // do not remove TOTAL_ prefix (shared by all), e.g. TOTAL_SENT_DATA. (None, "MethodFlags") => &["METHOD_FLAG_", "METHOD_FLAGS_"], // There are two "mask" entries which span multiple bits: KEY_CODE_MASK, KEY_MODIFIER_MASK -> CODE_MASK, MODIFIER_MASK. diff --git a/godot-codegen/src/tests.rs b/godot-codegen/src/tests.rs index 124af9781..c1f9c4196 100644 --- a/godot-codegen/src/tests.rs +++ b/godot-codegen/src/tests.rs @@ -80,7 +80,7 @@ fn test_snake_conversion() { ("JSON", "json"), ("JSONParseResult", "json_parse_result"), ("JSONRPC", "json_rpc"), - ("NetworkedMultiplayerENet", "networked_multiplayer_e_net"), + ("NetworkedMultiplayerENet", "networked_multiplayer_enet"), ("ObjectID", "object_id"), ("OpenXRAPIExtension", "open_xr_api_extension"), ("OpenXRIPBinding", "open_xr_ip_binding"), From fc030570172f8a85db4214f66aa921a5cce25481 Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Sat, 13 Jan 2024 22:42:16 +0100 Subject: [PATCH 02/10] Codegen: rename api_parser -> json_models, prefix all with "Json" --- godot-codegen/src/central_generator.rs | 60 +++++---- godot-codegen/src/class_generator.rs | 58 ++++---- godot-codegen/src/codegen_special_cases.rs | 10 +- godot-codegen/src/context.rs | 24 ++-- .../src/{api_parser.rs => json_models.rs} | 124 +++++++++--------- godot-codegen/src/lib.rs | 4 +- godot-codegen/src/special_cases.rs | 8 +- godot-codegen/src/util.rs | 25 ++-- godot-codegen/src/utilities_generator.rs | 4 +- 9 files changed, 164 insertions(+), 153 deletions(-) rename godot-codegen/src/{api_parser.rs => json_models.rs} (72%) diff --git a/godot-codegen/src/central_generator.rs b/godot-codegen/src/central_generator.rs index 9e07cae6a..7413d7724 100644 --- a/godot-codegen/src/central_generator.rs +++ b/godot-codegen/src/central_generator.rs @@ -11,7 +11,7 @@ use std::collections::HashMap; use std::hash::Hasher; use std::path::Path; -use crate::api_parser::*; +use crate::json_models::*; use crate::util::{ make_builtin_method_ptr_name, make_class_method_ptr_name, option_as_slice, ClassCodegenLevel, MethodTableKey, @@ -26,7 +26,7 @@ struct CentralItems { variant_op_enumerators_pascal: Vec, variant_op_enumerators_ord: Vec, global_enum_defs: Vec, - godot_version: Header, + godot_version: JsonHeader, } struct NamedMethodTable { @@ -154,8 +154,8 @@ pub(crate) struct BuiltinTypeInfo<'a> { /// If `variant_get_ptr_destructor` returns a non-null function pointer for this type. /// List is directly sourced from extension_api.json (information would also be in variant_destruct.cpp). pub has_destructor: bool, - pub constructors: Option<&'a Vec>, - pub operators: Option<&'a Vec>, + pub constructors: Option<&'a Vec>, + pub operators: Option<&'a Vec>, } // ---------------------------------------------------------------------------------------------------------------------------------------------- @@ -165,7 +165,7 @@ pub(crate) struct BuiltinTypeMap<'a> { } impl<'a> BuiltinTypeMap<'a> { - pub fn load(api: &'a ExtensionApi) -> Self { + pub fn load(api: &'a JsonExtensionApi) -> Self { Self { map: collect_builtin_types(api), } @@ -186,7 +186,7 @@ impl<'a> BuiltinTypeMap<'a> { // ---------------------------------------------------------------------------------------------------------------------------------------------- pub(crate) fn generate_sys_central_file( - api: &ExtensionApi, + api: &JsonExtensionApi, ctx: &mut Context, build_config: [&str; 2], sys_gen_path: &Path, @@ -200,7 +200,7 @@ pub(crate) fn generate_sys_central_file( } pub(crate) fn generate_sys_classes_file( - api: &ExtensionApi, + api: &JsonExtensionApi, sys_gen_path: &Path, watch: &mut godot_bindings::StopWatch, ctx: &mut Context, @@ -216,7 +216,7 @@ pub(crate) fn generate_sys_classes_file( } pub(crate) fn generate_sys_utilities_file( - api: &ExtensionApi, + api: &JsonExtensionApi, sys_gen_path: &Path, ctx: &mut Context, submit_fn: &mut SubmitFn, @@ -489,7 +489,7 @@ fn make_method_table(info: IndexedMethodTable) -> TokenStream { } pub(crate) fn generate_sys_builtin_methods_file( - api: &ExtensionApi, + api: &JsonExtensionApi, builtin_types: &BuiltinTypeMap, sys_gen_path: &Path, ctx: &mut Context, @@ -523,7 +523,7 @@ pub(crate) fn generate_core_mod_file(gen_path: &Path, submit_fn: &mut SubmitFn) } pub(crate) fn generate_core_central_file( - api: &ExtensionApi, + api: &JsonExtensionApi, ctx: &mut Context, build_config: [&str; 2], gen_path: &Path, @@ -626,7 +626,7 @@ fn make_sys_code(central_items: &CentralItems) -> TokenStream { } } -fn make_build_config(header: &Header) -> TokenStream { +fn make_build_config(header: &JsonHeader) -> TokenStream { let version_string = header .version_full_name .strip_prefix("Godot Engine ") @@ -753,7 +753,7 @@ fn make_core_code(central_items: &CentralItems) -> TokenStream { } fn make_central_items( - api: &ExtensionApi, + api: &JsonExtensionApi, build_config: [&str; 2], builtin_types: BuiltinTypeMap, ctx: &mut Context, @@ -762,7 +762,7 @@ fn make_central_items( for class in &api.builtin_class_sizes { for i in 0..2 { if class.build_configuration == build_config[i] { - for ClassSize { name, size } in &class.sizes { + for JsonClassSize { name, size } in &class.sizes { opaque_types[i].push(make_opaque_type(name, *size)); } break; @@ -879,7 +879,7 @@ fn make_builtin_lifecycle_table(builtin_types: &BuiltinTypeMap) -> TokenStream { } fn make_class_method_table( - api: &ExtensionApi, + api: &JsonExtensionApi, api_level: ClassCodegenLevel, ctx: &mut Context, ) -> TokenStream { @@ -963,7 +963,7 @@ fn make_named_accessors(accessors: &[AccessorMethod], fptr: &TokenStream) -> Tok } fn make_builtin_method_table( - api: &ExtensionApi, + api: &JsonExtensionApi, builtin_types: &BuiltinTypeMap, ctx: &mut Context, ) -> TokenStream { @@ -1011,7 +1011,7 @@ fn make_builtin_method_table( fn populate_class_methods( table: &mut IndexedMethodTable, - class: &Class, + class: &JsonClass, class_ty: &TyName, ctx: &mut Context, ) { @@ -1070,7 +1070,7 @@ fn populate_class_methods( fn populate_builtin_methods( table: &mut IndexedMethodTable, - builtin_class: &BuiltinClass, + builtin_class: &JsonBuiltin, builtin_name: &BuiltinName, ctx: &mut Context, ) { @@ -1128,7 +1128,7 @@ fn populate_builtin_methods( } fn make_class_method_init( - method: &ClassMethod, + method: &JsonClassMethod, class_var: &Ident, class_ty: &TyName, ) -> TokenStream { @@ -1156,7 +1156,7 @@ fn make_class_method_init( } fn make_builtin_method_init( - method: &BuiltinClassMethod, + method: &JsonBuiltinMethod, type_name: &BuiltinName, index: usize, ) -> TokenStream { @@ -1190,7 +1190,7 @@ fn make_builtin_method_init( /// Creates a map from "normalized" class names (lowercase without underscore, makes it easy to map from different conventions) /// to meta type information, including all the type name variants -fn collect_builtin_classes(api: &ExtensionApi) -> HashMap { +fn collect_builtin_classes(api: &JsonExtensionApi) -> HashMap { let mut class_map = HashMap::new(); for class in &api.builtin_classes { let normalized_name = class.name.to_ascii_lowercase(); @@ -1202,7 +1202,9 @@ fn collect_builtin_classes(api: &ExtensionApi) -> HashMap } /// Returns map from the JSON names (e.g. "PackedStringArray") to all the info. -pub(crate) fn collect_builtin_types(api: &ExtensionApi) -> HashMap> { +pub(crate) fn collect_builtin_types( + api: &JsonExtensionApi, +) -> HashMap> { let class_map = collect_builtin_classes(api); let variant_type_enum = api @@ -1230,8 +1232,8 @@ pub(crate) fn collect_builtin_types(api: &ExtensionApi) -> HashMap>; - let operators: Option<&Vec>; + let constructors: Option<&Vec>; + let operators: Option<&Vec>; if let Some(class) = class_map.get(&normalized) { class_name = class.name.clone(); has_destructor = class.has_destructor; @@ -1267,7 +1269,7 @@ pub(crate) fn collect_builtin_types(api: &ExtensionApi) -> HashMap Vec<&EnumConstant> { +fn collect_variant_operators(api: &JsonExtensionApi) -> Vec<&JsonEnumConstant> { let variant_operator_enum = api .global_enums .iter() @@ -1307,8 +1309,8 @@ fn make_opaque_type(name: &str, size: usize) -> TokenStream { fn make_variant_fns( type_names: &BuiltinName, has_destructor: bool, - constructors: Option<&Vec>, - operators: Option<&Vec>, + constructors: Option<&Vec>, + operators: Option<&Vec>, builtin_types: &HashMap, ) -> (TokenStream, TokenStream) { let (construct_decls, construct_inits) = @@ -1360,7 +1362,7 @@ fn make_variant_fns( fn make_construct_fns( type_names: &BuiltinName, - constructors: Option<&Vec>, + constructors: Option<&Vec>, builtin_types: &HashMap, ) -> (TokenStream, TokenStream) { let constructors = match constructors { @@ -1439,7 +1441,7 @@ fn make_construct_fns( /// Lists special cases for useful constructors fn make_extra_constructors( type_names: &BuiltinName, - constructors: &Vec, + constructors: &Vec, builtin_types: &HashMap, ) -> (Vec, Vec) { let mut extra_decls = Vec::with_capacity(constructors.len() - 2); @@ -1507,7 +1509,7 @@ fn make_destroy_fns(type_names: &BuiltinName, has_destructor: bool) -> (TokenStr fn make_operator_fns( type_names: &BuiltinName, - operators: Option<&Vec>, + operators: Option<&Vec>, json_name: &str, sys_name: &str, ) -> (TokenStream, TokenStream) { diff --git a/godot-codegen/src/class_generator.rs b/godot-codegen/src/class_generator.rs index 7e623eb69..73c5366c6 100644 --- a/godot-codegen/src/class_generator.rs +++ b/godot-codegen/src/class_generator.rs @@ -11,9 +11,9 @@ use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; use std::path::Path; -use crate::api_parser::*; use crate::central_generator::collect_builtin_types; use crate::context::NotificationEnum; +use crate::json_models::*; use crate::util::{ ident, make_string_name, option_as_slice, parse_native_structures_format, safe_ident, ClassCodegenLevel, MethodTableKey, NativeStructuresField, @@ -58,7 +58,7 @@ struct FnParam { } impl FnParam { - fn new_range(method_args: &Option>, ctx: &mut Context) -> Vec { + fn new_range(method_args: &Option>, ctx: &mut Context) -> Vec { option_as_slice(method_args) .iter() .map(|arg| Self::new(arg, ctx)) @@ -66,7 +66,7 @@ impl FnParam { } fn new_range_no_defaults( - method_args: &Option>, + method_args: &Option>, ctx: &mut Context, ) -> Vec { option_as_slice(method_args) @@ -75,7 +75,7 @@ impl FnParam { .collect() } - fn new(method_arg: &MethodArg, ctx: &mut Context) -> FnParam { + fn new(method_arg: &JsonMethodArg, ctx: &mut Context) -> FnParam { let name = safe_ident(&method_arg.name); let type_ = conv::to_rust_type(&method_arg.type_, method_arg.meta.as_ref(), ctx); let default_value = method_arg @@ -90,7 +90,7 @@ impl FnParam { } } - fn new_no_defaults(method_arg: &MethodArg, ctx: &mut Context) -> FnParam { + fn new_no_defaults(method_arg: &JsonMethodArg, ctx: &mut Context) -> FnParam { FnParam { name: safe_ident(&method_arg.name), type_: conv::to_rust_type(&method_arg.type_, method_arg.meta.as_ref(), ctx), @@ -108,7 +108,7 @@ struct FnReturn { } impl FnReturn { - fn new(return_value: &Option, ctx: &mut Context) -> Self { + fn new(return_value: &Option, ctx: &mut Context) -> Self { if let Some(ret) = return_value { let ty = conv::to_rust_type(&ret.type_, ret.meta.as_ref(), ctx); @@ -237,7 +237,7 @@ impl FnDefinitions { // ---------------------------------------------------------------------------------------------------------------------------------------------- pub(crate) fn generate_class_files( - api: &ExtensionApi, + api: &JsonExtensionApi, ctx: &mut Context, _build_config: [&str; 2], gen_path: &Path, @@ -280,7 +280,7 @@ pub(crate) fn generate_class_files( } pub(crate) fn generate_builtin_class_files( - api: &ExtensionApi, + api: &JsonExtensionApi, ctx: &mut Context, _build_config: [&str; 2], gen_path: &Path, @@ -325,7 +325,7 @@ pub(crate) fn generate_builtin_class_files( } pub(crate) fn generate_native_structures_files( - api: &ExtensionApi, + api: &JsonExtensionApi, ctx: &mut Context, _build_config: [&str; 2], gen_path: &Path, @@ -443,7 +443,7 @@ fn make_module_doc(class_name: &TyName) -> String { } fn make_constructor_and_default( - class: &Class, + class: &JsonClass, class_name: &TyName, ctx: &Context, ) -> (TokenStream, TokenStream) { @@ -504,7 +504,7 @@ fn make_constructor_and_default( (constructor, godot_default_impl) } -fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> GeneratedClass { +fn make_class(class: &JsonClass, class_name: &TyName, ctx: &mut Context) -> GeneratedClass { // Strings let godot_class_str = &class_name.godot_ty; let class_name_cstr = util::cstr_u8_slice(godot_class_str); @@ -848,7 +848,7 @@ fn workaround_constant_collision(all_constants: &mut Vec<(Ident, i32)>) { } fn make_builtin_class( - class: &BuiltinClass, + class: &JsonBuiltin, builtin_name: &TyName, inner_class_name: &TyName, ctx: &mut Context, @@ -864,8 +864,8 @@ fn make_builtin_class( let class_enums = class.enums.as_ref().map_or(Vec::new(), |class_enums| { class_enums .iter() - .map(BuiltinClassEnum::to_enum) - .collect::>() + .map(JsonBuiltinEnum::to_enum) + .collect::>() }); let FnDefinitions { @@ -911,7 +911,7 @@ fn make_builtin_class( } fn make_native_structure( - structure: &NativeStructure, + structure: &JsonNativeStructure, class_name: &TyName, ctx: &mut Context, ) -> GeneratedBuiltin { @@ -1086,7 +1086,7 @@ fn make_builtin_module_file(classes_and_modules: Vec) -> } fn make_methods( - methods: &[ClassMethod], + methods: &[JsonClassMethod], class_name: &TyName, api_level: &ClassCodegenLevel, ctx: &mut Context, @@ -1101,7 +1101,7 @@ fn make_methods( } fn make_builtin_methods( - methods: &[BuiltinClassMethod], + methods: &[JsonBuiltinMethod], builtin_name: &TyName, inner_class_name: &TyName, ctx: &mut Context, @@ -1113,7 +1113,7 @@ fn make_builtin_methods( FnDefinitions::expand(definitions) } -fn make_enums(enums: &[Enum], class_name: &TyName, _ctx: &Context) -> TokenStream { +fn make_enums(enums: &[JsonEnum], class_name: &TyName, _ctx: &Context) -> TokenStream { let definitions = enums .iter() .map(|e| util::make_enum_definition(e, Some(&class_name.godot_ty))); @@ -1124,7 +1124,7 @@ fn make_enums(enums: &[Enum], class_name: &TyName, _ctx: &Context) -> TokenStrea } fn make_constants( - constants: &[ClassConstant], + constants: &[JsonClassConstant], _class_name: &TyName, _ctx: &Context, ) -> TokenStream { @@ -1155,7 +1155,7 @@ fn make_special_builtin_methods(class_name: &TyName, _ctx: &Context) -> TokenStr } fn make_class_method_definition( - method: &ClassMethod, + method: &JsonClassMethod, class_name: &TyName, api_level: &ClassCodegenLevel, get_method_table: &Ident, @@ -1264,7 +1264,7 @@ fn make_class_method_definition( } fn make_builtin_method_definition( - method: &BuiltinClassMethod, + method: &JsonBuiltinMethod, builtin_name: &TyName, inner_class_name: &TyName, ctx: &mut Context, @@ -1278,7 +1278,7 @@ fn make_builtin_method_definition( let return_value = method .return_type .as_deref() - .map(MethodReturn::from_type_no_meta); + .map(JsonMethodReturn::from_type_no_meta); let fptr_access = if cfg!(feature = "codegen-lazy-fptrs") { let variant_type = quote! { sys::VariantType::#builtin_name }; @@ -1349,7 +1349,7 @@ fn make_builtin_method_definition( } pub(crate) fn make_utility_function_definition( - function: &UtilityFunction, + function: &JsonUtilityFunction, ctx: &mut Context, ) -> TokenStream { if codegen_special_cases::is_function_excluded(function, ctx) { @@ -1362,7 +1362,7 @@ pub(crate) fn make_utility_function_definition( let return_value = function .return_type .as_deref() - .map(MethodReturn::from_type_no_meta); + .map(JsonMethodReturn::from_type_no_meta); let ptrcall_invocation = quote! { let utility_fn = sys::utility_function_table().#fn_ptr; @@ -1844,7 +1844,7 @@ fn make_params_and_args(method_args: &[&FnParam]) -> (Vec, Vec TokenStream { } } -fn make_virtual_method(method: &ClassMethod, ctx: &mut Context) -> TokenStream { +fn make_virtual_method(method: &JsonClassMethod, ctx: &mut Context) -> TokenStream { let method_name = virtual_method_name(method); // Virtual methods are never static. @@ -1942,7 +1942,7 @@ fn make_virtual_method(method: &ClassMethod, ctx: &mut Context) -> TokenStream { } fn make_all_virtual_methods( - class: &Class, + class: &JsonClass, all_base_names: &[TyName], ctx: &mut Context, ) -> Vec { @@ -1977,14 +1977,14 @@ fn make_all_virtual_methods( .collect() } -fn get_methods_in_class(class: &Class) -> &[ClassMethod] { +fn get_methods_in_class(class: &JsonClass) -> &[JsonClassMethod] { match &class.methods { None => &[], Some(methods) => methods, } } -fn virtual_method_name(class_method: &ClassMethod) -> &str { +fn virtual_method_name(class_method: &JsonClassMethod) -> &str { // Matching the C++ convention, we remove the leading underscore // from virtual method names. let method_name = class_method diff --git a/godot-codegen/src/codegen_special_cases.rs b/godot-codegen/src/codegen_special_cases.rs index 2ee5d22a2..3e1cc0490 100644 --- a/godot-codegen/src/codegen_special_cases.rs +++ b/godot-codegen/src/codegen_special_cases.rs @@ -7,11 +7,11 @@ //! Codegen-dependent exclusions. Can be removed if feature `codegen-full` is removed. -use crate::api_parser::{BuiltinClassMethod, ClassMethod, UtilityFunction}; use crate::context::Context; +use crate::json_models::{JsonBuiltinMethod, JsonClassMethod, JsonUtilityFunction}; use crate::{special_cases, TyName}; -pub(crate) fn is_builtin_method_excluded(method: &BuiltinClassMethod) -> bool { +pub(crate) fn is_builtin_method_excluded(method: &JsonBuiltinMethod) -> bool { // TODO Fall back to varcall (recent addition in GDExtension API). // See https://github.com/godot-rust/gdext/issues/382. method.is_vararg @@ -56,7 +56,7 @@ fn is_type_excluded(ty: &str, ctx: &mut Context) -> bool { } pub(crate) fn is_class_method_excluded( - method: &ClassMethod, + method: &JsonClassMethod, is_virtual_impl: bool, ctx: &mut Context, ) -> bool { @@ -97,12 +97,12 @@ pub(crate) fn is_class_method_excluded( } #[cfg(feature = "codegen-full")] -pub(crate) fn is_function_excluded(_function: &UtilityFunction, _ctx: &mut Context) -> bool { +pub(crate) fn is_function_excluded(_function: &JsonUtilityFunction, _ctx: &mut Context) -> bool { false } #[cfg(not(feature = "codegen-full"))] -pub(crate) fn is_function_excluded(function: &UtilityFunction, ctx: &mut Context) -> bool { +pub(crate) fn is_function_excluded(function: &JsonUtilityFunction, ctx: &mut Context) -> bool { function .return_type .as_ref() diff --git a/godot-codegen/src/context.rs b/godot-codegen/src/context.rs index 1394a86f0..d8cfc280b 100644 --- a/godot-codegen/src/context.rs +++ b/godot-codegen/src/context.rs @@ -5,16 +5,20 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::api_parser::{BuiltinClass, BuiltinClassMethod, Class, ClassConstant, ClassMethod}; +use crate::json_models::{ + JsonBuiltin, JsonBuiltinMethod, JsonClass, JsonClassConstant, JsonClassMethod, +}; use crate::util::{option_as_slice, MethodTableKey}; -use crate::{codegen_special_cases, special_cases, util, ExtensionApi, GodotTy, RustTy, TyName}; +use crate::{ + codegen_special_cases, special_cases, util, GodotTy, JsonExtensionApi, RustTy, TyName, +}; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, ToTokens}; use std::collections::{HashMap, HashSet}; #[derive(Default)] pub(crate) struct Context<'a> { - engine_classes: HashMap, + engine_classes: HashMap, builtin_types: HashSet<&'a str>, native_structures_types: HashSet<&'a str>, singletons: HashSet<&'a str>, @@ -27,7 +31,7 @@ pub(crate) struct Context<'a> { } impl<'a> Context<'a> { - pub fn build_from_api(api: &'a ExtensionApi) -> Self { + pub fn build_from_api(api: &'a JsonExtensionApi) -> Self { let mut ctx = Self::default(); for class in api.singletons.iter() { @@ -127,7 +131,7 @@ impl<'a> Context<'a> { fn populate_notification_constants( class_name: &TyName, - constants: &[ClassConstant], + constants: &[JsonClassConstant], ctx: &mut Context, ) { let mut has_notifications = false; @@ -155,9 +159,9 @@ impl<'a> Context<'a> { } fn populate_class_table_indices( - class: &Class, + class: &JsonClass, class_name: &TyName, - methods: &[ClassMethod], + methods: &[JsonClassMethod], ctx: &mut Context, ) { if special_cases::is_class_deleted(class_name) { @@ -180,8 +184,8 @@ impl<'a> Context<'a> { } fn populate_builtin_class_table_indices( - builtin: &BuiltinClass, - methods: &[BuiltinClassMethod], + builtin: &JsonBuiltin, + methods: &[JsonBuiltinMethod], ctx: &mut Context, ) { let builtin_ty = TyName::from_godot(builtin.name.as_str()); @@ -203,7 +207,7 @@ impl<'a> Context<'a> { } } - pub fn get_engine_class(&self, class_name: &TyName) -> &Class { + pub fn get_engine_class(&self, class_name: &TyName) -> &JsonClass { self.engine_classes.get(class_name).unwrap() } diff --git a/godot-codegen/src/api_parser.rs b/godot-codegen/src/json_models.rs similarity index 72% rename from godot-codegen/src/api_parser.rs rename to godot-codegen/src/json_models.rs index 3d7625f90..e8e75cfe2 100644 --- a/godot-codegen/src/api_parser.rs +++ b/godot-codegen/src/json_models.rs @@ -6,7 +6,8 @@ */ // TODO remove this warning once impl is complete -#![allow(dead_code)] +// Several types have #[allow(dead_code)], can be subsequently removed. + #![allow(clippy::question_mark)] // in #[derive(DeJson)] use nanoserde::DeJson; @@ -15,19 +16,19 @@ use nanoserde::DeJson; // JSON models #[derive(DeJson)] -pub struct ExtensionApi { - pub header: Header, - pub builtin_class_sizes: Vec, - pub builtin_classes: Vec, - pub classes: Vec, - pub global_enums: Vec, - pub utility_functions: Vec, - pub native_structures: Vec, - pub singletons: Vec, +pub struct JsonExtensionApi { + pub header: JsonHeader, + pub builtin_class_sizes: Vec, + pub builtin_classes: Vec, + pub classes: Vec, + pub global_enums: Vec, + pub utility_functions: Vec, + pub native_structures: Vec, + pub singletons: Vec, } #[derive(DeJson, Clone, Debug)] -pub struct Header { +pub struct JsonHeader { pub version_major: u8, pub version_minor: u8, pub version_patch: u8, @@ -37,53 +38,53 @@ pub struct Header { } #[derive(DeJson)] -pub struct ClassSizes { +pub struct JsonClassSizes { pub build_configuration: String, - pub sizes: Vec, + pub sizes: Vec, } #[derive(DeJson)] -pub struct ClassSize { +pub struct JsonClassSize { pub name: String, pub size: usize, } #[derive(DeJson)] -pub struct BuiltinClass { +pub struct JsonBuiltin { pub name: String, pub indexing_return_type: Option, pub is_keyed: bool, - pub members: Option>, + // pub members: Option>, // pub constants: Option>, - pub enums: Option>, // no bitfield - pub operators: Vec, - pub methods: Option>, - pub constructors: Vec, + pub enums: Option>, // no bitfield + pub operators: Vec, + pub methods: Option>, + pub constructors: Vec, pub has_destructor: bool, } #[derive(DeJson)] -pub struct Class { +pub struct JsonClass { pub name: String, pub is_refcounted: bool, pub is_instantiable: bool, pub inherits: Option, pub api_type: String, - pub constants: Option>, - pub enums: Option>, - pub methods: Option>, + pub constants: Option>, + pub enums: Option>, + pub methods: Option>, // pub properties: Option>, // pub signals: Option>, } #[derive(DeJson)] -pub struct NativeStructure { +pub struct JsonNativeStructure { pub name: String, pub format: String, } #[derive(DeJson)] -pub struct Singleton { +pub struct JsonSingleton { pub name: String, // Note: `type` currently has always same value as `name`, thus redundant // #[nserde(rename = "type")] @@ -91,21 +92,21 @@ pub struct Singleton { } #[derive(DeJson)] -pub struct Enum { +pub struct JsonEnum { pub name: String, pub is_bitfield: bool, - pub values: Vec, + pub values: Vec, } #[derive(DeJson)] -pub struct BuiltinClassEnum { +pub struct JsonBuiltinEnum { pub name: String, - pub values: Vec, + pub values: Vec, } -impl BuiltinClassEnum { - pub(crate) fn to_enum(&self) -> Enum { - Enum { +impl JsonBuiltinEnum { + pub(crate) fn to_enum(&self) -> JsonEnum { + JsonEnum { name: self.name.clone(), is_bitfield: false, values: self.values.clone(), @@ -114,7 +115,7 @@ impl BuiltinClassEnum { } #[derive(DeJson, Clone)] -pub struct EnumConstant { +pub struct JsonEnumConstant { pub name: String, // i64 is common denominator for enum, bitfield and constant values. @@ -122,12 +123,12 @@ pub struct EnumConstant { pub value: i64, } -pub enum ConstValue { +pub enum JsonConstValue { I32(i32), I64(i64), } -impl EnumConstant { +impl JsonEnumConstant { pub fn to_enum_ord(&self) -> i32 { self.value.try_into().unwrap_or_else(|_| { panic!( @@ -146,21 +147,21 @@ impl EnumConstant { }) } - pub fn to_constant(&self) -> ConstValue { + pub fn to_constant(&self) -> JsonConstValue { if let Ok(value) = i32::try_from(self.value) { - ConstValue::I32(value) + JsonConstValue::I32(value) } else { - ConstValue::I64(self.value) + JsonConstValue::I64(self.value) } } } -pub type ClassConstant = EnumConstant; +pub type JsonClassConstant = JsonEnumConstant; /* // Constants of builtin types have a string value like "Vector2(1, 1)", hence also a type field #[derive(DeJson)] -pub struct BuiltinConstant { +pub struct JsonBuiltinConstant { pub name: String, #[nserde(rename = "type")] pub type_: String, @@ -169,21 +170,22 @@ pub struct BuiltinConstant { */ #[derive(DeJson)] -pub struct Operator { +pub struct JsonOperator { pub name: String, pub right_type: Option, // null if unary pub return_type: String, } #[derive(DeJson)] -pub struct Member { +pub struct JsonMember { pub name: String, #[nserde(rename = "type")] pub type_: String, } #[derive(DeJson)] -pub struct Property { +#[allow(dead_code)] +pub struct JsonProperty { #[nserde(rename = "type")] type_: String, name: String, @@ -193,55 +195,57 @@ pub struct Property { } #[derive(DeJson)] -pub struct Signal { +#[allow(dead_code)] +pub struct JsonSignal { name: String, - arguments: Option>, + arguments: Option>, } #[derive(DeJson)] -pub struct Constructor { +pub struct JsonConstructor { pub index: usize, - pub arguments: Option>, + pub arguments: Option>, } #[derive(DeJson)] -pub struct UtilityFunction { +pub struct JsonUtilityFunction { pub name: String, pub return_type: Option, + /// `"general"` or `"math"` pub category: String, pub is_vararg: bool, pub hash: i64, - pub arguments: Option>, + pub arguments: Option>, } #[derive(DeJson)] -pub struct BuiltinClassMethod { +pub struct JsonBuiltinMethod { pub name: String, pub return_type: Option, pub is_vararg: bool, pub is_const: bool, pub is_static: bool, pub hash: Option, - pub arguments: Option>, + pub arguments: Option>, } #[derive(DeJson, Clone)] -pub struct ClassMethod { +pub struct JsonClassMethod { pub name: String, pub is_const: bool, pub is_vararg: bool, pub is_static: bool, pub is_virtual: bool, pub hash: Option, - pub return_value: Option, - pub arguments: Option>, + pub return_value: Option, + pub arguments: Option>, } // Example: set_point_weight_scale -> // [ {name: "id", type: "int", meta: "int64"}, // {name: "weight_scale", type: "float", meta: "float"}, #[derive(DeJson, Clone)] -pub struct MethodArg { +pub struct JsonMethodArg { pub name: String, #[nserde(rename = "type")] pub type_: String, @@ -251,13 +255,13 @@ pub struct MethodArg { // Example: get_available_point_id -> {type: "int", meta: "int64"} #[derive(DeJson, Clone)] -pub struct MethodReturn { +pub struct JsonMethodReturn { #[nserde(rename = "type")] pub type_: String, pub meta: Option, } -impl MethodReturn { +impl JsonMethodReturn { pub fn from_type_no_meta(type_: &str) -> Self { Self { type_: type_.to_owned(), @@ -271,7 +275,7 @@ impl MethodReturn { pub fn load_extension_api( watch: &mut godot_bindings::StopWatch, -) -> (ExtensionApi, [&'static str; 2]) { +) -> (JsonExtensionApi, [&'static str; 2]) { // For float/double inference, see: // * https://github.com/godotengine/godot-proposals/issues/892 // * https://github.com/godotengine/godot-cpp/pull/728 @@ -292,7 +296,7 @@ pub fn load_extension_api( #[allow(clippy::useless_asref)] let json_str: &str = json.as_ref(); - let model: ExtensionApi = + let model: JsonExtensionApi = DeJson::deserialize_json(json_str).expect("failed to deserialize JSON"); watch.record("deserialize_json"); diff --git a/godot-codegen/src/lib.rs b/godot-codegen/src/lib.rs index a09267614..5084678ab 100644 --- a/godot-codegen/src/lib.rs +++ b/godot-codegen/src/lib.rs @@ -5,13 +5,13 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -mod api_parser; mod central_generator; mod class_generator; mod codegen_special_cases; mod context; mod conv; mod interface_generator; +mod json_models; mod special_cases; mod util; mod utilities_generator; @@ -19,7 +19,6 @@ mod utilities_generator; #[cfg(test)] mod tests; -use api_parser::{load_extension_api, ExtensionApi}; use central_generator::{ generate_core_central_file, generate_core_mod_file, generate_sys_central_file, generate_sys_classes_file, @@ -29,6 +28,7 @@ use class_generator::{ }; use context::Context; use interface_generator::generate_sys_interface_file; +use json_models::{load_extension_api, JsonExtensionApi}; use util::ident; use utilities_generator::generate_utilities_file; diff --git a/godot-codegen/src/special_cases.rs b/godot-codegen/src/special_cases.rs index d5745624d..a9f9b4083 100644 --- a/godot-codegen/src/special_cases.rs +++ b/godot-codegen/src/special_cases.rs @@ -23,12 +23,12 @@ #![allow(clippy::match_like_matches_macro)] // if there is only one rule -use crate::api_parser::{BuiltinClassMethod, ClassMethod}; +use crate::json_models::{JsonBuiltinMethod, JsonClassMethod}; use crate::Context; use crate::{codegen_special_cases, TyName}; #[rustfmt::skip] -pub(crate) fn is_class_method_deleted(class_name: &TyName, method: &ClassMethod, ctx: &mut Context) -> bool { +pub(crate) fn is_class_method_deleted(class_name: &TyName, method: &JsonClassMethod, ctx: &mut Context) -> bool { if codegen_special_cases::is_class_method_excluded(method, false, ctx){ return true; } @@ -195,7 +195,7 @@ pub(crate) fn is_method_excluded_from_default_params(class_name: Option<&TyName> /// should be returned to take precedence over general rules. Example: `FileAccess::get_pascal_string()` is mut, but would be const-qualified /// since it looks like a getter. #[rustfmt::skip] -pub(crate) fn is_class_method_const(class_name: &TyName, godot_method: &ClassMethod) -> Option { +pub(crate) fn is_class_method_const(class_name: &TyName, godot_method: &JsonClassMethod) -> Option { match (class_name.godot_ty.as_str(), godot_method.name.as_str()) { // Changed to const. | ("Object", "to_string") @@ -229,7 +229,7 @@ pub(crate) fn is_class_method_const(class_name: &TyName, godot_method: &ClassMet } /// True if builtin method is excluded. Does NOT check for type exclusion; use [`is_builtin_type_deleted`] for that. -pub(crate) fn is_builtin_method_deleted(_class_name: &TyName, method: &BuiltinClassMethod) -> bool { +pub(crate) fn is_builtin_method_deleted(_class_name: &TyName, method: &JsonBuiltinMethod) -> bool { // Currently only deleted if codegen. codegen_special_cases::is_builtin_method_excluded(method) } diff --git a/godot-codegen/src/util.rs b/godot-codegen/src/util.rs index fe0dce90b..93d6abf13 100644 --- a/godot-codegen/src/util.rs +++ b/godot-codegen/src/util.rs @@ -5,8 +5,9 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::api_parser::{ - BuiltinClassMethod, Class, ClassConstant, ClassMethod, ConstValue, Enum, UtilityFunction, +use crate::json_models::{ + JsonBuiltinMethod, JsonClass, JsonClassConstant, JsonClassMethod, JsonConstValue, JsonEnum, + JsonUtilityFunction, }; use crate::{conv, RustTy, TyName}; @@ -161,7 +162,7 @@ pub(crate) fn make_imports() -> TokenStream { } // Use &ClassMethod instead of &str, to make sure it's the original Godot name and no rename. -pub(crate) fn make_class_method_ptr_name(class_ty: &TyName, method: &ClassMethod) -> Ident { +pub(crate) fn make_class_method_ptr_name(class_ty: &TyName, method: &JsonClassMethod) -> Ident { format_ident!( "{}__{}", conv::to_snake_case(&class_ty.godot_ty), @@ -171,7 +172,7 @@ pub(crate) fn make_class_method_ptr_name(class_ty: &TyName, method: &ClassMethod pub(crate) fn make_builtin_method_ptr_name( builtin_ty: &TyName, - method: &BuiltinClassMethod, + method: &JsonBuiltinMethod, ) -> Ident { format_ident!( "{}__{}", @@ -180,7 +181,7 @@ pub(crate) fn make_builtin_method_ptr_name( ) } -pub(crate) fn make_utility_function_ptr_name(function: &UtilityFunction) -> Ident { +pub(crate) fn make_utility_function_ptr_name(function: &JsonUtilityFunction) -> Ident { safe_ident(&function.name) } @@ -204,7 +205,7 @@ pub fn make_sname_ptr(identifier: &str) -> TokenStream { } } -pub fn get_api_level(class: &Class) -> ClassCodegenLevel { +pub fn get_api_level(class: &JsonClass) -> ClassCodegenLevel { // Work around wrong classification in https://github.com/godotengine/godot/issues/86206. fn override_editor(class_name: &str) -> bool { cfg!(before_api = "4.3") @@ -228,7 +229,7 @@ pub fn get_api_level(class: &Class) -> ClassCodegenLevel { } } -pub fn make_enum_definition(enum_: &Enum, class_name: Option<&str>) -> TokenStream { +pub fn make_enum_definition(enum_: &JsonEnum, class_name: Option<&str>) -> TokenStream { // TODO enums which have unique ords could be represented as Rust enums // This would allow exhaustive matches (or at least auto-completed matches + #[non_exhaustive]). But even without #[non_exhaustive], // this might be a forward compatibility hazard, if Godot deprecates enumerators and adds new ones with existing ords. @@ -416,7 +417,7 @@ pub fn make_enum_definition(enum_: &Enum, class_name: Option<&str>) -> TokenStre } } -pub fn make_constant_definition(constant: &ClassConstant) -> TokenStream { +pub fn make_constant_definition(constant: &JsonClassConstant) -> TokenStream { let name = ident(&constant.name); let vis = if constant.name.starts_with("NOTIFICATION_") { quote! { pub(crate) } @@ -425,13 +426,13 @@ pub fn make_constant_definition(constant: &ClassConstant) -> TokenStream { }; match constant.to_constant() { - ConstValue::I32(value) => quote! { #vis const #name: i32 = #value; }, - ConstValue::I64(value) => quote! { #vis const #name: i64 = #value; }, + JsonConstValue::I32(value) => quote! { #vis const #name: i32 = #value; }, + JsonConstValue::I64(value) => quote! { #vis const #name: i64 = #value; }, } } /// Tries to interpret the constant as a notification one, and transforms it to a Rust identifier on success. -pub fn try_to_notification(constant: &ClassConstant) -> Option { +pub fn try_to_notification(constant: &JsonClassConstant) -> Option { constant .name .strip_prefix("NOTIFICATION_") @@ -441,7 +442,7 @@ pub fn try_to_notification(constant: &ClassConstant) -> Option { /// If an enum qualifies as "indexable" (can be used as array index), returns the number of possible values. /// /// See `godot::obj::IndexEnum` for what constitutes "indexable". -fn try_count_index_enum(enum_: &Enum) -> Option { +fn try_count_index_enum(enum_: &JsonEnum) -> Option { if enum_.is_bitfield || enum_.values.is_empty() { return None; } diff --git a/godot-codegen/src/utilities_generator.rs b/godot-codegen/src/utilities_generator.rs index f8eeabae8..876416573 100644 --- a/godot-codegen/src/utilities_generator.rs +++ b/godot-codegen/src/utilities_generator.rs @@ -10,11 +10,11 @@ use std::path::Path; use quote::quote; use crate::class_generator::make_utility_function_definition; -use crate::{api_parser::*, SubmitFn}; +use crate::{json_models::*, SubmitFn}; use crate::{util, Context}; pub(crate) fn generate_utilities_file( - api: &ExtensionApi, + api: &JsonExtensionApi, ctx: &mut Context, gen_path: &Path, submit_fn: &mut SubmitFn, From fa473896a7833563c44543c5a92a637c33de9fa1 Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Sun, 14 Jan 2024 00:23:10 +0100 Subject: [PATCH 03/10] Codegen: split domain and JSON models, part I (functions) --- godot-codegen/src/central_generator.rs | 16 +- godot-codegen/src/class_generator.rs | 407 +++++---------------- godot-codegen/src/codegen_special_cases.rs | 23 +- godot-codegen/src/context.rs | 3 +- godot-codegen/src/domain_mapping.rs | 183 +++++++++ godot-codegen/src/domain_models.rs | 406 ++++++++++++++++++++ godot-codegen/src/lib.rs | 2 + godot-codegen/src/special_cases.rs | 21 +- godot-codegen/src/util.rs | 5 +- godot-codegen/src/utilities_generator.rs | 12 +- 10 files changed, 740 insertions(+), 338 deletions(-) create mode 100644 godot-codegen/src/domain_mapping.rs create mode 100644 godot-codegen/src/domain_models.rs diff --git a/godot-codegen/src/central_generator.rs b/godot-codegen/src/central_generator.rs index 7413d7724..dfedf8ede 100644 --- a/godot-codegen/src/central_generator.rs +++ b/godot-codegen/src/central_generator.rs @@ -239,12 +239,12 @@ pub(crate) fn generate_sys_utilities_file( }; for function in api.utility_functions.iter() { - if codegen_special_cases::is_function_excluded(function, ctx) { + if special_cases::is_utility_function_deleted(function, ctx) { continue; } let fn_name_str = &function.name; - let field = util::make_utility_function_ptr_name(function); + let field = util::make_utility_function_ptr_name(fn_name_str); let hash = function.hash; table.method_decls.push(quote! { @@ -339,7 +339,8 @@ fn make_method_table(info: IndexedMethodTable) -> TokenStream { assert_eq!( last.method_inits.last().unwrap().index, method_count - 1, - "last method should have highest index" + "last method should have highest index (table {})", + table_name ); } else { assert_eq!(method_count, 0, "empty method table should have count 0"); @@ -1021,7 +1022,9 @@ fn populate_class_methods( let mut method_inits = vec![]; for method in option_as_slice(&class.methods) { - if special_cases::is_class_method_deleted(class_ty, method, ctx) { + // Virtual methods are not part of the class API itself, but exposed as an accompanying trait. + // Earlier code to detect virtuals: method.name.starts_with('_') + if special_cases::is_class_method_deleted(class_ty, method, ctx) || method.is_virtual { continue; } @@ -1034,6 +1037,11 @@ fn populate_class_methods( let method_init = make_class_method_init(method, &class_var, class_ty); method_inits.push(MethodInit { method_init, index }); + + println!( + "method {}, index {}, count {}", + method.name, index, table.method_count + ); table.method_count += 1; // If requested, add a named accessor for this method. diff --git a/godot-codegen/src/class_generator.rs b/godot-codegen/src/class_generator.rs index 73c5366c6..766e0551c 100644 --- a/godot-codegen/src/class_generator.rs +++ b/godot-codegen/src/class_generator.rs @@ -13,6 +13,8 @@ use std::path::Path; use crate::central_generator::collect_builtin_types; use crate::context::NotificationEnum; +use crate::domain_models::BuiltinMethod; +use crate::domain_models::*; use crate::json_models::*; use crate::util::{ ident, make_string_name, option_as_slice, parse_native_structures_format, safe_ident, @@ -51,136 +53,6 @@ impl FnReceiver { // ---------------------------------------------------------------------------------------------------------------------------------------------- -struct FnParam { - name: Ident, - type_: RustTy, - default_value: Option, -} - -impl FnParam { - fn new_range(method_args: &Option>, ctx: &mut Context) -> Vec { - option_as_slice(method_args) - .iter() - .map(|arg| Self::new(arg, ctx)) - .collect() - } - - fn new_range_no_defaults( - method_args: &Option>, - ctx: &mut Context, - ) -> Vec { - option_as_slice(method_args) - .iter() - .map(|arg| Self::new_no_defaults(arg, ctx)) - .collect() - } - - fn new(method_arg: &JsonMethodArg, ctx: &mut Context) -> FnParam { - let name = safe_ident(&method_arg.name); - let type_ = conv::to_rust_type(&method_arg.type_, method_arg.meta.as_ref(), ctx); - let default_value = method_arg - .default_value - .as_ref() - .map(|v| conv::to_rust_expr(v, &type_)); - - FnParam { - name, - type_, - default_value, - } - } - - fn new_no_defaults(method_arg: &JsonMethodArg, ctx: &mut Context) -> FnParam { - FnParam { - name: safe_ident(&method_arg.name), - type_: conv::to_rust_type(&method_arg.type_, method_arg.meta.as_ref(), ctx), - //type_: to_rust_type(&method_arg.type_, &method_arg.meta, ctx), - default_value: None, - } - } -} - -// ---------------------------------------------------------------------------------------------------------------------------------------------- - -struct FnReturn { - decl: TokenStream, - type_: Option, -} - -impl FnReturn { - fn new(return_value: &Option, ctx: &mut Context) -> Self { - if let Some(ret) = return_value { - let ty = conv::to_rust_type(&ret.type_, ret.meta.as_ref(), ctx); - - Self { - decl: ty.return_decl(), - type_: Some(ty), - } - } else { - Self { - decl: TokenStream::new(), - type_: None, - } - } - } - - fn type_tokens(&self) -> TokenStream { - match &self.type_ { - Some(RustTy::EngineClass { tokens, .. }) => { - quote! { Option<#tokens> } - } - Some(ty) => { - quote! { #ty } - } - _ => { - quote! { () } - } - } - } -} - -// ---------------------------------------------------------------------------------------------------------------------------------------------- - -enum FnQualifier { - Mut, // &mut self - Const, // &self - Static, // Self - Global, // (nothing) -} - -impl FnQualifier { - fn is_static_or_global(&self) -> bool { - matches!(self, Self::Static | Self::Global) - } -} - -impl FnQualifier { - fn for_method(is_const: bool, is_static: bool) -> FnQualifier { - if is_static { - FnQualifier::Static - } else if is_const { - FnQualifier::Const - } else { - FnQualifier::Mut - } - } -} - -// ---------------------------------------------------------------------------------------------------------------------------------------------- - -struct FnSignature<'a> { - function_name: &'a str, - surrounding_class: Option<&'a TyName>, // None if global function - is_private: bool, - is_virtual: bool, - is_vararg: bool, - qualifier: FnQualifier, - params: Vec, - return_value: FnReturn, -} - -// ---------------------------------------------------------------------------------------------------------------------------------------------- - struct FnCode { receiver: FnReceiver, varcall_invocation: TokenStream, @@ -1094,7 +966,12 @@ fn make_methods( let get_method_table = api_level.table_global_getter(); let definitions = methods.iter().map(|method| { - make_class_method_definition(method, class_name, api_level, &get_method_table, ctx) + match ClassMethod::from_json_outbound(method, class_name, ctx) { + None => FnDefinition::none(), + Some(method) => { + make_class_method_definition(&method, class_name, api_level, &get_method_table, ctx) + } + } }); FnDefinitions::expand(definitions) @@ -1106,9 +983,12 @@ fn make_builtin_methods( inner_class_name: &TyName, ctx: &mut Context, ) -> FnDefinitions { - let definitions = methods - .iter() - .map(|method| make_builtin_method_definition(method, builtin_name, inner_class_name, ctx)); + let definitions = methods.iter().map(|method| { + match BuiltinMethod::from_json(method, builtin_name, inner_class_name, ctx) { + None => FnDefinition::none(), + Some(method) => make_builtin_method_definition(&method, builtin_name, ctx), + } + }); FnDefinitions::expand(definitions) } @@ -1155,25 +1035,15 @@ fn make_special_builtin_methods(class_name: &TyName, _ctx: &Context) -> TokenStr } fn make_class_method_definition( - method: &JsonClassMethod, + method: &ClassMethod, class_name: &TyName, api_level: &ClassCodegenLevel, get_method_table: &Ident, ctx: &mut Context, ) -> FnDefinition { - if special_cases::is_class_method_deleted(class_name, method, ctx) { + let FnDirection::Outbound { hash } = method.direction() else { return FnDefinition::none(); - } - - let class_name_str = &class_name.godot_ty; - let godot_method_name = &method.name; - let rust_method_name = special_cases::maybe_rename_class_method(class_name, godot_method_name); - - // Override const-qualification for known special cases (FileAccess::get_16, StreamPeer::get_u16, etc.). - let mut is_actually_const = method.is_const; - if let Some(override_const) = special_cases::is_class_method_const(class_name, method) { - is_actually_const = override_const; - } + }; /* // TODO re-enable this once JSON/domain models are separated. @@ -1186,27 +1056,25 @@ fn make_class_method_definition( } }*/ - let receiver = make_receiver( - method.is_static, - //override_is_const.unwrap_or(method.is_const), - is_actually_const, - quote! { self.object_ptr }, - ); + let rust_method_name = method.name(); + let godot_method_name = method.godot_name(); + + let receiver = make_receiver(method.qualifier(), quote! { self.object_ptr }); let table_index = ctx.get_table_index(&MethodTableKey::ClassMethod { api_level: *api_level, class_ty: class_name.clone(), - method_name: method.name.clone(), + method_name: method.name().to_string(), }); - let maybe_instance_id = if method.is_static { + let maybe_instance_id = if method.qualifier() == FnQualifier::Static { quote! { None } } else { quote! { self.__checked_id() } }; let fptr_access = if cfg!(feature = "codegen-lazy-fptrs") { - let hash = method.hash.expect("hash present for class method"); + let class_name_str = &class_name.godot_ty; quote! { fptr_by_key(sys::lazy_keys::ClassMethodKey { class_name: #class_name_str, @@ -1245,16 +1113,7 @@ fn make_class_method_definition( }; make_function_definition( - &FnSignature { - function_name: rust_method_name, - surrounding_class: Some(class_name), - is_private: special_cases::is_method_private(class_name, godot_method_name), - is_virtual: false, - is_vararg: method.is_vararg, - qualifier: FnQualifier::for_method(method.is_const, method.is_static), - params: FnParam::new_range(&method.arguments, ctx), - return_value: FnReturn::new(&method.return_value, ctx), - }, + method, &FnCode { receiver, varcall_invocation, @@ -1264,26 +1123,19 @@ fn make_class_method_definition( } fn make_builtin_method_definition( - method: &JsonBuiltinMethod, + method: &BuiltinMethod, builtin_name: &TyName, - inner_class_name: &TyName, ctx: &mut Context, ) -> FnDefinition { - if special_cases::is_builtin_method_deleted(builtin_name, method) { - return FnDefinition::none(); - } - - let method_name_str = &method.name; + let FnDirection::Outbound { hash } = method.direction() else { + unreachable!("builtin methods are never virtual") + }; - let return_value = method - .return_type - .as_deref() - .map(JsonMethodReturn::from_type_no_meta); + let method_name_str = method.name(); let fptr_access = if cfg!(feature = "codegen-lazy-fptrs") { let variant_type = quote! { sys::VariantType::#builtin_name }; let variant_type_str = &builtin_name.godot_ty; - let hash = method.hash.expect("hash present for class method"); quote! { fptr_by_key(sys::lazy_keys::BuiltinMethodKey { @@ -1296,12 +1148,12 @@ fn make_builtin_method_definition( } else { let table_index = ctx.get_table_index(&MethodTableKey::BuiltinMethod { builtin_ty: builtin_name.clone(), - method_name: method.name.clone(), + method_name: method_name_str.to_string(), }); quote! { fptr_by_index(#table_index) } }; - let receiver = make_receiver(method.is_static, method.is_const, quote! { self.sys_ptr }); + let receiver = make_receiver(method.qualifier(), quote! { self.sys_ptr }); let object_ptr = &receiver.ffi_arg; let ptrcall_invocation = quote! { @@ -1327,19 +1179,7 @@ fn make_builtin_method_definition( }; make_function_definition( - &FnSignature { - function_name: method_name_str, - surrounding_class: Some(inner_class_name), - is_private: special_cases::is_method_private(builtin_name, &method.name), - is_virtual: false, - is_vararg: method.is_vararg, - qualifier: FnQualifier::for_method(method.is_const, method.is_static), - - // Disable default parameters for builtin classes. - // They are not public-facing and need more involved implementation (lifetimes etc). Also reduces number of symbols in API. - params: FnParam::new_range_no_defaults(&method.arguments, ctx), - return_value: FnReturn::new(&return_value, ctx), - }, + method, &FnCode { receiver, varcall_invocation, @@ -1348,21 +1188,9 @@ fn make_builtin_method_definition( ) } -pub(crate) fn make_utility_function_definition( - function: &JsonUtilityFunction, - ctx: &mut Context, -) -> TokenStream { - if codegen_special_cases::is_function_excluded(function, ctx) { - return TokenStream::new(); - } - - let function_name_str = &function.name; - let fn_ptr = util::make_utility_function_ptr_name(function); - - let return_value = function - .return_type - .as_deref() - .map(JsonMethodReturn::from_type_no_meta); +pub(crate) fn make_utility_function_definition(function: &UtilityFunction) -> TokenStream { + let function_name_str = function.name(); + let fn_ptr = util::make_utility_function_ptr_name(function_name_str); let ptrcall_invocation = quote! { let utility_fn = sys::utility_function_table().#fn_ptr; @@ -1386,16 +1214,7 @@ pub(crate) fn make_utility_function_definition( }; let definition = make_function_definition( - &FnSignature { - function_name: function_name_str, - surrounding_class: None, - is_private: false, - is_virtual: false, - is_vararg: function.is_vararg, - qualifier: FnQualifier::Global, - params: FnParam::new_range(&function.arguments, ctx), - return_value: FnReturn::new(&return_value, ctx), - }, + function, &FnCode { receiver: FnReceiver::global_function(), varcall_invocation, @@ -1415,14 +1234,14 @@ fn make_vis(is_private: bool) -> TokenStream { } } -fn make_function_definition(sig: &FnSignature, code: &FnCode) -> FnDefinition { +fn make_function_definition(sig: &dyn Function, code: &FnCode) -> FnDefinition { let has_default_params = function_uses_default_params(sig); let vis = if has_default_params { // Public API mapped by separate function. // Needs to be crate-public because default-arg builder lives outside of the module. quote! { pub(crate) } } else { - make_vis(sig.is_private) + make_vis(sig.is_private()) }; let (maybe_unsafe, safety_doc) = if function_uses_pointers(sig) { @@ -1438,13 +1257,13 @@ fn make_function_definition(sig: &FnSignature, code: &FnCode) -> FnDefinition { (TokenStream::new(), TokenStream::new()) }; - let [params, param_types, arg_names] = make_params_exprs(&sig.params); + let [params, param_types, arg_names] = make_params_exprs(sig.params()); - let godot_fn_name_str = sig.function_name; + let rust_function_name_str = sig.name(); let primary_fn_name = if has_default_params { - format_ident!("{}_full", safe_ident(godot_fn_name_str)) + format_ident!("{}_full", safe_ident(rust_function_name_str)) } else { - safe_ident(godot_fn_name_str) + safe_ident(rust_function_name_str) }; let (default_fn_code, default_structs_code) = if has_default_params { @@ -1453,15 +1272,15 @@ fn make_function_definition(sig: &FnSignature, code: &FnCode) -> FnDefinition { (TokenStream::new(), TokenStream::new()) }; - let return_ty = &sig.return_value.type_tokens(); + let return_ty = &sig.return_value().type_tokens(); let call_sig = quote! { ( #return_ty, #(#param_types),* ) }; - let return_decl = &sig.return_value.decl; + let return_decl = &sig.return_value().decl; let receiver_param = &code.receiver.param; - let primary_function = if sig.is_virtual { + let primary_function = if sig.is_virtual() { // Virtual functions quote! { @@ -1473,7 +1292,7 @@ fn make_function_definition(sig: &FnSignature, code: &FnCode) -> FnDefinition { unimplemented!() } } - } else if sig.is_vararg { + } else if sig.is_vararg() { // Varargs (usually varcall, but not necessarily -- utilities use ptrcall) // If the return type is not Variant, then convert to concrete target type @@ -1500,7 +1319,7 @@ fn make_function_definition(sig: &FnSignature, code: &FnCode) -> FnDefinition { // Always ptrcall, no varargs let ptrcall_invocation = &code.ptrcall_invocation; - let maybe_return_ty = &sig.return_value.type_; + let maybe_return_ty = &sig.return_value().type_; // This differentiation is needed because we need to differentiate between Option>, T and () as return types. // Rust traits don't provide specialization and thus would encounter overlapping blanket impls, so we cannot use the type system here. @@ -1538,18 +1357,18 @@ fn make_function_definition(sig: &FnSignature, code: &FnCode) -> FnDefinition { } fn make_function_definition_with_defaults( - sig: &FnSignature, + sig: &dyn Function, code: &FnCode, full_fn_name: &Ident, ) -> (TokenStream, TokenStream) { let (default_fn_params, required_fn_params): (Vec<_>, Vec<_>) = sig - .params + .params() .iter() .partition(|arg| arg.default_value.is_some()); - let simple_fn_name = safe_ident(sig.function_name); + let simple_fn_name = safe_ident(sig.name()); let extended_fn_name = format_ident!("{}_ex", simple_fn_name); - let vis = make_vis(sig.is_private); + let vis = make_vis(sig.is_private()); let (builder_doc, surround_class_prefix) = make_extender_doc(sig, &extended_fn_name); @@ -1572,7 +1391,7 @@ fn make_function_definition_with_defaults( let receiver_param = &code.receiver.param; let receiver_self = &code.receiver.self_prefix; let (required_params, required_args) = make_params_and_args(&required_fn_params); - let return_decl = &sig.return_value.decl; + let return_decl = &sig.return_value().decl; // Technically, the builder would not need a lifetime -- it could just maintain an `object_ptr` copy. // However, this increases the risk that it is used out of place (not immediately for a default-param call). @@ -1638,13 +1457,13 @@ fn make_function_definition_with_defaults( (functions, builders) } -fn make_extender_doc(sig: &FnSignature, extended_fn_name: &Ident) -> (String, TokenStream) { +fn make_extender_doc(sig: &dyn Function, extended_fn_name: &Ident) -> (String, TokenStream) { // Not in the above match, because this is true for both static/instance methods. // Static/instance is determined by first argument (always use fully qualified function call syntax). let surround_class_prefix; let builder_doc; - match sig.surrounding_class { + match sig.surrounding_class() { Some(TyName { rust_ty, .. }) => { surround_class_prefix = quote! { re_export::#rust_ty:: }; builder_doc = format!( @@ -1667,17 +1486,19 @@ fn make_extender_doc(sig: &FnSignature, extended_fn_name: &Ident) -> (String, To (builder_doc, surround_class_prefix) } -fn make_extender_receiver(sig: &FnSignature) -> ExtenderReceiver { - let builder_mut = if matches!(sig.qualifier, FnQualifier::Const) { - quote! {} - } else { - quote! { mut } +fn make_extender_receiver(sig: &dyn Function) -> ExtenderReceiver { + let builder_mut = match sig.qualifier() { + FnQualifier::Const | FnQualifier::Static => quote! {}, + FnQualifier::Mut => quote! { mut }, + FnQualifier::Global => { + unreachable!("default parameters not supported for global methods; {sig}") + } }; // Treat the object parameter like other parameters, as first in list. // Only add it if the method is not global or static. - match &sig.surrounding_class { - Some(surrounding_class) if !sig.qualifier.is_static_or_global() => { + match sig.surrounding_class() { + Some(surrounding_class) if !sig.qualifier().is_static_or_global() => { let class = &surrounding_class.rust_ty; ExtenderReceiver { @@ -1719,24 +1540,24 @@ struct Extender { } fn make_extender( - sig: &FnSignature, + sig: &dyn Function, object_fn_param: Option, default_fn_params: Vec<&FnParam>, ) -> Extender { // Note: could build a documentation string with default values here, but the Rust tokens are not very readable, // and often not helpful, such as Enum::from_ord(13). Maybe one day those could be resolved and curated. - let (lifetime, anon_lifetime) = if sig.qualifier.is_static_or_global() { + let (lifetime, anon_lifetime) = if sig.qualifier().is_static_or_global() { (TokenStream::new(), TokenStream::new()) } else { (quote! { <'a> }, quote! { <'_> }) }; - let all_fn_params = object_fn_param.iter().chain(&sig.params); + let all_fn_params = object_fn_param.iter().chain(sig.params().iter()); let len = all_fn_params.size_hint().0; let mut result = Extender { - builder_ty: format_ident!("Ex{}", conv::to_pascal_case(sig.function_name)), + builder_ty: format_ident!("Ex{}", conv::to_pascal_case(sig.name())), builder_lifetime: lifetime, builder_anon_lifetime: anon_lifetime, builder_methods: Vec::with_capacity(default_fn_params.len()), @@ -1784,27 +1605,23 @@ fn make_extender( result } -fn make_receiver(is_static: bool, is_const: bool, ffi_arg: TokenStream) -> FnReceiver { - // could reuse FnQualifier as parameter +fn make_receiver(qualifier: FnQualifier, ffi_arg_in: TokenStream) -> FnReceiver { + assert_ne!(qualifier, FnQualifier::Global, "expected class"); - let param = if is_static { - quote! {} - } else if is_const { - quote! { &self, } - } else { - quote! { &mut self, } - }; - - let ffi_arg = if is_static { - quote! { std::ptr::null_mut() } - } else { - ffi_arg + let param = match qualifier { + FnQualifier::Const => quote! { &self, }, + FnQualifier::Mut => quote! { &mut self, }, + FnQualifier::Static => quote! {}, + FnQualifier::Global => quote! {}, }; - let self_prefix = if is_static { - quote! { Self:: } + let (ffi_arg, self_prefix); + if matches!(qualifier, FnQualifier::Static) { + ffi_arg = quote! { std::ptr::null_mut() }; + self_prefix = quote! { Self:: }; } else { - quote! { self. } + ffi_arg = ffi_arg_in; + self_prefix = quote! { self. }; }; FnReceiver { @@ -1853,7 +1670,7 @@ fn make_virtual_methods_trait( ) -> TokenStream { let trait_name = ident(trait_name); - let virtual_method_fns = make_all_virtual_methods(class, all_base_names, ctx); + let virtual_method_fns = make_all_virtual_methods(class, class_name, all_base_names, ctx); let special_virtual_methods = special_virtual_methods(notification_enum_name); let trait_doc = make_virtual_trait_doc(class_name); @@ -1912,25 +1729,15 @@ fn special_virtual_methods(notification_enum_name: &Ident) -> TokenStream { } } -fn make_virtual_method(method: &JsonClassMethod, ctx: &mut Context) -> TokenStream { - let method_name = virtual_method_name(method); - +fn make_virtual_method(method: &ClassMethod) -> TokenStream { // Virtual methods are never static. - assert!(!method.is_static); + let qualifier = method.qualifier(); + assert!(matches!(qualifier, FnQualifier::Mut | FnQualifier::Const)); let definition = make_function_definition( - &FnSignature { - function_name: method_name, - surrounding_class: None, // no default parameters needed for virtual methods - is_private: false, - is_virtual: true, - is_vararg: false, - qualifier: FnQualifier::for_method(method.is_const, method.is_static), - params: FnParam::new_range(&method.arguments, ctx), - return_value: FnReturn::new(&method.return_value, ctx), - }, + method, &FnCode { - receiver: make_receiver(false, method.is_const, TokenStream::new()), + receiver: make_receiver(qualifier, TokenStream::new()), // make_return() requests following args, but they are not used for virtual methods. We can provide empty streams. varcall_invocation: TokenStream::new(), ptrcall_invocation: TokenStream::new(), @@ -1943,6 +1750,7 @@ fn make_virtual_method(method: &JsonClassMethod, ctx: &mut Context) -> TokenStre fn make_all_virtual_methods( class: &JsonClass, + class_name: &TyName, all_base_names: &[TyName], ctx: &mut Context, ) -> Vec { @@ -1968,11 +1776,8 @@ fn make_all_virtual_methods( all_virtuals .into_iter() .filter_map(|method| { - if codegen_special_cases::is_class_method_excluded(&method, true, ctx) { - None - } else { - Some(make_virtual_method(&method, ctx)) - } + ClassMethod::from_json_virtual(&method, class_name, ctx) + .map(|m| make_virtual_method(&m)) }) .collect() } @@ -1984,40 +1789,22 @@ fn get_methods_in_class(class: &JsonClass) -> &[JsonClassMethod] { } } -fn virtual_method_name(class_method: &JsonClassMethod) -> &str { - // Matching the C++ convention, we remove the leading underscore - // from virtual method names. - let method_name = class_method - .name - .strip_prefix('_') - .unwrap_or(&class_method.name); - - // As a special exception, a few classes define a virtual method - // called "_init" (distinct from the constructor), so we rename - // those to avoid a name conflict in our trait. - if method_name == "init" { - "init_ext" - } else { - method_name - } -} - -fn function_uses_pointers(sig: &FnSignature) -> bool { +fn function_uses_pointers(sig: &dyn Function) -> bool { let has_pointer_params = sig - .params + .params() .iter() .any(|param| matches!(param.type_, RustTy::RawPointer { .. })); - let has_pointer_return = matches!(sig.return_value.type_, Some(RustTy::RawPointer { .. })); + let has_pointer_return = matches!(sig.return_value().type_, Some(RustTy::RawPointer { .. })); // No short-circuiting due to variable decls, but that's fine. has_pointer_params || has_pointer_return } -fn function_uses_default_params(sig: &FnSignature) -> bool { - sig.params.iter().any(|arg| arg.default_value.is_some()) +fn function_uses_default_params(sig: &dyn Function) -> bool { + sig.params().iter().any(|arg| arg.default_value.is_some()) && !special_cases::is_method_excluded_from_default_params( - sig.surrounding_class, - sig.function_name, + sig.surrounding_class(), + sig.name(), ) } diff --git a/godot-codegen/src/codegen_special_cases.rs b/godot-codegen/src/codegen_special_cases.rs index 3e1cc0490..df67154a5 100644 --- a/godot-codegen/src/codegen_special_cases.rs +++ b/godot-codegen/src/codegen_special_cases.rs @@ -7,6 +7,8 @@ //! Codegen-dependent exclusions. Can be removed if feature `codegen-full` is removed. +// TODO make this file private and only accessed by special_cases.rs. + use crate::context::Context; use crate::json_models::{JsonBuiltinMethod, JsonClassMethod, JsonUtilityFunction}; use crate::{special_cases, TyName}; @@ -55,11 +57,7 @@ fn is_type_excluded(ty: &str, ctx: &mut Context) -> bool { is_rust_type_excluded(&conv::to_rust_type(ty, None, ctx)) } -pub(crate) fn is_class_method_excluded( - method: &JsonClassMethod, - is_virtual_impl: bool, - ctx: &mut Context, -) -> bool { +pub(crate) fn is_class_method_excluded(method: &JsonClassMethod, ctx: &mut Context) -> bool { let is_arg_or_return_excluded = |ty: &str, _ctx: &mut Context| { let class_deleted = special_cases::is_class_deleted(&TyName::from_godot(ty)); @@ -88,21 +86,22 @@ pub(crate) fn is_class_method_excluded( return true; } - // Virtual methods are not part of the class API itself, but exposed as an accompanying trait. - if !is_virtual_impl && method.name.starts_with('_') { - return true; - } - false } #[cfg(feature = "codegen-full")] -pub(crate) fn is_function_excluded(_function: &JsonUtilityFunction, _ctx: &mut Context) -> bool { +pub(crate) fn is_utility_function_excluded( + _function: &JsonUtilityFunction, + _ctx: &mut Context, +) -> bool { false } #[cfg(not(feature = "codegen-full"))] -pub(crate) fn is_function_excluded(function: &JsonUtilityFunction, ctx: &mut Context) -> bool { +pub(crate) fn is_utility_function_excluded( + function: &JsonUtilityFunction, + ctx: &mut Context, +) -> bool { function .return_type .as_ref() diff --git a/godot-codegen/src/context.rs b/godot-codegen/src/context.rs index d8cfc280b..b3f5ea602 100644 --- a/godot-codegen/src/context.rs +++ b/godot-codegen/src/context.rs @@ -169,7 +169,8 @@ impl<'a> Context<'a> { } for method in methods.iter() { - if special_cases::is_class_method_deleted(class_name, method, ctx) { + if special_cases::is_class_method_deleted(class_name, method, ctx) || method.is_virtual + { continue; } diff --git a/godot-codegen/src/domain_mapping.rs b/godot-codegen/src/domain_mapping.rs new file mode 100644 index 000000000..1cede139d --- /dev/null +++ b/godot-codegen/src/domain_mapping.rs @@ -0,0 +1,183 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +use crate::context::Context; +use crate::domain_models::{ + BuiltinMethod, ClassMethod, FnDirection, FnParam, FnQualifier, FnReturn, FunctionCommon, + UtilityFunction, +}; +use crate::json_models::{ + JsonBuiltinMethod, JsonClassMethod, JsonMethodReturn, JsonUtilityFunction, +}; +use crate::{special_cases, TyName}; + +impl BuiltinMethod { + pub fn from_json( + method: &JsonBuiltinMethod, + builtin_name: &TyName, + inner_class_name: &TyName, + ctx: &mut Context, + ) -> Option { + if special_cases::is_builtin_method_deleted(builtin_name, method) { + return None; + } + + let return_value = method + .return_type + .as_deref() + .map(JsonMethodReturn::from_type_no_meta); + + // use BuiltinMethod, but adopt values from above FnSignature expr + Some(Self { + common: FunctionCommon { + // Fill in these fields + name: method.name.clone(), + godot_name: method.name.clone(), + // Disable default parameters for builtin classes. + // They are not public-facing and need more involved implementation (lifetimes etc). Also reduces number of symbols in API. + parameters: FnParam::new_range_no_defaults(&method.arguments, ctx), + return_value: FnReturn::new(&return_value, ctx), + is_vararg: method.is_vararg, + is_private: special_cases::is_method_private(builtin_name, &method.name), + direction: FnDirection::Outbound { + hash: method.hash.expect("hash absent for builtin method"), + }, + }, + qualifier: FnQualifier::from_const_static(method.is_const, method.is_static), + surrounding_class: inner_class_name.clone(), + }) + } +} + +impl ClassMethod { + pub fn from_json_outbound( + method: &JsonClassMethod, + class_name: &TyName, + ctx: &mut Context, + ) -> Option { + if method.is_virtual { + return None; + } + + let hash = method + .hash + .expect("hash absent for non-virtual class method"); + + let rust_method_name = special_cases::maybe_rename_class_method(class_name, &method.name); + + Self::from_json_inner( + method, + rust_method_name, + class_name, + FnDirection::Outbound { hash }, + ctx, + ) + } + pub fn from_json_virtual( + method: &JsonClassMethod, + class_name: &TyName, + ctx: &mut Context, + ) -> Option { + if !method.is_virtual { + return None; + } + + assert!( + method.hash.is_none(), + "hash present for virtual class method" + ); + + let rust_method_name = Self::make_virtual_method_name(&method.name); + + Self::from_json_inner( + method, + rust_method_name, + class_name, + FnDirection::Virtual, + ctx, + ) + } + + fn from_json_inner( + method: &JsonClassMethod, + rust_method_name: &str, + class_name: &TyName, + direction: FnDirection, + ctx: &mut Context, + ) -> Option { + if special_cases::is_class_method_deleted(class_name, method, ctx) { + return None; + } + + let is_private = special_cases::is_method_private(class_name, &method.name); + + let godot_method_name = method.name.clone(); + + let qualifier = { + // Override const-qualification for known special cases (FileAccess::get_16, StreamPeer::get_u16, etc.). + let mut is_actually_const = method.is_const; + if let Some(override_const) = special_cases::is_class_method_const(class_name, method) { + is_actually_const = override_const; + } + + FnQualifier::from_const_static(is_actually_const, method.is_static) + }; + + Some(Self { + common: FunctionCommon { + name: rust_method_name.to_string(), + godot_name: godot_method_name, + parameters: FnParam::new_range(&method.arguments, ctx), + return_value: FnReturn::new(&method.return_value, ctx), + is_vararg: method.is_vararg, + is_private, + direction, + }, + qualifier, + surrounding_class: class_name.clone(), + }) + } + + fn make_virtual_method_name(godot_method_name: &str) -> &str { + // Remove leading underscore from virtual method names. + let method_name = godot_method_name + .strip_prefix('_') + .unwrap_or(godot_method_name); + + special_cases::maybe_rename_virtual_method(method_name) + } +} + +impl UtilityFunction { + pub fn from_json(function: &JsonUtilityFunction, ctx: &mut Context) -> Option { + if special_cases::is_utility_function_deleted(function, ctx) { + return None; + } + + let godot_method_name = function.name.clone(); + let rust_method_name = godot_method_name.clone(); // No change for now. + + let return_value = function + .return_type + .as_deref() + .map(JsonMethodReturn::from_type_no_meta); + + Some(Self { + common: FunctionCommon { + name: rust_method_name, + godot_name: godot_method_name, + parameters: FnParam::new_range(&function.arguments, ctx), + return_value: FnReturn::new(&return_value, ctx), + is_vararg: function.is_vararg, + is_private: false, + direction: FnDirection::Outbound { + hash: function.hash, + }, + }, + }) + } +} diff --git a/godot-codegen/src/domain_models.rs b/godot-codegen/src/domain_models.rs new file mode 100644 index 000000000..606c9234a --- /dev/null +++ b/godot-codegen/src/domain_models.rs @@ -0,0 +1,406 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Domain models + +use crate::context::Context; +use crate::json_models::{JsonMethodArg, JsonMethodReturn}; +use crate::util::{option_as_slice, safe_ident}; +use crate::{conv, RustTy, TyName}; +use proc_macro2::{Ident, TokenStream}; +use quote::quote; +use std::fmt; +use std::fmt::Display; + +pub struct BuiltinClass { + pub name: TyName, + pub enums: Vec, + pub operators: Vec, + pub methods: Vec, + pub constructors: Vec, + pub has_destructor: bool, +} + +pub struct Class { + pub name: String, + pub is_refcounted: bool, + pub is_instantiable: bool, + pub inherits: Option, + pub api_type: String, + pub constants: Vec, + pub enums: Vec, + pub methods: Vec, +} + +pub struct NativeStructure { + pub name: String, + pub format: String, +} + +pub struct Singleton { + pub name: String, + // Note: `type` currently has always same value as `name`, thus redundant + // type_: String, +} + +pub struct Enum { + pub name: String, + pub is_bitfield: bool, + pub values: Vec, +} + +pub struct BuiltinClassEnum { + pub name: String, + pub values: Vec, +} + +pub struct EnumConstant { + pub name: String, + + // i64 is common denominator for enum, bitfield and constant values. + // Note that values > i64::MAX will be implicitly wrapped, see https://github.com/not-fl3/nanoserde/issues/89. + pub value: i64, +} + +pub enum ConstValue { + I32(i32), + I64(i64), +} + +impl EnumConstant { + pub fn to_enum_ord(&self) -> i32 { + self.value.try_into().unwrap_or_else(|_| { + panic!( + "enum value {} = {} is out of range for i32, please report this", + self.name, self.value + ) + }) + } + + pub fn to_bitfield_ord(&self) -> u64 { + self.value.try_into().unwrap_or_else(|_| { + panic!( + "bitfield value {} = {} is negative, please report this", + self.name, self.value + ) + }) + } + + pub fn to_constant(&self) -> ConstValue { + if let Ok(value) = i32::try_from(self.value) { + ConstValue::I32(value) + } else { + ConstValue::I64(self.value) + } + } +} + +pub type ClassConstant = EnumConstant; + +/* +// Constants of builtin types have a string value like "Vector2(1, 1)", hence also a type field + +pub struct BuiltinConstant { + pub name: String, + #[nserde(rename = "type")] + pub type_: String, + pub value: String, +} +*/ + +pub struct Operator { + pub name: String, + pub right_type: Option, // null if unary + pub return_type: String, +} + +pub struct Constructor { + pub index: usize, + pub parameters: Vec, +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +/// Stuff that is in every of the "function" types. +pub struct FunctionCommon { + pub name: String, + pub godot_name: String, + pub parameters: Vec, + pub return_value: FnReturn, + pub is_vararg: bool, + pub is_private: bool, + pub direction: FnDirection, +} + +pub trait Function: Display { + // Required: + fn common(&self) -> &FunctionCommon; + fn qualifier(&self) -> FnQualifier; + fn surrounding_class(&self) -> Option<&TyName>; + + // Default: + fn name(&self) -> &str { + &self.common().name + } + fn godot_name(&self) -> &str { + &self.common().godot_name + } + fn params(&self) -> &[FnParam] { + &self.common().parameters + } + fn return_value(&self) -> &FnReturn { + &self.common().return_value + } + fn is_vararg(&self) -> bool { + self.common().is_vararg + } + fn is_private(&self) -> bool { + self.common().is_private + } + fn direction(&self) -> FnDirection { + self.common().direction + } + fn is_virtual(&self) -> bool { + matches!(self.direction(), FnDirection::Virtual) + } +} + +#[deprecated] +struct FnSignature<'a> { + function_name: &'a str, + surrounding_class: Option<&'a TyName>, // None if global function + is_private: bool, + is_virtual: bool, + is_vararg: bool, + qualifier: FnQualifier, + params: Vec, + return_value: FnReturn, +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +pub struct UtilityFunction { + pub(super) common: FunctionCommon, +} + +impl Function for UtilityFunction { + fn common(&self) -> &FunctionCommon { + &self.common + } + + fn qualifier(&self) -> FnQualifier { + FnQualifier::Global + } + + fn surrounding_class(&self) -> Option<&TyName> { + None + } +} + +impl Display for UtilityFunction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "utility function `{}`", self.name()) + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +pub struct BuiltinMethod { + // variant_type: + pub(super) common: FunctionCommon, + pub(super) qualifier: FnQualifier, + pub(super) surrounding_class: TyName, +} + +impl Function for BuiltinMethod { + fn common(&self) -> &FunctionCommon { + &self.common + } + + fn qualifier(&self) -> FnQualifier { + self.qualifier + } + + fn surrounding_class(&self) -> Option<&TyName> { + Some(&self.surrounding_class) + } +} + +impl fmt::Display for BuiltinMethod { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "builtin method `{}::{}`", + self.surrounding_class.rust_ty, + self.name(), + ) + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +pub struct ClassMethod { + pub(super) common: FunctionCommon, + pub(super) qualifier: FnQualifier, + pub(super) surrounding_class: TyName, +} + +impl ClassMethod {} + +impl Function for ClassMethod { + fn common(&self) -> &FunctionCommon { + &self.common + } + fn qualifier(&self) -> FnQualifier { + self.qualifier + } + fn surrounding_class(&self) -> Option<&TyName> { + Some(&self.surrounding_class) + } +} + +impl fmt::Display for ClassMethod { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "class method `{}::{}`", + self.surrounding_class.rust_ty, + self.name(), + ) + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +#[derive(Copy, Clone, Debug)] +pub enum FnDirection { + /// Godot -> Rust. + Virtual, + + /// Rust -> Godot. + Outbound { hash: i64 }, +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum FnQualifier { + Mut, // &mut self + Const, // &self + Static, // Self + Global, // (nothing) // TODO remove? +} + +impl FnQualifier { + pub fn from_const_static(is_const: bool, is_static: bool) -> FnQualifier { + if is_static { + assert!( + !is_const, + "const and static qualifiers are mutually exclusive" + ); + FnQualifier::Static + } else if is_const { + FnQualifier::Const + } else { + FnQualifier::Mut + } + } + + pub fn is_static_or_global(&self) -> bool { + matches!(self, Self::Static | Self::Global) + } +} +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +pub struct FnParam { + pub name: Ident, + pub type_: RustTy, + pub default_value: Option, +} + +impl FnParam { + pub fn new_range(method_args: &Option>, ctx: &mut Context) -> Vec { + option_as_slice(method_args) + .iter() + .map(|arg| Self::new(arg, ctx)) + .collect() + } + + pub fn new_range_no_defaults( + method_args: &Option>, + ctx: &mut Context, + ) -> Vec { + option_as_slice(method_args) + .iter() + .map(|arg| Self::new_no_defaults(arg, ctx)) + .collect() + } + + pub fn new(method_arg: &JsonMethodArg, ctx: &mut Context) -> FnParam { + let name = safe_ident(&method_arg.name); + let type_ = conv::to_rust_type(&method_arg.type_, method_arg.meta.as_ref(), ctx); + let default_value = method_arg + .default_value + .as_ref() + .map(|v| conv::to_rust_expr(v, &type_)); + + FnParam { + name, + type_, + default_value, + } + } + + pub fn new_no_defaults(method_arg: &JsonMethodArg, ctx: &mut Context) -> FnParam { + FnParam { + name: safe_ident(&method_arg.name), + type_: conv::to_rust_type(&method_arg.type_, method_arg.meta.as_ref(), ctx), + //type_: to_rust_type(&method_arg.type_, &method_arg.meta, ctx), + default_value: None, + } + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +pub struct FnReturn { + pub decl: TokenStream, + pub type_: Option, +} + +impl FnReturn { + pub fn new(return_value: &Option, ctx: &mut Context) -> Self { + if let Some(ret) = return_value { + let ty = conv::to_rust_type(&ret.type_, ret.meta.as_ref(), ctx); + + Self { + decl: ty.return_decl(), + type_: Some(ty), + } + } else { + Self { + decl: TokenStream::new(), + type_: None, + } + } + } + + pub fn type_tokens(&self) -> TokenStream { + match &self.type_ { + Some(RustTy::EngineClass { tokens, .. }) => { + quote! { Option<#tokens> } + } + Some(ty) => { + quote! { #ty } + } + _ => { + quote! { () } + } + } + } +} diff --git a/godot-codegen/src/lib.rs b/godot-codegen/src/lib.rs index 5084678ab..2ae7296e1 100644 --- a/godot-codegen/src/lib.rs +++ b/godot-codegen/src/lib.rs @@ -10,6 +10,8 @@ mod class_generator; mod codegen_special_cases; mod context; mod conv; +mod domain_mapping; +mod domain_models; mod interface_generator; mod json_models; mod special_cases; diff --git a/godot-codegen/src/special_cases.rs b/godot-codegen/src/special_cases.rs index a9f9b4083..3a8cf4789 100644 --- a/godot-codegen/src/special_cases.rs +++ b/godot-codegen/src/special_cases.rs @@ -23,13 +23,13 @@ #![allow(clippy::match_like_matches_macro)] // if there is only one rule -use crate::json_models::{JsonBuiltinMethod, JsonClassMethod}; +use crate::json_models::{JsonBuiltinMethod, JsonClassMethod, JsonUtilityFunction}; use crate::Context; use crate::{codegen_special_cases, TyName}; #[rustfmt::skip] pub(crate) fn is_class_method_deleted(class_name: &TyName, method: &JsonClassMethod, ctx: &mut Context) -> bool { - if codegen_special_cases::is_class_method_excluded(method, false, ctx){ + if codegen_special_cases::is_class_method_excluded(method, ctx){ return true; } @@ -245,6 +245,13 @@ pub(crate) fn is_builtin_type_scalar(name: &str) -> bool { name.chars().next().unwrap().is_ascii_lowercase() } +pub(crate) fn is_utility_function_deleted( + function: &JsonUtilityFunction, + ctx: &mut Context, +) -> bool { + codegen_special_cases::is_utility_function_excluded(function, ctx) +} + pub(crate) fn maybe_rename_class_method<'m>( class_name: &TyName, godot_method_name: &'m str, @@ -255,3 +262,13 @@ pub(crate) fn maybe_rename_class_method<'m>( _ => godot_method_name, } } + +// Maybe merge with above? +pub(crate) fn maybe_rename_virtual_method(rust_method_name: &str) -> &str { + // A few classes define a virtual method called "_init" (distinct from the constructor) + // -> rename those to avoid a name conflict in I* interface trait. + match rust_method_name { + "init" => "init_ext", + _ => rust_method_name, + } +} diff --git a/godot-codegen/src/util.rs b/godot-codegen/src/util.rs index 93d6abf13..a0c333b40 100644 --- a/godot-codegen/src/util.rs +++ b/godot-codegen/src/util.rs @@ -7,7 +7,6 @@ use crate::json_models::{ JsonBuiltinMethod, JsonClass, JsonClassConstant, JsonClassMethod, JsonConstValue, JsonEnum, - JsonUtilityFunction, }; use crate::{conv, RustTy, TyName}; @@ -181,8 +180,8 @@ pub(crate) fn make_builtin_method_ptr_name( ) } -pub(crate) fn make_utility_function_ptr_name(function: &JsonUtilityFunction) -> Ident { - safe_ident(&function.name) +pub(crate) fn make_utility_function_ptr_name(godot_function_name: &str) -> Ident { + safe_ident(godot_function_name) } #[cfg(since_api = "4.2")] diff --git a/godot-codegen/src/utilities_generator.rs b/godot-codegen/src/utilities_generator.rs index 876416573..8b76d0186 100644 --- a/godot-codegen/src/utilities_generator.rs +++ b/godot-codegen/src/utilities_generator.rs @@ -10,8 +10,9 @@ use std::path::Path; use quote::quote; use crate::class_generator::make_utility_function_definition; -use crate::{json_models::*, SubmitFn}; -use crate::{util, Context}; +use crate::domain_models::UtilityFunction; +use crate::json_models::*; +use crate::{util, Context, SubmitFn}; pub(crate) fn generate_utilities_file( api: &JsonExtensionApi, @@ -20,10 +21,9 @@ pub(crate) fn generate_utilities_file( submit_fn: &mut SubmitFn, ) { // note: category unused -> could be their own mod - let utility_fn_defs = api - .utility_functions - .iter() - .map(|utility_fn| make_utility_function_definition(utility_fn, ctx)); + let utility_fn_defs = api.utility_functions.iter().filter_map(|utility_fn| { + UtilityFunction::from_json(utility_fn, ctx).map(|f| make_utility_function_definition(&f)) + }); let imports = util::make_imports(); From 345554c981adf6a65c6a0396bbae504c99509957 Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Sun, 14 Jan 2024 17:21:25 +0100 Subject: [PATCH 04/10] Codegen: split domain and JSON models, part II (enums) --- godot-codegen/src/central_generator.rs | 9 +- godot-codegen/src/class_generator.rs | 26 ++-- godot-codegen/src/domain_mapping.rs | 97 ++++++++++++++- godot-codegen/src/domain_models.rs | 72 ++++++------ godot-codegen/src/json_models.rs | 22 ---- godot-codegen/src/lib.rs | 4 +- godot-codegen/src/util.rs | 157 +++++++++++++------------ 7 files changed, 239 insertions(+), 148 deletions(-) diff --git a/godot-codegen/src/central_generator.rs b/godot-codegen/src/central_generator.rs index dfedf8ede..84bc9d59c 100644 --- a/godot-codegen/src/central_generator.rs +++ b/godot-codegen/src/central_generator.rs @@ -11,6 +11,7 @@ use std::collections::HashMap; use std::hash::Hasher; use std::path::Path; +use crate::domain_models::Enum; use crate::json_models::*; use crate::util::{ make_builtin_method_ptr_name, make_class_method_ptr_name, option_as_slice, ClassCodegenLevel, @@ -822,13 +823,15 @@ fn make_central_items( .push(util::make_enumerator_ord_unsuffixed(op.to_enum_ord())); } - for enum_ in api.global_enums.iter() { + for json_enum in api.global_enums.iter() { // Skip those enums which are already explicitly handled - if matches!(enum_.name.as_str(), "Variant.Type" | "Variant.Operator") { + if matches!(json_enum.name.as_str(), "Variant.Type" | "Variant.Operator") { continue; } - let def = util::make_enum_definition(enum_, None); + let domain_enum = Enum::from_json(json_enum, None); + + let def = util::make_enum_definition(&domain_enum); result.global_enum_defs.push(def); } diff --git a/godot-codegen/src/class_generator.rs b/godot-codegen/src/class_generator.rs index 766e0551c..d8ec61f19 100644 --- a/godot-codegen/src/class_generator.rs +++ b/godot-codegen/src/class_generator.rs @@ -401,7 +401,13 @@ fn make_class(class: &JsonClass, class_name: &TyName, ctx: &mut Context) -> Gene } = make_methods(option_as_slice(&class.methods), class_name, &api_level, ctx); let enums = make_enums(option_as_slice(&class.enums), class_name, ctx); - let constants = make_constants(option_as_slice(&class.constants), class_name, ctx); + + let constants = option_as_slice(&class.constants) + .iter() + .map(ClassConstant::from_json) + .collect::>(); + + let constants = make_constants(&constants); let inherits_macro = format_ident!("inherits_transitive_{}", class_name.rust_ty); let (exportable_impl, exportable_macro_impl) = if ctx.is_exportable(class_name) { @@ -993,21 +999,19 @@ fn make_builtin_methods( FnDefinitions::expand(definitions) } -fn make_enums(enums: &[JsonEnum], class_name: &TyName, _ctx: &Context) -> TokenStream { - let definitions = enums - .iter() - .map(|e| util::make_enum_definition(e, Some(&class_name.godot_ty))); +fn make_enums(enums: &[JsonEnum], class_name: &TyName, _ctx: &mut Context) -> TokenStream { + let definitions = enums.iter().map(|json| { + let domain = Enum::from_json(json, Some(class_name)); + + util::make_enum_definition(&domain) + }); quote! { #( #definitions )* } } -fn make_constants( - constants: &[JsonClassConstant], - _class_name: &TyName, - _ctx: &Context, -) -> TokenStream { +fn make_constants(constants: &[ClassConstant]) -> TokenStream { let definitions = constants.iter().map(util::make_constant_definition); quote! { @@ -1064,7 +1068,7 @@ fn make_class_method_definition( let table_index = ctx.get_table_index(&MethodTableKey::ClassMethod { api_level: *api_level, class_ty: class_name.clone(), - method_name: method.name().to_string(), + method_name: method.godot_name().to_string(), }); let maybe_instance_id = if method.qualifier() == FnQualifier::Static { diff --git a/godot-codegen/src/domain_mapping.rs b/godot-codegen/src/domain_mapping.rs index 1cede139d..2f6423ee5 100644 --- a/godot-codegen/src/domain_mapping.rs +++ b/godot-codegen/src/domain_mapping.rs @@ -7,13 +7,19 @@ use crate::context::Context; use crate::domain_models::{ - BuiltinMethod, ClassMethod, FnDirection, FnParam, FnQualifier, FnReturn, FunctionCommon, - UtilityFunction, + BuiltinMethod, ClassConstant, ClassConstantValue, ClassMethod, Enum, Enumerator, + EnumeratorValue, FnDirection, FnParam, FnQualifier, FnReturn, FunctionCommon, UtilityFunction, }; use crate::json_models::{ - JsonBuiltinMethod, JsonClassMethod, JsonMethodReturn, JsonUtilityFunction, + JsonBuiltinMethod, JsonClassConstant, JsonClassMethod, JsonEnum, JsonEnumConstant, + JsonMethodReturn, JsonUtilityFunction, }; -use crate::{special_cases, TyName}; +use crate::util::ident; +use crate::{conv, special_cases, TyName}; +use proc_macro2::Ident; + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Functions impl BuiltinMethod { pub fn from_json( @@ -181,3 +187,86 @@ impl UtilityFunction { }) } } + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Enums + enumerator constants + +impl Enum { + pub fn from_json(json_enum: &JsonEnum, surrounding_class: Option<&TyName>) -> Self { + let godot_name = &json_enum.name; + let is_bitfield = json_enum.is_bitfield; + + let rust_enum_name = conv::make_enum_name_str(godot_name); + let rust_enumerator_names = { + let godot_enumerator_names = json_enum.values.iter().map(|e| e.name.as_str()).collect(); + let godot_class_name = surrounding_class.as_ref().map(|ty| ty.godot_ty.as_str()); + + conv::make_enumerator_names(godot_class_name, &rust_enum_name, godot_enumerator_names) + }; + + let enumerators = json_enum + .values + .iter() + .zip(rust_enumerator_names) + .map(|(json_constant, rust_name)| { + Enumerator::from_json(json_constant, rust_name, is_bitfield) + }) + .collect(); + + Self { + name: ident(&rust_enum_name), + godot_name: godot_name.clone(), + is_bitfield, + enumerators, + } + } +} + +impl Enumerator { + pub fn from_json(json: &JsonEnumConstant, rust_name: Ident, is_bitfield: bool) -> Self { + let value = if is_bitfield { + let ord = json.value.try_into().unwrap_or_else(|_| { + panic!( + "bitfield value {} = {} is negative; please report this", + json.name, json.value + ) + }); + + EnumeratorValue::Bitfield(ord) + } else { + let ord = json.value.try_into().unwrap_or_else(|_| { + panic!( + "enum value {} = {} is out of range for i32; please report this", + json.name, json.value + ) + }); + + EnumeratorValue::Enum(ord) + }; + + Self { + name: rust_name, + godot_name: json.name.clone(), + value, + } + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Constants + +impl ClassConstant { + pub fn from_json(json: &JsonClassConstant) -> Self { + // Godot types only use i32, but other extensions may have i64. Use smallest possible type. + let value = if let Ok(i32_value) = i32::try_from(json.value) { + ClassConstantValue::I32(i32_value) + } else { + ClassConstantValue::I64(json.value) + }; + + Self { + name: json.name.clone(), + value, + } + } +} diff --git a/godot-codegen/src/domain_models.rs b/godot-codegen/src/domain_models.rs index 606c9234a..d3ed4a0c2 100644 --- a/godot-codegen/src/domain_models.rs +++ b/godot-codegen/src/domain_models.rs @@ -5,6 +5,8 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +#![allow(dead_code)] // TODO remove when mapped + // ---------------------------------------------------------------------------------------------------------------------------------------------- // Domain models @@ -49,58 +51,60 @@ pub struct Singleton { } pub struct Enum { - pub name: String, + pub name: Ident, + pub godot_name: String, pub is_bitfield: bool, - pub values: Vec, + pub enumerators: Vec, } pub struct BuiltinClassEnum { pub name: String, - pub values: Vec, + pub values: Vec, } -pub struct EnumConstant { - pub name: String, +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Enumerators + +pub struct Enumerator { + pub name: Ident, + + pub godot_name: String, // i64 is common denominator for enum, bitfield and constant values. // Note that values > i64::MAX will be implicitly wrapped, see https://github.com/not-fl3/nanoserde/issues/89. - pub value: i64, + pub value: EnumeratorValue, } - -pub enum ConstValue { - I32(i32), - I64(i64), +pub enum EnumeratorValue { + Enum(i32), + Bitfield(u64), } -impl EnumConstant { - pub fn to_enum_ord(&self) -> i32 { - self.value.try_into().unwrap_or_else(|_| { - panic!( - "enum value {} = {} is out of range for i32, please report this", - self.name, self.value - ) - }) +impl EnumeratorValue { + pub fn to_i64(&self) -> i64 { + // Conversion is safe because i64 is used in the original JSON. + match self { + EnumeratorValue::Enum(i) => *i as i64, + EnumeratorValue::Bitfield(i) => *i as i64, + } } +} - pub fn to_bitfield_ord(&self) -> u64 { - self.value.try_into().unwrap_or_else(|_| { - panic!( - "bitfield value {} = {} is negative, please report this", - self.name, self.value - ) - }) - } +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Constants - pub fn to_constant(&self) -> ConstValue { - if let Ok(value) = i32::try_from(self.value) { - ConstValue::I32(value) - } else { - ConstValue::I64(self.value) - } - } +trait Constant { + fn name(&self) -> &str; } -pub type ClassConstant = EnumConstant; +pub struct ClassConstant { + pub name: String, + pub value: ClassConstantValue, +} + +pub enum ClassConstantValue { + I32(i32), + I64(i64), +} /* // Constants of builtin types have a string value like "Vector2(1, 1)", hence also a type field diff --git a/godot-codegen/src/json_models.rs b/godot-codegen/src/json_models.rs index e8e75cfe2..7d0760f06 100644 --- a/godot-codegen/src/json_models.rs +++ b/godot-codegen/src/json_models.rs @@ -123,11 +123,6 @@ pub struct JsonEnumConstant { pub value: i64, } -pub enum JsonConstValue { - I32(i32), - I64(i64), -} - impl JsonEnumConstant { pub fn to_enum_ord(&self) -> i32 { self.value.try_into().unwrap_or_else(|_| { @@ -137,23 +132,6 @@ impl JsonEnumConstant { ) }) } - - pub fn to_bitfield_ord(&self) -> u64 { - self.value.try_into().unwrap_or_else(|_| { - panic!( - "bitfield value {} = {} is negative, please report this", - self.name, self.value - ) - }) - } - - pub fn to_constant(&self) -> JsonConstValue { - if let Ok(value) = i32::try_from(self.value) { - JsonConstValue::I32(value) - } else { - JsonConstValue::I64(self.value) - } - } } pub type JsonClassConstant = JsonEnumConstant; diff --git a/godot-codegen/src/lib.rs b/godot-codegen/src/lib.rs index 2ae7296e1..ee60541d3 100644 --- a/godot-codegen/src/lib.rs +++ b/godot-codegen/src/lib.rs @@ -241,7 +241,7 @@ impl ToTokens for RustTy { /// Contains multiple naming conventions for types (classes, builtin classes, enums). // TODO(bromeon, 2023-09): see if it makes sense to unify this with TypeNames (which is mostly used in central generator) #[derive(Clone, Eq, PartialEq, Hash)] -pub(crate) struct TyName { +pub struct TyName { godot_ty: String, rust_ty: Ident, } @@ -276,7 +276,7 @@ impl ToTokens for TyName { // ---------------------------------------------------------------------------------------------------------------------------------------------- /// Contains naming conventions for modules. -pub(crate) struct ModName { +pub struct ModName { // godot_mod: String, rust_mod: Ident, } diff --git a/godot-codegen/src/util.rs b/godot-codegen/src/util.rs index a0c333b40..904fdf8c6 100644 --- a/godot-codegen/src/util.rs +++ b/godot-codegen/src/util.rs @@ -5,14 +5,13 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::json_models::{ - JsonBuiltinMethod, JsonClass, JsonClassConstant, JsonClassMethod, JsonConstValue, JsonEnum, -}; +use crate::json_models::{JsonBuiltinMethod, JsonClass, JsonClassConstant, JsonClassMethod}; use crate::{conv, RustTy, TyName}; use proc_macro2::{Ident, Literal, TokenStream}; use quote::{format_ident, quote}; +use crate::domain_models::{ClassConstant, ClassConstantValue, Enum, Enumerator, EnumeratorValue}; use std::fmt; #[derive(Clone, Eq, PartialEq, Debug)] @@ -228,71 +227,44 @@ pub fn get_api_level(class: &JsonClass) -> ClassCodegenLevel { } } -pub fn make_enum_definition(enum_: &JsonEnum, class_name: Option<&str>) -> TokenStream { +pub fn make_enum_definition(enum_: &Enum) -> TokenStream { // TODO enums which have unique ords could be represented as Rust enums // This would allow exhaustive matches (or at least auto-completed matches + #[non_exhaustive]). But even without #[non_exhaustive], // this might be a forward compatibility hazard, if Godot deprecates enumerators and adds new ones with existing ords. - let enum_name_str = conv::make_enum_name_str(&enum_.name); - let enum_name = ident(&enum_name_str); + let rust_enum_name = &enum_.name; // TODO remove once deprecated is removed. - let deprecated_enum_decl = if enum_name != enum_.name { - let deprecated_enum_name = ident(&enum_.name); - let msg = format!("Renamed to `{enum_name}`."); + let deprecated_enum_decl = if rust_enum_name != enum_.godot_name.as_str() { + let deprecated_enum_name = ident(&enum_.godot_name); + let msg = format!("Renamed to `{rust_enum_name}`."); quote! { #[deprecated = #msg] - pub type #deprecated_enum_name = #enum_name; + pub type #deprecated_enum_name = #rust_enum_name; } } else { TokenStream::new() }; - let godot_enumerators = &enum_.values; - let mut enumerators = Vec::with_capacity(godot_enumerators.len()); + let rust_enumerators = &enum_.enumerators; + + let mut enumerators = Vec::with_capacity(rust_enumerators.len()); let mut deprecated_enumerators = Vec::new(); // This is only used for enum ords (i32), not bitfield flags (u64). - let mut unique_ords = Vec::with_capacity(godot_enumerators.len()); - - let rust_enumerator_names = { - let original_enumerator_names = enum_.values.iter().map(|e| e.name.as_str()).collect(); - conv::make_enumerator_names(class_name, &enum_name_str, original_enumerator_names) - }; - - for (enumerator, enumerator_name) in godot_enumerators.iter().zip(rust_enumerator_names) { - let ordinal_lit = if enum_.is_bitfield { - let bitfield_ord: u64 = enumerator.to_bitfield_ord(); - make_bitfield_flag_ord(bitfield_ord) - } else { - let enum_ord: i32 = enumerator.to_enum_ord(); - unique_ords.push(enum_ord); - make_enumerator_ord(enum_ord) - }; - - let godot_name_str = &enumerator.name; - let doc_alias = if enumerator_name == godot_name_str { - TokenStream::new() - } else { - // Godot and Rust names differ -> add doc alias for searchability. - let msg = format!("Renamed to `{}`.", enumerator_name); - let deprecated_ident = ident(godot_name_str); + let mut unique_ords = Vec::with_capacity(rust_enumerators.len()); - // For now, list previous identifier at the end. - deprecated_enumerators.push(quote! { - #[deprecated = #msg] - pub const #deprecated_ident: Self = Self { ord: #ordinal_lit }; - }); + for enumerator in rust_enumerators.iter() { + let (def, deprecated_def) = make_enumerator_definition(enumerator); + enumerators.push(def); - quote! { - #[doc(alias = #godot_name_str)] - } - }; + if let Some(def) = deprecated_def { + deprecated_enumerators.push(def); + } - enumerators.push(quote! { - #doc_alias - pub const #enumerator_name: Self = Self { ord: #ordinal_lit }; - }); + if let EnumeratorValue::Enum(ord) = enumerator.value { + unique_ords.push(ord); + } } enumerators.extend(deprecated_enumerators); @@ -312,7 +284,7 @@ pub fn make_enum_definition(enum_: &JsonEnum, class_name: Option<&str>) -> Token // 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 { + impl crate::obj::IndexEnum for #rust_enum_name { const ENUMERATOR_COUNT: usize = #enum_max; } } @@ -331,7 +303,7 @@ pub fn make_enum_definition(enum_: &JsonEnum, class_name: Option<&str>) -> Token // impl #enum_name { // pub const UNSET: Self = Self { ord: 0 }; // } - impl std::ops::BitOr for #enum_name { + impl std::ops::BitOr for #rust_enum_name { type Output = Self; fn bitor(self, rhs: Self) -> Self::Output { @@ -342,7 +314,7 @@ pub fn make_enum_definition(enum_: &JsonEnum, class_name: Option<&str>) -> Token enum_ord_type = quote! { u64 }; self_as_trait = quote! { }; engine_impl = quote! { - impl crate::obj::EngineBitfield for #enum_name { + impl crate::obj::EngineBitfield for #rust_enum_name { fn try_from_ord(ord: u64) -> Option { Some(Self { ord }) } @@ -361,7 +333,7 @@ pub fn make_enum_definition(enum_: &JsonEnum, class_name: Option<&str>) -> Token enum_ord_type = quote! { i32 }; self_as_trait = quote! { }; engine_impl = quote! { - impl crate::obj::EngineEnum for #enum_name { + impl crate::obj::EngineEnum for #rust_enum_name { fn try_from_ord(ord: i32) -> Option { match ord { #( ord @ #unique_ords )|* => Some(Self { ord }), @@ -384,10 +356,10 @@ pub fn make_enum_definition(enum_: &JsonEnum, class_name: Option<&str>) -> Token #[repr(transparent)] #[derive(#( #derives ),*)] - pub struct #enum_name { + pub struct #rust_enum_name { ord: #enum_ord_type } - impl #enum_name { + impl #rust_enum_name { #( #enumerators )* @@ -397,17 +369,17 @@ pub fn make_enum_definition(enum_: &JsonEnum, class_name: Option<&str>) -> Token #index_enum_impl #bitfield_ops - impl crate::builtin::meta::GodotConvert for #enum_name { + impl crate::builtin::meta::GodotConvert for #rust_enum_name { type Via = #enum_ord_type; } - impl crate::builtin::meta::ToGodot for #enum_name { + impl crate::builtin::meta::ToGodot for #rust_enum_name { fn to_godot(&self) -> Self::Via { #self_as_trait::ord(*self) } } - impl crate::builtin::meta::FromGodot for #enum_name { + impl crate::builtin::meta::FromGodot for #rust_enum_name { fn try_from_godot(via: Self::Via) -> std::result::Result { #self_as_trait::try_from_ord(via) .ok_or_else(|| crate::builtin::meta::FromGodotError::InvalidEnum.into_error(via)) @@ -416,17 +388,56 @@ pub fn make_enum_definition(enum_: &JsonEnum, class_name: Option<&str>) -> Token } } -pub fn make_constant_definition(constant: &JsonClassConstant) -> TokenStream { - let name = ident(&constant.name); - let vis = if constant.name.starts_with("NOTIFICATION_") { +fn make_enumerator_definition(enumerator: &Enumerator) -> (TokenStream, Option) { + let ordinal_lit = match enumerator.value { + EnumeratorValue::Enum(ord) => make_enumerator_ord(ord), + EnumeratorValue::Bitfield(ord) => make_bitfield_flag_ord(ord), + }; + + let rust_ident = &enumerator.name; + let godot_name_str = &enumerator.godot_name; + + let (doc_alias, deprecated_def); + + if rust_ident == godot_name_str { + deprecated_def = None; + doc_alias = TokenStream::new(); + } else { + // Godot and Rust names differ -> add doc alias for searchability. + let msg = format!("Renamed to `{rust_ident}`."); + let deprecated_ident = ident(godot_name_str); + + // For now, list previous identifier at the end. + deprecated_def = Some(quote! { + #[deprecated = #msg] + pub const #deprecated_ident: Self = Self { ord: #ordinal_lit }; + }); + + doc_alias = quote! { + #[doc(alias = #godot_name_str)] + }; + }; + + let def = quote! { + #doc_alias + pub const #rust_ident: Self = Self { ord: #ordinal_lit }; + }; + + (def, deprecated_def) +} + +pub fn make_constant_definition(constant: &ClassConstant) -> TokenStream { + let constant_name = &constant.name; + let ident = ident(constant_name); + let vis = if constant_name.starts_with("NOTIFICATION_") { quote! { pub(crate) } } else { quote! { pub } }; - match constant.to_constant() { - JsonConstValue::I32(value) => quote! { #vis const #name: i32 = #value; }, - JsonConstValue::I64(value) => quote! { #vis const #name: i64 = #value; }, + match constant.value { + ClassConstantValue::I32(value) => quote! { #vis const #ident: i32 = #value; }, + ClassConstantValue::I64(value) => quote! { #vis const #ident: i64 = #value; }, } } @@ -441,15 +452,15 @@ pub fn try_to_notification(constant: &JsonClassConstant) -> Option { /// If an enum qualifies as "indexable" (can be used as array index), returns the number of possible values. /// /// See `godot::obj::IndexEnum` for what constitutes "indexable". -fn try_count_index_enum(enum_: &JsonEnum) -> Option { - if enum_.is_bitfield || enum_.values.is_empty() { +fn try_count_index_enum(enum_: &Enum) -> Option { + if enum_.is_bitfield || enum_.enumerators.is_empty() { return None; } // Sort by ordinal value. Allocates for every enum in the JSON, but should be OK (most enums are indexable). let enumerators = { - let mut enumerators = enum_.values.clone(); - enumerators.sort_by_key(|v| v.value); + let mut enumerators = enum_.enumerators.iter().collect::>(); + enumerators.sort_by_key(|v| v.value.to_i64()); enumerators }; @@ -457,21 +468,23 @@ fn try_count_index_enum(enum_: &JsonEnum) -> Option { // The presence of "MAX" indicates that Godot devs intended the enum to be used as an index. // The condition is not strictly necessary and could theoretically be relaxed; there would need to be concrete use cases though. let last = enumerators.last().unwrap(); // safe because of is_empty check above. - if !last.name.ends_with("_MAX") { + if !last.godot_name.ends_with("_MAX") { return None; } // The rest of the enumerators must be contiguous and without gaps (duplicates are OK). let mut last_value = 0; for enumerator in enumerators.iter() { - if last_value != enumerator.value && last_value + 1 != enumerator.value { + let e_value = enumerator.value.to_i64(); + + if last_value != e_value && last_value + 1 != e_value { return None; } - last_value = enumerator.value; + last_value = e_value; } - Some(last.value as usize) + Some(last_value as usize) } pub fn ident(s: &str) -> Ident { From 4570197d2d180872138c2a48957b0c1bba969637 Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Sun, 14 Jan 2024 20:42:42 +0100 Subject: [PATCH 05/10] Codegen: migrate builtin constructors/operators to domain models --- godot-codegen/src/central_generator.rs | 152 +++++++++++++------------ godot-codegen/src/domain_mapping.rs | 29 ++++- godot-codegen/src/domain_models.rs | 21 +++- godot-codegen/src/util.rs | 13 +-- 4 files changed, 125 insertions(+), 90 deletions(-) diff --git a/godot-codegen/src/central_generator.rs b/godot-codegen/src/central_generator.rs index 84bc9d59c..8b58b7e0d 100644 --- a/godot-codegen/src/central_generator.rs +++ b/godot-codegen/src/central_generator.rs @@ -11,7 +11,7 @@ use std::collections::HashMap; use std::hash::Hasher; use std::path::Path; -use crate::domain_models::Enum; +use crate::domain_models::{BuiltinMethod, Constructor, Enum, Function, Operator}; use crate::json_models::*; use crate::util::{ make_builtin_method_ptr_name, make_class_method_ptr_name, option_as_slice, ClassCodegenLevel, @@ -867,11 +867,23 @@ fn make_builtin_lifecycle_table(builtin_types: &BuiltinTypeMap) -> TokenStream { // Note: NIL is not part of this iteration, it will be added manually for ty in builtin_types.ordered() { + let empty = vec![]; + let constructors = ty.constructors.unwrap_or(&empty); + + let empty = vec![]; + let operators = ty.operators.unwrap_or(&empty); + let (decls, inits) = make_variant_fns( &ty.type_names, ty.has_destructor, - ty.constructors, - ty.operators, + &constructors + .iter() + .map(|ctor| Constructor::from_json(ctor)) + .collect::>(), + &operators + .iter() + .map(|op| Operator::from_json(op)) + .collect::>(), &builtin_types.map, ); @@ -1088,35 +1100,38 @@ fn populate_builtin_methods( let mut method_inits = vec![]; // Skip types such as int, float, bool. - // TODO separate BuiltinName + TyName needed? - if special_cases::is_builtin_type_deleted(&TyName::from_godot(&builtin_class.name)) { + // TODO separate BuiltinName + TyName needed? Of course not :) + let builtin_ty = TyName::from_godot(&builtin_class.name); + if special_cases::is_builtin_type_deleted(&builtin_ty) { return; } for method in option_as_slice(&builtin_class.methods) { - let builtin_ty = TyName::from_godot(&builtin_class.name); - if special_cases::is_builtin_method_deleted(&builtin_ty, method) { + // TODO temporary + let inner_builtin_name = TyName::from_godot(&format!("Inner{}", builtin_class.name)); + let Some(method) = BuiltinMethod::from_json(method, &builtin_ty, &inner_builtin_name, ctx) + else { continue; - } + }; let index = ctx.get_table_index(&MethodTableKey::BuiltinMethod { builtin_ty: builtin_ty.clone(), - method_name: method.name.clone(), + method_name: method.name().to_string(), }); - let method_init = make_builtin_method_init(method, builtin_name, index); + let method_init = make_builtin_method_init(&method, builtin_name, index); method_inits.push(MethodInit { method_init, index }); table.method_count += 1; // If requested, add a named accessor for this method. - if special_cases::is_named_accessor_in_table(&builtin_ty, &method.name) { + if special_cases::is_named_accessor_in_table(&builtin_ty, &method.name()) { let variant_type = &builtin_name.sys_variant_type; let variant_type_str = &builtin_name.json_builtin_name; - let method_name_str = method.name.as_str(); - let hash = method.hash.expect("hash present"); + let method_name_str = method.name(); + let hash = method.hash(); table.named_accessors.push(AccessorMethod { - name: make_builtin_method_ptr_name(&builtin_ty, method), + name: make_builtin_method_ptr_name(&builtin_ty, &method), index, lazy_key: quote! { crate::lazy_keys::BuiltinMethodKey { @@ -1167,21 +1182,16 @@ fn make_class_method_init( } fn make_builtin_method_init( - method: &JsonBuiltinMethod, + method: &BuiltinMethod, type_name: &BuiltinName, index: usize, ) -> TokenStream { - let method_name_str = method.name.as_str(); + let method_name_str = method.name(); let variant_type = &type_name.sys_variant_type; let variant_type_str = &type_name.json_builtin_name; - let hash = method.hash.unwrap_or_else(|| { - panic!( - "builtin method has no hash: {}::{}", - variant_type_str, method_name_str - ) - }); + let hash = method.hash(); // Could reuse lazy key, but less code like this -> faster parsing. quote! { @@ -1320,8 +1330,8 @@ fn make_opaque_type(name: &str, size: usize) -> TokenStream { fn make_variant_fns( type_names: &BuiltinName, has_destructor: bool, - constructors: Option<&Vec>, - operators: Option<&Vec>, + constructors: &[Constructor], + operators: &[Operator], builtin_types: &HashMap, ) -> (TokenStream, TokenStream) { let (construct_decls, construct_inits) = @@ -1373,17 +1383,12 @@ fn make_variant_fns( fn make_construct_fns( type_names: &BuiltinName, - constructors: Option<&Vec>, + constructors: &[Constructor], builtin_types: &HashMap, ) -> (TokenStream, TokenStream) { - let constructors = match constructors { - Some(c) => c, - None => return (TokenStream::new(), TokenStream::new()), - }; - - if is_trivial(type_names) { + if constructors.is_empty() || is_trivial(type_names) { return (TokenStream::new(), TokenStream::new()); - } + }; // Constructor vec layout: // [0]: default constructor @@ -1396,18 +1401,15 @@ fn make_construct_fns( assert_eq!(i, c.index); } - assert!(constructors[0].arguments.is_none()); + assert!( + constructors[0].raw_parameters.is_empty(), + "default constructor at index 0 must have no parameters" + ); - if let Some(args) = &constructors[1].arguments { - assert_eq!(args.len(), 1); - assert_eq!(args[0].name, "from"); - assert_eq!(args[0].type_, type_names.json_builtin_name); - } else { - panic!( - "type {}: no constructor args found for copy constructor", - type_names.json_builtin_name - ); - } + let args = &constructors[1].raw_parameters; + assert_eq!(args.len(), 1); + assert_eq!(args[0].name, "from"); + assert_eq!(args[0].type_, type_names.json_builtin_name); let construct_default = format_ident!("{}_construct_default", type_names.snake_case); let construct_copy = format_ident!("{}_construct_copy", type_names.snake_case); @@ -1452,7 +1454,7 @@ fn make_construct_fns( /// Lists special cases for useful constructors fn make_extra_constructors( type_names: &BuiltinName, - constructors: &Vec, + constructors: &[Constructor], builtin_types: &HashMap, ) -> (Vec, Vec) { let mut extra_decls = Vec::with_capacity(constructors.len() - 2); @@ -1460,36 +1462,40 @@ fn make_extra_constructors( let variant_type = &type_names.sys_variant_type; for (i, ctor) in constructors.iter().enumerate().skip(2) { - if let Some(args) = &ctor.arguments { - let type_name = &type_names.snake_case; - let construct_custom = if args.len() == 1 && args[0].name == "from" { - // Conversion constructor is named according to the source type - // String(NodePath from) => string_from_node_path - let arg_type = &builtin_types[&args[0].type_].type_names.snake_case; - format_ident!("{type_name}_from_{arg_type}") - } else { - // Type-specific constructor is named according to the argument names - // Vector3(float x, float y, float z) => vector3_from_x_y_z - let mut arg_names = args - .iter() - .fold(String::new(), |acc, arg| acc + &arg.name + "_"); - arg_names.pop(); // remove trailing '_' - format_ident!("{type_name}_from_{arg_names}") - }; + let args = &ctor.raw_parameters; + assert!( + !args.is_empty(), + "custom constructors must have at least 1 parameter" + ); - let construct_custom_str = construct_custom.to_string(); - extra_decls.push(quote! { + let type_name = &type_names.snake_case; + let construct_custom = if args.len() == 1 && args[0].name == "from" { + // Conversion constructor is named according to the source type + // String(NodePath from) => string_from_node_path + let arg_type = &builtin_types[&args[0].type_].type_names.snake_case; + format_ident!("{type_name}_from_{arg_type}") + } else { + // Type-specific constructor is named according to the argument names + // Vector3(float x, float y, float z) => vector3_from_x_y_z + let mut arg_names = args + .iter() + .fold(String::new(), |acc, arg| acc + &arg.name + "_"); + arg_names.pop(); // remove trailing '_' + format_ident!("{type_name}_from_{arg_names}") + }; + + let construct_custom_str = construct_custom.to_string(); + extra_decls.push(quote! { pub #construct_custom: unsafe extern "C" fn(GDExtensionUninitializedTypePtr, *const GDExtensionConstTypePtr), }); - let i = i as i32; - extra_inits.push(quote! { - #construct_custom: { - let fptr = unsafe { get_construct_fn(crate::#variant_type, #i) }; - crate::validate_builtin_lifecycle(fptr, #construct_custom_str) - }, - }); - } + let i = i as i32; + extra_inits.push(quote! { + #construct_custom: { + let fptr = unsafe { get_construct_fn(crate::#variant_type, #i) }; + crate::validate_builtin_lifecycle(fptr, #construct_custom_str) + }, + }); } (extra_decls, extra_inits) @@ -1520,12 +1526,12 @@ fn make_destroy_fns(type_names: &BuiltinName, has_destructor: bool) -> (TokenStr fn make_operator_fns( type_names: &BuiltinName, - operators: Option<&Vec>, + operators: &[Operator], json_name: &str, sys_name: &str, ) -> (TokenStream, TokenStream) { - if operators.is_none() - || !operators.unwrap().iter().any(|op| op.name == json_name) + if operators.is_empty() + || !operators.iter().any(|op| op.name == json_name) || is_trivial(type_names) { return (TokenStream::new(), TokenStream::new()); diff --git a/godot-codegen/src/domain_mapping.rs b/godot-codegen/src/domain_mapping.rs index 2f6423ee5..67262c2b3 100644 --- a/godot-codegen/src/domain_mapping.rs +++ b/godot-codegen/src/domain_mapping.rs @@ -7,17 +7,38 @@ use crate::context::Context; use crate::domain_models::{ - BuiltinMethod, ClassConstant, ClassConstantValue, ClassMethod, Enum, Enumerator, - EnumeratorValue, FnDirection, FnParam, FnQualifier, FnReturn, FunctionCommon, UtilityFunction, + BuiltinMethod, ClassConstant, ClassConstantValue, ClassMethod, Constructor, Enum, Enumerator, + EnumeratorValue, FnDirection, FnParam, FnQualifier, FnReturn, FunctionCommon, Operator, + UtilityFunction, }; use crate::json_models::{ - JsonBuiltinMethod, JsonClassConstant, JsonClassMethod, JsonEnum, JsonEnumConstant, - JsonMethodReturn, JsonUtilityFunction, + JsonBuiltinMethod, JsonClassConstant, JsonClassMethod, JsonConstructor, JsonEnum, + JsonEnumConstant, JsonMethodReturn, JsonOperator, JsonUtilityFunction, }; use crate::util::ident; use crate::{conv, special_cases, TyName}; use proc_macro2::Ident; +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Constructors, operators + +impl Constructor { + pub fn from_json(json: &JsonConstructor) -> Self { + Self { + index: json.index, // TODO use enum for Default/Copy/Other(index) + raw_parameters: json.arguments.as_ref().map_or(vec![], |vec| vec.clone()), + } + } +} + +impl Operator { + pub fn from_json(json: &JsonOperator) -> Self { + Self { + name: json.name.clone(), + } + } +} + // ---------------------------------------------------------------------------------------------------------------------------------------------- // Functions diff --git a/godot-codegen/src/domain_models.rs b/godot-codegen/src/domain_models.rs index d3ed4a0c2..daa1f20dc 100644 --- a/godot-codegen/src/domain_models.rs +++ b/godot-codegen/src/domain_models.rs @@ -17,7 +17,6 @@ use crate::{conv, RustTy, TyName}; use proc_macro2::{Ident, TokenStream}; use quote::quote; use std::fmt; -use std::fmt::Display; pub struct BuiltinClass { pub name: TyName, @@ -119,13 +118,14 @@ pub struct BuiltinConstant { pub struct Operator { pub name: String, - pub right_type: Option, // null if unary - pub return_type: String, + //pub right_type: Option, // null if unary + //pub return_type: String, } pub struct Constructor { pub index: usize, - pub parameters: Vec, + pub raw_parameters: Vec, + // pub parameters: Vec, } // ---------------------------------------------------------------------------------------------------------------------------------------------- @@ -141,7 +141,7 @@ pub struct FunctionCommon { pub direction: FnDirection, } -pub trait Function: Display { +pub trait Function: fmt::Display { // Required: fn common(&self) -> &FunctionCommon; fn qualifier(&self) -> FnQualifier; @@ -206,7 +206,7 @@ impl Function for UtilityFunction { } } -impl Display for UtilityFunction { +impl fmt::Display for UtilityFunction { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "utility function `{}`", self.name()) } @@ -221,6 +221,15 @@ pub struct BuiltinMethod { pub(super) surrounding_class: TyName, } +impl BuiltinMethod { + pub fn hash(&self) -> i64 { + match self.direction() { + FnDirection::Virtual => unreachable!("builtin method cannot be virtual"), + FnDirection::Outbound { hash } => hash, + } + } +} + impl Function for BuiltinMethod { fn common(&self) -> &FunctionCommon { &self.common diff --git a/godot-codegen/src/util.rs b/godot-codegen/src/util.rs index 904fdf8c6..fec556d48 100644 --- a/godot-codegen/src/util.rs +++ b/godot-codegen/src/util.rs @@ -5,13 +5,15 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::json_models::{JsonBuiltinMethod, JsonClass, JsonClassConstant, JsonClassMethod}; +use crate::json_models::{JsonClass, JsonClassConstant, JsonClassMethod}; use crate::{conv, RustTy, TyName}; use proc_macro2::{Ident, Literal, TokenStream}; use quote::{format_ident, quote}; -use crate::domain_models::{ClassConstant, ClassConstantValue, Enum, Enumerator, EnumeratorValue}; +use crate::domain_models::{ + BuiltinMethod, ClassConstant, ClassConstantValue, Enum, Enumerator, EnumeratorValue, Function, +}; use std::fmt; #[derive(Clone, Eq, PartialEq, Debug)] @@ -168,14 +170,11 @@ pub(crate) fn make_class_method_ptr_name(class_ty: &TyName, method: &JsonClassMe ) } -pub(crate) fn make_builtin_method_ptr_name( - builtin_ty: &TyName, - method: &JsonBuiltinMethod, -) -> Ident { +pub(crate) fn make_builtin_method_ptr_name(builtin_ty: &TyName, method: &BuiltinMethod) -> Ident { format_ident!( "{}__{}", conv::to_snake_case(&builtin_ty.godot_ty), - method.name + method.name() ) } From 7496d31db8a1bc4d2c0d8c8ee32cd4d615489082 Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Sun, 14 Jan 2024 21:04:51 +0100 Subject: [PATCH 06/10] Codegen: refactor class/builtins with new domain models --- godot-codegen/src/central_generator.rs | 494 +++++++-------------- godot-codegen/src/class_generator.rs | 235 ++++------ godot-codegen/src/codegen_special_cases.rs | 6 +- godot-codegen/src/context.rs | 2 +- godot-codegen/src/domain_mapping.rs | 294 +++++++++++- godot-codegen/src/domain_models.rs | 149 ++++++- godot-codegen/src/lib.rs | 40 +- godot-codegen/src/special_cases.rs | 7 +- godot-codegen/src/util.rs | 6 - 9 files changed, 680 insertions(+), 553 deletions(-) diff --git a/godot-codegen/src/central_generator.rs b/godot-codegen/src/central_generator.rs index 8b58b7e0d..11e1cd741 100644 --- a/godot-codegen/src/central_generator.rs +++ b/godot-codegen/src/central_generator.rs @@ -7,11 +7,12 @@ use proc_macro2::{Ident, Literal, TokenStream}; use quote::{format_ident, quote, ToTokens, TokenStreamExt}; -use std::collections::HashMap; -use std::hash::Hasher; use std::path::Path; -use crate::domain_models::{BuiltinMethod, Constructor, Enum, Function, Operator}; +use crate::domain_models::{ + BuiltinMethod, BuiltinVariant, ClassLike, Constructor, Enum, Enumerator, ExtensionApi, + Function, Operator, +}; use crate::json_models::*; use crate::util::{ make_builtin_method_ptr_name, make_class_method_ptr_name, option_as_slice, ClassCodegenLevel, @@ -117,85 +118,15 @@ struct AccessorMethod { // ---------------------------------------------------------------------------------------------------------------------------------------------- -pub struct BuiltinName { - /// Name in JSON: "int" or "PackedVector2Array" - pub json_builtin_name: String, - - /// "packed_vector2_array" - pub snake_case: String, - - /// "PACKED_VECTOR2_ARRAY" - //pub shout_case: String, - - /// GDEXTENSION_VARIANT_TYPE_PACKED_VECTOR2_ARRAY - pub sys_variant_type: Ident, -} - -impl Eq for BuiltinName {} - -impl PartialEq for BuiltinName { - fn eq(&self, other: &Self) -> bool { - self.json_builtin_name == other.json_builtin_name - } -} - -impl std::hash::Hash for BuiltinName { - fn hash(&self, state: &mut H) { - self.json_builtin_name.hash(state); - } -} - -// ---------------------------------------------------------------------------------------------------------------------------------------------- - -/// Allows collecting all builtin TypeNames before generating methods -pub(crate) struct BuiltinTypeInfo<'a> { - pub variant_type_ord: i32, - pub type_names: BuiltinName, - - /// If `variant_get_ptr_destructor` returns a non-null function pointer for this type. - /// List is directly sourced from extension_api.json (information would also be in variant_destruct.cpp). - pub has_destructor: bool, - pub constructors: Option<&'a Vec>, - pub operators: Option<&'a Vec>, -} - -// ---------------------------------------------------------------------------------------------------------------------------------------------- - -pub(crate) struct BuiltinTypeMap<'a> { - map: HashMap>, -} - -impl<'a> BuiltinTypeMap<'a> { - pub fn load(api: &'a JsonExtensionApi) -> Self { - Self { - map: collect_builtin_types(api), - } - } - - /// Returns an iterator over the builtin types, ordered by `VariantType` value. - fn ordered(&self) -> impl Iterator> { - let mut ordered: Vec<_> = self.map.values().collect(); - ordered.sort_by_key(|info| info.variant_type_ord); - ordered.into_iter() - } - - fn count(&self) -> usize { - self.map.len() - } -} - -// ---------------------------------------------------------------------------------------------------------------------------------------------- - pub(crate) fn generate_sys_central_file( - api: &JsonExtensionApi, + json_api: &JsonExtensionApi, + api: &ExtensionApi, ctx: &mut Context, - build_config: [&str; 2], sys_gen_path: &Path, submit_fn: &mut SubmitFn, ) { - let builtin_types = BuiltinTypeMap::load(api); - let central_items = make_central_items(api, build_config, builtin_types, ctx); - let sys_code = make_sys_code(¢ral_items); + let central_items = make_central_items(json_api, api, ctx); + let sys_code = make_sys_code(central_items); submit_fn(sys_gen_path.join("central.rs"), sys_code); } @@ -334,7 +265,6 @@ fn make_method_table(info: IndexedMethodTable) -> TokenStream { method_count, "number of methods does not match count" ); - // method_inits.sort_by_key(|init| init.index); if let Some(last) = method_init_groups.last() { assert_eq!( @@ -491,28 +421,26 @@ fn make_method_table(info: IndexedMethodTable) -> TokenStream { } pub(crate) fn generate_sys_builtin_methods_file( - api: &JsonExtensionApi, - builtin_types: &BuiltinTypeMap, + api: &ExtensionApi, sys_gen_path: &Path, ctx: &mut Context, submit_fn: &mut SubmitFn, ) { - let code = make_builtin_method_table(api, builtin_types, ctx); + let code = make_builtin_method_table(api, ctx); submit_fn(sys_gen_path.join("table_builtins.rs"), code); } pub(crate) fn generate_sys_builtin_lifecycle_file( - builtin_types: &BuiltinTypeMap, + api: &ExtensionApi, sys_gen_path: &Path, submit_fn: &mut SubmitFn, ) { - // TODO merge this and the one in central.rs, to only collect once - let code = make_builtin_lifecycle_table(builtin_types); + let code = make_builtin_lifecycle_table(api); submit_fn(sys_gen_path.join("table_builtins_lifecycle.rs"), code); } pub(crate) fn generate_core_mod_file(gen_path: &Path, submit_fn: &mut SubmitFn) { - // When invoked by another crate during unit-test (not integration test), don't run generator + // When invoked by another crate during unit-test (not integration test), don't run generator. let code = quote! { pub mod central; pub mod classes; @@ -525,20 +453,19 @@ pub(crate) fn generate_core_mod_file(gen_path: &Path, submit_fn: &mut SubmitFn) } pub(crate) fn generate_core_central_file( - api: &JsonExtensionApi, + json_api: &JsonExtensionApi, + api: &ExtensionApi, ctx: &mut Context, - build_config: [&str; 2], gen_path: &Path, submit_fn: &mut SubmitFn, ) { - let builtin_types = BuiltinTypeMap::load(api); - let central_items = make_central_items(api, build_config, builtin_types, ctx); + let central_items = make_central_items(json_api, api, ctx); let core_code = make_core_code(¢ral_items); submit_fn(gen_path.join("central.rs"), core_code); } -fn make_sys_code(central_items: &CentralItems) -> TokenStream { +fn make_sys_code(central_items: CentralItems) -> TokenStream { let CentralItems { opaque_types, variant_ty_enumerators_pascal, @@ -549,7 +476,7 @@ fn make_sys_code(central_items: &CentralItems) -> TokenStream { .. } = central_items; - let build_config_struct = make_build_config(godot_version); + let build_config_struct = make_build_config(&godot_version); let [opaque_32bit, opaque_64bit] = opaque_types; quote! { @@ -583,7 +510,7 @@ fn make_sys_code(central_items: &CentralItems) -> TokenStream { impl VariantType { #[doc(hidden)] pub fn from_sys(enumerator: GDExtensionVariantType) -> Self { - // Annoying, but only stable alternative is transmute(), which dictates enum size + // Annoying, but only stable alternative is transmute(), which dictates enum size. match enumerator { 0 => Self::Nil, #( @@ -712,7 +639,7 @@ fn make_core_code(central_items: &CentralItems) -> TokenStream { // TODO impl Clone, Debug, PartialEq, PartialOrd, Hash for VariantDispatch // TODO could use try_to().unwrap_unchecked(), since type is already verified. Also directly overload from_variant(). - // But this requires that all the variant types support this + // But this requires that all the variant types support this. quote! { use crate::builtin::*; use crate::engine::Object; @@ -755,15 +682,14 @@ fn make_core_code(central_items: &CentralItems) -> TokenStream { } fn make_central_items( - api: &JsonExtensionApi, - build_config: [&str; 2], - builtin_types: BuiltinTypeMap, + json_api: &JsonExtensionApi, + api: &ExtensionApi, ctx: &mut Context, ) -> CentralItems { let mut opaque_types = [Vec::new(), Vec::new()]; - for class in &api.builtin_class_sizes { + for class in &json_api.builtin_class_sizes { for i in 0..2 { - if class.build_configuration == build_config[i] { + if class.build_configuration == api.build_config[i] { for JsonClassSize { name, size } in &class.sizes { opaque_types[i].push(make_opaque_type(name, *size)); } @@ -776,7 +702,7 @@ fn make_central_items( // Generate builtin methods, now with info for all types available. // Separate vectors because that makes usage in quote! easier. - let len = builtin_types.count(); + let len = api.builtins.len(); let mut result = CentralItems { opaque_types, @@ -786,45 +712,42 @@ fn make_central_items( variant_op_enumerators_pascal: Vec::new(), variant_op_enumerators_ord: Vec::new(), global_enum_defs: Vec::new(), - godot_version: api.header.clone(), + godot_version: json_api.header.clone(), }; - // Note: NIL is not part of this iteration, it will be added manually - for ty in builtin_types.ordered() { - let (pascal_name, rust_ty, ord) = make_enumerator(&ty.type_names, ty.variant_type_ord, ctx); + // Note: NIL is not part of this iteration, it will be added manually. + for builtin in api.builtins.iter() { + let original_name = builtin.godot_original_name(); + let rust_ty = conv::to_rust_type(original_name, None, ctx); + let pascal_case = conv::to_pascal_case(original_name); + let ord = builtin.unsuffixed_ord_lit(); - result.variant_ty_enumerators_pascal.push(pascal_name); - result.variant_ty_enumerators_rust.push(rust_ty); + result + .variant_ty_enumerators_pascal + .push(ident(&pascal_case)); + result + .variant_ty_enumerators_rust + .push(rust_ty.to_token_stream()); result.variant_ty_enumerators_ord.push(ord); } for op in variant_operators { - let name = op - .name - .strip_prefix("OP_") - .expect("expected `OP_` prefix for variant operators"); + let pascal_name = conv::to_pascal_case(&op.name.to_string()); - if name == "MAX" { - continue; - } - - let op_enumerator_pascal = conv::shout_to_pascal(name); - let op_enumerator_pascal = if op_enumerator_pascal == "Module" { - "Modulo" + let enumerator_name = if pascal_name == "Module" { + ident("Modulo") } else { - &op_enumerator_pascal + ident(&pascal_name) }; - result - .variant_op_enumerators_pascal - .push(ident(op_enumerator_pascal)); + result.variant_op_enumerators_pascal.push(enumerator_name); result .variant_op_enumerators_ord - .push(util::make_enumerator_ord_unsuffixed(op.to_enum_ord())); + .push(op.value.unsuffixed_lit()); } - for json_enum in api.global_enums.iter() { - // Skip those enums which are already explicitly handled + for json_enum in json_api.global_enums.iter() { + // Skip those enums which are already explicitly handled. if matches!(json_enum.name.as_str(), "Variant.Type" | "Variant.Operator") { continue; } @@ -838,8 +761,10 @@ fn make_central_items( result } -fn make_builtin_lifecycle_table(builtin_types: &BuiltinTypeMap) -> TokenStream { - let len = builtin_types.count(); +fn make_builtin_lifecycle_table(api: &ExtensionApi) -> TokenStream { + let builtins = &api.builtins; + let len = builtins.len(); + let mut table = NamedMethodTable { table_name: ident("BuiltinLifecycleTable"), imports: quote! { @@ -865,27 +790,9 @@ fn make_builtin_lifecycle_table(builtin_types: &BuiltinTypeMap) -> TokenStream { method_count: 0, }; - // Note: NIL is not part of this iteration, it will be added manually - for ty in builtin_types.ordered() { - let empty = vec![]; - let constructors = ty.constructors.unwrap_or(&empty); - - let empty = vec![]; - let operators = ty.operators.unwrap_or(&empty); - - let (decls, inits) = make_variant_fns( - &ty.type_names, - ty.has_destructor, - &constructors - .iter() - .map(|ctor| Constructor::from_json(ctor)) - .collect::>(), - &operators - .iter() - .map(|op| Operator::from_json(op)) - .collect::>(), - &builtin_types.map, - ); + // Note: NIL is not part of this iteration, it will be added manually. + for variant in builtins.iter() { + let (decls, inits) = make_variant_fns(api, variant); table.method_decls.push(decls); table.method_inits.push(inits); @@ -978,11 +885,7 @@ fn make_named_accessors(accessors: &[AccessorMethod], fptr: &TokenStream) -> Tok result_api } -fn make_builtin_method_table( - api: &JsonExtensionApi, - builtin_types: &BuiltinTypeMap, - ctx: &mut Context, -) -> TokenStream { +fn make_builtin_method_table(api: &ExtensionApi, ctx: &mut Context) -> TokenStream { let mut table = IndexedMethodTable { table_name: ident("BuiltinMethodTable"), imports: TokenStream::new(), @@ -1013,13 +916,8 @@ fn make_builtin_method_table( method_count: 0, }; - // TODO reuse builtin_types without api - for builtin in api.builtin_classes.iter() { - let Some(builtin_type) = builtin_types.map.get(&builtin.name) else { - continue; // for Nil - }; - - populate_builtin_methods(&mut table, builtin, &builtin_type.type_names, ctx); + for builtin in api.builtins.iter() { + populate_builtin_methods(&mut table, builtin, ctx); } make_method_table(table) @@ -1093,40 +991,31 @@ fn populate_class_methods( fn populate_builtin_methods( table: &mut IndexedMethodTable, - builtin_class: &JsonBuiltin, - builtin_name: &BuiltinName, + builtin: &BuiltinVariant, ctx: &mut Context, ) { - let mut method_inits = vec![]; - - // Skip types such as int, float, bool. - // TODO separate BuiltinName + TyName needed? Of course not :) - let builtin_ty = TyName::from_godot(&builtin_class.name); - if special_cases::is_builtin_type_deleted(&builtin_ty) { + let Some(builtin_class) = builtin.associated_builtin_class() else { + // Ignore those where no class is generated (Object, int, bool etc.). return; - } + }; - for method in option_as_slice(&builtin_class.methods) { - // TODO temporary - let inner_builtin_name = TyName::from_godot(&format!("Inner{}", builtin_class.name)); - let Some(method) = BuiltinMethod::from_json(method, &builtin_ty, &inner_builtin_name, ctx) - else { - continue; - }; + let builtin_ty = builtin_class.name(); + let mut method_inits = vec![]; + for method in builtin_class.methods.iter() { let index = ctx.get_table_index(&MethodTableKey::BuiltinMethod { builtin_ty: builtin_ty.clone(), method_name: method.name().to_string(), }); - let method_init = make_builtin_method_init(&method, builtin_name, index); + let method_init = make_builtin_method_init(builtin, &method, index); method_inits.push(MethodInit { method_init, index }); table.method_count += 1; // If requested, add a named accessor for this method. - if special_cases::is_named_accessor_in_table(&builtin_ty, &method.name()) { - let variant_type = &builtin_name.sys_variant_type; - let variant_type_str = &builtin_name.json_builtin_name; + if special_cases::is_named_accessor_in_table(&builtin_ty, method.name()) { + let variant_type = builtin.sys_variant_type(); + let variant_type_str = builtin.godot_original_name(); let method_name_str = method.name(); let hash = method.hash(); @@ -1146,7 +1035,7 @@ fn populate_builtin_methods( } table.method_init_groups.push(MethodInitGroup::new( - &builtin_class.name, + &builtin_class.name().godot_ty, None, // load_builtin_method() doesn't need a StringName for the class, as it accepts the VariantType enum. method_inits, )); @@ -1182,14 +1071,14 @@ fn make_class_method_init( } fn make_builtin_method_init( + builtin: &BuiltinVariant, method: &BuiltinMethod, - type_name: &BuiltinName, index: usize, ) -> TokenStream { let method_name_str = method.name(); - let variant_type = &type_name.sys_variant_type; - let variant_type_str = &type_name.json_builtin_name; + let variant_type = builtin.sys_variant_type(); + let variant_type_str = builtin.godot_original_name(); let hash = method.hash(); @@ -1209,160 +1098,85 @@ fn make_builtin_method_init( } } -/// Creates a map from "normalized" class names (lowercase without underscore, makes it easy to map from different conventions) -/// to meta type information, including all the type name variants -fn collect_builtin_classes(api: &JsonExtensionApi) -> HashMap { - let mut class_map = HashMap::new(); - for class in &api.builtin_classes { - let normalized_name = class.name.to_ascii_lowercase(); - - class_map.insert(normalized_name, class); - } - - class_map -} - -/// Returns map from the JSON names (e.g. "PackedStringArray") to all the info. -pub(crate) fn collect_builtin_types( - api: &JsonExtensionApi, -) -> HashMap> { - let class_map = collect_builtin_classes(api); - - let variant_type_enum = api - .global_enums - .iter() - .find(|e| &e.name == "Variant.Type") - .expect("missing enum for VariantType in JSON"); - - // Collect all `BuiltinTypeInfo`s - let mut builtin_types_map = HashMap::new(); - for ty in &variant_type_enum.values { - let shout_case = ty - .name - .strip_prefix("TYPE_") - .expect("enum name begins with 'TYPE_'"); - - if shout_case == "NIL" || shout_case == "MAX" { - continue; - } - - // Lowercase without underscore, to map SHOUTY_CASE to shoutycase - let normalized = shout_case.to_ascii_lowercase().replace('_', ""); - - // TODO cut down on the number of cached functions generated - // e.g. there's no point in providing operator< for int - let class_name: String; - let has_destructor: bool; - let constructors: Option<&Vec>; - let operators: Option<&Vec>; - if let Some(class) = class_map.get(&normalized) { - class_name = class.name.clone(); - has_destructor = class.has_destructor; - constructors = Some(&class.constructors); - operators = Some(&class.operators); - } else { - assert_eq!(normalized, "object"); - class_name = "Object".to_string(); - has_destructor = false; - constructors = None; - operators = None; - } - - let type_names = BuiltinName { - json_builtin_name: class_name.clone(), - snake_case: conv::to_snake_case(&class_name), - //shout_case: shout_case.to_string(), - sys_variant_type: format_ident!("GDEXTENSION_VARIANT_TYPE_{}", shout_case), - }; - - let variant_type_ord = ty.to_enum_ord(); - builtin_types_map.insert( - type_names.json_builtin_name.clone(), - BuiltinTypeInfo { - variant_type_ord, - type_names, - has_destructor, - constructors, - operators, - }, - ); - } - builtin_types_map -} +fn collect_variant_operators(api: &ExtensionApi) -> Vec<&Enumerator> { + println!( + "{:?}", + api.global_enums.iter().map(|e| &e.name).collect::>() + ); -fn collect_variant_operators(api: &JsonExtensionApi) -> Vec<&JsonEnumConstant> { let variant_operator_enum = api .global_enums .iter() - .find(|e| &e.name == "Variant.Operator") + .find(|e| &e.name == "VariantOperator") // in JSON: "Variant.Operator" .expect("missing enum for VariantOperator in JSON"); - variant_operator_enum.values.iter().collect() -} - -/// Only used for `VariantType` enum at the moment. -/// -/// Returns **unsuffixed** ordinals, to allow for different platform types of the C definition. Adjust if suffixed is desired, for type safety. -fn make_enumerator( - type_names: &BuiltinName, - value: i32, - ctx: &mut Context, -) -> (Ident, TokenStream, Literal) { - let enumerator_name = &type_names.json_builtin_name; - let pascal_name = conv::to_pascal_case(enumerator_name); - let rust_ty = conv::to_rust_type(enumerator_name, None, ctx); - let ord = util::make_enumerator_ord_unsuffixed(value); - - (ident(&pascal_name), rust_ty.to_token_stream(), ord) + variant_operator_enum.enumerators.iter().collect() } fn make_opaque_type(name: &str, size: usize) -> TokenStream { let name = conv::to_pascal_case(name); let (first, rest) = name.split_at(1); - // Capitalize: "int" -> "Int" + // Capitalize: "int" -> "Int". let ident = format_ident!("Opaque{}{}", first.to_ascii_uppercase(), rest); quote! { pub type #ident = crate::opaque::Opaque<#size>; } } -fn make_variant_fns( - type_names: &BuiltinName, - has_destructor: bool, - constructors: &[Constructor], - operators: &[Operator], - builtin_types: &HashMap, -) -> (TokenStream, TokenStream) { - let (construct_decls, construct_inits) = - make_construct_fns(type_names, constructors, builtin_types); - let (destroy_decls, destroy_inits) = make_destroy_fns(type_names, has_destructor); - let (op_eq_decls, op_eq_inits) = make_operator_fns(type_names, operators, "==", "EQUAL"); - let (op_lt_decls, op_lt_inits) = make_operator_fns(type_names, operators, "<", "LESS"); +fn make_variant_fns(api: &ExtensionApi, builtin: &BuiltinVariant) -> (TokenStream, TokenStream) { + let (special_decls, special_inits); + if let Some(builtin_class) = builtin.associated_builtin_class() { + let (construct_decls, construct_inits) = + make_construct_fns(api, builtin, &builtin_class.constructors); + + let (destroy_decls, destroy_inits) = + make_destroy_fns(builtin, builtin_class.has_destructor); - let to_variant = format_ident!("{}_to_variant", type_names.snake_case); - let from_variant = format_ident!("{}_from_variant", type_names.snake_case); + let (op_eq_decls, op_eq_inits) = + make_operator_fns(builtin, &builtin_class.operators, "==", "EQUAL"); + + let (op_lt_decls, op_lt_inits) = + make_operator_fns(builtin, &builtin_class.operators, "<", "LESS"); + + special_decls = quote! { + #op_eq_decls + #op_lt_decls + #construct_decls + #destroy_decls + }; + special_inits = quote! { + #op_eq_inits + #op_lt_inits + #construct_inits + #destroy_inits + }; + } else { + special_decls = TokenStream::new(); + special_inits = TokenStream::new(); + }; + + let snake_case = builtin.snake_name(); + let to_variant = format_ident!("{}_to_variant", snake_case); + let from_variant = format_ident!("{}_from_variant", snake_case); let to_variant_str = to_variant.to_string(); let from_variant_str = from_variant.to_string(); - let variant_type = &type_names.sys_variant_type; + let variant_type = builtin.sys_variant_type(); let variant_type = quote! { crate::#variant_type }; - // Field declaration + // Field declaration. // The target types are uninitialized-ptrs, because Godot performs placement new on those: // https://github.com/godotengine/godot/blob/b40b35fb39f0d0768d7ec2976135adffdce1b96d/core/variant/variant_internal.h#L1535-L1535 let decl = quote! { pub #to_variant: unsafe extern "C" fn(GDExtensionUninitializedVariantPtr, GDExtensionTypePtr), pub #from_variant: unsafe extern "C" fn(GDExtensionUninitializedTypePtr, GDExtensionVariantPtr), - #op_eq_decls - #op_lt_decls - #construct_decls - #destroy_decls + #special_decls }; - // Field initialization in new() + // Field initialization in new(). let init = quote! { #to_variant: { let fptr = unsafe { get_to_variant_fn(#variant_type) }; @@ -1372,21 +1186,18 @@ fn make_variant_fns( let fptr = unsafe { get_from_variant_fn(#variant_type) }; crate::validate_builtin_lifecycle(fptr, #from_variant_str) }, - #op_eq_inits - #op_lt_inits - #construct_inits - #destroy_inits + #special_inits }; (decl, init) } fn make_construct_fns( - type_names: &BuiltinName, + api: &ExtensionApi, + builtin: &BuiltinVariant, constructors: &[Constructor], - builtin_types: &HashMap, ) -> (TokenStream, TokenStream) { - if constructors.is_empty() || is_trivial(type_names) { + if constructors.is_empty() { return (TokenStream::new(), TokenStream::new()); }; @@ -1396,7 +1207,7 @@ fn make_construct_fns( // [2]: (optional) typically the most common conversion constructor (e.g. StringName -> String) // rest: (optional) other conversion constructors and multi-arg constructors (e.g. Vector3(x, y, z)) - // Sanity checks -- ensure format is as expected + // Sanity checks -- ensure format is as expected. for (i, c) in constructors.iter().enumerate() { assert_eq!(i, c.index); } @@ -1409,16 +1220,18 @@ fn make_construct_fns( let args = &constructors[1].raw_parameters; assert_eq!(args.len(), 1); assert_eq!(args[0].name, "from"); - assert_eq!(args[0].type_, type_names.json_builtin_name); + assert_eq!(args[0].type_, builtin.godot_original_name()); + + let builtin_snake_name = builtin.snake_name(); + let variant_type = builtin.sys_variant_type(); - let construct_default = format_ident!("{}_construct_default", type_names.snake_case); - let construct_copy = format_ident!("{}_construct_copy", type_names.snake_case); + let construct_default = format_ident!("{builtin_snake_name}_construct_default"); + let construct_copy = format_ident!("{builtin_snake_name}_construct_copy"); let construct_default_str = construct_default.to_string(); let construct_copy_str = construct_copy.to_string(); - let variant_type = &type_names.sys_variant_type; let (construct_extra_decls, construct_extra_inits) = - make_extra_constructors(type_names, constructors, builtin_types); + make_extra_constructors(api, builtin, constructors); // Target types are uninitialized pointers, because Godot uses placement-new for raw pointer constructions. Callstack: // https://github.com/godotengine/godot/blob/b40b35fb39f0d0768d7ec2976135adffdce1b96d/core/extension/gdextension_interface.cpp#L511 @@ -1453,13 +1266,13 @@ fn make_construct_fns( /// Lists special cases for useful constructors fn make_extra_constructors( - type_names: &BuiltinName, + api: &ExtensionApi, + builtin: &BuiltinVariant, constructors: &[Constructor], - builtin_types: &HashMap, ) -> (Vec, Vec) { let mut extra_decls = Vec::with_capacity(constructors.len() - 2); let mut extra_inits = Vec::with_capacity(constructors.len() - 2); - let variant_type = &type_names.sys_variant_type; + let variant_type = builtin.sys_variant_type(); for (i, ctor) in constructors.iter().enumerate().skip(2) { let args = &ctor.raw_parameters; @@ -1468,14 +1281,15 @@ fn make_extra_constructors( "custom constructors must have at least 1 parameter" ); - let type_name = &type_names.snake_case; + let type_name = builtin.snake_name(); let construct_custom = if args.len() == 1 && args[0].name == "from" { - // Conversion constructor is named according to the source type + // Conversion constructor is named according to the source type: // String(NodePath from) => string_from_node_path - let arg_type = &builtin_types[&args[0].type_].type_names.snake_case; + + let arg_type = api.builtin_by_original_name(&args[0].type_).snake_name(); format_ident!("{type_name}_from_{arg_type}") } else { - // Type-specific constructor is named according to the argument names + // Type-specific constructor is named according to the argument names: // Vector3(float x, float y, float z) => vector3_from_x_y_z let mut arg_names = args .iter() @@ -1501,14 +1315,14 @@ fn make_extra_constructors( (extra_decls, extra_inits) } -fn make_destroy_fns(type_names: &BuiltinName, has_destructor: bool) -> (TokenStream, TokenStream) { - if !has_destructor || is_trivial(type_names) { +fn make_destroy_fns(builtin: &BuiltinVariant, has_destructor: bool) -> (TokenStream, TokenStream) { + if !has_destructor { return (TokenStream::new(), TokenStream::new()); } - let destroy = format_ident!("{}_destroy", type_names.snake_case); + let destroy = format_ident!("{}_destroy", builtin.snake_name()); let destroy_str = destroy.to_string(); - let variant_type = &type_names.sys_variant_type; + let variant_type = builtin.sys_variant_type(); let decls = quote! { pub #destroy: unsafe extern "C" fn(GDExtensionTypePtr), @@ -1525,35 +1339,33 @@ fn make_destroy_fns(type_names: &BuiltinName, has_destructor: bool) -> (TokenStr } fn make_operator_fns( - type_names: &BuiltinName, + builtin: &BuiltinVariant, operators: &[Operator], - json_name: &str, + json_symbol: &str, sys_name: &str, ) -> (TokenStream, TokenStream) { - if operators.is_empty() - || !operators.iter().any(|op| op.name == json_name) - || is_trivial(type_names) - { + // If there are no operators for that builtin type, or none of the operator matches symbol, then don't generate function. + if operators.is_empty() || !operators.iter().any(|op| op.symbol == json_symbol) { return (TokenStream::new(), TokenStream::new()); } let operator = format_ident!( "{}_operator_{}", - type_names.snake_case, + builtin.snake_name(), sys_name.to_ascii_lowercase() ); let operator_str = operator.to_string(); - let variant_type = &type_names.sys_variant_type; + let variant_type = builtin.sys_variant_type(); let variant_type = quote! { crate::#variant_type }; let sys_ident = format_ident!("GDEXTENSION_VARIANT_OP_{}", sys_name); - // Field declaration + // Field declaration. let decl = quote! { pub #operator: unsafe extern "C" fn(GDExtensionConstTypePtr, GDExtensionConstTypePtr, GDExtensionTypePtr), }; - // Field initialization in new() + // Field initialization in new(). let init = quote! { #operator: { let fptr = unsafe { get_operator_fn(crate::#sys_ident, #variant_type, #variant_type) }; @@ -1563,11 +1375,3 @@ fn make_operator_fns( (decl, init) } - -/// Returns true if the type is so trivial that most of its operations are directly provided by Rust, and there is no need -/// to expose the construct/destruct/operator methods from Godot -fn is_trivial(type_names: &BuiltinName) -> bool { - let list = ["bool", "int", "float"]; - - list.contains(&type_names.json_builtin_name.as_str()) -} diff --git a/godot-codegen/src/class_generator.rs b/godot-codegen/src/class_generator.rs index d8ec61f19..075795107 100644 --- a/godot-codegen/src/class_generator.rs +++ b/godot-codegen/src/class_generator.rs @@ -11,19 +11,16 @@ use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; use std::path::Path; -use crate::central_generator::collect_builtin_types; use crate::context::NotificationEnum; use crate::domain_models::BuiltinMethod; use crate::domain_models::*; -use crate::json_models::*; use crate::util::{ ident, make_string_name, option_as_slice, parse_native_structures_format, safe_ident, ClassCodegenLevel, MethodTableKey, NativeStructuresField, }; use crate::{ - codegen_special_cases, conv, special_cases, util, Context, GeneratedBuiltin, - GeneratedBuiltinModule, GeneratedClass, GeneratedClassModule, ModName, RustTy, SubmitFn, - TyName, + conv, special_cases, util, Context, GeneratedBuiltin, GeneratedBuiltinModule, GeneratedClass, + GeneratedClassModule, ModName, RustTy, SubmitFn, TyName, }; // ---------------------------------------------------------------------------------------------------------------------------------------------- @@ -36,7 +33,6 @@ struct FnReceiver { ffi_arg: TokenStream, /// `Self::`, `self.` - #[allow(dead_code)] // TODO remove as soon as used self_prefix: TokenStream, } @@ -109,7 +105,7 @@ impl FnDefinitions { // ---------------------------------------------------------------------------------------------------------------------------------------------- pub(crate) fn generate_class_files( - api: &JsonExtensionApi, + api: &ExtensionApi, ctx: &mut Context, _build_config: [&str; 2], gen_path: &Path, @@ -120,25 +116,16 @@ pub(crate) fn generate_class_files( let mut modules = vec![]; for class in api.classes.iter() { - let class_name = TyName::from_godot(&class.name); - let module_name = ModName::from_godot(&class.name); - - if special_cases::is_class_deleted(&class_name) - || codegen_special_cases::is_class_excluded(class_name.godot_ty.as_str()) - { - continue; - } - - let generated_class = make_class(class, &class_name, ctx); + let generated_class = make_class(&class, ctx); let file_contents = generated_class.code; - let out_path = gen_path.join(format!("{}.rs", module_name.rust_mod)); + let out_path = gen_path.join(format!("{}.rs", class.mod_name().rust_mod)); submit_fn(out_path, file_contents); modules.push(GeneratedClassModule { - class_name, - module_name, + class_name: class.name().clone(), + module_name: class.mod_name().clone(), own_notification_enum_name: generated_class.notification_enum.try_to_own_name(), inherits_macro_ident: generated_class.inherits_macro_ident, is_pub_sidecar: generated_class.has_sidecar_module, @@ -152,7 +139,7 @@ pub(crate) fn generate_class_files( } pub(crate) fn generate_builtin_class_files( - api: &JsonExtensionApi, + api: &ExtensionApi, ctx: &mut Context, _build_config: [&str; 2], gen_path: &Path, @@ -161,23 +148,16 @@ pub(crate) fn generate_builtin_class_files( let _ = std::fs::remove_dir_all(gen_path); std::fs::create_dir_all(gen_path).expect("create classes directory"); - let builtin_types_map = collect_builtin_types(api); - let mut modules = vec![]; - for class in api.builtin_classes.iter() { - let module_name = ModName::from_godot(&class.name); - let builtin_name = TyName::from_godot(&class.name); - let inner_builtin_name = TyName::from_godot(&format!("Inner{}", class.name)); - - if special_cases::is_builtin_type_deleted(&builtin_name) { + for class in api.builtins.iter() { + let Some(class) = class.builtin_class.as_ref() else { continue; - } + }; - let _type_info = builtin_types_map - .get(&class.name) - .unwrap_or_else(|| panic!("builtin type not found: {}", class.name)); + // let godot_class_name = &class.name().godot_ty; + let module_name = class.mod_name(); - let generated_class = make_builtin_class(class, &builtin_name, &inner_builtin_name, ctx); + let generated_class = make_builtin_class(&class, ctx); let file_contents = generated_class.code; let out_path = gen_path.join(format!("{}.rs", module_name.rust_mod)); @@ -185,8 +165,8 @@ pub(crate) fn generate_builtin_class_files( submit_fn(out_path, file_contents); modules.push(GeneratedBuiltinModule { - class_name: inner_builtin_name, - module_name, + symbol_ident: class.inner_name().clone(), + module_name: module_name.clone(), }); } @@ -197,7 +177,7 @@ pub(crate) fn generate_builtin_class_files( } pub(crate) fn generate_native_structures_files( - api: &JsonExtensionApi, + api: &ExtensionApi, ctx: &mut Context, _build_config: [&str; 2], gen_path: &Path, @@ -219,7 +199,7 @@ pub(crate) fn generate_native_structures_files( submit_fn(out_path, file_contents); modules.push(GeneratedBuiltinModule { - class_name, + symbol_ident: class_name.rust_ty.clone(), module_name, }); } @@ -314,12 +294,8 @@ fn make_module_doc(class_name: &TyName) -> String { ) } -fn make_constructor_and_default( - class: &JsonClass, - class_name: &TyName, - ctx: &Context, -) -> (TokenStream, TokenStream) { - let godot_class_name = &class.name; +fn make_constructor_and_default(class: &Class, ctx: &Context) -> (TokenStream, TokenStream) { + let godot_class_name = &class.name().godot_ty; let godot_class_stringname = make_string_name(godot_class_name); // Note: this could use class_name() but is not yet done due to upcoming lazy-load refactoring. //let class_name_obj = quote! { ::class_name() }; @@ -362,6 +338,7 @@ fn make_constructor_and_default( } let godot_default_impl = if has_godot_default_impl { + let class_name = &class.name().rust_ty; quote! { impl crate::obj::cap::GodotDefault for #class_name { fn __godot_default() -> crate::obj::Gd { @@ -376,7 +353,9 @@ fn make_constructor_and_default( (constructor, godot_default_impl) } -fn make_class(class: &JsonClass, class_name: &TyName, ctx: &mut Context) -> GeneratedClass { +fn make_class(class: &Class, ctx: &mut Context) -> GeneratedClass { + let class_name = class.name(); + // Strings let godot_class_str = &class_name.godot_ty; let class_name_cstr = util::cstr_u8_slice(godot_class_str); @@ -391,23 +370,18 @@ fn make_class(class: &JsonClass, class_name: &TyName, ctx: &mut Context) -> Gene None => (quote! { () }, None), }; - let (constructor, godot_default_impl) = make_constructor_and_default(class, class_name, ctx); - let api_level = util::get_api_level(class); + let (constructor, godot_default_impl) = make_constructor_and_default(class, ctx); + let api_level = class.api_level; let init_level = api_level.to_init_level(); let FnDefinitions { functions: methods, builders, - } = make_methods(option_as_slice(&class.methods), class_name, &api_level, ctx); + } = make_methods(&class.methods, class_name, &api_level, ctx); - let enums = make_enums(option_as_slice(&class.enums), class_name, ctx); - - let constants = option_as_slice(&class.constants) - .iter() - .map(ClassConstant::from_json) - .collect::>(); + let enums = make_enums(&class.enums); - let constants = make_constants(&constants); + let constants = make_constants(&class.constants); let inherits_macro = format_ident!("inherits_transitive_{}", class_name.rust_ty); let (exportable_impl, exportable_macro_impl) = if ctx.is_exportable(class_name) { @@ -463,7 +437,6 @@ fn make_class(class: &JsonClass, class_name: &TyName, ctx: &mut Context) -> Gene let module_doc = make_module_doc(class_name); let virtual_trait = make_virtual_methods_trait( class, - class_name, &all_bases, &virtual_trait_str, ¬ification_enum_name, @@ -725,40 +698,22 @@ fn workaround_constant_collision(all_constants: &mut Vec<(Ident, i32)>) { } } -fn make_builtin_class( - class: &JsonBuiltin, - builtin_name: &TyName, - inner_class_name: &TyName, - ctx: &mut Context, -) -> GeneratedBuiltin { - let outer_class = - if let RustTy::BuiltinIdent(ident) = conv::to_rust_type(&class.name, None, ctx) { - ident - } else { - panic!("Rust type `{}` categorized wrong", class.name) - }; - let inner_class = &inner_class_name.rust_ty; +fn make_builtin_class(class: &BuiltinClass, ctx: &mut Context) -> GeneratedBuiltin { + let godot_name = &class.name().godot_ty; - let class_enums = class.enums.as_ref().map_or(Vec::new(), |class_enums| { - class_enums - .iter() - .map(JsonBuiltinEnum::to_enum) - .collect::>() - }); + let RustTy::BuiltinIdent(outer_class) = conv::to_rust_type(godot_name, None, ctx) else { + panic!("Rust type `{}` categorized wrong", godot_name) + }; + let inner_class = class.inner_name(); let FnDefinitions { functions: methods, builders, - } = make_builtin_methods( - option_as_slice(&class.methods), - builtin_name, - inner_class_name, - ctx, - ); + } = make_builtin_methods(&class.methods, class.name(), ctx); let imports = util::make_imports(); - let enums = make_enums(&class_enums, builtin_name, ctx); - let special_constructors = make_special_builtin_methods(builtin_name, ctx); + let enums = make_enums(&class.enums); + let special_constructors = make_special_builtin_methods(class.name(), ctx); // mod re_export needed, because class should not appear inside the file module, and we can't re-export private struct as pub let code = quote! { @@ -789,7 +744,7 @@ fn make_builtin_class( } fn make_native_structure( - structure: &JsonNativeStructure, + structure: &NativeStructure, class_name: &TyName, ctx: &mut Context, ) -> GeneratedBuiltin { @@ -948,13 +903,13 @@ fn make_builtin_module_file(classes_and_modules: Vec) -> let decls = classes_and_modules.iter().map(|m| { let GeneratedBuiltinModule { module_name, - class_name, + symbol_ident, .. } = m; quote! { mod #module_name; - pub use #module_name::#class_name; + pub use #module_name::#symbol_ident; } }); @@ -964,7 +919,7 @@ fn make_builtin_module_file(classes_and_modules: Vec) -> } fn make_methods( - methods: &[JsonClassMethod], + methods: &[ClassMethod], class_name: &TyName, api_level: &ClassCodegenLevel, ctx: &mut Context, @@ -972,39 +927,26 @@ fn make_methods( let get_method_table = api_level.table_global_getter(); let definitions = methods.iter().map(|method| { - match ClassMethod::from_json_outbound(method, class_name, ctx) { - None => FnDefinition::none(), - Some(method) => { - make_class_method_definition(&method, class_name, api_level, &get_method_table, ctx) - } - } + make_class_method_definition(method, class_name, api_level, &get_method_table, ctx) }); FnDefinitions::expand(definitions) } fn make_builtin_methods( - methods: &[JsonBuiltinMethod], + methods: &[BuiltinMethod], builtin_name: &TyName, - inner_class_name: &TyName, ctx: &mut Context, ) -> FnDefinitions { - let definitions = methods.iter().map(|method| { - match BuiltinMethod::from_json(method, builtin_name, inner_class_name, ctx) { - None => FnDefinition::none(), - Some(method) => make_builtin_method_definition(&method, builtin_name, ctx), - } - }); + let definitions = methods + .iter() + .map(|method| make_builtin_method_definition(method, builtin_name, ctx)); FnDefinitions::expand(definitions) } -fn make_enums(enums: &[JsonEnum], class_name: &TyName, _ctx: &mut Context) -> TokenStream { - let definitions = enums.iter().map(|json| { - let domain = Enum::from_json(json, Some(class_name)); - - util::make_enum_definition(&domain) - }); +fn make_enums(enums: &[Enum]) -> TokenStream { + let definitions = enums.iter().map(util::make_enum_definition); quote! { #( #definitions )* @@ -1049,17 +991,6 @@ fn make_class_method_definition( return FnDefinition::none(); }; - /* - // TODO re-enable this once JSON/domain models are separated. - // Getters in particular are re-qualified as const (if there isn't already an override). - if override_is_const.is_none() && option_as_slice(&method.arguments).is_empty() { - if rust_method_name.starts_with("get_") { - // Many getters are mutably qualified (GltfAccessor::get_max, CameraAttributes::get_exposure_multiplier, ...). - // As a default, set those to const. - override_is_const = Some(true); - } - }*/ - let rust_method_name = method.name(); let godot_method_name = method.godot_name(); @@ -1665,8 +1596,7 @@ fn make_params_and_args(method_args: &[&FnParam]) -> (Vec, Vec TokenStream { let trait_name = ident(trait_name); - let virtual_method_fns = make_all_virtual_methods(class, class_name, all_base_names, ctx); + let virtual_method_fns = make_all_virtual_methods(class, all_base_names, ctx); let special_virtual_methods = special_virtual_methods(notification_enum_name); - let trait_doc = make_virtual_trait_doc(class_name); + let trait_doc = make_virtual_trait_doc(class.name()); quote! { #[doc = #trait_doc] @@ -1733,7 +1663,11 @@ fn special_virtual_methods(notification_enum_name: &Ident) -> TokenStream { } } -fn make_virtual_method(method: &ClassMethod) -> TokenStream { +fn make_virtual_method(method: &ClassMethod) -> Option { + if !method.is_virtual() { + return None; + } + // Virtual methods are never static. let qualifier = method.qualifier(); assert!(matches!(qualifier, FnQualifier::Mut | FnQualifier::Const)); @@ -1749,48 +1683,41 @@ fn make_virtual_method(method: &ClassMethod) -> TokenStream { ); // Virtual methods have no builders. - definition.into_functions_only() + Some(definition.into_functions_only()) } fn make_all_virtual_methods( - class: &JsonClass, - class_name: &TyName, + class: &Class, all_base_names: &[TyName], ctx: &mut Context, ) -> Vec { - let mut all_virtuals = vec![]; - let mut extend_virtuals = |class| { - all_virtuals.extend( - get_methods_in_class(class) - .iter() - .filter(|m| m.is_virtual) - .cloned(), - ); - }; + let mut all_tokens = vec![]; - // Get virtuals defined on the current class. - extend_virtuals(class); - - // Add virtuals from superclasses. - for base in all_base_names { - let superclass = ctx.get_engine_class(base); - extend_virtuals(superclass); + for method in class.methods.iter() { + // Assumes that inner function filters on is_virtual. + if let Some(tokens) = make_virtual_method(method) { + all_tokens.push(tokens); + } } - all_virtuals - .into_iter() - .filter_map(|method| { - ClassMethod::from_json_virtual(&method, class_name, ctx) - .map(|m| make_virtual_method(&m)) - }) - .collect() -} + for base_name in all_base_names { + let json_base_class = ctx.get_engine_class(base_name); + for json_method in option_as_slice(&json_base_class.methods) { + // FIXME temporary workaround, the ctx doesn't cross-over borrowed fields in ctx + let hack_ptr = ctx as *const _ as *mut _; + let hack_ctx = unsafe { &mut *hack_ptr }; // UB -fn get_methods_in_class(class: &JsonClass) -> &[JsonClassMethod] { - match &class.methods { - None => &[], - Some(methods) => methods, + if let Some(method) = + ClassMethod::from_json_virtual(json_method, class.name(), hack_ctx) + { + if let Some(tokens) = make_virtual_method(&method) { + all_tokens.push(tokens); + } + } + } } + + all_tokens } fn function_uses_pointers(sig: &dyn Function) -> bool { diff --git a/godot-codegen/src/codegen_special_cases.rs b/godot-codegen/src/codegen_special_cases.rs index df67154a5..29ea9be4e 100644 --- a/godot-codegen/src/codegen_special_cases.rs +++ b/godot-codegen/src/codegen_special_cases.rs @@ -20,12 +20,12 @@ pub(crate) fn is_builtin_method_excluded(method: &JsonBuiltinMethod) -> bool { } #[cfg(not(feature = "codegen-full"))] -pub(crate) fn is_class_excluded(class: &str) -> bool { - !SELECTED_CLASSES.contains(&class) +pub(crate) fn is_class_excluded(godot_class_name: &str) -> bool { + !SELECTED_CLASSES.contains(&godot_class_name) } #[cfg(feature = "codegen-full")] -pub(crate) fn is_class_excluded(_class: &str) -> bool { +pub(crate) fn is_class_excluded(_godot_class_name: &str) -> bool { false } diff --git a/godot-codegen/src/context.rs b/godot-codegen/src/context.rs index b3f5ea602..308a7e0e0 100644 --- a/godot-codegen/src/context.rs +++ b/godot-codegen/src/context.rs @@ -17,7 +17,7 @@ use quote::{format_ident, ToTokens}; use std::collections::{HashMap, HashSet}; #[derive(Default)] -pub(crate) struct Context<'a> { +pub struct Context<'a> { engine_classes: HashMap, builtin_types: HashSet<&'a str>, native_structures_types: HashSet<&'a str>, diff --git a/godot-codegen/src/domain_mapping.rs b/godot-codegen/src/domain_mapping.rs index 67262c2b3..5fe3c26fc 100644 --- a/godot-codegen/src/domain_mapping.rs +++ b/godot-codegen/src/domain_mapping.rs @@ -7,17 +7,271 @@ use crate::context::Context; use crate::domain_models::{ - BuiltinMethod, ClassConstant, ClassConstantValue, ClassMethod, Constructor, Enum, Enumerator, - EnumeratorValue, FnDirection, FnParam, FnQualifier, FnReturn, FunctionCommon, Operator, - UtilityFunction, + BuiltinClass, BuiltinMethod, BuiltinVariant, Class, ClassCommons, ClassConstant, + ClassConstantValue, ClassMethod, Constructor, Enum, Enumerator, EnumeratorValue, ExtensionApi, + FnDirection, FnParam, FnQualifier, FnReturn, FunctionCommon, NativeStructure, Operator, + Singleton, UtilityFunction, }; use crate::json_models::{ - JsonBuiltinMethod, JsonClassConstant, JsonClassMethod, JsonConstructor, JsonEnum, - JsonEnumConstant, JsonMethodReturn, JsonOperator, JsonUtilityFunction, + JsonBuiltin, JsonBuiltinMethod, JsonClass, JsonClassConstant, JsonClassMethod, JsonConstructor, + JsonEnum, JsonEnumConstant, JsonExtensionApi, JsonMethodReturn, JsonNativeStructure, + JsonOperator, JsonSingleton, JsonUtilityFunction, }; -use crate::util::ident; -use crate::{conv, special_cases, TyName}; +use crate::util::{get_api_level, ident, option_as_slice}; +use crate::{codegen_special_cases, conv, special_cases, ModName, TyName}; use proc_macro2::Ident; +use std::collections::HashMap; + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Top-level + +/* + +pub struct ExtensionApiDomain { + pub builtins: Vec, + pub classes: Vec, + pub enums: Vec, + pub native_structures: Vec, + pub singletons: Vec, + pub utility_functions: Vec, +} + */ +impl ExtensionApi { + pub fn from_json( + json: &JsonExtensionApi, + build_config: [&'static str; 2], + ctx: &mut Context, + ) -> Self { + Self { + builtins: BuiltinVariant::all_from_json(&json.global_enums, &json.builtin_classes, ctx), + classes: json + .classes + .iter() + .filter_map(|json| Class::from_json(json, ctx)) + .collect(), + singletons: json.singletons.iter().map(Singleton::from_json).collect(), + native_structures: json + .native_structures + .iter() + .map(NativeStructure::from_json) + .collect(), + utility_functions: json + .utility_functions + .iter() + .filter_map(|json| UtilityFunction::from_json(json, ctx)) + .collect(), + global_enums: json + .global_enums + .iter() + .map(|json| Enum::from_json(json, None)) + .collect(), + build_config, + } + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Builtins + classes + singletons + +impl Class { + pub fn from_json(json: &JsonClass, ctx: &mut Context) -> Option { + let ty_name = TyName::from_godot(&json.name); + if special_cases::is_class_deleted(&ty_name) + || codegen_special_cases::is_class_excluded(ty_name.godot_ty.as_str()) + { + return None; + } + + let mod_name = ModName::from_godot(&ty_name.godot_ty); + + let constants = option_as_slice(&json.constants) + .iter() + .map(ClassConstant::from_json) + .collect(); + + let enums = option_as_slice(&json.enums) + .iter() + .map(|e| { + let surrounding_class = Some(&ty_name); + Enum::from_json(e, surrounding_class) + }) + .collect(); + + let methods = option_as_slice(&json.methods) + .iter() + .filter_map(|m| { + let surrounding_class = &ty_name; + ClassMethod::from_json(m, surrounding_class, ctx) + }) + .collect(); + + Some(Self { + common: ClassCommons { + name: ty_name, + mod_name, + }, + is_refcounted: json.is_refcounted, + is_instantiable: json.is_instantiable, + inherits: json.inherits.clone(), + api_level: get_api_level(json), + constants, + enums, + methods, + }) + } +} + +impl BuiltinClass { + pub fn from_json(json: &JsonBuiltin, ctx: &mut Context) -> Option { + let ty_name = TyName::from_godot(&json.name); + + if special_cases::is_builtin_type_deleted(&ty_name) { + return None; + } + + let inner_name = TyName::from_godot(&format!("Inner{}", ty_name.godot_ty)); + let mod_name = ModName::from_godot(&ty_name.godot_ty); + + let operators = json.operators.iter().map(Operator::from_json).collect(); + + let methods = option_as_slice(&json.methods) + .iter() + .filter_map(|m| { + let inner_class_name = &ty_name; + BuiltinMethod::from_json(m, &ty_name, inner_class_name, ctx) + }) + .collect(); + + let constructors = json + .constructors + .iter() + .map(Constructor::from_json) + .collect(); + + let has_destructor = json.has_destructor; + + let enums = option_as_slice(&json.enums) + .iter() + .map(|e| { + let surrounding_class = Some(&ty_name); + Enum::from_json(&e.to_enum(), surrounding_class) + }) + .collect(); + + Some(Self { + common: ClassCommons { + name: ty_name, + mod_name, + }, + inner_name, + operators, + methods, + constructors, + has_destructor, + enums, + }) + } +} + +impl Singleton { + pub fn from_json(json: &JsonSingleton) -> Self { + Self { + name: TyName::from_godot(&json.name), + } + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Builtin variants + +/* +pub struct BuiltinVariant { + pub(super) name: String, + pub(super) ty: Option, + pub variant_type_ord: i32, +} + */ + +impl BuiltinVariant { + /// Returns all builtins, ordered by enum ordinal value. + pub fn all_from_json( + global_enums: &[JsonEnum], + builtin_classes: &[JsonBuiltin], + ctx: &mut Context, + ) -> Vec { + fn normalize(name: &str) -> String { + name.to_ascii_lowercase().replace('_', "") + } + + let variant_type_enum = global_enums + .iter() + .find(|e| &e.name == "Variant.Type") + .expect("missing enum for VariantType in JSON"); + + // Make HashMap from builtin_classes, keyed by a normalized version of their names (all-lower, no underscores) + let builtin_classes: HashMap = builtin_classes + .iter() + .map(|c| (normalize(&c.name), c)) + .collect(); + + let mut all = variant_type_enum + .values + .iter() + .filter_map(|e| { + let shout_case = e + .name + .strip_prefix("TYPE_") + .expect("variant enumerator lacks prefix 'TYPE_'"); + + if shout_case == "NIL" || shout_case == "MAX" { + return None; + } + + let name = normalize(shout_case); + let ty = builtin_classes.get(&name).map(|b| *b); + let ord = e.to_enum_ord(); + + Some(Self::from_json(shout_case, ord, ty, ctx)) + }) + .collect::>(); + + all.sort_by_key(|v| v.variant_type_ord); + all + } + + pub fn from_json( + json_variant_enumerator_name: &str, + json_variant_enumerator_ord: i32, + json_builtin: Option<&JsonBuiltin>, + ctx: &mut Context, + ) -> Self { + let builtin_class; + let godot_original_name; + + if let Some(json_builtin) = json_builtin { + builtin_class = BuiltinClass::from_json(json_builtin, ctx); + godot_original_name = json_builtin.name.clone(); + // assert!( + // builtin_class.is_some() || special_cases::is_class_deleted(), + // "no builtin class `{}`", + // json_builtin.name + // ); + } else { + assert_eq!(json_variant_enumerator_name, "OBJECT"); + + builtin_class = None; + godot_original_name = "Object".to_string(); + }; + + Self { + godot_original_name, + godot_shout_name: json_variant_enumerator_name.to_string(), // Without `TYPE_` prefix. + godot_snake_name: conv::to_snake_case(&json_variant_enumerator_name), + builtin_class, + variant_type_ord: json_variant_enumerator_ord, + } + } +} // ---------------------------------------------------------------------------------------------------------------------------------------------- // Constructors, operators @@ -34,7 +288,7 @@ impl Constructor { impl Operator { pub fn from_json(json: &JsonOperator) -> Self { Self { - name: json.name.clone(), + symbol: json.name.clone(), } } } @@ -81,6 +335,18 @@ impl BuiltinMethod { } impl ClassMethod { + pub fn from_json( + method: &JsonClassMethod, + class_name: &TyName, + ctx: &mut Context, + ) -> Option { + if method.is_virtual { + Self::from_json_virtual(method, class_name, ctx) + } else { + Self::from_json_outbound(method, class_name, ctx) + } + } + pub fn from_json_outbound( method: &JsonClassMethod, class_name: &TyName, @@ -291,3 +557,15 @@ impl ClassConstant { } } } + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Native structures + +impl NativeStructure { + pub fn from_json(json: &JsonNativeStructure) -> Self { + Self { + name: json.name.clone(), + format: json.format.clone(), + } + } +} diff --git a/godot-codegen/src/domain_models.rs b/godot-codegen/src/domain_models.rs index daa1f20dc..c0f321224 100644 --- a/godot-codegen/src/domain_models.rs +++ b/godot-codegen/src/domain_models.rs @@ -12,39 +12,145 @@ use crate::context::Context; use crate::json_models::{JsonMethodArg, JsonMethodReturn}; -use crate::util::{option_as_slice, safe_ident}; -use crate::{conv, RustTy, TyName}; -use proc_macro2::{Ident, TokenStream}; -use quote::quote; +use crate::util::{option_as_slice, safe_ident, ClassCodegenLevel}; +use crate::{conv, ModName, RustTy, TyName}; + +use proc_macro2::{Ident, Literal, TokenStream}; +use quote::{format_ident, quote}; use std::fmt; -pub struct BuiltinClass { +pub struct ExtensionApi { + pub builtins: Vec, + pub classes: Vec, + pub singletons: Vec, + pub native_structures: Vec, + pub utility_functions: Vec, + pub global_enums: Vec, + pub build_config: [&'static str; 2], +} + +impl ExtensionApi { + /// O(n) search time, often leads to O(n^2), but less than 40 builtins total. + pub fn builtin_by_original_name(&self, name: &str) -> &BuiltinVariant { + self.builtins + .iter() + .find(|b| b.godot_original_name() == name) + .unwrap_or_else(|| panic!("builtin_by_name: invalid `{}`", name)) + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Builtins + classes + singletons + +pub trait ClassLike { + fn common(&self) -> &ClassCommons; + + fn name(&self) -> &TyName { + &self.common().name + } + + fn mod_name(&self) -> &ModName { + &self.common().mod_name + } +} + +pub struct ClassCommons { pub name: TyName, - pub enums: Vec, - pub operators: Vec, + pub mod_name: ModName, +} + +pub struct BuiltinClass { + pub(super) common: ClassCommons, + pub(super) inner_name: TyName, pub methods: Vec, pub constructors: Vec, + pub operators: Vec, pub has_destructor: bool, + pub enums: Vec, +} + +impl BuiltinClass { + pub fn inner_name(&self) -> &Ident { + &self.inner_name.rust_ty + } +} + +impl ClassLike for BuiltinClass { + fn common(&self) -> &ClassCommons { + &self.common + } +} + +/// All information about a builtin type, including its type (if available). +pub struct BuiltinVariant { + pub(super) godot_original_name: String, + pub(super) godot_shout_name: String, + pub(super) godot_snake_name: String, + pub(super) builtin_class: Option, + + pub variant_type_ord: i32, +} + +impl BuiltinVariant { + /// Name in JSON for the class: `"int"` or `"PackedVector2Array"`. + pub fn godot_original_name(&self) -> &str { + &self.godot_original_name + } + + /// Name in JSON: `"INT"` or `"PACKED_VECTOR2_ARRAY"`. + pub fn godot_shout_name(&self) -> &str { + &self.godot_shout_name + } + + /// snake_case name like `"packed_vector2_array"`. + pub fn snake_name(&self) -> &str { + &self.godot_snake_name + } + + /// Excludes variant types if: + /// - There is no builtin class definition in the JSON. For example, `OBJECT` is a variant type but has no corresponding class. + /// - The type is so trivial that most of its operations are directly provided by Rust, and there is no need + /// to expose the construct/destruct/operator methods from Godot (e.g. `int`, `bool`). + /// + /// See also `BuiltinClass::from_json()` + pub fn associated_builtin_class(&self) -> Option<&BuiltinClass> { + self.builtin_class.as_ref() + } + + /// Returns an ident like `GDEXTENSION_VARIANT_TYPE_PACKED_VECTOR2_ARRAY`. + pub fn sys_variant_type(&self) -> Ident { + format_ident!("GDEXTENSION_VARIANT_TYPE_{}", self.godot_shout_name()) + } + + pub fn unsuffixed_ord_lit(&self) -> Literal { + Literal::i32_unsuffixed(self.variant_type_ord) + } } pub struct Class { - pub name: String, + pub(super) common: ClassCommons, pub is_refcounted: bool, pub is_instantiable: bool, pub inherits: Option, - pub api_type: String, + pub api_level: ClassCodegenLevel, pub constants: Vec, pub enums: Vec, pub methods: Vec, } +impl ClassLike for Class { + fn common(&self) -> &ClassCommons { + &self.common + } +} + pub struct NativeStructure { pub name: String, pub format: String, } pub struct Singleton { - pub name: String, + pub name: TyName, // Note: `type` currently has always same value as `name`, thus redundant // type_: String, } @@ -56,11 +162,6 @@ pub struct Enum { pub enumerators: Vec, } -pub struct BuiltinClassEnum { - pub name: String, - pub values: Vec, -} - // ---------------------------------------------------------------------------------------------------------------------------------------------- // Enumerators @@ -86,6 +187,14 @@ impl EnumeratorValue { EnumeratorValue::Bitfield(i) => *i as i64, } } + + /// This method is needed for platform-dependent types like raw `VariantOperator`, which can be `i32` or `u32`. + /// Do not suffix them. + /// + /// See also `BuiltinVariant::unsuffixed_ord_lit()`. + pub fn unsuffixed_lit(&self) -> Literal { + Literal::i64_unsuffixed(self.to_i64()) + } } // ---------------------------------------------------------------------------------------------------------------------------------------------- @@ -117,7 +226,7 @@ pub struct BuiltinConstant { */ pub struct Operator { - pub name: String, + pub symbol: String, //pub right_type: Option, // null if unary //pub return_type: String, } @@ -166,12 +275,12 @@ pub trait Function: fmt::Display { fn is_private(&self) -> bool { self.common().is_private } - fn direction(&self) -> FnDirection { - self.common().direction - } fn is_virtual(&self) -> bool { matches!(self.direction(), FnDirection::Virtual) } + fn direction(&self) -> FnDirection { + self.common().direction + } } #[deprecated] @@ -269,9 +378,11 @@ impl Function for ClassMethod { fn common(&self) -> &FunctionCommon { &self.common } + fn qualifier(&self) -> FnQualifier { self.qualifier } + fn surrounding_class(&self) -> Option<&TyName> { Some(&self.surrounding_class) } diff --git a/godot-codegen/src/lib.rs b/godot-codegen/src/lib.rs index ee60541d3..ea85570c3 100644 --- a/godot-codegen/src/lib.rs +++ b/godot-codegen/src/lib.rs @@ -36,9 +36,10 @@ use utilities_generator::generate_utilities_file; use crate::central_generator::{ generate_sys_builtin_lifecycle_file, generate_sys_builtin_methods_file, - generate_sys_utilities_file, BuiltinTypeMap, + generate_sys_utilities_file, }; use crate::context::NotificationEnum; +use crate::domain_models::ExtensionApi; use proc_macro2::{Ident, TokenStream}; use quote::{quote, ToTokens}; use std::path::{Path, PathBuf}; @@ -68,27 +69,30 @@ pub fn generate_sys_files( h_path: &Path, watch: &mut godot_bindings::StopWatch, ) { - let (api, build_config) = load_extension_api(watch); - let mut ctx = Context::build_from_api(&api); + let (json_api, build_config) = load_extension_api(watch); + + let mut ctx = Context::build_from_api(&json_api); watch.record("build_context"); - generate_sys_central_file(&api, &mut ctx, build_config, sys_gen_path, &mut submit_fn); + let api = ExtensionApi::from_json(&json_api, build_config, &mut ctx); + watch.record("map_domain_models"); + + generate_sys_central_file(&json_api, &api, &mut ctx, sys_gen_path, &mut submit_fn); watch.record("generate_central_file"); - let builtin_types = BuiltinTypeMap::load(&api); - generate_sys_builtin_methods_file(&api, &builtin_types, sys_gen_path, &mut ctx, &mut submit_fn); + generate_sys_builtin_methods_file(&api, sys_gen_path, &mut ctx, &mut submit_fn); watch.record("generate_builtin_methods_file"); - generate_sys_builtin_lifecycle_file(&builtin_types, sys_gen_path, &mut submit_fn); + generate_sys_builtin_lifecycle_file(&api, sys_gen_path, &mut submit_fn); watch.record("generate_builtin_lifecycle_file"); - generate_sys_classes_file(&api, sys_gen_path, watch, &mut ctx, &mut submit_fn); + generate_sys_classes_file(&json_api, sys_gen_path, watch, &mut ctx, &mut submit_fn); // watch records inside the function. - generate_sys_utilities_file(&api, sys_gen_path, &mut ctx, &mut submit_fn); + generate_sys_utilities_file(&json_api, sys_gen_path, &mut ctx, &mut submit_fn); watch.record("generate_utilities_file"); - let is_godot_4_0 = api.header.version_major == 4 && api.header.version_minor == 0; + let is_godot_4_0 = json_api.header.version_major == 4 && json_api.header.version_minor == 0; generate_sys_interface_file(h_path, sys_gen_path, is_godot_4_0, &mut submit_fn); watch.record("generate_interface_file"); } @@ -102,7 +106,10 @@ pub fn generate_core_files(core_gen_path: &Path) { let mut ctx = Context::build_from_api(&api); watch.record("build_context"); - generate_core_central_file(&api, &mut ctx, build_config, core_gen_path, &mut submit_fn); + let domain = ExtensionApi::from_json(&api, build_config, &mut ctx); + watch.record("map_domain_models"); + + generate_core_central_file(&api, &domain, &mut ctx, core_gen_path, &mut submit_fn); watch.record("generate_central_file"); generate_utilities_file(&api, &mut ctx, core_gen_path, &mut submit_fn); @@ -111,7 +118,7 @@ pub fn generate_core_files(core_gen_path: &Path) { // Class files -- currently output in godot-core; could maybe be separated cleaner // Note: deletes entire generated directory! generate_class_files( - &api, + &domain, &mut ctx, build_config, &core_gen_path.join("classes"), @@ -120,7 +127,7 @@ pub fn generate_core_files(core_gen_path: &Path) { watch.record("generate_class_files"); generate_builtin_class_files( - &api, + &domain, &mut ctx, build_config, &core_gen_path.join("builtin_classes"), @@ -129,7 +136,7 @@ pub fn generate_core_files(core_gen_path: &Path) { watch.record("generate_builtin_class_files"); generate_native_structures_files( - &api, + &domain, &mut ctx, build_config, &core_gen_path.join("native"), @@ -163,7 +170,7 @@ struct GodotTy { // ---------------------------------------------------------------------------------------------------------------------------------------------- #[derive(Clone, Debug)] -enum RustTy { +pub enum RustTy { /// `bool`, `Vector3i` BuiltinIdent(Ident), @@ -276,6 +283,7 @@ impl ToTokens for TyName { // ---------------------------------------------------------------------------------------------------------------------------------------------- /// Contains naming conventions for modules. +#[derive(Clone)] pub struct ModName { // godot_mod: String, rust_mod: Ident, @@ -317,6 +325,6 @@ struct GeneratedClassModule { } struct GeneratedBuiltinModule { - class_name: TyName, + symbol_ident: Ident, module_name: ModName, } diff --git a/godot-codegen/src/special_cases.rs b/godot-codegen/src/special_cases.rs index 3a8cf4789..60cc768c9 100644 --- a/godot-codegen/src/special_cases.rs +++ b/godot-codegen/src/special_cases.rs @@ -223,8 +223,13 @@ pub(crate) fn is_class_method_const(class_name: &TyName, godot_method: &JsonClas | ("StreamPeer", "get_double") => Some(false), */ + + _ => { + // TODO Many getters are mutably qualified (GltfAccessor::get_max, CameraAttributes::get_exposure_multiplier, ...). + // As a default, set those to const. - _ => None, + None + }, } } diff --git a/godot-codegen/src/util.rs b/godot-codegen/src/util.rs index fec556d48..9b53645b5 100644 --- a/godot-codegen/src/util.rs +++ b/godot-codegen/src/util.rs @@ -576,12 +576,6 @@ pub(crate) fn make_enumerator_ord(ord: i32) -> Literal { Literal::i32_suffixed(ord) } -/// This method is needed for platform-dependent types like raw `VariantOperator`, which can be `i32` or `u32`. -/// Do not suffix them. -pub(crate) fn make_enumerator_ord_unsuffixed(ord: i32) -> Literal { - Literal::i32_unsuffixed(ord) -} - pub(crate) fn make_bitfield_flag_ord(ord: u64) -> Literal { Literal::u64_suffixed(ord) } From 4e6d2ae2a248b30643a6653e7f3ca0af3fadb4cc Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Sun, 21 Jan 2024 12:43:07 +0100 Subject: [PATCH 07/10] Add option to `check.sh` for fixing clippy --- check.sh | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/check.sh b/check.sh index c1c26831c..b63813834 100755 --- a/check.sh +++ b/check.sh @@ -26,9 +26,10 @@ If no commands are specified, the following commands will be run: Commands: fmt format code, fail if bad - clippy validate clippy lints test run unit tests (no Godot needed) itest run integration tests (from within Godot) + clippy validate clippy lints + klippy validate + fix clippy doc generate docs for 'godot' crate dok generate docs and open in browser @@ -137,6 +138,18 @@ function cmd_clippy() { -D warnings } +function cmd_klippy() { + run cargo clippy --fix --all-targets "${extraCargoArgs[@]}" -- \ + -D clippy::suspicious \ + -D clippy::style \ + -D clippy::complexity \ + -D clippy::perf \ + -D clippy::dbg_macro \ + -D clippy::todo \ + -D clippy::unimplemented \ + -D warnings +} + function cmd_test() { run cargo test "${extraCargoArgs[@]}" } @@ -178,7 +191,7 @@ for arg in "$@"; do --double) extraCargoArgs+=("--features" "godot/double-precision") ;; - fmt | clippy | test | itest | doc | dok) + fmt | test | itest | clippy | klippy | doc | dok) cmds+=("$arg") ;; -f | --filter) From b2430b2813c18d825123be3ce6246ceaf404afc3 Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Sat, 20 Jan 2024 11:13:20 +0100 Subject: [PATCH 08/10] Codegen: move builtin sizes to domain model --- godot-codegen/src/central_generator.rs | 167 +++++++-------------- godot-codegen/src/class_generator.rs | 59 +++----- godot-codegen/src/codegen_special_cases.rs | 19 +-- godot-codegen/src/context.rs | 4 +- godot-codegen/src/domain_mapping.rs | 151 ++++++++++++------- godot-codegen/src/domain_models.rs | 80 ++++++++-- godot-codegen/src/json_models.rs | 12 +- godot-codegen/src/lib.rs | 32 ++-- godot-codegen/src/special_cases.rs | 10 +- godot-codegen/src/util.rs | 32 ++-- godot-codegen/src/utilities_generator.rs | 17 +-- 11 files changed, 316 insertions(+), 267 deletions(-) diff --git a/godot-codegen/src/central_generator.rs b/godot-codegen/src/central_generator.rs index 11e1cd741..b29deb8c9 100644 --- a/godot-codegen/src/central_generator.rs +++ b/godot-codegen/src/central_generator.rs @@ -10,15 +10,11 @@ use quote::{format_ident, quote, ToTokens, TokenStreamExt}; use std::path::Path; use crate::domain_models::{ - BuiltinMethod, BuiltinVariant, ClassLike, Constructor, Enum, Enumerator, ExtensionApi, - Function, Operator, + BuiltinMethod, BuiltinVariant, Class, ClassLike, ClassMethod, Constructor, Enumerator, + ExtensionApi, FnDirection, Function, GodotApiVersion, Operator, }; -use crate::json_models::*; -use crate::util::{ - make_builtin_method_ptr_name, make_class_method_ptr_name, option_as_slice, ClassCodegenLevel, - MethodTableKey, -}; -use crate::{codegen_special_cases, conv, ident, special_cases, util, Context, SubmitFn, TyName}; +use crate::util::{make_table_accessor_name, ClassCodegenLevel, MethodTableKey}; +use crate::{conv, ident, special_cases, util, Context, SubmitFn, TyName}; struct CentralItems { opaque_types: [Vec; 2], @@ -28,7 +24,7 @@ struct CentralItems { variant_op_enumerators_pascal: Vec, variant_op_enumerators_ord: Vec, global_enum_defs: Vec, - godot_version: JsonHeader, + godot_version: GodotApiVersion, } struct NamedMethodTable { @@ -119,20 +115,19 @@ struct AccessorMethod { // ---------------------------------------------------------------------------------------------------------------------------------------------- pub(crate) fn generate_sys_central_file( - json_api: &JsonExtensionApi, api: &ExtensionApi, ctx: &mut Context, sys_gen_path: &Path, submit_fn: &mut SubmitFn, ) { - let central_items = make_central_items(json_api, api, ctx); + let central_items = make_central_items(api, ctx); let sys_code = make_sys_code(central_items); submit_fn(sys_gen_path.join("central.rs"), sys_code); } pub(crate) fn generate_sys_classes_file( - api: &JsonExtensionApi, + api: &ExtensionApi, sys_gen_path: &Path, watch: &mut godot_bindings::StopWatch, ctx: &mut Context, @@ -148,9 +143,8 @@ pub(crate) fn generate_sys_classes_file( } pub(crate) fn generate_sys_utilities_file( - api: &JsonExtensionApi, + api: &ExtensionApi, sys_gen_path: &Path, - ctx: &mut Context, submit_fn: &mut SubmitFn, ) { let mut table = NamedMethodTable { @@ -171,13 +165,9 @@ pub(crate) fn generate_sys_utilities_file( }; for function in api.utility_functions.iter() { - if special_cases::is_utility_function_deleted(function, ctx) { - continue; - } - - let fn_name_str = &function.name; + let fn_name_str = function.name(); let field = util::make_utility_function_ptr_name(fn_name_str); - let hash = function.hash; + let hash = function.hash(); table.method_decls.push(quote! { pub #field: crate::UtilityFunctionBind, @@ -453,13 +443,12 @@ pub(crate) fn generate_core_mod_file(gen_path: &Path, submit_fn: &mut SubmitFn) } pub(crate) fn generate_core_central_file( - json_api: &JsonExtensionApi, api: &ExtensionApi, ctx: &mut Context, gen_path: &Path, submit_fn: &mut SubmitFn, ) { - let central_items = make_central_items(json_api, api, ctx); + let central_items = make_central_items(api, ctx); let core_code = make_core_code(¢ral_items); submit_fn(gen_path.join("central.rs"), core_code); @@ -555,14 +544,13 @@ fn make_sys_code(central_items: CentralItems) -> TokenStream { } } -fn make_build_config(header: &JsonHeader) -> TokenStream { - let version_string = header - .version_full_name - .strip_prefix("Godot Engine ") - .unwrap_or(&header.version_full_name); - let major = header.version_major; - let minor = header.version_minor; - let patch = header.version_patch; +fn make_build_config(header: &GodotApiVersion) -> TokenStream { + let GodotApiVersion { + major, + minor, + patch, + version_string, + } = header; // Should this be mod? quote! { @@ -681,21 +669,13 @@ fn make_core_code(central_items: &CentralItems) -> TokenStream { } } -fn make_central_items( - json_api: &JsonExtensionApi, - api: &ExtensionApi, - ctx: &mut Context, -) -> CentralItems { +fn make_central_items(api: &ExtensionApi, ctx: &mut Context) -> CentralItems { let mut opaque_types = [Vec::new(), Vec::new()]; - for class in &json_api.builtin_class_sizes { - for i in 0..2 { - if class.build_configuration == api.build_config[i] { - for JsonClassSize { name, size } in &class.sizes { - opaque_types[i].push(make_opaque_type(name, *size)); - } - break; - } - } + + for b in api.builtin_sizes.iter() { + let index = b.config.is_64bit() as usize; + + opaque_types[index].push(make_opaque_type(&b.builtin_original_name, b.size)); } let variant_operators = collect_variant_operators(api); @@ -712,7 +692,7 @@ fn make_central_items( variant_op_enumerators_pascal: Vec::new(), variant_op_enumerators_ord: Vec::new(), global_enum_defs: Vec::new(), - godot_version: json_api.header.clone(), + godot_version: api.godot_version.clone(), }; // Note: NIL is not part of this iteration, it will be added manually. @@ -746,15 +726,13 @@ fn make_central_items( .push(op.value.unsuffixed_lit()); } - for json_enum in json_api.global_enums.iter() { - // Skip those enums which are already explicitly handled. - if matches!(json_enum.name.as_str(), "Variant.Type" | "Variant.Operator") { + for enum_ in api.global_enums.iter() { + // Skip those enums which are already manually handled. + if enum_.name == "VariantType" || enum_.name == "VariantOperator" { continue; } - let domain_enum = Enum::from_json(json_enum, None); - - let def = util::make_enum_definition(&domain_enum); + let def = util::make_enum_definition(enum_); result.global_enum_defs.push(def); } @@ -802,7 +780,7 @@ fn make_builtin_lifecycle_table(api: &ExtensionApi) -> TokenStream { } fn make_class_method_table( - api: &JsonExtensionApi, + api: &ExtensionApi, api_level: ClassCodegenLevel, ctx: &mut Context, ) -> TokenStream { @@ -834,17 +812,10 @@ fn make_class_method_table( method_count: 0, }; - for class in api.classes.iter() { - let class_ty = TyName::from_godot(&class.name); - if special_cases::is_class_deleted(&class_ty) - || codegen_special_cases::is_class_excluded(&class.name) - || util::get_api_level(class) != api_level - { - continue; - } - - populate_class_methods(&mut table, class, &class_ty, ctx); - } + api.classes + .iter() + .filter(|c| c.api_level == api_level) + .for_each(|c| populate_class_methods(&mut table, c, ctx)); table.pre_init_code = quote! { let fetch_fptr = interface.classdb_get_method_bind.expect("classdb_get_method_bind absent"); @@ -882,6 +853,7 @@ fn make_named_accessors(accessors: &[AccessorMethod], fptr: &TokenStream) -> Tok result_api.append_all(code.into_iter()); } + result_api } @@ -923,48 +895,33 @@ fn make_builtin_method_table(api: &ExtensionApi, ctx: &mut Context) -> TokenStre make_method_table(table) } -fn populate_class_methods( - table: &mut IndexedMethodTable, - class: &JsonClass, - class_ty: &TyName, - ctx: &mut Context, -) { +fn populate_class_methods(table: &mut IndexedMethodTable, class: &Class, ctx: &mut Context) { // Note: already checked outside whether class is active in codegen. - let class_var = format_ident!("sname_{}", &class.name); + let class_ty = class.name(); + let class_var = format_ident!("sname_{}", class_ty.godot_ty); let mut method_inits = vec![]; - for method in option_as_slice(&class.methods) { + for method in class.methods.iter() { // Virtual methods are not part of the class API itself, but exposed as an accompanying trait. - // Earlier code to detect virtuals: method.name.starts_with('_') - if special_cases::is_class_method_deleted(class_ty, method, ctx) || method.is_virtual { + let FnDirection::Outbound { hash } = method.direction() else { continue; - } + }; // Note: varcall/ptrcall is only decided at call time; the method bind is the same for both. - let index = ctx.get_table_index(&MethodTableKey::ClassMethod { - api_level: util::get_api_level(class), - class_ty: class_ty.clone(), - method_name: method.name.clone(), - }); + let index = ctx.get_table_index(&MethodTableKey::from_class(class, method)); - let method_init = make_class_method_init(method, &class_var, class_ty); + let method_init = make_class_method_init(method, hash, &class_var, class_ty); method_inits.push(MethodInit { method_init, index }); - - println!( - "method {}, index {}, count {}", - method.name, index, table.method_count - ); table.method_count += 1; // If requested, add a named accessor for this method. - if special_cases::is_named_accessor_in_table(class_ty, &method.name) { + if special_cases::is_named_accessor_in_table(class_ty, method.godot_name()) { let class_name_str = class_ty.godot_ty.as_str(); - let method_name_str = method.name.as_str(); - let hash = method.hash.expect("hash present"); + let method_name_str = method.name(); table.named_accessors.push(AccessorMethod { - name: make_class_method_ptr_name(class_ty, method), + name: make_table_accessor_name(class_ty, method), index, lazy_key: quote! { crate::lazy_keys::ClassMethodKey { @@ -1003,24 +960,21 @@ fn populate_builtin_methods( let mut method_inits = vec![]; for method in builtin_class.methods.iter() { - let index = ctx.get_table_index(&MethodTableKey::BuiltinMethod { - builtin_ty: builtin_ty.clone(), - method_name: method.name().to_string(), - }); + let index = ctx.get_table_index(&MethodTableKey::from_builtin(builtin_class, method)); - let method_init = make_builtin_method_init(builtin, &method, index); + let method_init = make_builtin_method_init(builtin, method, index); method_inits.push(MethodInit { method_init, index }); table.method_count += 1; // If requested, add a named accessor for this method. - if special_cases::is_named_accessor_in_table(&builtin_ty, method.name()) { + if special_cases::is_named_accessor_in_table(builtin_ty, method.godot_name()) { let variant_type = builtin.sys_variant_type(); let variant_type_str = builtin.godot_original_name(); let method_name_str = method.name(); let hash = method.hash(); table.named_accessors.push(AccessorMethod { - name: make_builtin_method_ptr_name(&builtin_ty, &method), + name: make_table_accessor_name(builtin_ty, method), index, lazy_key: quote! { crate::lazy_keys::BuiltinMethodKey { @@ -1043,19 +997,13 @@ fn populate_builtin_methods( } fn make_class_method_init( - method: &JsonClassMethod, + method: &ClassMethod, + hash: i64, class_var: &Ident, class_ty: &TyName, ) -> TokenStream { let class_name_str = class_ty.godot_ty.as_str(); - let method_name_str = method.name.as_str(); - - let hash = method.hash.unwrap_or_else(|| { - panic!( - "class method has no hash: {}::{}", - class_ty.godot_ty, method_name_str - ) - }); + let method_name_str = method.godot_name(); // Could reuse lazy key, but less code like this -> faster parsing. quote! { @@ -1099,11 +1047,6 @@ fn make_builtin_method_init( } fn collect_variant_operators(api: &ExtensionApi) -> Vec<&Enumerator> { - println!( - "{:?}", - api.global_enums.iter().map(|e| &e.name).collect::>() - ); - let variant_operator_enum = api .global_enums .iter() @@ -1113,8 +1056,8 @@ fn collect_variant_operators(api: &ExtensionApi) -> Vec<&Enumerator> { variant_operator_enum.enumerators.iter().collect() } -fn make_opaque_type(name: &str, size: usize) -> TokenStream { - let name = conv::to_pascal_case(name); +fn make_opaque_type(godot_original_name: &str, size: usize) -> TokenStream { + let name = conv::to_pascal_case(godot_original_name); let (first, rest) = name.split_at(1); // Capitalize: "int" -> "Int". diff --git a/godot-codegen/src/class_generator.rs b/godot-codegen/src/class_generator.rs index 075795107..ea3f3c585 100644 --- a/godot-codegen/src/class_generator.rs +++ b/godot-codegen/src/class_generator.rs @@ -16,7 +16,7 @@ use crate::domain_models::BuiltinMethod; use crate::domain_models::*; use crate::util::{ ident, make_string_name, option_as_slice, parse_native_structures_format, safe_ident, - ClassCodegenLevel, MethodTableKey, NativeStructuresField, + MethodTableKey, NativeStructuresField, }; use crate::{ conv, special_cases, util, Context, GeneratedBuiltin, GeneratedBuiltinModule, GeneratedClass, @@ -116,7 +116,7 @@ pub(crate) fn generate_class_files( let mut modules = vec![]; for class in api.classes.iter() { - let generated_class = make_class(&class, ctx); + let generated_class = make_class(class, ctx); let file_contents = generated_class.code; let out_path = gen_path.join(format!("{}.rs", class.mod_name().rust_mod)); @@ -157,7 +157,7 @@ pub(crate) fn generate_builtin_class_files( // let godot_class_name = &class.name().godot_ty; let module_name = class.mod_name(); - let generated_class = make_builtin_class(&class, ctx); + let generated_class = make_builtin_class(class, ctx); let file_contents = generated_class.code; let out_path = gen_path.join(format!("{}.rs", module_name.rust_mod)); @@ -377,7 +377,7 @@ fn make_class(class: &Class, ctx: &mut Context) -> GeneratedClass { let FnDefinitions { functions: methods, builders, - } = make_methods(&class.methods, class_name, &api_level, ctx); + } = make_methods(class, &class.methods, ctx); let enums = make_enums(&class.enums); @@ -709,7 +709,7 @@ fn make_builtin_class(class: &BuiltinClass, ctx: &mut Context) -> GeneratedBuilt let FnDefinitions { functions: methods, builders, - } = make_builtin_methods(&class.methods, class.name(), ctx); + } = make_builtin_methods(class, &class.methods, ctx); let imports = util::make_imports(); let enums = make_enums(&class.enums); @@ -918,29 +918,24 @@ fn make_builtin_module_file(classes_and_modules: Vec) -> } } -fn make_methods( - methods: &[ClassMethod], - class_name: &TyName, - api_level: &ClassCodegenLevel, - ctx: &mut Context, -) -> FnDefinitions { - let get_method_table = api_level.table_global_getter(); +fn make_methods(class: &Class, methods: &[ClassMethod], ctx: &mut Context) -> FnDefinitions { + let get_method_table = class.api_level.table_global_getter(); - let definitions = methods.iter().map(|method| { - make_class_method_definition(method, class_name, api_level, &get_method_table, ctx) - }); + let definitions = methods + .iter() + .map(|method| make_class_method_definition(class, method, &get_method_table, ctx)); FnDefinitions::expand(definitions) } fn make_builtin_methods( + builtin_class: &BuiltinClass, methods: &[BuiltinMethod], - builtin_name: &TyName, ctx: &mut Context, ) -> FnDefinitions { let definitions = methods .iter() - .map(|method| make_builtin_method_definition(method, builtin_name, ctx)); + .map(|method| make_builtin_method_definition(builtin_class, method, ctx)); FnDefinitions::expand(definitions) } @@ -981,9 +976,8 @@ fn make_special_builtin_methods(class_name: &TyName, _ctx: &Context) -> TokenStr } fn make_class_method_definition( + class: &Class, method: &ClassMethod, - class_name: &TyName, - api_level: &ClassCodegenLevel, get_method_table: &Ident, ctx: &mut Context, ) -> FnDefinition { @@ -996,11 +990,7 @@ fn make_class_method_definition( let receiver = make_receiver(method.qualifier(), quote! { self.object_ptr }); - let table_index = ctx.get_table_index(&MethodTableKey::ClassMethod { - api_level: *api_level, - class_ty: class_name.clone(), - method_name: method.godot_name().to_string(), - }); + let table_index = ctx.get_table_index(&MethodTableKey::from_class(class, method)); let maybe_instance_id = if method.qualifier() == FnQualifier::Static { quote! { None } @@ -1009,7 +999,7 @@ fn make_class_method_definition( }; let fptr_access = if cfg!(feature = "codegen-lazy-fptrs") { - let class_name_str = &class_name.godot_ty; + let class_name_str = &class.name().godot_ty; quote! { fptr_by_key(sys::lazy_keys::ClassMethodKey { class_name: #class_name_str, @@ -1058,15 +1048,16 @@ fn make_class_method_definition( } fn make_builtin_method_definition( + builtin_class: &BuiltinClass, method: &BuiltinMethod, - builtin_name: &TyName, ctx: &mut Context, ) -> FnDefinition { let FnDirection::Outbound { hash } = method.direction() else { unreachable!("builtin methods are never virtual") }; - let method_name_str = method.name(); + let builtin_name = builtin_class.name(); + let method_name_str = method.godot_name(); let fptr_access = if cfg!(feature = "codegen-lazy-fptrs") { let variant_type = quote! { sys::VariantType::#builtin_name }; @@ -1081,10 +1072,8 @@ fn make_builtin_method_definition( }) } } else { - let table_index = ctx.get_table_index(&MethodTableKey::BuiltinMethod { - builtin_ty: builtin_name.clone(), - method_name: method_name_str.to_string(), - }); + let table_index = ctx.get_table_index(&MethodTableKey::from_builtin(builtin_class, method)); + quote! { fptr_by_index(#table_index) } }; @@ -1703,13 +1692,15 @@ fn make_all_virtual_methods( for base_name in all_base_names { let json_base_class = ctx.get_engine_class(base_name); for json_method in option_as_slice(&json_base_class.methods) { + if !json_method.is_virtual { + continue; + } + // FIXME temporary workaround, the ctx doesn't cross-over borrowed fields in ctx let hack_ptr = ctx as *const _ as *mut _; let hack_ctx = unsafe { &mut *hack_ptr }; // UB - if let Some(method) = - ClassMethod::from_json_virtual(json_method, class.name(), hack_ctx) - { + if let Some(method) = ClassMethod::from_json(json_method, class.name(), hack_ctx) { if let Some(tokens) = make_virtual_method(&method) { all_tokens.push(tokens); } diff --git a/godot-codegen/src/codegen_special_cases.rs b/godot-codegen/src/codegen_special_cases.rs index 29ea9be4e..8166745c4 100644 --- a/godot-codegen/src/codegen_special_cases.rs +++ b/godot-codegen/src/codegen_special_cases.rs @@ -57,18 +57,19 @@ fn is_type_excluded(ty: &str, ctx: &mut Context) -> bool { is_rust_type_excluded(&conv::to_rust_type(ty, None, ctx)) } +#[cfg(feature = "codegen-full")] +fn is_type_excluded(_ty: &str, _ctx: &mut Context) -> bool { + false +} + pub(crate) fn is_class_method_excluded(method: &JsonClassMethod, ctx: &mut Context) -> bool { let is_arg_or_return_excluded = |ty: &str, _ctx: &mut Context| { - let class_deleted = special_cases::is_class_deleted(&TyName::from_godot(ty)); + // First check if the type is explicitly deleted. In Godot, type names are unique without further categorization, + // so passing in a class name while checking for any types is fine. + let class_deleted = special_cases::is_godot_type_deleted(&TyName::from_godot(ty)); - #[cfg(not(feature = "codegen-full"))] - { - class_deleted || is_type_excluded(ty, _ctx) - } - #[cfg(feature = "codegen-full")] - { - class_deleted - } + // Then also check if the type is excluded from codegen (due to current Cargo feature. RHS is always false in full-codegen. + class_deleted || is_type_excluded(ty, _ctx) }; // Exclude if return type contains an excluded type. diff --git a/godot-codegen/src/context.rs b/godot-codegen/src/context.rs index 308a7e0e0..f9bcecdb7 100644 --- a/godot-codegen/src/context.rs +++ b/godot-codegen/src/context.rs @@ -6,7 +6,7 @@ */ use crate::json_models::{ - JsonBuiltin, JsonBuiltinMethod, JsonClass, JsonClassConstant, JsonClassMethod, + JsonBuiltinClass, JsonBuiltinMethod, JsonClass, JsonClassConstant, JsonClassMethod, }; use crate::util::{option_as_slice, MethodTableKey}; use crate::{ @@ -185,7 +185,7 @@ impl<'a> Context<'a> { } fn populate_builtin_class_table_indices( - builtin: &JsonBuiltin, + builtin: &JsonBuiltinClass, methods: &[JsonBuiltinMethod], ctx: &mut Context, ) { diff --git a/godot-codegen/src/domain_mapping.rs b/godot-codegen/src/domain_mapping.rs index 5fe3c26fc..f33831d41 100644 --- a/godot-codegen/src/domain_mapping.rs +++ b/godot-codegen/src/domain_mapping.rs @@ -7,35 +7,24 @@ use crate::context::Context; use crate::domain_models::{ - BuiltinClass, BuiltinMethod, BuiltinVariant, Class, ClassCommons, ClassConstant, - ClassConstantValue, ClassMethod, Constructor, Enum, Enumerator, EnumeratorValue, ExtensionApi, - FnDirection, FnParam, FnQualifier, FnReturn, FunctionCommon, NativeStructure, Operator, - Singleton, UtilityFunction, + BuildConfiguration, BuiltinClass, BuiltinMethod, BuiltinSize, BuiltinVariant, Class, + ClassCommons, ClassConstant, ClassConstantValue, ClassMethod, Constructor, Enum, Enumerator, + EnumeratorValue, ExtensionApi, FnDirection, FnParam, FnQualifier, FnReturn, FunctionCommon, + GodotApiVersion, NativeStructure, Operator, Singleton, UtilityFunction, }; use crate::json_models::{ - JsonBuiltin, JsonBuiltinMethod, JsonClass, JsonClassConstant, JsonClassMethod, JsonConstructor, - JsonEnum, JsonEnumConstant, JsonExtensionApi, JsonMethodReturn, JsonNativeStructure, - JsonOperator, JsonSingleton, JsonUtilityFunction, + JsonBuiltinClass, JsonBuiltinMethod, JsonBuiltinSizes, JsonClass, JsonClassConstant, + JsonClassMethod, JsonConstructor, JsonEnum, JsonEnumConstant, JsonExtensionApi, JsonHeader, + JsonMethodReturn, JsonNativeStructure, JsonOperator, JsonSingleton, JsonUtilityFunction, }; use crate::util::{get_api_level, ident, option_as_slice}; -use crate::{codegen_special_cases, conv, special_cases, ModName, TyName}; +use crate::{conv, special_cases, ModName, TyName}; use proc_macro2::Ident; use std::collections::HashMap; // ---------------------------------------------------------------------------------------------------------------------------------------------- // Top-level -/* - -pub struct ExtensionApiDomain { - pub builtins: Vec, - pub classes: Vec, - pub enums: Vec, - pub native_structures: Vec, - pub singletons: Vec, - pub utility_functions: Vec, -} - */ impl ExtensionApi { pub fn from_json( json: &JsonExtensionApi, @@ -66,7 +55,30 @@ impl ExtensionApi { .map(|json| Enum::from_json(json, None)) .collect(), build_config, + godot_version: GodotApiVersion::from_json(&json.header), + builtin_sizes: Self::builtin_size_from_json(&json.builtin_class_sizes), + } + } + + fn builtin_size_from_json(json_builtin_sizes: &[JsonBuiltinSizes]) -> Vec { + let mut result = Vec::new(); + + for json_builtin_size in json_builtin_sizes { + let build_config_str = json_builtin_size.build_configuration.as_str(); + let config = BuildConfiguration::from_json(build_config_str); + + if config.is_applicable() { + for size_for_config in &json_builtin_size.sizes { + result.push(BuiltinSize { + builtin_original_name: size_for_config.name.clone(), + config, + size: size_for_config.size, + }); + } + } } + + result } } @@ -76,9 +88,7 @@ impl ExtensionApi { impl Class { pub fn from_json(json: &JsonClass, ctx: &mut Context) -> Option { let ty_name = TyName::from_godot(&json.name); - if special_cases::is_class_deleted(&ty_name) - || codegen_special_cases::is_class_excluded(ty_name.godot_ty.as_str()) - { + if special_cases::is_class_deleted(&ty_name) { return None; } @@ -122,7 +132,7 @@ impl Class { } impl BuiltinClass { - pub fn from_json(json: &JsonBuiltin, ctx: &mut Context) -> Option { + pub fn from_json(json: &JsonBuiltinClass, ctx: &mut Context) -> Option { let ty_name = TyName::from_godot(&json.name); if special_cases::is_builtin_type_deleted(&ty_name) { @@ -184,19 +194,11 @@ impl Singleton { // ---------------------------------------------------------------------------------------------------------------------------------------------- // Builtin variants -/* -pub struct BuiltinVariant { - pub(super) name: String, - pub(super) ty: Option, - pub variant_type_ord: i32, -} - */ - impl BuiltinVariant { /// Returns all builtins, ordered by enum ordinal value. pub fn all_from_json( global_enums: &[JsonEnum], - builtin_classes: &[JsonBuiltin], + builtin_classes: &[JsonBuiltinClass], ctx: &mut Context, ) -> Vec { fn normalize(name: &str) -> String { @@ -209,7 +211,7 @@ impl BuiltinVariant { .expect("missing enum for VariantType in JSON"); // Make HashMap from builtin_classes, keyed by a normalized version of their names (all-lower, no underscores) - let builtin_classes: HashMap = builtin_classes + let builtin_classes: HashMap = builtin_classes .iter() .map(|c| (normalize(&c.name), c)) .collect(); @@ -218,20 +220,25 @@ impl BuiltinVariant { .values .iter() .filter_map(|e| { - let shout_case = e + let json_shout_case = e .name .strip_prefix("TYPE_") .expect("variant enumerator lacks prefix 'TYPE_'"); - if shout_case == "NIL" || shout_case == "MAX" { + if json_shout_case == "NIL" || json_shout_case == "MAX" { return None; } - let name = normalize(shout_case); - let ty = builtin_classes.get(&name).map(|b| *b); - let ord = e.to_enum_ord(); + let name = normalize(json_shout_case); + let json_builtin_class = builtin_classes.get(&name).copied(); + let json_ord = e.to_enum_ord(); - Some(Self::from_json(shout_case, ord, ty, ctx)) + Some(Self::from_json( + json_shout_case, + json_ord, + json_builtin_class, + ctx, + )) }) .collect::>(); @@ -242,20 +249,17 @@ impl BuiltinVariant { pub fn from_json( json_variant_enumerator_name: &str, json_variant_enumerator_ord: i32, - json_builtin: Option<&JsonBuiltin>, + json_builtin_class: Option<&JsonBuiltinClass>, ctx: &mut Context, ) -> Self { let builtin_class; let godot_original_name; - if let Some(json_builtin) = json_builtin { + // Nil, int, float etc. are not represented by a BuiltinVariant. + // Object has no BuiltinClass, but still gets its BuiltinVariant instance. + if let Some(json_builtin) = json_builtin_class { builtin_class = BuiltinClass::from_json(json_builtin, ctx); godot_original_name = json_builtin.name.clone(); - // assert!( - // builtin_class.is_some() || special_cases::is_class_deleted(), - // "no builtin class `{}`", - // json_builtin.name - // ); } else { assert_eq!(json_variant_enumerator_name, "OBJECT"); @@ -266,7 +270,7 @@ impl BuiltinVariant { Self { godot_original_name, godot_shout_name: json_variant_enumerator_name.to_string(), // Without `TYPE_` prefix. - godot_snake_name: conv::to_snake_case(&json_variant_enumerator_name), + godot_snake_name: conv::to_snake_case(json_variant_enumerator_name), builtin_class, variant_type_ord: json_variant_enumerator_ord, } @@ -293,6 +297,38 @@ impl Operator { } } +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Build config + version + +impl BuildConfiguration { + pub fn from_json(json: &str) -> Self { + match json { + "float_32" => Self::Float32, + "float_64" => Self::Float64, + "double_32" => Self::Double32, + "double_64" => Self::Double64, + _ => panic!("invalid build configuration: {}", json), + } + } +} + +impl GodotApiVersion { + pub fn from_json(json: &JsonHeader) -> Self { + let version_string = json + .version_full_name + .strip_prefix("Godot Engine ") + .unwrap_or(&json.version_full_name) + .to_string(); + + Self { + major: json.version_major, + minor: json.version_minor, + patch: json.version_patch, + version_string, + } + } +} + // ---------------------------------------------------------------------------------------------------------------------------------------------- // Functions @@ -340,6 +376,12 @@ impl ClassMethod { class_name: &TyName, ctx: &mut Context, ) -> Option { + assert!(!special_cases::is_class_deleted(class_name)); + + if special_cases::is_class_method_deleted(class_name, method, ctx) { + return None; + } + if method.is_virtual { Self::from_json_virtual(method, class_name, ctx) } else { @@ -347,15 +389,12 @@ impl ClassMethod { } } - pub fn from_json_outbound( + fn from_json_outbound( method: &JsonClassMethod, class_name: &TyName, ctx: &mut Context, ) -> Option { - if method.is_virtual { - return None; - } - + assert!(!method.is_virtual); let hash = method .hash .expect("hash absent for non-virtual class method"); @@ -370,15 +409,13 @@ impl ClassMethod { ctx, ) } - pub fn from_json_virtual( + + fn from_json_virtual( method: &JsonClassMethod, class_name: &TyName, ctx: &mut Context, ) -> Option { - if !method.is_virtual { - return None; - } - + assert!(method.is_virtual); assert!( method.hash.is_none(), "hash present for virtual class method" diff --git a/godot-codegen/src/domain_models.rs b/godot-codegen/src/domain_models.rs index c0f321224..211f38fb4 100644 --- a/godot-codegen/src/domain_models.rs +++ b/godot-codegen/src/domain_models.rs @@ -27,6 +27,10 @@ pub struct ExtensionApi { pub utility_functions: Vec, pub global_enums: Vec, pub build_config: [&'static str; 2], + pub godot_version: GodotApiVersion, + + /// Map `(original Godot name, build config) -> builtin size` in bytes. + pub builtin_sizes: Vec, } impl ExtensionApi { @@ -214,17 +218,6 @@ pub enum ClassConstantValue { I64(i64), } -/* -// Constants of builtin types have a string value like "Vector2(1, 1)", hence also a type field - -pub struct BuiltinConstant { - pub name: String, - #[nserde(rename = "type")] - pub type_: String, - pub value: String, -} -*/ - pub struct Operator { pub symbol: String, //pub right_type: Option, // null if unary @@ -237,6 +230,62 @@ pub struct Constructor { // pub parameters: Vec, } +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Build config + version + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +pub enum BuildConfiguration { + Float32, + Float64, + Double32, + Double64, +} + +impl BuildConfiguration { + #[cfg(feature = "double-precision")] + pub fn is_applicable(self) -> bool { + matches!(self, Self::Double32 | Self::Double64) + } + + #[cfg(not(feature = "double-precision"))] + pub fn is_applicable(self) -> bool { + matches!(self, Self::Float32 | Self::Float64) + } + + // Rewrite the above using #[cfg]. + #[cfg(feature = "double-precision")] + pub fn all_applicable() -> [BuildConfiguration; 2] { + [BuildConfiguration::Double32, BuildConfiguration::Double64] + } + + #[cfg(not(feature = "double-precision"))] + pub fn all_applicable() -> [BuildConfiguration; 2] { + [BuildConfiguration::Float32, BuildConfiguration::Float64] + } + + pub fn is_64bit(self) -> bool { + matches!(self, Self::Float64 | Self::Double64) + } +} + +pub struct BuiltinSize { + pub builtin_original_name: String, + pub config: BuildConfiguration, + pub size: usize, +} + +/// Godot API version (from the JSON; not runtime version). +// Could be consolidated with versions in other part of codegen, e.g. the one in godot-bindings. +#[derive(Clone)] +pub struct GodotApiVersion { + pub major: u8, + pub minor: u8, + pub patch: u8, + + /// Without "Godot Engine " prefix. + pub version_string: String, +} + // ---------------------------------------------------------------------------------------------------------------------------------------------- /// Stuff that is in every of the "function" types. @@ -301,6 +350,15 @@ pub struct UtilityFunction { pub(super) common: FunctionCommon, } +impl UtilityFunction { + pub fn hash(&self) -> i64 { + match self.direction() { + FnDirection::Virtual => unreachable!("utility function cannot be virtual"), + FnDirection::Outbound { hash } => hash, + } + } +} + impl Function for UtilityFunction { fn common(&self) -> &FunctionCommon { &self.common diff --git a/godot-codegen/src/json_models.rs b/godot-codegen/src/json_models.rs index 7d0760f06..e6618a0a3 100644 --- a/godot-codegen/src/json_models.rs +++ b/godot-codegen/src/json_models.rs @@ -18,8 +18,8 @@ use nanoserde::DeJson; #[derive(DeJson)] pub struct JsonExtensionApi { pub header: JsonHeader, - pub builtin_class_sizes: Vec, - pub builtin_classes: Vec, + pub builtin_class_sizes: Vec, + pub builtin_classes: Vec, pub classes: Vec, pub global_enums: Vec, pub utility_functions: Vec, @@ -38,19 +38,19 @@ pub struct JsonHeader { } #[derive(DeJson)] -pub struct JsonClassSizes { +pub struct JsonBuiltinSizes { pub build_configuration: String, - pub sizes: Vec, + pub sizes: Vec, } #[derive(DeJson)] -pub struct JsonClassSize { +pub struct JsonBuiltinSizeForConfig { pub name: String, pub size: usize, } #[derive(DeJson)] -pub struct JsonBuiltin { +pub struct JsonBuiltinClass { pub name: String, pub indexing_return_type: Option, pub is_keyed: bool, diff --git a/godot-codegen/src/lib.rs b/godot-codegen/src/lib.rs index ea85570c3..092c7599d 100644 --- a/godot-codegen/src/lib.rs +++ b/godot-codegen/src/lib.rs @@ -77,7 +77,11 @@ pub fn generate_sys_files( let api = ExtensionApi::from_json(&json_api, build_config, &mut ctx); watch.record("map_domain_models"); - generate_sys_central_file(&json_api, &api, &mut ctx, sys_gen_path, &mut submit_fn); + // TODO if ctx is no longer needed for below functions: + // Deallocate all the JSON models; no longer needed for codegen. + // drop(json_api); + + generate_sys_central_file(&api, &mut ctx, sys_gen_path, &mut submit_fn); watch.record("generate_central_file"); generate_sys_builtin_methods_file(&api, sys_gen_path, &mut ctx, &mut submit_fn); @@ -86,13 +90,13 @@ pub fn generate_sys_files( generate_sys_builtin_lifecycle_file(&api, sys_gen_path, &mut submit_fn); watch.record("generate_builtin_lifecycle_file"); - generate_sys_classes_file(&json_api, sys_gen_path, watch, &mut ctx, &mut submit_fn); + generate_sys_classes_file(&api, sys_gen_path, watch, &mut ctx, &mut submit_fn); // watch records inside the function. - generate_sys_utilities_file(&json_api, sys_gen_path, &mut ctx, &mut submit_fn); + generate_sys_utilities_file(&api, sys_gen_path, &mut submit_fn); watch.record("generate_utilities_file"); - let is_godot_4_0 = json_api.header.version_major == 4 && json_api.header.version_minor == 0; + let is_godot_4_0 = api.godot_version.major == 4 && api.godot_version.minor == 0; generate_sys_interface_file(h_path, sys_gen_path, is_godot_4_0, &mut submit_fn); watch.record("generate_interface_file"); } @@ -102,23 +106,27 @@ pub fn generate_core_files(core_gen_path: &Path) { generate_core_mod_file(core_gen_path, &mut submit_fn); - let (api, build_config) = load_extension_api(&mut watch); - let mut ctx = Context::build_from_api(&api); + let (json_api, build_config) = load_extension_api(&mut watch); + let mut ctx = Context::build_from_api(&json_api); watch.record("build_context"); - let domain = ExtensionApi::from_json(&api, build_config, &mut ctx); + let api = ExtensionApi::from_json(&json_api, build_config, &mut ctx); watch.record("map_domain_models"); - generate_core_central_file(&api, &domain, &mut ctx, core_gen_path, &mut submit_fn); + // TODO if ctx is no longer needed for below functions: + // Deallocate all the JSON models; no longer needed for codegen. + // drop(json_api); + + generate_core_central_file(&api, &mut ctx, core_gen_path, &mut submit_fn); watch.record("generate_central_file"); - generate_utilities_file(&api, &mut ctx, core_gen_path, &mut submit_fn); + generate_utilities_file(&api, core_gen_path, &mut submit_fn); watch.record("generate_utilities_file"); // Class files -- currently output in godot-core; could maybe be separated cleaner // Note: deletes entire generated directory! generate_class_files( - &domain, + &api, &mut ctx, build_config, &core_gen_path.join("classes"), @@ -127,7 +135,7 @@ pub fn generate_core_files(core_gen_path: &Path) { watch.record("generate_class_files"); generate_builtin_class_files( - &domain, + &api, &mut ctx, build_config, &core_gen_path.join("builtin_classes"), @@ -136,7 +144,7 @@ pub fn generate_core_files(core_gen_path: &Path) { watch.record("generate_builtin_class_files"); generate_native_structures_files( - &domain, + &api, &mut ctx, build_config, &core_gen_path.join("native"), diff --git a/godot-codegen/src/special_cases.rs b/godot-codegen/src/special_cases.rs index 60cc768c9..17652f129 100644 --- a/godot-codegen/src/special_cases.rs +++ b/godot-codegen/src/special_cases.rs @@ -48,14 +48,18 @@ pub(crate) fn is_class_method_deleted(class_name: &TyName, method: &JsonClassMet } } -#[rustfmt::skip] pub(crate) fn is_class_deleted(class_name: &TyName) -> bool { + codegen_special_cases::is_class_excluded(&class_name.godot_ty) + || is_godot_type_deleted(class_name) +} + +pub(crate) fn is_godot_type_deleted(ty_name: &TyName) -> bool { // Exclude experimental APIs unless opted-in. - if !cfg!(feature = "experimental-godot-api") && is_class_experimental(class_name) { + if !cfg!(feature = "experimental-godot-api") && is_class_experimental(ty_name) { return true; } - let class_name = class_name.godot_ty.as_str(); + let class_name = ty_name.godot_ty.as_str(); // OpenXR has not been available for macOS before 4.2. // See e.g. https://github.com/GodotVR/godot-xr-tools/issues/479. diff --git a/godot-codegen/src/util.rs b/godot-codegen/src/util.rs index 9b53645b5..100275930 100644 --- a/godot-codegen/src/util.rs +++ b/godot-codegen/src/util.rs @@ -5,14 +5,15 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::json_models::{JsonClass, JsonClassConstant, JsonClassMethod}; +use crate::json_models::{JsonClass, JsonClassConstant}; use crate::{conv, RustTy, TyName}; use proc_macro2::{Ident, Literal, TokenStream}; use quote::{format_ident, quote}; use crate::domain_models::{ - BuiltinMethod, ClassConstant, ClassConstantValue, Enum, Enumerator, EnumeratorValue, Function, + BuiltinClass, BuiltinMethod, Class, ClassConstant, ClassConstantValue, ClassLike, ClassMethod, + Enum, Enumerator, EnumeratorValue, Function, }; use std::fmt; @@ -98,6 +99,21 @@ pub(crate) enum MethodTableKey { } impl MethodTableKey { + pub fn from_class(class: &Class, method: &ClassMethod) -> MethodTableKey { + Self::ClassMethod { + api_level: class.api_level, + class_ty: class.name().clone(), + method_name: method.godot_name().to_string(), + } + } + + pub fn from_builtin(builtin_class: &BuiltinClass, method: &BuiltinMethod) -> MethodTableKey { + Self::BuiltinMethod { + builtin_ty: builtin_class.name().clone(), + method_name: method.godot_name().to_string(), + } + } + /// Maps the method table key to a "category", meaning a distinct method table. /// /// Categories have independent address spaces for indices, meaning they begin again at 0 for each new category. @@ -162,19 +178,11 @@ pub(crate) fn make_imports() -> TokenStream { } // Use &ClassMethod instead of &str, to make sure it's the original Godot name and no rename. -pub(crate) fn make_class_method_ptr_name(class_ty: &TyName, method: &JsonClassMethod) -> Ident { +pub(crate) fn make_table_accessor_name(class_ty: &TyName, method: &dyn Function) -> Ident { format_ident!( "{}__{}", conv::to_snake_case(&class_ty.godot_ty), - method.name - ) -} - -pub(crate) fn make_builtin_method_ptr_name(builtin_ty: &TyName, method: &BuiltinMethod) -> Ident { - format_ident!( - "{}__{}", - conv::to_snake_case(&builtin_ty.godot_ty), - method.name() + method.godot_name() ) } diff --git a/godot-codegen/src/utilities_generator.rs b/godot-codegen/src/utilities_generator.rs index 8b76d0186..56e82bf64 100644 --- a/godot-codegen/src/utilities_generator.rs +++ b/godot-codegen/src/utilities_generator.rs @@ -10,20 +10,19 @@ use std::path::Path; use quote::quote; use crate::class_generator::make_utility_function_definition; -use crate::domain_models::UtilityFunction; -use crate::json_models::*; -use crate::{util, Context, SubmitFn}; +use crate::domain_models::ExtensionApi; +use crate::{util, SubmitFn}; pub(crate) fn generate_utilities_file( - api: &JsonExtensionApi, - ctx: &mut Context, + api: &ExtensionApi, gen_path: &Path, submit_fn: &mut SubmitFn, ) { - // note: category unused -> could be their own mod - let utility_fn_defs = api.utility_functions.iter().filter_map(|utility_fn| { - UtilityFunction::from_json(utility_fn, ctx).map(|f| make_utility_function_definition(&f)) - }); + // Note: category unused -> could be their own mod. + let utility_fn_defs = api + .utility_functions + .iter() + .map(make_utility_function_definition); let imports = util::make_imports(); From 0eb3c4daec316254737c0a0532a771da0d528554 Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Sun, 21 Jan 2024 14:26:26 +0100 Subject: [PATCH 09/10] Codegen: reorganize symbols in `models` directory --- godot-codegen/src/central_generator.rs | 6 +- godot-codegen/src/class_generator.rs | 6 +- godot-codegen/src/codegen_special_cases.rs | 8 +- godot-codegen/src/context.rs | 9 +- godot-codegen/src/conv/type_conversions.rs | 3 +- godot-codegen/src/lib.rs | 166 ++--------------- .../{domain_models.rs => models/domain.rs} | 167 ++++++++++++++++-- .../src/{ => models}/domain_mapping.rs | 8 +- .../src/{json_models.rs => models/json.rs} | 2 +- godot-codegen/src/models/mod.rs | 11 ++ godot-codegen/src/models/tokens.rs | 6 + godot-codegen/src/special_cases.rs | 5 +- godot-codegen/src/util.rs | 10 +- godot-codegen/src/utilities_generator.rs | 2 +- 14 files changed, 210 insertions(+), 199 deletions(-) rename godot-codegen/src/{domain_models.rs => models/domain.rs} (77%) rename godot-codegen/src/{ => models}/domain_mapping.rs (99%) rename godot-codegen/src/{json_models.rs => models/json.rs} (99%) create mode 100644 godot-codegen/src/models/mod.rs create mode 100644 godot-codegen/src/models/tokens.rs diff --git a/godot-codegen/src/central_generator.rs b/godot-codegen/src/central_generator.rs index b29deb8c9..c1b8835c1 100644 --- a/godot-codegen/src/central_generator.rs +++ b/godot-codegen/src/central_generator.rs @@ -9,12 +9,12 @@ use proc_macro2::{Ident, Literal, TokenStream}; use quote::{format_ident, quote, ToTokens, TokenStreamExt}; use std::path::Path; -use crate::domain_models::{ +use crate::models::domain::{ BuiltinMethod, BuiltinVariant, Class, ClassLike, ClassMethod, Constructor, Enumerator, - ExtensionApi, FnDirection, Function, GodotApiVersion, Operator, + ExtensionApi, FnDirection, Function, GodotApiVersion, Operator, TyName, }; use crate::util::{make_table_accessor_name, ClassCodegenLevel, MethodTableKey}; -use crate::{conv, ident, special_cases, util, Context, SubmitFn, TyName}; +use crate::{conv, ident, special_cases, util, Context, SubmitFn}; struct CentralItems { opaque_types: [Vec; 2], diff --git a/godot-codegen/src/class_generator.rs b/godot-codegen/src/class_generator.rs index ea3f3c585..e84c53734 100644 --- a/godot-codegen/src/class_generator.rs +++ b/godot-codegen/src/class_generator.rs @@ -12,15 +12,15 @@ use quote::{format_ident, quote}; use std::path::Path; use crate::context::NotificationEnum; -use crate::domain_models::BuiltinMethod; -use crate::domain_models::*; +use crate::models::domain::BuiltinMethod; +use crate::models::domain::*; use crate::util::{ ident, make_string_name, option_as_slice, parse_native_structures_format, safe_ident, MethodTableKey, NativeStructuresField, }; use crate::{ conv, special_cases, util, Context, GeneratedBuiltin, GeneratedBuiltinModule, GeneratedClass, - GeneratedClassModule, ModName, RustTy, SubmitFn, TyName, + GeneratedClassModule, ModName, SubmitFn, }; // ---------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/godot-codegen/src/codegen_special_cases.rs b/godot-codegen/src/codegen_special_cases.rs index 8166745c4..0678c5a44 100644 --- a/godot-codegen/src/codegen_special_cases.rs +++ b/godot-codegen/src/codegen_special_cases.rs @@ -10,8 +10,9 @@ // TODO make this file private and only accessed by special_cases.rs. use crate::context::Context; -use crate::json_models::{JsonBuiltinMethod, JsonClassMethod, JsonUtilityFunction}; -use crate::{special_cases, TyName}; +use crate::models::domain::TyName; +use crate::models::json::{JsonBuiltinMethod, JsonClassMethod, JsonUtilityFunction}; +use crate::special_cases; pub(crate) fn is_builtin_method_excluded(method: &JsonBuiltinMethod) -> bool { // TODO Fall back to varcall (recent addition in GDExtension API). @@ -31,7 +32,8 @@ pub(crate) fn is_class_excluded(_godot_class_name: &str) -> bool { #[cfg(not(feature = "codegen-full"))] fn is_type_excluded(ty: &str, ctx: &mut Context) -> bool { - use crate::{conv, RustTy}; + use crate::conv; + use crate::models::domain::RustTy; fn is_rust_type_excluded(ty: &RustTy) -> bool { match ty { diff --git a/godot-codegen/src/context.rs b/godot-codegen/src/context.rs index f9bcecdb7..7791d94bd 100644 --- a/godot-codegen/src/context.rs +++ b/godot-codegen/src/context.rs @@ -5,13 +5,12 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::json_models::{ +use crate::models::domain::{GodotTy, RustTy, TyName}; +use crate::models::json::{ JsonBuiltinClass, JsonBuiltinMethod, JsonClass, JsonClassConstant, JsonClassMethod, }; use crate::util::{option_as_slice, MethodTableKey}; -use crate::{ - codegen_special_cases, special_cases, util, GodotTy, JsonExtensionApi, RustTy, TyName, -}; +use crate::{codegen_special_cases, special_cases, util, JsonExtensionApi}; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, ToTokens}; use std::collections::{HashMap, HashSet}; @@ -332,7 +331,7 @@ impl ToTokens for NotificationEnum { /// Maintains class hierarchy. Uses Rust class names, not Godot ones. #[derive(Default)] -pub(crate) struct InheritanceTree { +pub struct InheritanceTree { derived_to_base: HashMap, } diff --git a/godot-codegen/src/conv/type_conversions.rs b/godot-codegen/src/conv/type_conversions.rs index 2696e1d2f..bf2c27173 100644 --- a/godot-codegen/src/conv/type_conversions.rs +++ b/godot-codegen/src/conv/type_conversions.rs @@ -12,9 +12,10 @@ use quote::{quote, ToTokens}; use std::fmt; use crate::context::Context; +use crate::models::domain::{GodotTy, RustTy, TyName}; use crate::special_cases::is_builtin_type_scalar; use crate::util::{ident, unmap_meta}; -use crate::{conv, GodotTy, ModName, RustTy, TyName}; +use crate::{conv, ModName}; // ---------------------------------------------------------------------------------------------------------------------------------------------- // Godot -> Rust types diff --git a/godot-codegen/src/lib.rs b/godot-codegen/src/lib.rs index 092c7599d..5ceb481c9 100644 --- a/godot-codegen/src/lib.rs +++ b/godot-codegen/src/lib.rs @@ -10,10 +10,8 @@ mod class_generator; mod codegen_special_cases; mod context; mod conv; -mod domain_mapping; -mod domain_models; mod interface_generator; -mod json_models; +mod models; mod special_cases; mod util; mod utilities_generator; @@ -21,27 +19,22 @@ mod utilities_generator; #[cfg(test)] mod tests; -use central_generator::{ - generate_core_central_file, generate_core_mod_file, generate_sys_central_file, - generate_sys_classes_file, -}; -use class_generator::{ - generate_builtin_class_files, generate_class_files, generate_native_structures_files, -}; -use context::Context; -use interface_generator::generate_sys_interface_file; -use json_models::{load_extension_api, JsonExtensionApi}; -use util::ident; -use utilities_generator::generate_utilities_file; - use crate::central_generator::{ - generate_sys_builtin_lifecycle_file, generate_sys_builtin_methods_file, + generate_core_central_file, generate_core_mod_file, generate_sys_builtin_lifecycle_file, + generate_sys_builtin_methods_file, generate_sys_central_file, generate_sys_classes_file, generate_sys_utilities_file, }; -use crate::context::NotificationEnum; -use crate::domain_models::ExtensionApi; +use crate::class_generator::{ + generate_builtin_class_files, generate_class_files, generate_native_structures_files, +}; +use crate::context::{Context, NotificationEnum}; +use crate::interface_generator::generate_sys_interface_file; +use crate::models::domain::{ExtensionApi, TyName}; +use crate::models::json::{load_extension_api, JsonExtensionApi}; +use crate::util::ident; +use crate::utilities_generator::generate_utilities_file; use proc_macro2::{Ident, TokenStream}; -use quote::{quote, ToTokens}; +use quote::ToTokens; use std::path::{Path, PathBuf}; pub type SubmitFn = dyn FnMut(PathBuf, TokenStream); @@ -155,139 +148,6 @@ pub fn generate_core_files(core_gen_path: &Path) { watch.write_stats_to(&core_gen_path.join("codegen-stats.txt")); } -// ---------------------------------------------------------------------------------------------------------------------------------------------- -// Shared utility types - -// Same as above, without lifetimes - -#[derive(Clone, Eq, PartialEq, Hash, Debug)] -struct GodotTy { - ty: String, - meta: Option, -} - -// impl GodotTy { -// fn new<'a>(ty: &'a String, meta: &'a Option) -> Self { -// Self { -// ty: ty.clone(), -// meta: meta.clone(), -// } -// } -// } - -// ---------------------------------------------------------------------------------------------------------------------------------------------- - -#[derive(Clone, Debug)] -pub enum RustTy { - /// `bool`, `Vector3i` - BuiltinIdent(Ident), - - /// `Array` - BuiltinArray(TokenStream), - - /// C-style raw pointer to a `RustTy`. - RawPointer { inner: Box, is_const: bool }, - - /// `Array>` - EngineArray { - tokens: TokenStream, - #[allow(dead_code)] // only read in minimal config - elem_class: String, - }, - - /// `module::Enum` - EngineEnum { - tokens: TokenStream, - /// `None` for globals - #[allow(dead_code)] // only read in minimal config - surrounding_class: Option, - }, - - /// `module::Bitfield` - EngineBitfield { - tokens: TokenStream, - /// `None` for globals - #[allow(dead_code)] // only read in minimal config - surrounding_class: Option, - }, - - /// `Gd` - EngineClass { - /// Tokens with full `Gd` - tokens: TokenStream, - /// only inner `T` - #[allow(dead_code)] // only read in minimal config - inner_class: Ident, - }, -} - -impl RustTy { - pub fn return_decl(&self) -> TokenStream { - match self { - Self::EngineClass { tokens, .. } => quote! { -> Option<#tokens> }, - other => quote! { -> #other }, - } - } -} - -impl ToTokens for RustTy { - fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - RustTy::BuiltinIdent(ident) => ident.to_tokens(tokens), - RustTy::BuiltinArray(path) => path.to_tokens(tokens), - RustTy::RawPointer { - inner, - is_const: true, - } => quote! { *const #inner }.to_tokens(tokens), - RustTy::RawPointer { - inner, - is_const: false, - } => 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), - } - } -} - -// ---------------------------------------------------------------------------------------------------------------------------------------------- - -/// Contains multiple naming conventions for types (classes, builtin classes, enums). -// TODO(bromeon, 2023-09): see if it makes sense to unify this with TypeNames (which is mostly used in central generator) -#[derive(Clone, Eq, PartialEq, Hash)] -pub struct TyName { - godot_ty: String, - rust_ty: Ident, -} - -impl TyName { - fn from_godot(godot_ty: &str) -> Self { - Self { - godot_ty: godot_ty.to_owned(), - rust_ty: ident(&conv::to_pascal_case(godot_ty)), - } - } - - fn description(&self) -> String { - if self.rust_ty == self.godot_ty { - self.godot_ty.clone() - } else { - format!("{} [renamed {}]", self.godot_ty, self.rust_ty) - } - } - - fn virtual_trait_name(&self) -> String { - format!("I{}", self.rust_ty) - } -} - -impl ToTokens for TyName { - fn to_tokens(&self, tokens: &mut TokenStream) { - self.rust_ty.to_tokens(tokens) - } -} - // ---------------------------------------------------------------------------------------------------------------------------------------------- /// Contains naming conventions for modules. diff --git a/godot-codegen/src/domain_models.rs b/godot-codegen/src/models/domain.rs similarity index 77% rename from godot-codegen/src/domain_models.rs rename to godot-codegen/src/models/domain.rs index 211f38fb4..0eedba5f4 100644 --- a/godot-codegen/src/domain_models.rs +++ b/godot-codegen/src/models/domain.rs @@ -11,12 +11,12 @@ // Domain models use crate::context::Context; -use crate::json_models::{JsonMethodArg, JsonMethodReturn}; -use crate::util::{option_as_slice, safe_ident, ClassCodegenLevel}; -use crate::{conv, ModName, RustTy, TyName}; +use crate::models::json::{JsonMethodArg, JsonMethodReturn}; +use crate::util::{ident, option_as_slice, safe_ident, ClassCodegenLevel}; +use crate::{conv, ModName}; use proc_macro2::{Ident, Literal, TokenStream}; -use quote::{format_ident, quote}; +use quote::{format_ident, quote, ToTokens}; use std::fmt; pub struct ExtensionApi { @@ -64,8 +64,8 @@ pub struct ClassCommons { } pub struct BuiltinClass { - pub(super) common: ClassCommons, - pub(super) inner_name: TyName, + pub common: ClassCommons, + pub inner_name: TyName, pub methods: Vec, pub constructors: Vec, pub operators: Vec, @@ -87,10 +87,10 @@ impl ClassLike for BuiltinClass { /// All information about a builtin type, including its type (if available). pub struct BuiltinVariant { - pub(super) godot_original_name: String, - pub(super) godot_shout_name: String, - pub(super) godot_snake_name: String, - pub(super) builtin_class: Option, + pub godot_original_name: String, + pub godot_shout_name: String, + pub godot_snake_name: String, + pub builtin_class: Option, pub variant_type_ord: i32, } @@ -132,7 +132,7 @@ impl BuiltinVariant { } pub struct Class { - pub(super) common: ClassCommons, + pub common: ClassCommons, pub is_refcounted: bool, pub is_instantiable: bool, pub inherits: Option, @@ -347,7 +347,7 @@ struct FnSignature<'a> { // ---------------------------------------------------------------------------------------------------------------------------------------------- pub struct UtilityFunction { - pub(super) common: FunctionCommon, + pub common: FunctionCommon, } impl UtilityFunction { @@ -383,9 +383,9 @@ impl fmt::Display for UtilityFunction { pub struct BuiltinMethod { // variant_type: - pub(super) common: FunctionCommon, - pub(super) qualifier: FnQualifier, - pub(super) surrounding_class: TyName, + pub common: FunctionCommon, + pub qualifier: FnQualifier, + pub surrounding_class: TyName, } impl BuiltinMethod { @@ -425,9 +425,9 @@ impl fmt::Display for BuiltinMethod { // ---------------------------------------------------------------------------------------------------------------------------------------------- pub struct ClassMethod { - pub(super) common: FunctionCommon, - pub(super) qualifier: FnQualifier, - pub(super) surrounding_class: TyName, + pub common: FunctionCommon, + pub qualifier: FnQualifier, + pub surrounding_class: TyName, } impl ClassMethod {} @@ -586,3 +586,134 @@ impl FnReturn { } } } + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Godot type + +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +pub struct GodotTy { + pub ty: String, + pub meta: Option, +} + +// impl GodotTy { +// fn new<'a>(ty: &'a String, meta: &'a Option) -> Self { +// Self { +// ty: ty.clone(), +// meta: meta.clone(), +// } +// } +// } + +// ---------------------------------------------------------------------------------------------------------------------------------------------- +// Rust type + +#[derive(Clone, Debug)] +pub enum RustTy { + /// `bool`, `Vector3i` + BuiltinIdent(Ident), + + /// `Array` + BuiltinArray(TokenStream), + + /// C-style raw pointer to a `RustTy`. + RawPointer { inner: Box, is_const: bool }, + + /// `Array>` + EngineArray { + tokens: TokenStream, + #[allow(dead_code)] // only read in minimal config + elem_class: String, + }, + + /// `module::Enum` + EngineEnum { + tokens: TokenStream, + /// `None` for globals + #[allow(dead_code)] // only read in minimal config + surrounding_class: Option, + }, + + /// `module::Bitfield` + EngineBitfield { + tokens: TokenStream, + /// `None` for globals + #[allow(dead_code)] // only read in minimal config + surrounding_class: Option, + }, + + /// `Gd` + EngineClass { + /// Tokens with full `Gd` + tokens: TokenStream, + /// only inner `T` + #[allow(dead_code)] // only read in minimal config + inner_class: Ident, + }, +} + +impl RustTy { + pub fn return_decl(&self) -> TokenStream { + match self { + Self::EngineClass { tokens, .. } => quote! { -> Option<#tokens> }, + other => quote! { -> #other }, + } + } +} + +impl ToTokens for RustTy { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + RustTy::BuiltinIdent(ident) => ident.to_tokens(tokens), + RustTy::BuiltinArray(path) => path.to_tokens(tokens), + RustTy::RawPointer { + inner, + is_const: true, + } => quote! { *const #inner }.to_tokens(tokens), + RustTy::RawPointer { + inner, + is_const: false, + } => 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), + } + } +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------- + +/// Contains multiple naming conventions for types (classes, builtin classes, enums). +#[derive(Clone, Eq, PartialEq, Hash)] +pub struct TyName { + pub godot_ty: String, + pub rust_ty: Ident, +} + +impl TyName { + pub fn from_godot(godot_ty: &str) -> Self { + Self { + godot_ty: godot_ty.to_owned(), + rust_ty: ident(&conv::to_pascal_case(godot_ty)), + } + } + + pub fn description(&self) -> String { + if self.rust_ty == self.godot_ty { + self.godot_ty.clone() + } else { + format!("{} [renamed {}]", self.godot_ty, self.rust_ty) + } + } + + pub fn virtual_trait_name(&self) -> String { + format!("I{}", self.rust_ty) + } +} + +impl ToTokens for TyName { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.rust_ty.to_tokens(tokens) + } +} diff --git a/godot-codegen/src/domain_mapping.rs b/godot-codegen/src/models/domain_mapping.rs similarity index 99% rename from godot-codegen/src/domain_mapping.rs rename to godot-codegen/src/models/domain_mapping.rs index f33831d41..c63f30f19 100644 --- a/godot-codegen/src/domain_mapping.rs +++ b/godot-codegen/src/models/domain_mapping.rs @@ -6,19 +6,19 @@ */ use crate::context::Context; -use crate::domain_models::{ +use crate::models::domain::{ BuildConfiguration, BuiltinClass, BuiltinMethod, BuiltinSize, BuiltinVariant, Class, ClassCommons, ClassConstant, ClassConstantValue, ClassMethod, Constructor, Enum, Enumerator, EnumeratorValue, ExtensionApi, FnDirection, FnParam, FnQualifier, FnReturn, FunctionCommon, - GodotApiVersion, NativeStructure, Operator, Singleton, UtilityFunction, + GodotApiVersion, NativeStructure, Operator, Singleton, TyName, UtilityFunction, }; -use crate::json_models::{ +use crate::models::json::{ JsonBuiltinClass, JsonBuiltinMethod, JsonBuiltinSizes, JsonClass, JsonClassConstant, JsonClassMethod, JsonConstructor, JsonEnum, JsonEnumConstant, JsonExtensionApi, JsonHeader, JsonMethodReturn, JsonNativeStructure, JsonOperator, JsonSingleton, JsonUtilityFunction, }; use crate::util::{get_api_level, ident, option_as_slice}; -use crate::{conv, special_cases, ModName, TyName}; +use crate::{conv, special_cases, ModName}; use proc_macro2::Ident; use std::collections::HashMap; diff --git a/godot-codegen/src/json_models.rs b/godot-codegen/src/models/json.rs similarity index 99% rename from godot-codegen/src/json_models.rs rename to godot-codegen/src/models/json.rs index e6618a0a3..ce9060b40 100644 --- a/godot-codegen/src/json_models.rs +++ b/godot-codegen/src/models/json.rs @@ -105,7 +105,7 @@ pub struct JsonBuiltinEnum { } impl JsonBuiltinEnum { - pub(crate) fn to_enum(&self) -> JsonEnum { + pub fn to_enum(&self) -> JsonEnum { JsonEnum { name: self.name.clone(), is_bitfield: false, diff --git a/godot-codegen/src/models/mod.rs b/godot-codegen/src/models/mod.rs new file mode 100644 index 000000000..f7f24c737 --- /dev/null +++ b/godot-codegen/src/models/mod.rs @@ -0,0 +1,11 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +pub mod domain; +pub mod domain_mapping; +pub mod json; +pub mod tokens; diff --git a/godot-codegen/src/models/tokens.rs b/godot-codegen/src/models/tokens.rs new file mode 100644 index 000000000..072c2d5d1 --- /dev/null +++ b/godot-codegen/src/models/tokens.rs @@ -0,0 +1,6 @@ +/* + * Copyright (c) godot-rust; Bromeon and contributors. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ diff --git a/godot-codegen/src/special_cases.rs b/godot-codegen/src/special_cases.rs index 17652f129..3ee3a0658 100644 --- a/godot-codegen/src/special_cases.rs +++ b/godot-codegen/src/special_cases.rs @@ -23,9 +23,10 @@ #![allow(clippy::match_like_matches_macro)] // if there is only one rule -use crate::json_models::{JsonBuiltinMethod, JsonClassMethod, JsonUtilityFunction}; +use crate::codegen_special_cases; +use crate::models::domain::TyName; +use crate::models::json::{JsonBuiltinMethod, JsonClassMethod, JsonUtilityFunction}; use crate::Context; -use crate::{codegen_special_cases, TyName}; #[rustfmt::skip] pub(crate) fn is_class_method_deleted(class_name: &TyName, method: &JsonClassMethod, ctx: &mut Context) -> bool { diff --git a/godot-codegen/src/util.rs b/godot-codegen/src/util.rs index 100275930..e7bfcbf08 100644 --- a/godot-codegen/src/util.rs +++ b/godot-codegen/src/util.rs @@ -5,15 +5,15 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use crate::json_models::{JsonClass, JsonClassConstant}; -use crate::{conv, RustTy, TyName}; +use crate::conv; +use crate::models::json::{JsonClass, JsonClassConstant}; use proc_macro2::{Ident, Literal, TokenStream}; use quote::{format_ident, quote}; -use crate::domain_models::{ +use crate::models::domain::{ BuiltinClass, BuiltinMethod, Class, ClassConstant, ClassConstantValue, ClassLike, ClassMethod, - Enum, Enumerator, EnumeratorValue, Function, + Enum, Enumerator, EnumeratorValue, Function, RustTy, TyName, }; use std::fmt; @@ -79,7 +79,7 @@ impl ClassCodegenLevel { // Could potentially save a lot of string allocations with lifetimes. // See also crate::lazy_keys. #[derive(Eq, PartialEq, Hash)] -pub(crate) enum MethodTableKey { +pub enum MethodTableKey { ClassMethod { api_level: ClassCodegenLevel, class_ty: TyName, diff --git a/godot-codegen/src/utilities_generator.rs b/godot-codegen/src/utilities_generator.rs index 56e82bf64..17ab8a4fb 100644 --- a/godot-codegen/src/utilities_generator.rs +++ b/godot-codegen/src/utilities_generator.rs @@ -10,7 +10,7 @@ use std::path::Path; use quote::quote; use crate::class_generator::make_utility_function_definition; -use crate::domain_models::ExtensionApi; +use crate::models::domain::ExtensionApi; use crate::{util, SubmitFn}; pub(crate) fn generate_utilities_file( From e9c2b72ab6b1082e96666eb0c2375d38b9cb6f78 Mon Sep 17 00:00:00 2001 From: Jan Haller Date: Sun, 21 Jan 2024 14:30:50 +0100 Subject: [PATCH 10/10] Codegen: remove `build_config` from domain model --- godot-codegen/src/class_generator.rs | 3 --- godot-codegen/src/lib.rs | 11 ++++------- godot-codegen/src/models/domain.rs | 1 - godot-codegen/src/models/domain_mapping.rs | 7 +------ godot-codegen/src/models/json.rs | 20 ++------------------ godot-ffi/src/opaque.rs | 7 +++++++ 6 files changed, 14 insertions(+), 35 deletions(-) diff --git a/godot-codegen/src/class_generator.rs b/godot-codegen/src/class_generator.rs index e84c53734..d9e1e683b 100644 --- a/godot-codegen/src/class_generator.rs +++ b/godot-codegen/src/class_generator.rs @@ -107,7 +107,6 @@ impl FnDefinitions { pub(crate) fn generate_class_files( api: &ExtensionApi, ctx: &mut Context, - _build_config: [&str; 2], gen_path: &Path, submit_fn: &mut SubmitFn, ) { @@ -141,7 +140,6 @@ pub(crate) fn generate_class_files( pub(crate) fn generate_builtin_class_files( api: &ExtensionApi, ctx: &mut Context, - _build_config: [&str; 2], gen_path: &Path, submit_fn: &mut SubmitFn, ) { @@ -179,7 +177,6 @@ pub(crate) fn generate_builtin_class_files( pub(crate) fn generate_native_structures_files( api: &ExtensionApi, ctx: &mut Context, - _build_config: [&str; 2], gen_path: &Path, submit_fn: &mut SubmitFn, ) { diff --git a/godot-codegen/src/lib.rs b/godot-codegen/src/lib.rs index 5ceb481c9..99c88c314 100644 --- a/godot-codegen/src/lib.rs +++ b/godot-codegen/src/lib.rs @@ -62,12 +62,12 @@ pub fn generate_sys_files( h_path: &Path, watch: &mut godot_bindings::StopWatch, ) { - let (json_api, build_config) = load_extension_api(watch); + let json_api = load_extension_api(watch); let mut ctx = Context::build_from_api(&json_api); watch.record("build_context"); - let api = ExtensionApi::from_json(&json_api, build_config, &mut ctx); + let api = ExtensionApi::from_json(&json_api, &mut ctx); watch.record("map_domain_models"); // TODO if ctx is no longer needed for below functions: @@ -99,11 +99,11 @@ pub fn generate_core_files(core_gen_path: &Path) { generate_core_mod_file(core_gen_path, &mut submit_fn); - let (json_api, build_config) = load_extension_api(&mut watch); + let json_api = load_extension_api(&mut watch); let mut ctx = Context::build_from_api(&json_api); watch.record("build_context"); - let api = ExtensionApi::from_json(&json_api, build_config, &mut ctx); + let api = ExtensionApi::from_json(&json_api, &mut ctx); watch.record("map_domain_models"); // TODO if ctx is no longer needed for below functions: @@ -121,7 +121,6 @@ pub fn generate_core_files(core_gen_path: &Path) { generate_class_files( &api, &mut ctx, - build_config, &core_gen_path.join("classes"), &mut submit_fn, ); @@ -130,7 +129,6 @@ pub fn generate_core_files(core_gen_path: &Path) { generate_builtin_class_files( &api, &mut ctx, - build_config, &core_gen_path.join("builtin_classes"), &mut submit_fn, ); @@ -139,7 +137,6 @@ pub fn generate_core_files(core_gen_path: &Path) { generate_native_structures_files( &api, &mut ctx, - build_config, &core_gen_path.join("native"), &mut submit_fn, ); diff --git a/godot-codegen/src/models/domain.rs b/godot-codegen/src/models/domain.rs index 0eedba5f4..48ec18e41 100644 --- a/godot-codegen/src/models/domain.rs +++ b/godot-codegen/src/models/domain.rs @@ -26,7 +26,6 @@ pub struct ExtensionApi { pub native_structures: Vec, pub utility_functions: Vec, pub global_enums: Vec, - pub build_config: [&'static str; 2], pub godot_version: GodotApiVersion, /// Map `(original Godot name, build config) -> builtin size` in bytes. diff --git a/godot-codegen/src/models/domain_mapping.rs b/godot-codegen/src/models/domain_mapping.rs index c63f30f19..e3d7259dd 100644 --- a/godot-codegen/src/models/domain_mapping.rs +++ b/godot-codegen/src/models/domain_mapping.rs @@ -26,11 +26,7 @@ use std::collections::HashMap; // Top-level impl ExtensionApi { - pub fn from_json( - json: &JsonExtensionApi, - build_config: [&'static str; 2], - ctx: &mut Context, - ) -> Self { + pub fn from_json(json: &JsonExtensionApi, ctx: &mut Context) -> Self { Self { builtins: BuiltinVariant::all_from_json(&json.global_enums, &json.builtin_classes, ctx), classes: json @@ -54,7 +50,6 @@ impl ExtensionApi { .iter() .map(|json| Enum::from_json(json, None)) .collect(), - build_config, godot_version: GodotApiVersion::from_json(&json.header), builtin_sizes: Self::builtin_size_from_json(&json.builtin_class_sizes), } diff --git a/godot-codegen/src/models/json.rs b/godot-codegen/src/models/json.rs index ce9060b40..bb1a10ad9 100644 --- a/godot-codegen/src/models/json.rs +++ b/godot-codegen/src/models/json.rs @@ -251,22 +251,7 @@ impl JsonMethodReturn { // ---------------------------------------------------------------------------------------------------------------------------------------------- // Implementation -pub fn load_extension_api( - watch: &mut godot_bindings::StopWatch, -) -> (JsonExtensionApi, [&'static str; 2]) { - // For float/double inference, see: - // * https://github.com/godotengine/godot-proposals/issues/892 - // * https://github.com/godotengine/godot-cpp/pull/728 - // Have to do target_pointer_width check after code generation - // So pass a [32bit, 64bit] around of appropriate precision - // For why see: https://github.com/rust-lang/rust/issues/42587 - let build_config: [&'static str; 2] = { - if cfg!(feature = "double-precision") { - ["double_32", "double_64"] - } else { - ["float_32", "float_64"] - } - }; +pub fn load_extension_api(watch: &mut godot_bindings::StopWatch) -> JsonExtensionApi { // Use type inference, so we can accept both String (dynamically resolved) and &str (prebuilt). // #[allow]: as_ref() acts as impl AsRef, but with conditional compilation @@ -279,6 +264,5 @@ pub fn load_extension_api( watch.record("deserialize_json"); println!("Parsed extension_api.json for version {:?}", model.header); - - (model, build_config) + model } diff --git a/godot-ffi/src/opaque.rs b/godot-ffi/src/opaque.rs index a713eb615..c0b543259 100644 --- a/godot-ffi/src/opaque.rs +++ b/godot-ffi/src/opaque.rs @@ -12,6 +12,13 @@ /// /// Note: due to `align(4)` / `align(8)` and not `packed` repr, this type may be bigger than `N` bytes /// (which should be OK since C++ just needs to read/write those `N` bytes reliably). +/// +/// +/// For float/double inference, see: +/// * https://github.com/godotengine/godot-proposals/issues/892 +/// * https://github.com/godotengine/godot-cpp/pull/728 +/// +/// We have to do a `target_pointer_width` check *after* code generation, see https://github.com/rust-lang/rust/issues/42587. #[cfg_attr(target_pointer_width = "32", repr(C, align(4)))] #[cfg_attr(target_pointer_width = "64", repr(C, align(8)))] #[derive(Copy, Clone)]