Skip to content

Commit 62e6ec3

Browse files
authored
Merge pull request #1323 from godot-rust/qol/option-dyn-gd
`AsArg` now supports `Option<DynGd>`
2 parents d8069a0 + 1415e1c commit 62e6ec3

File tree

7 files changed

+169
-38
lines changed

7 files changed

+169
-38
lines changed

godot-core/src/meta/args/as_arg.rs

Lines changed: 96 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ where
143143
T: Inherits<Base>,
144144
Base: GodotClass,
145145
{
146+
//noinspection RsConstantConditionIf - false positive in IDE for `T::IS_SAME_CLASS`.
146147
fn into_arg<'arg>(self) -> CowArg<'arg, Gd<Base>>
147148
where
148149
Self: 'arg,
@@ -199,7 +200,7 @@ where
199200
}
200201
}
201202

202-
// Convert `DynGd` -> `Gd` (with upcast).
203+
/// Convert `DynGd` -> `Gd` (with upcast).
203204
impl<T, D, Base> AsArg<Gd<Base>> for &DynGd<T, D>
204205
where
205206
T: Inherits<Base>,
@@ -223,9 +224,68 @@ where
223224
}
224225
}
225226

227+
// ----------------------------------------------------------------------------------------------------------------------------------------------
228+
// Null arguments
229+
230+
/// Private struct for passing null arguments to optional object parameters.
231+
///
232+
/// This struct implements `AsArg` for both `Option<Gd<T>>` and `Option<DynGd<T, D>>`, allowing [`Gd::null_arg()`] and [`DynGd::null_arg()`]
233+
/// to share implementation.
234+
///
235+
/// Not public, as `impl AsArg<...>` is used by `null_arg()` methods.
236+
pub(crate) struct NullArg<T>(pub std::marker::PhantomData<*mut T>);
237+
238+
impl<T> AsArg<Option<Gd<T>>> for NullArg<T>
239+
where
240+
T: GodotClass,
241+
{
242+
fn into_arg<'arg>(self) -> CowArg<'arg, Option<Gd<T>>>
243+
where
244+
Self: 'arg,
245+
{
246+
CowArg::Owned(None)
247+
}
248+
}
249+
250+
impl<T, D> AsArg<Option<DynGd<T, D>>> for NullArg<T>
251+
where
252+
T: GodotClass,
253+
D: ?Sized + 'static,
254+
{
255+
fn into_arg<'arg>(self) -> CowArg<'arg, Option<DynGd<T, D>>>
256+
where
257+
Self: 'arg,
258+
{
259+
CowArg::Owned(None)
260+
}
261+
}
262+
226263
// ----------------------------------------------------------------------------------------------------------------------------------------------
227264
// Optional object (Gd + DynGd) impls
228265

266+
/// Convert `&Gd` -> `Option<Gd>` (with upcast).
267+
impl<T, Base> AsArg<Option<Gd<Base>>> for &Gd<T>
268+
where
269+
T: Inherits<Base>,
270+
Base: GodotClass + Bounds<Declarer = bounds::DeclEngine>,
271+
{
272+
fn into_arg<'arg>(self) -> CowArg<'arg, Option<Gd<Base>>>
273+
where
274+
Self: 'arg,
275+
{
276+
// Upcasting to an owned value Gd<Base> requires cloning. Optimized path in into_ffi_arg().
277+
CowArg::Owned(Some(self.clone().upcast::<Base>()))
278+
}
279+
280+
fn into_ffi_arg<'arg>(self) -> FfiArg<'arg, Option<Gd<Base>>>
281+
where
282+
Self: 'arg,
283+
{
284+
let arg = ObjectArg::from_gd(self);
285+
FfiArg::FfiObject(arg)
286+
}
287+
}
288+
229289
/// Convert `Option<&Gd>` -> `Option<Gd>` (with upcast).
230290
impl<T, Base> AsArg<Option<Gd<Base>>> for Option<&Gd<T>>
231291
where
@@ -252,21 +312,22 @@ where
252312
}
253313
}
254314

255-
/// Convert `&Gd` -> `Option<Gd>` (with upcast).
256-
impl<T, Base> AsArg<Option<Gd<Base>>> for &Gd<T>
315+
/// Convert `&DynGd` -> `Option<DynGd>` (with upcast).
316+
impl<T, D, Base> AsArg<Option<DynGd<Base, D>>> for &DynGd<T, D>
257317
where
258318
T: Inherits<Base>,
319+
D: ?Sized,
259320
Base: GodotClass + Bounds<Declarer = bounds::DeclEngine>,
260321
{
261-
fn into_arg<'arg>(self) -> CowArg<'arg, Option<Gd<Base>>>
322+
fn into_arg<'arg>(self) -> CowArg<'arg, Option<DynGd<Base, D>>>
262323
where
263324
Self: 'arg,
264325
{
265-
// Upcasting to an owned value Gd<Base> requires cloning. Optimized path in into_ffi_arg().
266-
CowArg::Owned(Some(self.clone().upcast::<Base>()))
326+
// Upcasting to an owned value DynGd<Base, D> requires cloning. Optimized path in into_ffi_arg().
327+
CowArg::Owned(Some(self.clone().upcast()))
267328
}
268329

269-
fn into_ffi_arg<'arg>(self) -> FfiArg<'arg, Option<Gd<Base>>>
330+
fn into_ffi_arg<'arg>(self) -> FfiArg<'arg, Option<DynGd<Base, D>>>
270331
where
271332
Self: 'arg,
272333
{
@@ -299,6 +360,34 @@ where
299360
}
300361
}
301362

