Skip to content

Commit f25f65c

Browse files
bors[bot]Bromeon
andauthored
Merge #114
114: Fix base class registration, better panic coverage r=Bromeon a=Bromeon Fixes #88. Co-authored-by: Jan Haller <[email protected]>
2 parents 17487d6 + 2a94b6c commit f25f65c

File tree

10 files changed

+173
-72
lines changed

10 files changed

+173
-72
lines changed

godot-codegen/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ const SELECTED_CLASSES: &[&str] = &[
269269
"CollisionShape2D",
270270
"Control",
271271
"FileAccess",
272+
"HTTPRequest",
272273
"Input",
273274
"Label",
274275
"MainLoop",

godot-core/src/init/mod.rs

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,31 @@ pub unsafe fn __gdext_load_library<E: ExtensionLibrary>(
1818
panic!("Attempted to call Godot engine from unit-tests; use integration tests for this.")
1919
}
2020

21-
sys::initialize(interface, library);
21+
let init_code = || {
22+
sys::initialize(interface, library);
2223

23-
let mut handle = InitHandle::new();
24+
let mut handle = InitHandle::new();
2425

25-
let success = E::load_library(&mut handle);
26-
// No early exit, unclear if Godot still requires output parameters to be set
26+
let success = E::load_library(&mut handle);
27+
// No early exit, unclear if Godot still requires output parameters to be set
2728

28-
let godot_init_params = sys::GDExtensionInitialization {
29-
minimum_initialization_level: handle.lowest_init_level().to_sys(),
30-
userdata: std::ptr::null_mut(),
31-
initialize: Some(ffi_initialize_layer),
32-
deinitialize: Some(ffi_deinitialize_layer),
33-
};
29+
let godot_init_params = sys::GDExtensionInitialization {
30+
minimum_initialization_level: handle.lowest_init_level().to_sys(),
31+
userdata: std::ptr::null_mut(),
32+
initialize: Some(ffi_initialize_layer),
33+
deinitialize: Some(ffi_deinitialize_layer),
34+
};
35+
36+
*init = godot_init_params;
37+
INIT_HANDLE = Some(handle);
3438

35-
let is_success = /*handle.*/success as u8;
39+
success as u8
40+
};
3641

37-
*init = godot_init_params;
38-
INIT_HANDLE = Some(handle);
42+
let ctx = || "error when loading GDExtension library";
43+
let is_success = crate::private::handle_panic(ctx, init_code);
3944

40-
is_success
45+
is_success.unwrap_or(0)
4146
}
4247

4348
#[doc(hidden)]
@@ -49,16 +54,34 @@ unsafe extern "C" fn ffi_initialize_layer(
4954
_userdata: *mut std::ffi::c_void,
5055
init_level: sys::GDExtensionInitializationLevel,
5156
) {
52-
let handle = INIT_HANDLE.as_mut().unwrap();
53-
handle.run_init_function(InitLevel::from_sys(init_level));
57+
let ctx = || {
58+
format!(
59+
"failed to initialize GDExtension layer `{:?}`",
60+
InitLevel::from_sys(init_level)
61+
)
62+
};
63+
64+
crate::private::handle_panic(ctx, || {
65+
let handle = INIT_HANDLE.as_mut().unwrap();
66+
handle.run_init_function(InitLevel::from_sys(init_level));
67+
});
5468
}
5569

5670
unsafe extern "C" fn ffi_deinitialize_layer(
5771
_userdata: *mut std::ffi::c_void,
5872
init_level: sys::GDExtensionInitializationLevel,
5973
) {
60-
let handle = INIT_HANDLE.as_mut().unwrap();
61-
handle.run_deinit_function(InitLevel::from_sys(init_level));
74+
let ctx = || {
75+
format!(
76+
"failed to deinitialize GDExtension layer `{:?}`",
77+
InitLevel::from_sys(init_level)
78+
)
79+
};
80+
81+
crate::private::handle_panic(ctx, || {
82+
let handle = INIT_HANDLE.as_mut().unwrap();
83+
handle.run_deinit_function(InitLevel::from_sys(init_level));
84+
});
6285
}
6386

6487
// ----------------------------------------------------------------------------------------------------------------------------------------------

godot-core/src/lib.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,32 @@ pub mod private {
4848
sys::plugin_foreach!(__GODOT_PLUGIN_REGISTRY; visitor);
4949
}
5050

