Skip to content

Commit acb0696

Browse files
bors[bot]mhoff12358Bromeon
authored
Merge #78
78: Make any Object Variants with a nullptr inside instead be of type Nil. r=Bromeon a=mhoff12358 Partially resolves #77 This addresses the root issue, but requires all property setters with Object subclass arguments to use Variant rather than Gd<T>. Co-authored-by: mhoff <[email protected]> Co-authored-by: Jan Haller <[email protected]>
2 parents 188aa69 + b0c091b commit acb0696

File tree

6 files changed

+111
-27
lines changed

6 files changed

+111
-27
lines changed

godot-core/src/builtin/node_path.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ impl Display for NodePath {
6262

6363
impl_builtin_traits! {
6464
for NodePath {
65+
Default => node_path_construct_default;
6566
Clone => node_path_construct_copy;
6667
Drop => node_path_destroy;
6768
}

godot-core/src/builtin/variant/impls.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ mod impls {
133133
use super::*;
134134

135135
impl_variant_traits!(bool, bool_to_variant, bool_from_variant, Bool);
136-
impl_variant_traits!(Dictionary, dictionary_to_variant, dictionary_from_variant, Dictionary);
137136
impl_variant_traits!(Vector2, vector2_to_variant, vector2_from_variant, Vector2);
138137
impl_variant_traits!(Vector3, vector3_to_variant, vector3_from_variant, Vector3);
139138
impl_variant_traits!(Vector4, vector4_to_variant, vector4_from_variant, Vector4);
@@ -142,6 +141,21 @@ mod impls {
142141
impl_variant_traits!(Color, color_to_variant, color_from_variant, Color);
143142
impl_variant_traits!(GodotString, string_to_variant, string_from_variant, String);
144143
impl_variant_traits!(StringName, string_name_to_variant, string_name_from_variant, StringName);
144+
impl_variant_traits!(NodePath, node_path_to_variant, node_path_from_variant, NodePath);
145+
/* TODO provide those, as soon as `Default` is available. Also consider auto-generating.
146+
impl_variant_traits!(Rect2, rect2_to_variant, rect2_from_variant, Rect2);
147+
impl_variant_traits!(Rect2i, rect2i_to_variant, rect2i_from_variant, Rect2i);
148+
impl_variant_traits!(Plane, plane_to_variant, plane_from_variant, Plane);
149+
impl_variant_traits!(Quaternion, quaternion_to_variant, quaternion_from_variant, Quaternion);
150+
impl_variant_traits!(Aabb, aabb_to_variant, aabb_from_variant, AABB);
151+
impl_variant_traits!(Basis, basis_to_variant, basis_from_variant, Basis);
152+
impl_variant_traits!(Transform2D, transform_2d_to_variant, transform_2d_from_variant, Transform2D);
153+
impl_variant_traits!(Transform3D, transform_3d_to_variant, transform_3d_from_variant, Transform3D);
154+
impl_variant_traits!(Projection, projection_to_variant, projection_from_variant, Projection);
155+
impl_variant_traits!(Rid, rid_to_variant, rid_from_variant, RID);
156+
impl_variant_traits!(Callable, callable_to_variant, callable_from_variant, Callable);
157+
impl_variant_traits!(Signal, signal_to_variant, signal_from_variant, Signal);
158+
*/
145159
impl_variant_traits!(Array, array_to_variant, array_from_variant, Array);
146160
impl_variant_traits!(PackedByteArray, packed_byte_array_to_variant, packed_byte_array_from_variant, PackedByteArray);
147161
impl_variant_traits!(PackedInt32Array, packed_int32_array_to_variant, packed_int32_array_from_variant, PackedInt32Array);
@@ -152,6 +166,7 @@ mod impls {
152166
impl_variant_traits!(PackedVector2Array, packed_vector2_array_to_variant, packed_vector2_array_from_variant, PackedVector2Array);
153167
impl_variant_traits!(PackedVector3Array, packed_vector3_array_to_variant, packed_vector3_array_from_variant, PackedVector3Array);
154168
impl_variant_traits!(PackedColorArray, packed_color_array_to_variant, packed_color_array_from_variant, PackedColorArray);
169+
impl_variant_traits!(Dictionary, dictionary_to_variant, dictionary_from_variant, Dictionary);
155170

156171
impl_variant_traits!(i64, int_to_variant, int_from_variant, Int, GDEXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_INT64);
157172
impl_variant_traits_int!(i8, GDEXTENSION_METHOD_ARGUMENT_METADATA_INT_IS_INT8);

godot-core/src/builtin/variant/mod.rs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,41 @@ impl Variant {
5959
}
6060

6161
/// Checks whether the variant is empty (`null` value in GDScript).
62+
///
63+
/// See also [`Self::get_type`].
6264
pub fn is_nil(&self) -> bool {
63-
self.sys_type() == sys::GDEXTENSION_VARIANT_TYPE_NIL
65+
// Use get_type() rather than sys_type(), to also cover nullptr OBJECT as NIL
66+
self.get_type() == VariantType::Nil
6467
}
6568

66-
// TODO test
69+
/// Returns the type that is currently held by this variant.
70+
///
71+
/// If this variant holds a type `Object` but no instance (represented as a null object pointer), then `Nil` will be returned for
72+
/// consistency. This may deviate from Godot behavior -- for example, calling `Node::get_node_or_null()` with an invalid
73+
/// path returns a variant that has type `Object` but acts like `Nil` for all practical purposes.
6774
pub fn get_type(&self) -> VariantType {
68-
let ty_sys = unsafe { interface_fn!(variant_get_type)(self.var_sys()) };
69-
VariantType::from_sys(ty_sys)
75+
let sys_type = self.sys_type();
76+
77+
// There is a special case when the Variant has type OBJECT, but the Object* is null.
78+
let is_null_object = if sys_type == sys::GDEXTENSION_VARIANT_TYPE_OBJECT {
79+
// SAFETY: we checked that the raw type is OBJECT, so we can interpret the type-ptr as address of an object-ptr.
80+
let object_ptr = unsafe {
81+
crate::obj::raw_object_init(|type_ptr| {
82+
let converter = sys::builtin_fn!(object_from_variant);
83+
converter(type_ptr, self.var_sys());
84+
})
85+
};
86+
87+
object_ptr.is_null()
88+
} else {
89+
false
90+
};
91+
92+
if is_null_object {
93+
VariantType::Nil
94+
} else {
95+
VariantType::from_sys(sys_type)
96+
}
7097
}
7198

7299
// TODO test

godot-core/src/obj/gd.rs

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -366,11 +366,7 @@ impl<T: GodotClass> Gd<T> {
366366
let class_tag = interface_fn!(classdb_get_class_tag)(class_name.string_sys());
367367
let cast_object_ptr = interface_fn!(object_cast_to)(self.obj_sys(), class_tag);
368368

369-
if cast_object_ptr.is_null() {
370-
None
371-
} else {
372-
Some(Gd::from_obj_sys(cast_object_ptr))
373-
}
369+
sys::ptr_then(cast_object_ptr, |ptr| Gd::from_obj_sys(ptr))
374370
}
375371

376372
pub(crate) fn as_ref_counted<R>(&self, apply: impl Fn(&mut engine::RefCounted) -> R) -> R {
@@ -500,32 +496,40 @@ impl<T: GodotClass> GodotFfi for Gd<T> {
500496

501497
impl<T: GodotClass> Gd<T> {
502498
/// Runs `init_fn` on the address of a pointer (initialized to null). If that pointer is still null after the `init_fn` call,
503-
/// then `None` will be returned; otherwise `from_obj_sys(ptr)`.
499+
/// then `None` will be returned; otherwise `Gd::from_obj_sys(ptr)`.
504500
///
505501
/// # Safety
506-
/// `init_fn` must be a function that correctly handles an "type pointer" pointing to an "object pointer"
502+
/// `init_fn` must be a function that correctly handles a _type pointer_ pointing to an _object pointer_.
507503
#[doc(hidden)]
508504
// TODO unsafe on init_fn instead of this fn?
509505
pub unsafe fn from_sys_init_opt(init_fn: impl FnOnce(sys::GDExtensionTypePtr)) -> Option<Self> {
510506
// Note: see _call_native_mb_ret_obj() in godot-cpp, which does things quite different (e.g. querying the instance binding).
511507

512-
// return_ptr has type GDExtensionTypePtr = GDExtensionObjectPtr* = OpaqueObject* = Object**
513-
// (in other words, the type-ptr contains the _address_ of an object-ptr).
514-
let mut object_ptr: sys::GDExtensionObjectPtr = ptr::null_mut();
515-
let return_ptr: *mut sys::GDExtensionObjectPtr = ptr::addr_of_mut!(object_ptr);
516-
517-
init_fn(return_ptr as sys::GDExtensionTypePtr);
518-
519-
// We don't need to know if Object** is null, but if Object* is null; return_ptr has the address of a local (never null).
520-
if object_ptr.is_null() {
521-
None
522-
} else {
523-
let obj = Gd::from_obj_sys(object_ptr); // equivalent to Gd::from_sys(return_ptr)
524-
Some(obj)
525-
}
508+
// Initialize pointer with given function, return Some(ptr) on success and None otherwise
509+
let object_ptr = raw_object_init(init_fn);
510+
sys::ptr_then(object_ptr, |ptr| Gd::from_obj_sys(ptr))
526511
}
527512
}
528513

514+
/// Runs `init_fn` on the address of a pointer (initialized to null), then returns that pointer, possibly still null.
515+
///
516+
/// # Safety
517+
/// `init_fn` must be a function that correctly handles a _type pointer_ pointing to an _object pointer_.
518+
#[doc(hidden)]
519+
pub unsafe fn raw_object_init(
520+
init_fn: impl FnOnce(sys::GDExtensionTypePtr),
521+
) -> sys::GDExtensionObjectPtr {
522+
// return_ptr has type GDExtensionTypePtr = GDExtensionObjectPtr* = OpaqueObject* = Object**
523+
// (in other words, the type-ptr contains the _address_ of an object-ptr).
524+
let mut object_ptr: sys::GDExtensionObjectPtr = ptr::null_mut();
525+
let return_ptr: *mut sys::GDExtensionObjectPtr = ptr::addr_of_mut!(object_ptr);
526+
527+
init_fn(return_ptr as sys::GDExtensionTypePtr);
528+
529+
// We don't need to know if Object** is null, but if Object* is null; return_ptr has the address of a local (never null).
530+
object_ptr
531+
}
532+
529533
/// Destructor with semantics depending on memory strategy.
530534
///
531535
/// * If this `Gd` smart pointer holds a reference-counted type, this will decrement the reference counter.

godot-ffi/src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,3 +203,16 @@ pub fn force_mut_ptr<T>(ptr: *const T) -> *mut T {
203203
pub fn to_const_ptr<T>(ptr: *mut T) -> *const T {
204204
ptr as *const T
205205
}
206+
207+
/// If `ptr` is not null, returns `Some(mapper(ptr))`; otherwise `None`.
208+
pub fn ptr_then<T, R, F>(ptr: *mut T, mapper: F) -> Option<R>
209+
where
210+
F: FnOnce(*mut T) -> R,
211+
{
212+
// Could also use NonNull in signature, but for this project we always deal with FFI raw pointers
213+
if ptr.is_null() {
214+
None
215+
} else {
216+
Some(mapper(ptr))
217+
}
218+
}

itest/rust/src/variant_test.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
*/
66

77
use crate::itest;
8-
use godot::builtin::{FromVariant, GodotString, StringName, ToVariant, Variant, Vector2, Vector3};
8+
use godot::builtin::{
9+
FromVariant, GodotString, NodePath, StringName, ToVariant, Variant, Vector2, Vector3,
10+
};
11+
use godot::engine::Node3D;
912
use godot::obj::InstanceId;
1013
use godot::sys::{GodotFfi, VariantOperator, VariantType};
1114
use std::cmp::Ordering;
@@ -23,6 +26,7 @@ pub fn run() -> bool {
2326
ok &= variant_evaluate_total_order();
2427
ok &= variant_sys_conversion();
2528
ok &= variant_sys_conversion2();
29+
ok &= variant_null_object_is_nil();
2630
ok
2731
}
2832

@@ -197,6 +201,26 @@ fn variant_sys_conversion() {
197201
assert_eq!(v2, v);
198202
}
199203

204+
#[itest]
205+
fn variant_null_object_is_nil() {
206+
use godot::sys;
207+
208+
let mut node = Node3D::new_alloc();
209+
let node_path = NodePath::from("res://NonExisting.tscn");
210+
211+
// Simulates an object that is returned but null
212+
// Use reflection to get a variant as return type
213+
let variant = node.call("get_node_or_null".into(), &[node_path.to_variant()]);
214+
let raw_type: sys::GDExtensionVariantType =
215+
unsafe { sys::interface_fn!(variant_get_type)(variant.var_sys()) };
216+
217+
// Verify that this appears as NIL to the user, even though it's internally OBJECT with a null object pointer
218+
assert_eq!(raw_type, sys::GDEXTENSION_VARIANT_TYPE_OBJECT);
219+
assert_eq!(variant.get_type(), VariantType::Nil);
220+
221+
node.free();
222+
}
223+
200224
#[itest]
201225
fn variant_sys_conversion2() {
202226
/*

0 commit comments

Comments
 (0)