363+
/// Convert `Option<&DynGd>` -> `Option<DynGd>` (with upcast).
364+
impl<T, D, Base> AsArg<Option<DynGd<Base, D>>> for Option<&DynGd<T, D>>
365+
where
366+
T: Inherits<Base>,
367+
D: ?Sized,
368+
Base: GodotClass + Bounds<Declarer = bounds::DeclEngine>,
369+
{
370+
fn into_arg<'arg>(self) -> CowArg<'arg, Option<DynGd<Base, D>>>
371+
where
372+
Self: 'arg,
373+
{
374+
// Upcasting to an owned value Gd<Base> requires cloning. Optimized path in into_ffi_arg().
375+
match self {
376+
Some(gd_ref) => AsArg::into_arg(gd_ref),
377+
None => CowArg::Owned(None),
378+
}
379+
}
380+
381+
fn into_ffi_arg<'arg>(self) -> FfiArg<'arg, Option<DynGd<Base, D>>>
382+
where
383+
Self: 'arg,
384+
{
385+
let option_gd: Option<&Gd<T>> = self.map(|v| &**v); // as_deref() not working.
386+
let arg = ObjectArg::from_option_gd(option_gd);
387+
FfiArg::FfiObject(arg)
388+
}
389+
}
390+
302391
// ----------------------------------------------------------------------------------------------------------------------------------------------
303392
// Public helper functions (T|&T -> AsArg)
304393

godot-core/src/meta/args/mod.rs

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,19 @@ mod ref_arg;
1313
// ----------------------------------------------------------------------------------------------------------------------------------------------
1414
// Public APIs
1515

16-
pub use as_arg::{
17-
owned_into_arg, ref_to_arg, ArgPassing, AsArg, ByObject, ByOption, ByRef, ByValue, ToArg,
18-
};
1916
// ----------------------------------------------------------------------------------------------------------------------------------------------
2017
// Internal APIs
2118

2219
// Solely public for itest/convert_test.rs.
20+
pub(crate) use as_arg::NullArg;
21+
pub use as_arg::{
22+
owned_into_arg, ref_to_arg, ArgPassing, AsArg, ByObject, ByOption, ByRef, ByValue, ToArg,
23+
};
24+
#[cfg(not(feature = "trace"))]
25+
pub(crate) use cow_arg::{CowArg, FfiArg};
26+
// Integration test only.
2327
#[cfg(feature = "trace")]
2428
#[doc(hidden)]
2529
pub use cow_arg::{CowArg, FfiArg};
26-
#[cfg(not(feature = "trace"))]
27-
pub(crate) use cow_arg::{CowArg, FfiArg};
28-
#[allow(unused)] // TODO(v0.4): replace contents with newer changes
2930
pub use object_arg::ObjectArg;
30-
pub use ref_arg::RefArg;
31-
32-
// #[doc(hidden)]
33-
// pub use cow_arg::*;
34-
//
35-
// #[doc(hidden)]
36-
// pub use ref_arg::*;
31+
pub(crate) use ref_arg::RefArg;

godot-core/src/meta/args/ref_arg.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ use crate::sys;
1616

1717
/// Simple reference wrapper, used when passing arguments by-ref to Godot APIs.
1818
///
19-
/// This type is often used as the result of [`ToGodot::to_godot()`], if `Self` is not a `Copy` type.
19+
/// This type is exclusively used at the FFI boundary, to avoid unnecessary cloning of values.
20+
///
21+
/// Private type. Cannot be `pub(crate)` because it's used in `#[doc(hidden)]` associate type `GodotType::ToFfi<'f>`, and `GodotType` is public.
22+
#[doc(hidden)]
2023
pub struct RefArg<'r, T> {
2124
/// Only `None` if `T: GodotNullableFfi` and `T::is_null()` is true.
2225
shared_ref: Option<&'r T>,

godot-core/src/meta/sealed.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ impl<T: ArrayElement> Sealed for Array<T> {}
6262
impl<T: GodotClass> Sealed for Gd<T> {}
6363
impl<T: GodotClass> Sealed for RawGd<T> {}
6464
impl<T: GodotClass, D: ?Sized> Sealed for DynGd<T, D> {}
65+
impl<T: GodotClass, D: ?Sized + 'static> Sealed for Option<DynGd<T, D>> {}
6566
impl<T> Sealed for Option<T>
6667
where
6768
T: GodotType,