51-
pub fn print_panic(err: Box<dyn std::any::Any + Send>) {
51+
fn print_panic(err: Box<dyn std::any::Any + Send>) {
5252
if let Some(s) = err.downcast_ref::<&'static str>() {
53-
log::godot_error!("rust-panic: {s}");
53+
log::godot_error!("Panic msg: {s}");
5454
} else if let Some(s) = err.downcast_ref::<String>() {
55-
log::godot_error!("rust-panic: {s}");
55+
log::godot_error!("Panic msg: {s}");
5656
} else {
57-
log::godot_error!("rust-panic of type ID {:?}", err.type_id());
57+
log::godot_error!("Rust panic of type ID {:?}", err.type_id());
58+
}
59+
}
60+
61+
/// Executes `code`. If a panic is thrown, it is caught and an error message is printed to Godot.
62+
///
63+
/// Returns `None` if a panic occurred, and `Some(result)` with the result of `code` otherwise.
64+
pub fn handle_panic<E, F, R, S>(error_context: E, code: F) -> Option<R>
65+
where
66+
E: FnOnce() -> S,
67+
F: FnOnce() -> R + std::panic::UnwindSafe,
68+
S: std::fmt::Display,
69+
{
70+
match std::panic::catch_unwind(code) {
71+
Ok(result) => Some(result),
72+
Err(err) => {
73+
log::godot_error!("Rust function panicked. Context: {}", error_context());
74+
print_panic(err);
75+
None
76+
}
5877
}
5978
}
6079
}

