Skip to content

Commit acae2b0

Browse files
authored
Merge pull request #873 from jrb0001/callable-panic
Don't abort on panic inside Callable
2 parents 92c73b8 + 1b24a5f commit acae2b0

File tree

4 files changed

+241
-13
lines changed

4 files changed

+241
-13
lines changed

godot-core/src/builtin/callable.rs

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use godot_ffi as sys;
99

1010
use crate::builtin::{inner, StringName, Variant, VariantArray};
1111
use crate::classes::Object;
12-
use crate::meta::{GodotType, ToGodot};
12+
use crate::meta::{CallContext, GodotType, ToGodot};
1313
use crate::obj::bounds::DynMemory;
1414
use crate::obj::Bounds;
1515
use crate::obj::{Gd, GodotClass, InstanceId};
@@ -392,10 +392,19 @@ mod custom_callable {
392392
) {
393393
let arg_refs: &[&Variant] = Variant::borrow_ref_slice(p_args, p_argument_count as usize);
394394

395-
let c: &mut C = CallableUserdata::inner_from_raw(callable_userdata);
395+
let name = {
396+
let c: &C = CallableUserdata::inner_from_raw(callable_userdata);
397+
c.to_string()
398+
};
399+
let ctx = CallContext::custom_callable(name.as_str());
396400

397-
let result = c.invoke(arg_refs);
398-
crate::meta::varcall_return_checked(result, r_return, r_error);
401+
crate::private::handle_varcall_panic(&ctx, &mut *r_error, move || {
402+
// Get the RustCallable again inside closure so it doesn't have to be UnwindSafe.
403+
let c: &mut C = CallableUserdata::inner_from_raw(callable_userdata);
404+
let result = c.invoke(arg_refs);
405+
crate::meta::varcall_return_checked(result, r_return, r_error);
406+
Ok(())
407+
});
399408
}
400409

