Skip to content

Commit 07c34e7

Browse files
authored
Merge pull request #1289 from agbo-interactive/pr/dnikdel/core-level-classes
Add `ClassCodegenLevel::Core`
2 parents 454eb73 + 843d263 commit 07c34e7

File tree

9 files changed

+203
-67
lines changed

9 files changed

+203
-67
lines changed

godot-codegen/src/generator/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ pub fn generate_sys_module_file(sys_gen_path: &Path, submit_fn: &mut SubmitFn) {
4747
let code = quote! {
4848
pub mod table_builtins;
4949
pub mod table_builtins_lifecycle;
50+
pub mod table_core_classes;
5051
pub mod table_servers_classes;
5152
pub mod table_scene_classes;
5253
pub mod table_editor_classes;

godot-codegen/src/models/domain.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -805,14 +805,15 @@ impl ToTokens for ModName {
805805
/// At which stage a class function pointer is loaded.
806806
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
807807
pub enum ClassCodegenLevel {
808+
Core,
808809
Servers,
809810
Scene,
810811
Editor,
811812
}
812813

813814
impl ClassCodegenLevel {
814-
pub fn with_tables() -> [Self; 3] {
815-
[Self::Servers, Self::Scene, Self::Editor]
815+
pub fn with_tables() -> [Self; 4] {
816+
[Self::Core, Self::Servers, Self::Scene, Self::Editor]
816817
}
817818

818819
pub fn table_global_getter(self) -> Ident {
@@ -829,6 +830,7 @@ impl ClassCodegenLevel {
829830

830831
pub fn lower(self) -> &'static str {
831832
match self {
833+
Self::Core => "core",
832834
Self::Servers => "servers",
833835
Self::Scene => "scene",
834836
Self::Editor => "editor",
@@ -837,6 +839,7 @@ impl ClassCodegenLevel {
837839

838840
fn upper(self) -> &'static str {
839841
match self {
842+
Self::Core => "Core",
840843
Self::Servers => "Servers",
841844
Self::Scene => "Scene",
842845
Self::Editor => "Editor",
@@ -845,6 +848,7 @@ impl ClassCodegenLevel {
845848

846849
pub fn to_init_level(self) -> TokenStream {
847850
match self {
851+
Self::Core => quote! { crate::init::InitLevel::Core },
848852
Self::Servers => quote! { crate::init::InitLevel::Servers },
849853
Self::Scene => quote! { crate::init::InitLevel::Scene },
850854
Self::Editor => quote! { crate::init::InitLevel::Editor },

godot-codegen/src/special_cases/special_cases.rs

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
use proc_macro2::Ident;
3131

3232
use crate::conv::to_enum_type_uncached;
33-
use crate::models::domain::{Enum, RustTy, TyName, VirtualMethodPresence};
33+
use crate::models::domain::{ClassCodegenLevel, Enum, RustTy, TyName, VirtualMethodPresence};
3434
use crate::models::json::{JsonBuiltinMethod, JsonClassMethod, JsonSignal, JsonUtilityFunction};
3535
use crate::special_cases::codegen_special_cases;
3636
use crate::util::option_as_slice;
@@ -985,34 +985,74 @@ pub fn get_derived_virtual_method_presence(class_name: &TyName, godot_method_nam
985985
}
986986
}
987987

988+
/// Initialization order for Godot (see https://github.com/godotengine/godot/blob/master/main/main.cpp)
989+
/// - Main::setup()
990+
/// - register_core_types()
991+
/// - register_early_core_singletons()
992+
/// - initialize_extensions(GDExtension::INITIALIZATION_LEVEL_CORE)
993+
/// - Main::setup2()
994+
/// - register_server_types()
995+
/// - initialize_extensions(GDExtension::INITIALIZATION_LEVEL_SERVERS)
996+
/// - register_core_singletons() ...possibly a bug. Should this be before LEVEL_SERVERS?
997+
/// - register_scene_types()
998+
/// - register_scene_singletons()
999+
/// - initialize_extensions(GDExtension::INITIALIZATION_LEVEL_SCENE)
1000+
/// - IF EDITOR
1001+
/// - register_editor_types()
1002+
/// - initialize_extensions(GDExtension::INITIALIZATION_LEVEL_EDITOR)
1003+
/// - register_server_singletons() ...another weird one.
1004+
/// - Autoloads, etc.
9881005
#[rustfmt::skip]
989-
pub fn is_class_level_server(class_name: &str) -> bool {
990-
// Unclear on if some of these classes should be registered earlier than `Scene`:
991-
// - `RenderData` + `RenderDataExtension`
992-
// - `RenderSceneData` + `RenderSceneDataExtension`
993-
994-
match class_name {
995-
// TODO: These should actually be at level `Core`
996-
| "Object" | "OpenXRExtensionWrapperExtension"
997-
998-
// Declared final (un-inheritable) in Rust, but those are still servers.
999-
| "AudioServer" | "CameraServer" | "NavigationServer2D" | "NavigationServer3D" | "RenderingServer" | "TranslationServer" | "XRServer"
1000-
1001-
// PhysicsServer2D
1006+
pub fn classify_codegen_level(class_name: &str) -> Option<ClassCodegenLevel> {
1007+
let level = match class_name {
1008+
// See register_core_types() in https://github.com/godotengine/godot/blob/master/core/register_core_types.cpp,
1009+
// which is called before Core level is initialized.
1010+
// Currently only promoting super basic classes to Core level since this is a brittle hardcoded list (feel free expand as
1011+
// necessary based on careful evaluation of register_core_types).
1012+
| "Object" | "RefCounted" | "Resource" | "MainLoop" | "GDExtension"
1013+
=> ClassCodegenLevel::Core,
1014+
1015+
// See register_early_core_singletons() in https://github.com/godotengine/godot/blob/master/core/register_core_types.cpp,
1016+
// which is called before Core level is initialized.
1017+
| "ProjectSettings" | "Engine" | "OS" | "Time"
1018+
=> ClassCodegenLevel::Core,
1019+
1020+
// Symbols from another extension could be available in Core, but since GDExtension can currently not guarantee
1021+
// the order of different extensions being loaded, we prevent implicit dependencies and require Server.
1022+
| "OpenXRExtensionWrapperExtension"
1023+
=> ClassCodegenLevel::Servers,
1024+
1025+
// See register_server_types() in https://github.com/godotengine/godot/blob/master/servers/register_server_types.cpp
10021026
| "PhysicsDirectBodyState2D" | "PhysicsDirectBodyState2DExtension"
10031027
| "PhysicsDirectSpaceState2D" | "PhysicsDirectSpaceState2DExtension"
10041028
| "PhysicsServer2D" | "PhysicsServer2DExtension"
10051029
| "PhysicsServer2DManager"
1006-
1007-
// PhysicsServer3D
10081030
| "PhysicsDirectBodyState3D" | "PhysicsDirectBodyState3DExtension"
10091031
| "PhysicsDirectSpaceState3D" | "PhysicsDirectSpaceState3DExtension"
10101032
| "PhysicsServer3D" | "PhysicsServer3DExtension"
10111033
| "PhysicsServer3DManager"
10121034
| "PhysicsServer3DRenderingServerHandler"
1013-
1014-
=> true, _ => false
1015-
}
1035+
| "RenderData" | "RenderDataExtension"
1036+
| "RenderSceneData" | "RenderSceneDataExtension"
1037+
=> ClassCodegenLevel::Servers,
1038+
// Declared final (un-inheritable) in Rust, but those are still servers.
1039+
| "AudioServer" | "CameraServer" | "NavigationServer2D" | "NavigationServer3D" | "RenderingServer" | "TranslationServer" | "XRServer" | "DisplayServer"
1040+
=> ClassCodegenLevel::Servers,
1041+
1042+
// Work around wrong classification in https://github.com/godotengine/godot/issues/86206.
1043+
// https://github.com/godotengine/godot/issues/103867
1044+
"OpenXRInteractionProfileEditorBase"
1045+
| "OpenXRInteractionProfileEditor"
1046+
| "OpenXRBindingModifierEditor" if cfg!(before_api = "4.5")
1047+
=> ClassCodegenLevel::Editor,
1048+
// https://github.com/godotengine/godot/issues/86206
1049+
"ResourceImporterOggVorbis" | "ResourceImporterMP3" if cfg!(before_api = "4.3")
1050+
=> ClassCodegenLevel::Editor,
1051+
1052+
// No special-case override for this class.
1053+
_ => return None,
1054+
};
1055+
Some(level)
10161056
}
10171057

10181058
/// Whether a generated enum is `pub(crate)`; useful for manual re-exports.

godot-codegen/src/util.rs

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -51,32 +51,26 @@ pub fn make_sname_ptr(identifier: &str) -> TokenStream {
5151
}
5252

5353
pub fn get_api_level(class: &JsonClass) -> ClassCodegenLevel {
54-
// Work around wrong classification in https://github.com/godotengine/godot/issues/86206.
55-
fn override_editor(class_name: &str) -> bool {
56-
match class_name {
57-
// https://github.com/godotengine/godot/issues/103867
58-
"OpenXRInteractionProfileEditorBase"
59-
| "OpenXRInteractionProfileEditor"
60-
| "OpenXRBindingModifierEditor" => cfg!(before_api = "4.5"),
61-
62-
// https://github.com/godotengine/godot/issues/86206
63-
"ResourceImporterOggVorbis" | "ResourceImporterMP3" => cfg!(before_api = "4.3"),
64-
65-
_ => false,
66-
}
54+
// NOTE: We have to use a whitelist of known classes because Godot doesn't separate these out
55+
// beyond "editor" and "core" and some classes are also mis-classified in the JSON depending on the Godot version.
56+
if let Some(forced_classification) = special_cases::classify_codegen_level(&class.name) {
57+
return forced_classification;
6758
}
6859

69-
if special_cases::is_class_level_server(&class.name) {
70-
ClassCodegenLevel::Servers
71-
} else if class.api_type == "editor" || override_editor(&class.name) {
72-
ClassCodegenLevel::Editor
73-
} else if class.api_type == "core" {
74-
ClassCodegenLevel::Scene
75-
} else {
76-
panic!(
77-
"class {} has unknown API type {}",
78-
class.name, class.api_type
79-
)
60+
// NOTE: Right now, Godot reports everything that's not "editor" as "core" in `extension_api.json`.
61+
// If it wasn't picked up by classify_codegen_level, and Godot reports it as "core" we will treat it as a scene class.
62+
match class.api_type.as_str() {
63+
"editor" => ClassCodegenLevel::Editor,
64+
"core" => ClassCodegenLevel::Scene,
65+
"extension" => ClassCodegenLevel::Scene,
66+
"editor_extension" => ClassCodegenLevel::Editor,
67+
_ => {
68+
// we don't know this classification
69+
panic!(
70+
"class {} has unknown API type {}",
71+
class.name, class.api_type
72+
);
73+
}
8074
}
8175
}
8276

godot-ffi/src/binding/mod.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
*/
77

88
use crate::{
9-
BuiltinLifecycleTable, BuiltinMethodTable, ClassEditorMethodTable, ClassSceneMethodTable,
10-
ClassServersMethodTable, GDExtensionClassLibraryPtr, GDExtensionInterface,
11-
GdextRuntimeMetadata, ManualInitCell, UtilityFunctionTable,
9+
BuiltinLifecycleTable, BuiltinMethodTable, ClassCoreMethodTable, ClassEditorMethodTable,
10+
ClassSceneMethodTable, ClassServersMethodTable, GDExtensionClassLibraryPtr,
11+
GDExtensionInterface, GdextRuntimeMetadata, ManualInitCell, UtilityFunctionTable,
1212
};
1313

1414
#[cfg(feature = "experimental-threads")]
@@ -34,6 +34,7 @@ pub(crate) struct GodotBinding {
3434
interface: GDExtensionInterface,
3535
library: ClassLibraryPtr,
3636
global_method_table: BuiltinLifecycleTable,
37+
class_core_method_table: ManualInitCell<ClassCoreMethodTable>,
3738
class_server_method_table: ManualInitCell<ClassServersMethodTable>,
3839
class_scene_method_table: ManualInitCell<ClassSceneMethodTable>,
3940
class_editor_method_table: ManualInitCell<ClassEditorMethodTable>,
@@ -56,6 +57,7 @@ impl GodotBinding {
5657
interface,
5758
library: ClassLibraryPtr(library),
5859
global_method_table,
60+
class_core_method_table: ManualInitCell::new(),
5961
class_server_method_table: ManualInitCell::new(),
6062
class_scene_method_table: ManualInitCell::new(),
6163
class_editor_method_table: ManualInitCell::new(),
@@ -148,6 +150,20 @@ pub unsafe fn class_servers_api() -> &'static ClassServersMethodTable {
148150
)
149151
}
150152

153+
/// # Safety
154+
///
155+
/// - The Godot binding must have been initialized before calling this function.
156+
/// - The class core method table must have been initialized before calling this function.
157+
///
158+
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
159+
#[inline(always)]
160+
pub unsafe fn class_core_api() -> &'static ClassCoreMethodTable {
161+
get_table(
162+
&get_binding().class_core_method_table,
163+
"cannot fetch classes; init level 'Core' not yet loaded",
164+
)
165+
}
166+
151167
/// # Safety
152168
///
153169
/// - The Godot binding must have been initialized before calling this function.
@@ -251,6 +267,20 @@ pub(crate) unsafe fn get_binding() -> &'static GodotBinding {
251267
BindingStorage::get_binding_unchecked()
252268
}
253269

270+
/// # Safety
271+
///
272+
/// - The Godot binding must have been initialized before calling this function.
273+
/// - Must only be called once.
274+
///
275+
/// If "experimental-threads" is not enabled, then this must be called from the same thread that the bindings were initialized from.
276+
pub(crate) unsafe fn initialize_class_core_method_table(table: ClassCoreMethodTable) {
277+
initialize_table(
278+
&get_binding().class_core_method_table,
279+
table,
280+
"classes (Core level)",
281+
)
282+
}
283+
254284
/// # Safety
255285
///
256286
/// - The Godot binding must have been initialized before calling this function.

godot-ffi/src/lib.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ pub use gen::interface::*;
8181
// Method tables
8282
pub use gen::table_builtins::*;
8383
pub use gen::table_builtins_lifecycle::*;
84+
pub use gen::table_core_classes::*;
8485
pub use gen::table_editor_classes::*;
8586
pub use gen::table_scene_classes::*;
8687
pub use gen::table_servers_classes::*;
@@ -101,8 +102,9 @@ mod binding;
101102

102103
pub use binding::*;
103104
use binding::{
104-
initialize_binding, initialize_builtin_method_table, initialize_class_editor_method_table,
105-
initialize_class_scene_method_table, initialize_class_server_method_table, runtime_metadata,
105+
initialize_binding, initialize_builtin_method_table, initialize_class_core_method_table,
106+
initialize_class_editor_method_table, initialize_class_scene_method_table,
107+
initialize_class_server_method_table, runtime_metadata,
106108
};
107109

108110
#[cfg(not(wasm_nothreads))]
@@ -313,9 +315,18 @@ pub unsafe fn load_class_method_table(api_level: InitLevel) {
313315
let (class_count, method_count);
314316
match api_level {
315317
InitLevel::Core => {
316-
// Currently we don't need to do anything in `Core`, this may change in the future.
317-
class_count = 0;
318-
method_count = 0;
318+
// SAFETY: The interface has been initialized and this function hasn't been called before.
319+
unsafe {
320+
#[cfg(feature = "codegen-lazy-fptrs")]
321+
initialize_class_core_method_table(ClassCoreMethodTable::load());
322+
#[cfg(not(feature = "codegen-lazy-fptrs"))]
323+
initialize_class_core_method_table(ClassCoreMethodTable::load(
324+
interface,
325+
&mut string_names,
326+
));
327+
}
328+
class_count = ClassCoreMethodTable::CLASS_COUNT;
329+
method_count = ClassCoreMethodTable::METHOD_COUNT;
319330
}
320331
InitLevel::Servers => {
321332
// SAFETY: The interface has been initialized and this function hasn't been called before.
@@ -386,7 +397,7 @@ pub unsafe fn godot_has_feature(
386397
// Issue a raw C call to OS.has_feature(tag_string).
387398

388399
// SAFETY: Called from main thread, interface has been initialized, and the scene api has been initialized.
389-
let method_bind = unsafe { class_scene_api() }.os__has_feature();
400+
let method_bind = unsafe { class_core_api() }.os__has_feature();
390401

391402
// SAFETY: Called from main thread, and interface has been initialized.
392403
let interface = unsafe { get_interface() };

itest/rust/src/lib.rs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
use godot::init::{gdextension, ExtensionLibrary, InitLevel};
9+
use godot::sys::Global;
910

1011
mod benchmarks;
1112
mod builtin_tests;
@@ -18,10 +19,39 @@ mod register_tests;
1819
// ----------------------------------------------------------------------------------------------------------------------------------------------
1920
// Entry point
2021

22+
static LEVELS_SEEN: Global<Vec<InitLevel>> = Global::default();
23+
2124
#[gdextension(entry_symbol = itest_init)]
2225
unsafe impl ExtensionLibrary for framework::IntegrationTests {
26+
fn min_level() -> InitLevel {
27+
InitLevel::Core
28+
}
2329
fn on_level_init(level: InitLevel) {
24-
// Testing that we can initialize and use `Object`-derived classes during `Servers` init level. See `object_tests::init_level_test`.
25-
object_tests::initialize_init_level_test(level);
30+
LEVELS_SEEN.lock().push(level);
31+
match level {
32+
InitLevel::Core => {
33+
// Make sure we can access early core singletons.
34+
object_tests::test_early_core_singletons();
35+
}
36+
InitLevel::Servers => {
37+
// Make sure we can access server singletons by now.
38+
object_tests::test_server_singletons();
39+
}
40+
InitLevel::Scene => {}
41+
InitLevel::Editor => {}
42+
}
43+
}
44+
}
45+
46+
// Ensure that we saw all the init levels expected.
47+
#[crate::framework::itest]
48+
fn observed_all_init_levels() {
49+
let levels_seen = LEVELS_SEEN.lock().clone();
50+
assert_eq!(levels_seen[0], InitLevel::Core);
51+
assert_eq!(levels_seen[1], InitLevel::Servers);
52+
assert_eq!(levels_seen[2], InitLevel::Scene);
53+
// NOTE: some tests don't see editor mode
54+
if let Some(level_3) = levels_seen.get(3) {
55+
assert_eq!(*level_3, InitLevel::Editor);
2656
}
2757
}

0 commit comments

Comments
 (0)