godot-core/src/macros.rs

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -66,24 +66,24 @@ macro_rules! gdext_register_method_inner {
6666
ret: sys::GDExtensionVariantPtr,
6767
err: *mut sys::GDExtensionCallError,
6868
) {
69-
let result = ::std::panic::catch_unwind(|| {
70-
<Sig as SignatureTuple>::varcall::< $Class >(
71-
instance_ptr,
72-
args,
73-
ret,
74-
err,
75-
|inst, params| {
76-
let ( $($param,)* ) = params;
77-
inst.$method_name( $( $param, )* )
78-
},
79-
stringify!($method_name),
80-
)
81-
});
82-
83-
if let Err(e) = result {
84-
$crate::log::godot_error!("Rust function panicked: {}", stringify!($method_name));
85-
$crate::private::print_panic(e);
86-
69+
let success = $crate::private::handle_panic(
70+
|| stringify!($method_name),
71+
|| {
72+
<Sig as SignatureTuple>::varcall::< $Class >(
73+
instance_ptr,
74+
args,
75+
ret,
76+
err,
77+
|inst, params| {
78+
let ( $($param,)* ) = params;
79+
inst.$method_name( $( $param, )* )
80+
},
81+
stringify!($method_name),
82+
);
83+
}
84+
);
85+
86+
if success.is_none() {
8787
// Signal error and set return type to Nil
8888
(*err).error = sys::GDEXTENSION_CALL_ERROR_INVALID_METHOD; // no better fitting enum?
8989
sys::interface_fn!(variant_new_nil)(ret);
@@ -100,23 +100,23 @@ macro_rules! gdext_register_method_inner {
100100
args: *const sys::GDExtensionConstTypePtr,
101101
ret: sys::GDExtensionTypePtr,
102102
) {
103-
let result = ::std::panic::catch_unwind(|| {
104-
<Sig as SignatureTuple>::ptrcall::< $Class >(
105-
instance_ptr,
106-
args,
107-
ret,
108-
|inst, params| {
109-
let ( $($param,)* ) = params;
110-
inst.$method_name( $( $param, )* )
111-
},
112-
stringify!($method_name),
113-
);
114-
});
115-
116-
if let Err(e) = result {
117-
$crate::log::godot_error!("Rust function panicked: {}", stringify!($method_name));
118-
$crate::private::print_panic(e);
119-
103+
let success = $crate::private::handle_panic(
104+
|| stringify!($method_name),
105+
|| {
106+
<Sig as SignatureTuple>::ptrcall::< $Class >(
107+
instance_ptr,
108+
args,
109+
ret,
110+
|inst, params| {
111+
let ( $($param,)* ) = params;
112+
inst.$method_name( $( $param, )* )
113+
},
114+
stringify!($method_name),
115+
);
116+
}
117+
);
118+
119+
if success.is_none() {
120120
// TODO set return value to T::default()?
121121
}
122122
}

godot-core/src/obj/traits.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ where
2525
/// Defines the memory strategy.
2626
type Mem: mem::Memory;
2727

28+
/// The name of the class, under which it is registered in Godot.
29+
///
30+
/// This may deviate from the Rust struct name: `HttpRequest::CLASS_NAME == "HTTPRequest"`.
2831
const CLASS_NAME: &'static str;
2932
}
3033

godot-core/src/registry.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -233,16 +233,23 @@ fn register_class_raw(info: ClassRegistrationInfo) {
233233
.expect("class defined (parent_class_name)");
234234

235235
unsafe {
236-
interface_fn!(classdb_register_extension_class)(
236+
// Try to register class...
237+
#[allow(clippy::let_unit_value)] // notifies us if Godot API ever adds a return type.
238+
let _: () = interface_fn!(classdb_register_extension_class)(
237239
sys::get_library(),
238240
class_name.string_sys(),
239241
parent_class_name.string_sys(),
240242
ptr::addr_of!(info.godot_params),
241243
);
242-
}
243244

244-
// std::mem::forget(class_name);
245-
// std::mem::forget(parent_class_name);
245+
// ...then see if it worked.
246+
// This is necessary because the above registration does not report errors (apart from console output).
247+
let tag = interface_fn!(classdb_get_class_tag)(class_name.string_sys());
248+
assert!(
249+
!tag.is_null(),
250+
"failed to register class `{class_name}`; check preceding Godot stderr messages",
251+
);
252+
}
246253

247254
// ...then custom symbols
248255

godot-macros/src/derive_godot_class.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,9 @@ pub fn transform(input: TokenStream) -> ParseResult<TokenStream> {
2222
let fields = parse_fields(class)?;
2323

2424
let base_ty = &struct_cfg.base_ty;
25-
let base_ty_str = struct_cfg.base_ty.to_string();
2625
let class_name = &class.name;
2726
let class_name_str = class.name.to_string();
28-
let inherits_macro = format_ident!("inherits_transitive_{}", &base_ty_str);
27+
let inherits_macro = format_ident!("inherits_transitive_{}", base_ty);
2928

3029
let prv = quote! { ::godot::private };
3130
let deref_impl = make_deref_impl(class_name, &fields);
@@ -57,7 +56,7 @@ pub fn transform(input: TokenStream) -> ParseResult<TokenStream> {
5756
::godot::sys::plugin_add!(__GODOT_PLUGIN_REGISTRY in #prv; #prv::ClassPlugin {
5857
class_name: #class_name_str,
5958
component: #prv::PluginComponent::ClassDef {
60-
base_class_name: #base_ty_str,
59+
base_class_name: <::godot::engine::#base_ty as ::godot::obj::GodotClass>::CLASS_NAME,
6160
generated_create_fn: #create_fn,
6261
free_fn: #prv::callbacks::free::<#class_name>,
6362
},

godot-macros/src/itest.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,13 @@ pub fn transform(input: TokenStream) -> Result<TokenStream, Error> {
4040
pub fn #test_name() -> bool {
4141
println!(#init_msg);
4242

43-
let result = ::std::panic::catch_unwind(
43+
// Explicit type to prevent tests from returning a value
44+
let success: Option<()> = godot::private::handle_panic(
45+
|| #error_msg,
4446
|| #body
4547
);
4648

47-
if let Err(e) = result {
48-
::godot::log::godot_error!(#error_msg);
49-
::godot::private::print_panic(e);
50-
false
51-
} else {
52-
true
53-
}
49+
success.is_some()
5450
}
5551
})
5652
}

itest/rust/src/codegen_test.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*/
6+
7+
// This file tests the presence and naming of generated symbols, not their functionality.
8+
9+
use crate::itest;
10+
11+
use godot::engine::HttpRequest;
12+
use godot::prelude::*;
13+
14+
pub fn run() -> bool {
15+
let mut ok = true;
16+
ok &= codegen_class_renamed();
17+
ok &= codegen_base_renamed();
18+
ok
19+
}
20+
21+
#[itest]
22+
fn codegen_class_renamed() {
23+
// Known as `HTTPRequest` in Godot
24+
let obj = HttpRequest::new_alloc();
25+
obj.free();
26+
}
27+
28+
#[itest]
29+
fn codegen_base_renamed() {
30+
// The registration is done at startup time, so it may already fail during GDExtension init.
31+
// Nevertheless, try to instantiate an object with base HttpRequest here.
32+
33+
let obj = Gd::with_base(|base| TestBaseRenamed { base });
34+
let _id = obj.instance_id();
35+
36+
obj.free();
37+
}
38+
39+
#[derive(GodotClass)]
40+
#[class(base=HttpRequest)]
41+
pub struct TestBaseRenamed {
42+
#[base]
43+
base: Base<HttpRequest>,
44+
}
45+
46+
#[godot_api]
47+
impl GodotExt for TestBaseRenamed {
48+
fn init(base: Base<HttpRequest>) -> Self {
49+
TestBaseRenamed { base }
50+
}
51+
}

itest/rust/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use std::panic::UnwindSafe;
1212
mod array_test;
1313
mod base_test;
1414
mod builtin_test;
15+
mod codegen_test;
1516
mod dictionary_test;
1617
mod enum_test;
1718
mod export_test;
@@ -29,6 +30,7 @@ fn run_tests() -> bool {
2930
let mut ok = true;
3031
ok &= base_test::run();
3132
ok &= builtin_test::run();
33+
ok &= codegen_test::run();
3234
ok &= enum_test::run();
3335
ok &= export_test::run();
3436
ok &= gdscript_ffi_test::run();

0 commit comments

Comments
 (0)