Skip to content

Commit

Permalink
Cover init layer with handle_panic, allow unwind-safe return types
Browse files Browse the repository at this point in the history
  • Loading branch information
Bromeon committed Feb 5, 2023
1 parent ea9e732 commit 84a8c42
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 36 deletions.
59 changes: 41 additions & 18 deletions godot-core/src/init/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,31 @@ pub unsafe fn __gdext_load_library<E: ExtensionLibrary>(
panic!("Attempted to call Godot engine from unit-tests; use integration tests for this.")
}

sys::initialize(interface, library);
let init_code = || {
sys::initialize(interface, library);

let mut handle = InitHandle::new();
let mut handle = InitHandle::new();

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

let godot_init_params = sys::GDExtensionInitialization {
minimum_initialization_level: handle.lowest_init_level().to_sys(),
userdata: std::ptr::null_mut(),
initialize: Some(ffi_initialize_layer),
deinitialize: Some(ffi_deinitialize_layer),
};
let godot_init_params = sys::GDExtensionInitialization {
minimum_initialization_level: handle.lowest_init_level().to_sys(),
userdata: std::ptr::null_mut(),
initialize: Some(ffi_initialize_layer),
deinitialize: Some(ffi_deinitialize_layer),
};

*init = godot_init_params;
INIT_HANDLE = Some(handle);

let is_success = /*handle.*/success as u8;
success as u8
};

*init = godot_init_params;
INIT_HANDLE = Some(handle);
let ctx = || "error when loading GDExtension library";
let is_success = crate::private::handle_panic(ctx, init_code);

is_success
is_success.unwrap_or(0)
}

#[doc(hidden)]
Expand All @@ -49,16 +54,34 @@ unsafe extern "C" fn ffi_initialize_layer(
_userdata: *mut std::ffi::c_void,
init_level: sys::GDExtensionInitializationLevel,
) {
let handle = INIT_HANDLE.as_mut().unwrap();
handle.run_init_function(InitLevel::from_sys(init_level));
let ctx = || {
format!(
"failed to initialize GDExtension layer `{:?}`",
InitLevel::from_sys(init_level)
)
};

crate::private::handle_panic(ctx, || {
let handle = INIT_HANDLE.as_mut().unwrap();
handle.run_init_function(InitLevel::from_sys(init_level));
});
}

unsafe extern "C" fn ffi_deinitialize_layer(
_userdata: *mut std::ffi::c_void,
init_level: sys::GDExtensionInitializationLevel,
) {
let handle = INIT_HANDLE.as_mut().unwrap();
handle.run_deinit_function(InitLevel::from_sys(init_level));
let ctx = || {
format!(
"failed to deinitialize GDExtension layer `{:?}`",
InitLevel::from_sys(init_level)
)
};

crate::private::handle_panic(ctx, || {
let handle = INIT_HANDLE.as_mut().unwrap();
handle.run_deinit_function(InitLevel::from_sys(init_level));
});
}

// ----------------------------------------------------------------------------------------------------------------------------------------------
Expand Down
25 changes: 13 additions & 12 deletions godot-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,29 +50,30 @@ pub mod private {

fn print_panic(err: Box<dyn std::any::Any + Send>) {
if let Some(s) = err.downcast_ref::<&'static str>() {
log::godot_error!("rust-panic: {s}");
log::godot_error!("Panic msg: {s}");
} else if let Some(s) = err.downcast_ref::<String>() {
log::godot_error!("rust-panic: {s}");
log::godot_error!("Panic msg: {s}");
} else {
log::godot_error!("rust-panic of type ID {:?}", err.type_id());
log::godot_error!("Rust panic of type ID {:?}", err.type_id());
}
}

/// Executes `code`. If a panic is thrown, it is caught and an error message is printed to Godot.
///
/// Returns `true` if a panic occurred.
pub fn handle_panic<E, F, S>(error_context: E, code: F) -> bool
/// Returns `None` if a panic occurred, and `Some(result)` with the result of `code` otherwise.
pub fn handle_panic<E, F, R, S>(error_context: E, code: F) -> Option<R>
where
E: FnOnce() -> S,
F: FnOnce() + std::panic::UnwindSafe,
F: FnOnce() -> R + std::panic::UnwindSafe,
S: std::fmt::Display,
{
if let Err(e) = std::panic::catch_unwind(code) {
log::godot_error!("Rust function panicked: {}", error_context());
print_panic(e);
true
} else {
false
match std::panic::catch_unwind(code) {
Ok(result) => Some(result),
Err(err) => {
log::godot_error!("Rust function panicked. Context: {}", error_context());
print_panic(err);
None
}
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions godot-core/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ macro_rules! gdext_register_method_inner {
ret: sys::GDExtensionVariantPtr,
err: *mut sys::GDExtensionCallError,
) {
let has_panicked = $crate::private::handle_panic(
let success = $crate::private::handle_panic(
|| stringify!($method_name),
|| {
<Sig as SignatureTuple>::varcall::< $Class >(
Expand All @@ -83,7 +83,7 @@ macro_rules! gdext_register_method_inner {
}
);

if has_panicked {
if success.is_none() {
// Signal error and set return type to Nil
(*err).error = sys::GDEXTENSION_CALL_ERROR_INVALID_METHOD; // no better fitting enum?
sys::interface_fn!(variant_new_nil)(ret);
Expand All @@ -100,7 +100,7 @@ macro_rules! gdext_register_method_inner {
args: *const sys::GDExtensionConstTypePtr,
ret: sys::GDExtensionTypePtr,
) {
let has_panicked = $crate::private::handle_panic(
let success = $crate::private::handle_panic(
|| stringify!($method_name),
|| {
<Sig as SignatureTuple>::ptrcall::< $Class >(
Expand All @@ -116,7 +116,7 @@ macro_rules! gdext_register_method_inner {
}
);

if has_panicked {
if success.is_none() {
// TODO set return value to T::default()?
}
}
Expand Down
5 changes: 3 additions & 2 deletions godot-macros/src/itest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@ pub fn transform(input: TokenStream) -> Result<TokenStream, Error> {
pub fn #test_name() -> bool {
println!(#init_msg);

let has_panicked = godot::private::handle_panic(
// Explicit type to prevent tests from returning a value
let success: Option<()> = godot::private::handle_panic(
|| #error_msg,
|| #body
);

!has_panicked
success.is_some()
}
})
}

0 comments on commit 84a8c42

Please sign in to comment.