Skip to content

Commit

Permalink
init by API level
Browse files Browse the repository at this point in the history
  • Loading branch information
Bromeon committed Aug 19, 2023
1 parent 8aec2ce commit 40b21e8
Show file tree
Hide file tree
Showing 6 changed files with 284 additions and 173 deletions.
195 changes: 67 additions & 128 deletions godot-codegen/src/central_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ struct CentralItems {
godot_version: Header,
}

#[derive(Default)]
struct ClassMethodItems {
struct MethodTableInfo {
table_name: Ident,
ctor_parameters: TokenStream,
pre_init_code: TokenStream,
method_decls: Vec<TokenStream>,
method_inits: Vec<TokenStream>,
Expand Down Expand Up @@ -79,48 +80,35 @@ pub(crate) fn generate_sys_classes_file(
sys_gen_path: &Path,
submit_fn: &mut SubmitFn,
) {
let ClassMethodItems {
for api_level in ["Server", "Scene", "Editor"] {
let code = make_class_method_table(api, api_level, ctx);
let filename = format!("class_{}.rs", api_level.to_ascii_lowercase());

submit_fn(sys_gen_path.join(filename), code);
}
}

/// Generate code for a method table based on shared layout.
fn make_method_table(info: MethodTableInfo) -> TokenStream {
let MethodTableInfo {
table_name,
ctor_parameters,
pre_init_code,
method_decls,
method_inits,
} = make_class_method_items(api, ctx);

let code = quote! {
use crate as sys;

type MethodBind = sys::GDExtensionMethodBindPtr;

fn unwrap_fn_ptr(
method: MethodBind,
class_name: &str,
method_name: &str,
hash: i64,
) -> MethodBind {
crate::out!("Load class method {}::{} (hash {})...", class_name, method_name, hash);
if method.is_null() {
panic!(
"Failed to load class method {}::{} (hash {}).\n\
Make sure gdext and Godot are compatible: https://godot-rust.github.io/book/gdext/advanced/compatibility.html",
class_name,
method_name,
hash
)
}

method
}
} = info;

return quote! {
#[allow(non_snake_case)]
pub struct ClassMethodTable {
pub struct #table_name {
#( #method_decls, )*
}

impl ClassMethodTable {
impl #table_name {
// #[allow]: some classes have no own methods (only inherited ones), so their StringNames are never referenced.
#[allow(unused_variables)]
//#[allow(unused_variables)]
pub fn load(
interface: &crate::GDExtensionInterface,
string_names: &mut crate::StringCache,
#ctor_parameters
) -> Self {
#pre_init_code

Expand All @@ -130,8 +118,6 @@ pub(crate) fn generate_sys_classes_file(
}
}
};

submit_fn(sys_gen_path.join("classes.rs"), code);
}

pub(crate) fn generate_sys_builtins_file(
Expand All @@ -143,75 +129,10 @@ pub(crate) fn generate_sys_builtins_file(
// TODO merge this and the one in central.rs, to only collect once
let builtin_types_map = collect_builtin_types(api);

let ClassMethodItems {
pre_init_code,
method_decls,
method_inits,
} = make_builtin_method_items(api, &builtin_types_map);

let code = quote! {
use crate as sys;

// GDExtensionPtrBuiltInMethod
type BuiltinMethodBind = unsafe extern "C" fn(
p_base: sys::GDExtensionTypePtr,
p_args: *const sys::GDExtensionConstTypePtr,
r_return: sys::GDExtensionTypePtr,
p_argument_count: ::std::os::raw::c_int,
);

fn unwrap_fn_ptr(
method: sys::GDExtensionPtrBuiltInMethod,
variant_type: &str,
method_name: &str,
hash: i64,
) -> BuiltinMethodBind {
crate::out!("Load builtin method {}::{} (hash {})...", variant_type, method_name, hash);
method.unwrap_or_else(|| {
panic!(
"Failed to load builtin method {}::{} (hash {}).\n\
Make sure gdext and Godot are compatible: https://godot-rust.github.io/book/gdext/advanced/compatibility.html",
variant_type,
method_name,
hash
)
})
}

#[allow(non_snake_case)]
pub struct BuiltinMethodTable {
#( #method_decls, )*
}

impl BuiltinMethodTable {
pub fn load(
interface: &crate::GDExtensionInterface,
string_names: &mut crate::StringCache,
) -> Self {
#pre_init_code

Self {
#( #method_inits, )*
}
}
}
};

let code = make_builtin_method_table(api, &builtin_types_map);
submit_fn(sys_gen_path.join("builtin_classes.rs"), code);
}

pub(crate) fn generate_sys_mod_file(core_gen_path: &Path, submit_fn: &mut SubmitFn) {
let code = quote! {
pub mod builtin_classes;
pub mod central;
pub mod classes;
pub mod gdextension_interface;
pub mod interface;
};

submit_fn(core_gen_path.join("mod.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
let code = quote! {
Expand Down Expand Up @@ -556,59 +477,77 @@ fn make_central_items(
result
}

fn make_class_method_items(api: &ExtensionApi, ctx: &mut Context) -> ClassMethodItems {
let mut items = ClassMethodItems::default();
let mut class_inits = Vec::new();
fn make_class_method_table(api: &ExtensionApi, api_level: &str, ctx: &mut Context) -> TokenStream {
let mut table = MethodTableInfo {
table_name: format_ident!("Class{}MethodTable", api_level),
ctor_parameters: quote! {
interface: &crate::GDExtensionInterface,
string_names: &mut crate::StringCache,
},
pre_init_code: TokenStream::new(), // late-init, depends on class string names
method_decls: vec![],
method_inits: vec![],
};

let mut class_sname_decls = Vec::new();
for class in api.classes.iter() {
if is_class_excluded(&class.name) {
if is_class_excluded(&class.name) || util::get_api_level(class) != api_level {
continue;
}

let class_var = format_ident!("sname_{}", &class.name);
let initializer_expr = util::make_sname_ptr(&class.name);

if populate_class_methods(&mut items, class, &class_var, ctx) {
// Only create class variable if there any methods have been added.
class_inits.push(quote! {
if populate_class_methods(&mut table, class, &class_var, ctx) {
// Only create class variable if any methods have been added.
class_sname_decls.push(quote! {
let #class_var = #initializer_expr;
});
}
}

items.pre_init_code = quote! {
table.pre_init_code = quote! {
let get_method_bind = interface.classdb_get_method_bind.expect("classdb_get_method_bind absent");

#( #class_inits )*
#( #class_sname_decls )*
};

items
make_method_table(table)
}

fn make_builtin_method_items(
fn make_builtin_method_table(
api: &ExtensionApi,
builtin_types_map: &HashMap<String, BuiltinTypeInfo<'_>>,
) -> ClassMethodItems {
let mut items = ClassMethodItems::default();
) -> TokenStream {
let mut table = MethodTableInfo {
table_name: ident("BuiltinMethodTable"),
ctor_parameters: quote! {
interface: &crate::GDExtensionInterface,
string_names: &mut crate::StringCache,
},
pre_init_code: quote! {
use crate as sys;
let get_builtin_method = interface.variant_get_ptr_builtin_method.expect("variant_get_ptr_builtin_method absent");
},
method_decls: vec![],
method_inits: vec![],
};

for builtin in api.builtin_classes.iter() {
println!("builtin: {}", builtin.name);
let Some(builtin_type) = builtin_types_map.get(&builtin.name) else {
continue // for Nil
};

populate_builtin_methods(&mut items, builtin, &builtin_type.type_names);
populate_builtin_methods(&mut table, builtin, &builtin_type.type_names);
}

items.pre_init_code = quote! {
let get_builtin_method = interface.variant_get_ptr_builtin_method.expect("variant_get_ptr_builtin_method absent");
};
items
make_method_table(table)
}

/// Returns whether at least 1 method was added.
fn populate_class_methods(
items: &mut ClassMethodItems,
table: &mut MethodTableInfo,
class: &Class,
class_var: &Ident,
ctx: &mut Context,
Expand All @@ -626,16 +565,16 @@ fn populate_class_methods(
let method_decl = make_class_method_decl(&method_field);
let method_init = make_class_method_init(method, &method_field, class_var, class_name_str);

items.method_decls.push(method_decl);
items.method_inits.push(method_init);
table.method_decls.push(method_decl);
table.method_inits.push(method_init);
methods_added = true;
}

methods_added
}

fn populate_builtin_methods(
items: &mut ClassMethodItems,
items: &mut MethodTableInfo,
builtin_class: &BuiltinClass,
type_name: &TypeNames,
) {
Expand All @@ -656,7 +595,7 @@ fn populate_builtin_methods(

fn make_class_method_decl(method_field: &Ident) -> TokenStream {
// Note: varcall/ptrcall is only decided at call time; the method bind is the same for both.
quote! { pub #method_field: MethodBind }
quote! { pub #method_field: crate::ClassMethodBind }
}

fn make_class_method_init(
Expand All @@ -680,13 +619,13 @@ fn make_class_method_init(
let method_bind = unsafe {
get_method_bind(#class_var, #method_sname, #hash)
};
unwrap_fn_ptr(method_bind, #class_name_str, #method_name_str, #hash)
crate::validate_class_method(method_bind, #class_name_str, #method_name_str, #hash)
}
}
}

fn make_builtin_method_decl(_method: &BuiltinClassMethod, method_field: &Ident) -> TokenStream {
quote! { pub #method_field: BuiltinMethodBind }
quote! { pub #method_field: crate::BuiltinMethodBind }
}

fn make_builtin_method_init(
Expand All @@ -712,7 +651,7 @@ fn make_builtin_method_init(
let method_bind = unsafe {
get_builtin_method(sys::#variant_type, #method_sname, #hash)
};
unwrap_fn_ptr(method_bind, #variant_type_str, #method_name_str, #hash)
crate::validate_builtin_method(method_bind, #variant_type_str, #method_name_str, #hash)
}
}
}
Expand Down
24 changes: 20 additions & 4 deletions godot-codegen/src/class_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -497,10 +497,20 @@ fn make_class(class: &Class, class_name: &TyName, ctx: &mut Context) -> Generate
};

let constructor = make_constructor(class, ctx);
let get_method_table = format_ident!(
"class_{}_api",
util::get_api_level(class).to_ascii_lowercase()
);

let FnDefinitions {
functions: methods,
builders,
} = make_methods(option_as_slice(&class.methods), class_name, ctx);
} = make_methods(
option_as_slice(&class.methods),
class_name,
get_method_table,
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);
Expand Down Expand Up @@ -1001,10 +1011,15 @@ fn make_builtin_module_file(classes_and_modules: Vec<GeneratedBuiltinModule>) ->
}
}

fn make_methods(methods: &[ClassMethod], class_name: &TyName, ctx: &mut Context) -> FnDefinitions {
fn make_methods(
methods: &[ClassMethod],
class_name: &TyName,
get_method_table: Ident,
ctx: &mut Context,
) -> FnDefinitions {
let definitions = methods
.iter()
.map(|method| make_method_definition(method, class_name, ctx));
.map(|method| make_method_definition(method, class_name, get_method_table, ctx));

FnDefinitions::expand(definitions)
}
Expand Down Expand Up @@ -1149,6 +1164,7 @@ fn is_function_excluded(function: &UtilityFunction, ctx: &mut Context) -> bool {
fn make_method_definition(
method: &ClassMethod,
class_name: &TyName,
get_method_table: Ident,
ctx: &mut Context,
) -> FnDefinition {
if is_method_excluded(method, false, ctx) || special_cases::is_deleted(class_name, &method.name)
Expand Down Expand Up @@ -1187,7 +1203,7 @@ fn make_method_definition(
let fn_ptr = util::make_class_method_ptr_name(&class_name.godot_ty, method);

let init_code = quote! {
let __method_bind = sys::class_method_table().#fn_ptr;
let __method_bind = sys::#get_method_table().#fn_ptr;
let __call_fn = #function_provider;
};

Expand Down
4 changes: 1 addition & 3 deletions godot-codegen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ 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, generate_sys_mod_file,
generate_sys_classes_file,
};
use class_generator::{
generate_builtin_class_files, generate_class_files, generate_native_structures_files,
Expand Down Expand Up @@ -60,8 +60,6 @@ pub fn generate_sys_files(
h_path: &Path,
watch: &mut godot_bindings::StopWatch,
) {
generate_sys_mod_file(sys_gen_path, &mut submit_fn);

let (api, build_config) = load_extension_api(watch);
let mut ctx = Context::build_from_api(&api);
watch.record("build_context");
Expand Down
Loading

0 comments on commit 40b21e8

Please sign in to comment.