godot-core/src/obj/dyn_gd.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,13 @@ where
403403
pub fn into_gd(self) -> Gd<T> {
404404
self.obj
405405
}
406+
407+
/// Represents `null` when passing a dynamic object argument to Godot.
408+
///
409+
/// See [`Gd::null_arg()`]
410+
pub fn null_arg() -> impl meta::AsArg<Option<DynGd<T, D>>> {
411+
meta::NullArg(std::marker::PhantomData)
412+
}
406413
}
407414

408415
impl<T, D> DynGd<T, D>
@@ -606,6 +613,16 @@ where
606613
}
607614
}
608615

616+
impl<T, D> meta::ArrayElement for Option<DynGd<T, D>>
617+
where
618+
T: GodotClass,
619+
D: ?Sized + 'static,
620+
{
621+
fn element_type_string() -> String {
622+
DynGd::<T, D>::element_type_string()
623+
}
624+
}
625+
609626
impl<T, D> Var for DynGd<T, D>
610627
where
611628
T: GodotClass,

godot-core/src/obj/gd.rs

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use sys::{static_assert_eq_size_align, SysPtr as _};
1414
use crate::builtin::{Callable, GString, NodePath, StringName, Variant};
1515
use crate::meta::error::{ConvertError, FromFfiError};
1616
use crate::meta::{
17-
ArrayElement, AsArg, CallContext, ClassName, CowArg, FromGodot, GodotConvert, GodotType,
17+
ArrayElement, AsArg, CallContext, ClassName, FromGodot, GodotConvert, GodotType,
1818
PropertyHintInfo, RefArg, ToGodot,
1919
};
2020
use crate::obj::{
@@ -816,22 +816,7 @@ where
816816
/// let mut shape: Gd<Node> = some_node();
817817
/// shape.set_owner(Gd::null_arg());
818818
pub fn null_arg() -> impl AsArg<Option<Gd<T>>> {
819-
// Anonymous struct that creates None for optional object arguments.
820-
struct NullGdArg<T>(std::marker::PhantomData<*mut T>);
821-
822-
impl<T> AsArg<Option<Gd<T>>> for NullGdArg<T>
823-
where
824-
T: GodotClass,
825-
{
826-
fn into_arg<'arg>(self) -> CowArg<'arg, Option<Gd<T>>>
827-
where
828-
Self: 'arg,
829-
{
830-
CowArg::Owned(None)
831-
}
832-
}
833-
834-
NullGdArg(std::marker::PhantomData)
819+
meta::NullArg(std::marker::PhantomData)
835820
}
836821
}
837822

itest/rust/src/object_tests/dyn_gd_test.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,47 @@ fn dyn_gd_store_in_godot_array() {
430430
*/
431431
}
432432

433+
#[itest]
434+
fn dyn_gd_as_arg() {
435+
let refc_health = Gd::from_object(RefcHealth { hp: 42 }).into_dyn();
436+
let node_health = foreign::NodeHealth::new_alloc().into_dyn();
437+
let typed_none = None::<&DynGd<RefcHealth, dyn Health>>;
438+
439+
// Array<DynGd>.
440+
let array: Array<DynGd<Object, dyn Health>> = array![&refc_health, &node_health];
441+
assert_eq!(array.len(), 2);
442+
443+
let first = array.at(0);
444+
assert_eq!(first.dyn_bind().get_hitpoints(), 42);
445+
446+
let second = array.at(1);
447+
assert_eq!(second.dyn_bind().get_hitpoints(), 100);
448+
449+
// Array<Option<DynGd>>.
450+
let opt_array: Array<Option<DynGd<Object, dyn Health>>> = array![
451+
Some(&refc_health),
452+
Some(&node_health),
453+
typed_none,
454+
DynGd::null_arg(),
455+
];
456+
assert_eq!(opt_array.len(), 4);
457+
458+
let first = opt_array.at(0).expect("element 0 is Some");
459+
assert_eq!(first.dyn_bind().get_hitpoints(), 42);
460+
461+
let second = opt_array.at(1).expect("element 1 is Some");
462+
assert_eq!(second.dyn_bind().get_hitpoints(), 100);
463+
464+
let third = opt_array.at(2);
465+
assert!(third.is_none(), "element 2 is None");
466+
467+
let fourth = opt_array.at(3);
468+
assert!(fourth.is_none(), "element 3 is None (null_arg)");
469+
470+
// Clean up manually managed objects.
471+
opt_array.at(1).unwrap().free();
472+
}
473+
433474
#[itest]
434475
fn dyn_gd_error_unregistered_trait() {
435476
trait UnrelatedTrait {}

0 commit comments

Comments
 (0)