401410
pub unsafe extern "C" fn rust_callable_call_fn<F>(
@@ -409,10 +418,19 @@ mod custom_callable {
409418
{
410419
let arg_refs: &[&Variant] = Variant::borrow_ref_slice(p_args, p_argument_count as usize);
411420

412-
let w: &mut FnWrapper<F> = CallableUserdata::inner_from_raw(callable_userdata);
413-
414-
let result = (w.rust_function)(arg_refs);
415-
crate::meta::varcall_return_checked(result, r_return, r_error);
421+
let name = {
422+
let w: &FnWrapper<F> = CallableUserdata::inner_from_raw(callable_userdata);
423+
w.name.to_string()
424+
};
425+
let ctx = CallContext::custom_callable(name.as_str());
426+
427+
crate::private::handle_varcall_panic(&ctx, &mut *r_error, move || {
428+
// Get the FnWrapper again inside closure so the FnMut doesn't have to be UnwindSafe.
429+
let w: &mut FnWrapper<F> = CallableUserdata::inner_from_raw(callable_userdata);
430+
let result = (w.rust_function)(arg_refs);
431+
crate::meta::varcall_return_checked(result, r_return, r_error);
432+
Ok(())
433+
});
416434
}
417435

418436
pub unsafe extern "C" fn rust_callable_destroy<T>(callable_userdata: *mut std::ffi::c_void) {

godot-core/src/meta/signature.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,14 @@ impl<'a> CallContext<'a> {
627627
}
628628
}
629629

630+
/// Call from Godot into a custom Callable.
631+
pub fn custom_callable(function_name: &'a str) -> Self {
632+
Self {
633+
class_name: Cow::Borrowed("<Callable>"),
634+
function_name,
635+
}
636+
}
637+
630638
/// Outbound call from Rust into the engine, class/builtin APIs.
631639
pub const fn outbound(class_name: &'a str, function_name: &'a str) -> Self {
632640
Self {

itest/rust/src/builtin_tests/containers/callable_test.rs

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
* License, v. 2.0. If a copy of the MPL was not distributed with this
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
7-
87
use godot::builtin::inner::InnerCallable;
98
use godot::builtin::{varray, Callable, GString, StringName, Variant};
109
use godot::classes::{Node2D, Object};
1110
use godot::meta::ToGodot;
1211
use godot::obj::{NewAlloc, NewGd};
1312
use godot::register::{godot_api, GodotClass};
13+
use std::fmt::{Display, Formatter};
14+
use std::hash::Hasher;
15+
use std::sync::atomic::{AtomicU32, Ordering};
1416

1517
use crate::framework::itest;
1618

@@ -159,10 +161,10 @@ impl CallableRefcountTest {
159161
// Tests and infrastructure for custom callables
160162

161163
#[cfg(since_api = "4.2")]
162-
mod custom_callable {
164+
pub mod custom_callable {
163165
use super::*;
164166
use crate::framework::assert_eq_self;
165-
use godot::builtin::Dictionary;
167+
use godot::builtin::{Dictionary, RustCallable};
166168
use std::fmt;
167169
use std::hash::Hash;
168170
use std::sync::{Arc, Mutex};
@@ -287,6 +289,29 @@ mod custom_callable {
287289
assert_eq!(eq_count(&bt), 1, "hash collision, eq for b needed");
288290
}
289291

292+
#[itest]
293+
fn callable_callv_panic_from_fn() {
294+
let received = Arc::new(AtomicU32::new(0));
295+
let received_callable = received.clone();
296+
let callable = Callable::from_fn("test", move |_args| {
297+
panic!("TEST: {}", received_callable.fetch_add(1, Ordering::SeqCst))
298+
});
299+
300+
assert_eq!(Variant::nil(), callable.callv(varray![]));
301+
302+
assert_eq!(1, received.load(Ordering::SeqCst));
303+
}
304+
305+
#[itest]
306+
fn callable_callv_panic_from_custom() {
307+
let received = Arc::new(AtomicU32::new(0));
308+
let callable = Callable::from_custom(PanicCallable(received.clone()));
309+
310+
assert_eq!(Variant::nil(), callable.callv(varray![]));
311+
312+
assert_eq!(1, received.load(Ordering::SeqCst));
313+
}
314+
290315
struct Adder {
291316
sum: i32,
292317

@@ -365,6 +390,32 @@ mod custom_callable {
365390
fn hash_count(tracker: &Arc<Mutex<Tracker>>) -> usize {
366391
tracker.lock().unwrap().hash_counter
367392
}
393+
394+
pub struct PanicCallable(pub Arc<AtomicU32>);
395+
396+
impl PartialEq for PanicCallable {
397+
fn eq(&self, other: &Self) -> bool {
398+
Arc::ptr_eq(&self.0, &other.0)
399+
}
400+
}
401+
402+
impl Hash for PanicCallable {
403+
fn hash<H: Hasher>(&self, state: &mut H) {
404+
state.write_usize(Arc::as_ptr(&self.0) as usize)
405+
}
406+
}
407+
408+
impl Display for PanicCallable {
409+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
410+
write!(f, "test")
411+
}
412+
}
413+
414+
impl RustCallable for PanicCallable {
415+
fn invoke(&mut self, _args: &[&Variant]) -> Result<Variant, ()> {
416+
panic!("TEST: {}", self.0.fetch_add(1, Ordering::SeqCst))
417+
}
418+
}
368419
}
369420

370421
// ----------------------------------------------------------------------------------------------------------------------------------------------

itest/rust/src/builtin_tests/containers/signal_test.rs

Lines changed: 153 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,10 @@
55
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
66
*/
77

8-
use std::cell::Cell;
9-
108
use godot::builtin::{Callable, GString, Signal, StringName, Variant};
119
use godot::meta::ToGodot;
1210
use godot::register::{godot_api, GodotClass};
11+
use std::cell::Cell;
1312

1413
use godot::classes::{Object, RefCounted};
1514
use godot::obj::{Base, Gd, NewAlloc, NewGd, WithBaseField};
@@ -149,3 +148,155 @@ fn connect_signal() {
149148

150149
receiver.free();
151150
}
151+
152+
#[cfg(since_api = "4.2")]
153+
mod custom_callable {
154+
use godot::builtin::{Callable, Signal, StringName};
155+
use godot::meta::ToGodot;
156+
use std::sync::atomic::{AtomicU32, Ordering};
157+
use std::sync::Arc;
158+
159+
use godot::classes::Node;
160+
use godot::obj::{Gd, NewAlloc};
161+
162+
use crate::builtin_tests::containers::callable_test::custom_callable::PanicCallable;
163+
use crate::framework::{itest, TestContext};
164+
165+
#[itest]
166+
fn connect_signal_panic_user_from_fn() {
167+
connect_signal_panic_shared(
168+
"test_signal",
169+
connect_signal_panic_from_fn,
170+
|node| {
171+
node.add_user_signal("test_signal".into());
172+
},
173+
|node| {
174+
node.emit_signal(StringName::from("test_signal"), &[987i64.to_variant()]);
175+
},
176+
);
177+
}
178+
179+
#[itest]
180+
fn connect_signal_panic_user_from_custom() {
181+
connect_signal_panic_shared(
182+
"test_signal",
183+
connect_signal_panic_from_custom,
184+
|node| {
185+
node.add_user_signal("test_signal".into());
186+
},
187+
|node| {
188+
node.emit_signal(StringName::from("test_signal"), &[987i64.to_variant()]);
189+
},
190+
);
191+
}
192+
193+
#[itest]
194+
fn connect_signal_panic_from_fn_tree_entered(ctx: &TestContext) {
195+
connect_signal_panic_shared(
196+
"tree_entered",
197+
connect_signal_panic_from_fn,
198+
|_node| {},
199+
|node| {
200+
ctx.scene_tree.clone().add_child(&*node);
201+
ctx.scene_tree.clone().remove_child(&*node);
202+
},
203+
);
204+
}
205+
206+
#[itest]
207+
fn connect_signal_panic_from_custom_tree_entered(ctx: &TestContext) {
208+
connect_signal_panic_shared(
209+
"tree_entered",
210+
connect_signal_panic_from_custom,
211+
|_node| {},
212+
|node| {
213+
ctx.scene_tree.clone().add_child(&*node);
214+
ctx.scene_tree.clone().remove_child(&*node);
215+
},
216+
);
217+
}
218+
219+
#[itest]
220+
fn connect_signal_panic_from_fn_tree_exiting(ctx: &TestContext) {
221+
connect_signal_panic_shared(
222+
"tree_exiting",
223+
connect_signal_panic_from_fn,
224+
|_node| {},
225+
|node| {
226+
ctx.scene_tree.clone().add_child(&*node);
227+
ctx.scene_tree.clone().remove_child(&*node);
228+
},
229+
);
230+
}
231+
232+
#[itest]
233+
fn connect_signal_panic_from_custom_tree_exiting(ctx: &TestContext) {
234+
connect_signal_panic_shared(
235+
"tree_exiting",
236+
connect_signal_panic_from_custom,
237+
|_node| {},
238+
|node| {
239+
ctx.scene_tree.clone().add_child(&*node);
240+
ctx.scene_tree.clone().remove_child(&*node);
241+
},
242+
);
243+
}
244+
245+
#[itest]
246+
fn connect_signal_panic_from_fn_tree_exited(ctx: &TestContext) {
247+
connect_signal_panic_shared(
248+
"tree_exited",
249+
connect_signal_panic_from_fn,
250+
|_node| {},
251+
|node| {
252+
ctx.scene_tree.clone().add_child(&*node);
253+
ctx.scene_tree.clone().remove_child(&*node);
254+
},
255+
);
256+
}
257+
258+
#[itest]
259+
fn connect_signal_panic_from_custom_tree_exited(ctx: &TestContext) {
260+
connect_signal_panic_shared(
261+
"tree_exited",
262+
connect_signal_panic_from_custom,
263+
|_node| {},
264+
|node| {
265+
ctx.scene_tree.clone().add_child(&*node);
266+
ctx.scene_tree.clone().remove_child(&*node);
267+
},
268+
);
269+
}
270+
271+
fn connect_signal_panic_shared(
272+
signal: &str,
273+
callable: impl FnOnce(Arc<AtomicU32>) -> Callable,
274+
before: impl FnOnce(&mut Gd<Node>),
275+
emit: impl FnOnce(&mut Gd<Node>),
276+
) {
277+
let mut node = Node::new_alloc();
278+
before(&mut node);
279+
280+
let signal = Signal::from_object_signal(&node, signal);
281+
282+
let received = Arc::new(AtomicU32::new(0));
283+
let callable = callable(received.clone());
284+
signal.connect(callable, 0);
285+
286+
emit(&mut node);
287+
288+
assert_eq!(1, received.load(Ordering::SeqCst));
289+
290+
node.free();
291+
}
292+
293+
fn connect_signal_panic_from_fn(received: Arc<AtomicU32>) -> Callable {
294+
Callable::from_fn("test", move |_args| {
295+
panic!("TEST: {}", received.fetch_add(1, Ordering::SeqCst))
296+
})
297+
}
298+
299+
fn connect_signal_panic_from_custom(received: Arc<AtomicU32>) -> Callable {
300+
Callable::from_custom(PanicCallable(received))
301+
}
302+
}

0 commit comments

Comments
 (0)