Skip to content

Commit f7f8853

Browse files
committed
Implement lazy function pointer loading, based on HashMap
1 parent 4a59767 commit f7f8853

File tree

14 files changed

+317
-44
lines changed

14 files changed

+317
-44
lines changed

godot-codegen/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ categories = ["game-engines", "graphics"]
1111
default = ["codegen-fmt"]
1212
codegen-fmt = []
1313
codegen-full = []
14+
codegen-lazy-fptrs = []
1415
double-precision = []
1516
custom-godot = ["godot-bindings/custom-godot"]
1617
experimental-godot-api = []

godot-codegen/src/central_generator.rs

Lines changed: 166 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,24 @@ struct NamedMethodTable {
3939
method_count: usize,
4040
}
4141

42+
#[allow(dead_code)] // for lazy feature
4243
struct IndexedMethodTable {
4344
table_name: Ident,
4445
imports: TokenStream,
4546
ctor_parameters: TokenStream,
4647
pre_init_code: TokenStream,
4748
fptr_type: TokenStream,
4849
method_inits: Vec<MethodInit>,
50+
lazy_key_type: TokenStream,
51+
lazy_method_init: TokenStream,
4952
named_accessors: Vec<AccessorMethod>,
5053
class_count: usize,
5154
method_count: usize,
5255
}
5356

5457
// ----------------------------------------------------------------------------------------------------------------------------------------------
5558

