@@ -15,61 +15,79 @@ use crate::builtin::{StringName, Variant};
15
15
use crate :: classes:: Object ;
16
16
use crate :: meta:: PropertyInfo ;
17
17
use crate :: obj:: { bounds, cap, AsDyn , Base , Bounds , Gd , GodotClass , Inherits , UserClass } ;
18
+ use crate :: private:: { handle_panic, PanicPayload } ;
18
19
use crate :: registry:: plugin:: ErasedDynGd ;
19
20
use crate :: storage:: { as_storage, InstanceStorage , Storage , StorageRefCounted } ;
20
21
use godot_ffi as sys;
21
22
use std:: any:: Any ;
22
23
use sys:: conv:: u32_to_usize;
23
24
use sys:: interface_fn;
24
25
25
- // Creation callback has `p_notify_postinitialize` parameter since 4.4: https://github.com/godotengine/godot/pull/91018.
26
+ /// Godot FFI default constructor.
27
+ ///
28
+ /// If the `init()` constructor panics, null is returned.
29
+ ///
30
+ /// Creation callback has `p_notify_postinitialize` parameter since 4.4: <https://github.com/godotengine/godot/pull/91018>.
26
31
#[ cfg( since_api = "4.4" ) ]
27
32
pub unsafe extern "C" fn create < T : cap:: GodotDefault > (
28
33
_class_userdata : * mut std:: ffi:: c_void ,
29
34
_notify_postinitialize : sys:: GDExtensionBool ,
30
35
) -> sys:: GDExtensionObjectPtr {
31
- create_custom ( T :: __godot_user_init)
36
+ create_custom ( T :: __godot_user_init) . unwrap_or ( std :: ptr :: null_mut ( ) )
32
37
}
33
38
34
39
#[ cfg( before_api = "4.4" ) ]
35
40
pub unsafe extern "C" fn create < T : cap:: GodotDefault > (
36
41
_class_userdata : * mut std:: ffi:: c_void ,
37
42
) -> sys:: GDExtensionObjectPtr {
38
- create_custom ( T :: __godot_user_init)
43
+ create_custom ( T :: __godot_user_init) . unwrap_or ( std :: ptr :: null_mut ( ) )
39
44
}
40
45
46
+ /// Godot FFI function for recreating a GDExtension instance, e.g. after a hot reload.
47
+ ///
48
+ /// If the `init()` constructor panics, null is returned.
41
49
#[ cfg( since_api = "4.2" ) ]
42
50
pub unsafe extern "C" fn recreate < T : cap:: GodotDefault > (
43
51
_class_userdata : * mut std:: ffi:: c_void ,
44
52
object : sys:: GDExtensionObjectPtr ,
45
53
) -> sys:: GDExtensionClassInstancePtr {
46
54
create_rust_part_for_existing_godot_part ( T :: __godot_user_init, object)
55
+ . unwrap_or ( std:: ptr:: null_mut ( ) )
47
56
}
48
57
49
- pub ( crate ) fn create_custom < T , F > ( make_user_instance : F ) -> sys:: GDExtensionObjectPtr
58
+ pub ( crate ) fn create_custom < T , F > (
59
+ make_user_instance : F ,
60
+ ) -> Result < sys:: GDExtensionObjectPtr , PanicPayload >
50
61
where
51
62
T : GodotClass ,
52
63
F : FnOnce ( Base < T :: Base > ) -> T ,
53
64
{
54
65
let base_class_name = T :: Base :: class_name ( ) ;
55
-
56
66
let base_ptr = unsafe { interface_fn ! ( classdb_construct_object) ( base_class_name. string_sys ( ) ) } ;
57
67
58
- create_rust_part_for_existing_godot_part ( make_user_instance, base_ptr) ;
68
+ match create_rust_part_for_existing_godot_part ( make_user_instance, base_ptr) {
69
+ Ok ( _extension_ptr) => Ok ( base_ptr) ,
70
+ Err ( payload) => {
71
+ // Creation of extension object failed; we must now also destroy the base object to avoid leak.
72
+ // SAFETY: `base_ptr` was just created above.
73
+ unsafe { interface_fn ! ( object_destroy) ( base_ptr) } ;
74
+
75
+ Err ( payload)
76
+ }
77
+ }
59
78
60
79
// std::mem::forget(base_class_name);
61
- base_ptr
62
80
}
63
81
64
- // with GDExt, custom object consists from two parts: Godot object and Rust object, that are
65
- // bound to each other. this method takes the first by pointer, creates the second with
66
- // supplied state and binds them together. that's used for both brand-new objects creation and
67
- // hot reload - during hot-reload, Rust objects are disposed and then created again with an
68
- // updated code, so that 's necessary to link them to Godot objects again .
82
+ /// Add Rust-side state for a GDExtension base object.
83
+ ///
84
+ /// With godot-rust, custom objects consist of two parts: the Godot object and the Rust object. This method takes the Godot part by pointer,
85
+ /// creates the Rust part with the supplied state, and links them together. This is used for both brand-new object creation and hot reload.
86
+ /// During hot reload, Rust objects are disposed of and then created again with updated code, so it 's necessary to re- link them to Godot objects.
69
87
fn create_rust_part_for_existing_godot_part < T , F > (
70
88
make_user_instance : F ,
71
89
base_ptr : sys:: GDExtensionObjectPtr ,
72
- ) -> sys:: GDExtensionClassInstancePtr
90
+ ) -> Result < sys:: GDExtensionClassInstancePtr , PanicPayload >
73
91
where
74
92
T : GodotClass ,
75
93
F : FnOnce ( Base < T :: Base > ) -> T ,
79
97
//out!("create callback: {}", class_name.backing);
80
98
81
99
let base = unsafe { Base :: from_sys ( base_ptr) } ;
82
- let user_instance = make_user_instance ( unsafe { Base :: from_base ( & base) } ) ;
100
+
101
+ // User constructor init() can panic, which crashes the engine if unhandled.
102
+ let context = || format ! ( "panic during {class_name}::init() constructor" ) ;
103
+ let code = || make_user_instance ( unsafe { Base :: from_base ( & base) } ) ;
104
+ let user_instance = handle_panic ( context, std:: panic:: AssertUnwindSafe ( code) ) ?;
105
+ // Print shouldn't be necessary as panic itself is printed. If this changes, re-enable in error case:
106
+ // godot_error!("failed to create instance of {class_name}; Rust init() panicked");
83
107
84
108
let instance = InstanceStorage :: < T > :: construct ( user_instance, base) ;
85
109
let instance_ptr = instance. into_raw ( ) ;
97
121
}
98
122
99
123
// std::mem::forget(class_name);
100
- instance_ptr
124
+ Ok ( instance_ptr)
101
125
}
102
126
103
127
pub unsafe extern "C" fn free < T : GodotClass > (
@@ -387,20 +411,10 @@ pub unsafe extern "C" fn validate_property<T: cap::GodotValidateProperty>(
387
411
388
412
sys:: conv:: SYS_TRUE
389
413
}
414
+
390
415
// ----------------------------------------------------------------------------------------------------------------------------------------------
391
416
// Safe, higher-level methods
392
417
393
- /// Abstracts the `GodotDefault` away, for contexts where this trait bound is not statically available
394
- pub fn erased_init < T : cap:: GodotDefault > ( base : Box < dyn Any > ) -> Box < dyn Any > {
395
- let concrete = base
396
- . downcast :: < Base < <T as GodotClass >:: Base > > ( )
397
- . expect ( "erased_init: bad type erasure" ) ;
398
- let extracted: Base < _ > = sys:: unbox ( concrete) ;
399
-
400
- let instance = T :: __godot_user_init ( extracted) ;
401
- Box :: new ( instance)
402
- }
403
-
404
418
pub fn register_class_by_builder < T : cap:: GodotRegisterClass > ( _class_builder : & mut dyn Any ) {
405
419
// TODO use actual argument, once class builder carries state
406
420
// let class_builder = class_builder
0 commit comments