59+
#[cfg_attr(feature = "codegen-lazy-fptrs", allow(dead_code))]
5660
struct MethodInit {
5761
method_init: TokenStream,
5862
index: usize,
@@ -69,6 +73,7 @@ impl ToTokens for MethodInit {
6973
struct AccessorMethod {
7074
name: Ident,
7175
index: usize,
76+
lazy_key: TokenStream,
7277
}
7378

7479
// ----------------------------------------------------------------------------------------------------------------------------------------------
@@ -260,14 +265,17 @@ fn make_named_method_table(info: NamedMethodTable) -> TokenStream {
260265
}
261266
}
262267

263-
fn make_indexed_method_table(info: IndexedMethodTable) -> TokenStream {
268+
#[cfg(not(feature = "codegen-lazy-fptrs"))]
269+
fn make_method_table(info: IndexedMethodTable) -> TokenStream {
264270
let IndexedMethodTable {
265271
table_name,
266272
imports,
267273
ctor_parameters,
268274
pre_init_code,
269275
fptr_type,
270276
mut method_inits,
277+
lazy_key_type: _,
278+
lazy_method_init: _,
271279
named_accessors,
272280
class_count,
273281
method_count,
@@ -334,6 +342,79 @@ fn make_indexed_method_table(info: IndexedMethodTable) -> TokenStream {
334342
}
335343
}
336344

345+
#[cfg(feature = "codegen-lazy-fptrs")]
346+
fn make_method_table(info: IndexedMethodTable) -> TokenStream {
347+
let IndexedMethodTable {
348+
table_name,
349+
imports,
350+
ctor_parameters: _,
351+
pre_init_code: _,
352+
fptr_type,
353+
method_inits: _,
354+
lazy_key_type,
355+
lazy_method_init,
356+
named_accessors,
357+
class_count,
358+
method_count,
359+
} = info;
360+
361+
// Editor table can be empty, if the Godot binary is compiled without editor.
362+
let unused_attr = (method_count == 0).then(|| quote! { #[allow(unused_variables)] });
363+
let named_method_api = make_named_accessors(&named_accessors, &fptr_type);
364+
365+
// Assumes that inits already have a trailing comma.
366+
// This is necessary because some generators emit multiple lines (statements) per element.
367+
quote! {
368+
#imports
369+
use crate::StringCache;
370+
use std::collections::HashMap;
371+
use std::cell::RefCell;
372+
373+
// Exists to be stored inside RefCell.
374+
struct InnerTable {
375+
// 'static because at this point, the interface and lifecycle tables are globally available.
376+
string_cache: StringCache<'static>,
377+
function_pointers: HashMap<#lazy_key_type, #fptr_type>,
378+
}
379+
380+
// Note: get_method_bind and other function pointers could potentially be stored as fields in table, to avoid interface_fn!.
381+
pub struct #table_name {
382+
inner: RefCell<InnerTable>,
383+
}
384+
385+
impl #table_name {
386+
pub const CLASS_COUNT: usize = #class_count;
387+
pub const METHOD_COUNT: usize = #method_count;
388+
389+
#unused_attr
390+
pub fn load() -> Self {
391+
// SAFETY: interface and lifecycle tables are initialized at this point, so we can get 'static references to them.
392+
let (interface, lifecycle_table) = unsafe {
393+
(crate::get_interface(), crate::method_table())
394+
};
395+
396+
Self {
397+
inner: RefCell::new(InnerTable {
398+
string_cache: StringCache::new(interface, lifecycle_table),
399+
function_pointers: HashMap::new(),
400+
}),
401+
}
402+
}
403+
404+
#[inline(always)]
405+
pub fn fptr_by_key(&self, key: #lazy_key_type) -> #fptr_type {
406+
let mut guard = self.inner.borrow_mut();
407+
let inner = &mut *guard;
408+
*inner.function_pointers.entry(key.clone()).or_insert_with(|| {
409+
#lazy_method_init
410+
})
411+
}
412+
413+
#named_method_api
414+
}
415+
}
416+
}
417+
337418
pub(crate) fn generate_sys_builtin_methods_file(
338419
api: &ExtensionApi,
339420
builtin_types: &BuiltinTypeMap,
@@ -739,6 +820,18 @@ fn make_class_method_table(
739820
pre_init_code: TokenStream::new(), // late-init, depends on class string names
740821
fptr_type: quote! { crate::ClassMethodBind },
741822
method_inits: vec![],
823+
lazy_key_type: quote! { crate::lazy_keys::ClassMethodKey },
824+
lazy_method_init: quote! {
825+
let get_method_bind = crate::interface_fn!(classdb_get_method_bind);
826+
crate::load_class_method(
827+
get_method_bind,
828+
&mut inner.string_cache,
829+
None,
830+
key.class_name,
831+
key.method_name,
832+
key.hash
833+
)
834+
},
742835
named_accessors: vec![],
743836
class_count: 0,
744837
method_count: 0,
@@ -775,18 +868,33 @@ fn make_class_method_table(
775868
#( #class_sname_decls )*
776869
};
777870

778-
make_indexed_method_table(table)
871+
make_method_table(table)
779872
}
780873

781874
/// For index-based method tables, have select methods exposed by name for internal use.
782875
fn make_named_accessors(accessors: &[AccessorMethod], fptr: &TokenStream) -> TokenStream {
783876
let mut result_api = TokenStream::new();
784877

785-
for AccessorMethod { name, index } in accessors {
786-
let code = quote! {
787-
#[inline(always)]
788-
pub fn #name(&self) -> #fptr {
789-
self.fptr_by_index(#index)
878+
for accessor in accessors {
879+
let AccessorMethod {
880+
name,
881+
index,
882+
lazy_key,
883+
} = accessor;
884+
885+
let code = if cfg!(feature = "codegen-lazy-fptrs") {
886+
quote! {
887+
#[inline(always)]
888+
pub fn #name(&self) -> #fptr {
889+
self.fptr_by_key(#lazy_key)
890+
}
891+
}
892+
} else {
893+
quote! {
894+
#[inline(always)]
895+
pub fn #name(&self) -> #fptr {
896+
self.fptr_by_index(#index)
897+
}
790898
}
791899
};
792900

@@ -813,6 +921,18 @@ fn make_builtin_method_table(
813921
},
814922
fptr_type: quote! { crate::BuiltinMethodBind },
815923
method_inits: vec![],
924+
lazy_key_type: quote! { crate::lazy_keys::BuiltinMethodKey },
925+
lazy_method_init: quote! {
926+
let get_builtin_method = crate::interface_fn!(variant_get_ptr_builtin_method);
927+
crate::load_builtin_method(
928+
get_builtin_method,
929+
&mut inner.string_cache,
930+
key.variant_type.sys(),
931+
key.variant_type_str,
932+
key.method_name,
933+
key.hash
934+
)
935+
},
816936
named_accessors: vec![],
817937
class_count: 0,
818938
method_count: 0,
@@ -828,7 +948,7 @@ fn make_builtin_method_table(
828948
table.class_count += 1;
829949
}
830950

831-
make_indexed_method_table(table)
951+
make_method_table(table)
832952
}
833953

834954
fn populate_class_methods(
@@ -856,9 +976,20 @@ fn populate_class_methods(
856976

857977
// If requested, add a named accessor for this method.
858978
if special_cases::is_named_accessor_in_table(class_ty, &method.name) {
979+
let class_name_str = class_ty.godot_ty.as_str();
980+
let method_name_str = method.name.as_str();
981+
let hash = method.hash.expect("hash present");
982+
859983
table.named_accessors.push(AccessorMethod {
860984
name: make_class_method_ptr_name(class_ty, method),
861985
index,
986+
lazy_key: quote! {
987+
crate::lazy_keys::ClassMethodKey {
988+
class_name: #class_name_str,
989+
method_name: #method_name_str,
990+
hash: #hash,
991+
}
992+
},
862993
});
863994
}
864995
}
@@ -888,9 +1019,22 @@ fn populate_builtin_methods(
8881019

8891020
// If requested, add a named accessor for this method.
8901021
if special_cases::is_named_accessor_in_table(&builtin_ty, &method.name) {
1022+
let variant_type = &builtin_name.sys_variant_type;
1023+
let variant_type_str = &builtin_name.json_builtin_name;
1024+
let method_name_str = method.name.as_str();
1025+
let hash = method.hash.expect("hash present");
1026+
8911027
table.named_accessors.push(AccessorMethod {
8921028
name: make_builtin_method_ptr_name(&builtin_ty, method),
8931029
index,
1030+
lazy_key: quote! {
1031+
crate::lazy_keys::BuiltinMethodKey {
1032+
variant_type: #variant_type,
1033+
variant_type_str: #variant_type_str,
1034+
method_name: #method_name_str,
1035+
hash: #hash,
1036+
}
1037+
},
8941038
});
8951039
}
8961040
}
@@ -911,8 +1055,9 @@ fn make_class_method_init(
9111055
)
9121056
});
9131057

1058+
// Could reuse lazy key, but less code like this -> faster parsing.
9141059
quote! {
915-
crate::load_class_method(get_method_bind, string_names, #class_var, #class_name_str, #method_name_str, #hash),
1060+
crate::load_class_method(get_method_bind, string_names, Some(#class_var), #class_name_str, #method_name_str, #hash),
9161061
}
9171062
}
9181063

@@ -933,8 +1078,19 @@ fn make_builtin_method_init(
9331078
)
9341079
});
9351080

1081+
// Could reuse lazy key, but less code like this -> faster parsing.
9361082
quote! {
937-
{let _ = #index;crate::load_builtin_method(get_builtin_method, string_names, sys::#variant_type, #variant_type_str, #method_name_str, #hash)},
1083+
{
1084+
let _ = #index;
1085+
crate::load_builtin_method(
1086+
get_builtin_method,
1087+
string_names,
1088+
sys::#variant_type,
1089+
#variant_type_str,
1090+
#method_name_str,
1091+
#hash
1092+
)
1093+
},
9381094
}
9391095
}
9401096

godot-codegen/src/class_generator.rs

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,7 +1055,7 @@ fn make_methods(
10551055
let get_method_table = api_level.table_global_getter();
10561056

10571057
let definitions = methods.iter().map(|method| {
1058-
make_method_definition(method, class_name, api_level, &get_method_table, ctx)
1058+
make_class_method_definition(method, class_name, api_level, &get_method_table, ctx)
10591059
});
10601060

10611061
FnDefinitions::expand(definitions)
@@ -1113,7 +1113,7 @@ fn make_special_builtin_methods(class_name: &TyName, _ctx: &Context) -> TokenStr
11131113
}
11141114
}
11151115

1116-
fn make_method_definition(
1116+
fn make_class_method_definition(
11171117
method: &ClassMethod,
11181118
class_name: &TyName,
11191119
api_level: &ClassCodegenLevel,
@@ -1133,6 +1133,7 @@ fn make_method_definition(
11331133
}
11341134
}*/
11351135

1136+
let class_name_str = &class_name.godot_ty;
11361137
let method_name_str = special_cases::maybe_renamed(class_name, &method.name);
11371138

11381139
let receiver = make_receiver(
@@ -1153,9 +1154,22 @@ fn make_method_definition(
11531154
quote! { Some(self.instance_id) }
11541155
};
11551156

1157+
let fptr_access = if cfg!(feature = "codegen-lazy-fptrs") {
1158+
let hash = method.hash.expect("hash present for class method");
1159+
quote! {
1160+
fptr_by_key(sys::lazy_keys::ClassMethodKey {
1161+
class_name: #class_name_str,
1162+
method_name: #method_name_str,
1163+
hash: #hash,
1164+
})
1165+
}
1166+
} else {
1167+
quote! { fptr_by_index(#table_index) }
1168+
};
1169+
11561170
let object_ptr = &receiver.ffi_arg;
11571171
let ptrcall_invocation = quote! {
1158-
let method_bind = sys::#get_method_table().fptr_by_index(#table_index);
1172+
let method_bind = sys::#get_method_table().#fptr_access;
11591173

11601174
<CallSig as PtrcallSignatureTuple>::out_class_ptrcall::<RetMarshal>(
11611175
method_bind,
@@ -1167,7 +1181,7 @@ fn make_method_definition(
11671181
};
11681182

11691183
let varcall_invocation = quote! {
1170-
let method_bind = sys::#get_method_table().fptr_by_index(#table_index);
1184+
let method_bind = sys::#get_method_table().#fptr_access;
11711185

11721186
<CallSig as VarcallSignatureTuple>::out_class_varcall(
11731187
method_bind,
@@ -1215,16 +1229,32 @@ fn make_builtin_method_definition(
12151229
.as_deref()
12161230
.map(MethodReturn::from_type_no_meta);
12171231

1218-
let table_index = ctx.get_table_index(&MethodTableKey::BuiltinMethod {
1219-
builtin_ty: builtin_name.clone(),
1220-
method_name: method.name.clone(),
1221-
});
1232+
let fptr_access = if cfg!(feature = "codegen-lazy-fptrs") {
1233+
let variant_type = quote! { sys::VariantType::#builtin_name };
1234+
let variant_type_str = &builtin_name.godot_ty;
1235+
let hash = method.hash.expect("hash present for class method");
1236+
1237+
quote! {
1238+
fptr_by_key(sys::lazy_keys::BuiltinMethodKey {
1239+
variant_type: #variant_type,
1240+
variant_type_str: #variant_type_str,
1241+
method_name: #method_name_str,
1242+
hash: #hash,
1243+
})
1244+
}
1245+
} else {
1246+
let table_index = ctx.get_table_index(&MethodTableKey::BuiltinMethod {
1247+
builtin_ty: builtin_name.clone(),
1248+
method_name: method.name.clone(),
1249+
});
1250+
quote! { fptr_by_index(#table_index) }
1251+
};
12221252

12231253
let receiver = make_receiver(method.is_static, method.is_const, quote! { self.sys_ptr });
12241254
let object_ptr = &receiver.ffi_arg;
12251255

12261256
let ptrcall_invocation = quote! {
1227-
let method_bind = sys::builtin_method_table().fptr_by_index(#table_index);
1257+
let method_bind = sys::builtin_method_table().#fptr_access;
12281258

12291259
<CallSig as PtrcallSignatureTuple>::out_builtin_ptrcall::<RetMarshal>(
12301260
method_bind,

0 commit comments

Comments
 (0)