Skip to content

Commit a6859b9

Browse files
authored
Merge pull request #1310 from godot-rust/perf/togodot-by-ref
Restore `ToGodot` pass-by-ref for objects
2 parents 369e9d3 + 6357d47 commit a6859b9

File tree

10 files changed

+191
-76
lines changed

10 files changed

+191
-76
lines changed

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

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ use std::ffi::CStr;
99

1010
use crate::builtin::{GString, NodePath, StringName, Variant};
1111
use crate::meta::sealed::Sealed;
12-
use crate::meta::traits::GodotFfiVariant;
13-
use crate::meta::{CowArg, GodotType, ToGodot};
12+
use crate::meta::traits::{GodotFfiVariant, GodotNullableFfi};
13+
use crate::meta::{CowArg, GodotType, ObjectArg, ToGodot};
1414
use crate::obj::{bounds, Bounds, DynGd, Gd, GodotClass, Inherits};
1515

1616
/// Implicit conversions for arguments passed to Godot APIs.
@@ -423,7 +423,9 @@ impl AsArg<NodePath> for &String {
423423
/// See [`ToGodot::Pass`].
424424
pub trait ArgPassing: Sealed {
425425
/// Return type: `T` or `&'r T`.
426-
type Output<'r, T: 'r>;
426+
type Output<'r, T: 'r>
427+
where
428+
Self: 'r;
427429

428430
/// FFI argument type: `T::Ffi` or `T::ToFfi<'f>`.
429431
#[doc(hidden)]
@@ -515,7 +517,90 @@ impl ArgPassing for ByRef {
515517
T::Via: GodotType,
516518
{
517519
// Use by-ref conversion if possible, avoiding unnecessary clones when passing to FFI.
518-
value.to_godot().to_ffi()
520+
GodotType::to_ffi(value.to_godot())
521+
}
522+
}
523+
524+
/// Pass arguments to Godot by object pointer (for objects only).
525+
///
526+
/// Currently distinct from [`ByRef`] to not interfere with the blanket impl for `&T` for all `ByRef` types. Semantics are largely the same.
527+
///
528+
/// See [`ToGodot::Pass`].
529+
pub enum ByObject {}
530+
impl Sealed for ByObject {}
531+
impl ArgPassing for ByObject {
532+
type Output<'r, T: 'r> = &'r T;
533+
534+
type FfiOutput<'f, T>
535+
= ObjectArg
536+
where
537+
T: GodotType + 'f;
538+
539+
fn ref_to_owned_via<T>(value: &T) -> T::Via
540+
where
541+
T: ToGodot<Pass = Self>,
542+
T::Via: Clone,
543+
{
544+
// For ByObject types, do like ByRef: clone the reference to get owned value.
545+
value.to_godot().clone()
546+
}
547+
548+
fn ref_to_ffi<T>(value: &T) -> ObjectArg
549+
where
550+
T: ToGodot<Pass = Self>,
551+
T::Via: GodotType,
552+
{
553+
let obj_ref: &T::Via = value.to_godot(); // implements GodotType.
554+
unsafe { obj_ref.as_object_arg() }
555+
}
556+
}
557+
558+
/// Pass optional arguments by returning `Option<&T::Via>`, allowing delegation to underlying type's strategy.
559+
///
560+
/// This enables `Option<T>` to benefit from the underlying type's efficient passing without cloning. [`ByRef`] doesn't support this because it
561+
/// would transform `Option<T>` to `&Option<T>`; however, we need `Option<&T>` instead.
562+
///
563+
/// See [`ToGodot::Pass`].
564+
pub enum ByOption<Via> {
565+
// Uses `Via` generic type to work around the near-impossibility of Output<'r, T> pointing to a metafunction that transforms Option<T> to
566+
// Option<&'r T>. Such a metafunction cannot be implemented via trait (overlapping impls cause coherence issues), and we would need to
567+
// pollute also the other `By*` types by anything. Using a generic parameter on the trait rather than the associated type avoids that.
568+
_Phantom(std::marker::PhantomData<Via>),
569+
}
570+
impl<Via> Sealed for ByOption<Via> {}
571+
impl<Via> ArgPassing for ByOption<Via>
572+
where
573+
Via: GodotType,
574+
for<'f> Via::ToFfi<'f>: GodotNullableFfi,
575+
{
576+
type Output<'r, T: 'r>
577+
= Option<&'r Via>
578+
where
579+
Self: 'r;
580+
581+
type FfiOutput<'f, T>
582+
= <Via as GodotType>::ToFfi<'f>
583+
where
584+
T: GodotType + 'f;
585+
586+
// value: &Option<U>
587+
// return: T::Via = Option<U::Via>
588+
fn ref_to_owned_via<T>(value: &T) -> T::Via
589+
where
590+
T: ToGodot<Pass = Self>,
591+
T::Via: Clone,
592+
{
593+
value.to_godot_owned()
594+
}
595+
596+
fn ref_to_ffi<T>(value: &T) -> Self::FfiOutput<'_, T::Via>
597+
where
598+
T: ToGodot<Pass = Self>,
599+
T::Via: GodotType,
600+
{
601+
// Reuse pattern from impl GodotType for Option<T>:
602+
// Convert Option<&Via> to Option<Via::ToFfi> and then flatten to Via::ToFfi with null handling.
603+
GodotNullableFfi::flatten_option(value.to_godot().map(|via_ref| via_ref.to_ffi()))
519604
}
520605
}
521606

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ mod ref_arg;
1313
// ----------------------------------------------------------------------------------------------------------------------------------------------
1414
// Public APIs
1515

16-
pub use as_arg::{owned_into_arg, ref_to_arg, ArgPassing, AsArg, ByRef, ByValue, ToArg};
16+
pub use as_arg::{
17+
owned_into_arg, ref_to_arg, ArgPassing, AsArg, ByObject, ByOption, ByRef, ByValue, ToArg,
18+
};
1719
// ----------------------------------------------------------------------------------------------------------------------------------------------
1820
// Internal APIs
1921

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

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ use std::ptr;
99

1010
use godot_ffi::{ExtVariantType, GodotFfi, GodotNullableFfi, PtrcallType};
1111

12-
use crate::obj::{Gd, GodotClass, Inherits, RawGd};
12+
use crate::builtin::Variant;
13+
use crate::meta::error::ConvertError;
14+
use crate::meta::traits::GodotFfiVariant;
15+
use crate::obj::{Gd, GodotClass};
1316
use crate::{obj, sys};
1417

1518
/// View for object arguments passed to the Godot engine. Never owning; must be null or backed by `Gd<T>`.
@@ -34,29 +37,13 @@ impl ObjectArg {
3437
}
3538
}
3639

37-
/// Creates `ObjectArg` from a `RawGd`.
40+
/// Creates `ObjectArg` from an `Option<Gd>`.
3841
///
3942
/// # Safety
40-
/// The referenced `RawGd` must remain valid for the lifetime of this `ObjectArg`.
41-
pub unsafe fn from_raw_gd<T: GodotClass>(obj: &RawGd<T>) -> Self {
42-
// Runtime check is necessary, to ensure that object is still alive and has correct runtime type.
43-
if !obj.is_null() {
44-
obj.check_rtti("from_raw_gd");
45-
}
46-
47-
Self {
48-
object_ptr: obj.obj_sys(),
49-
}
50-
}
51-
52-
/// Creates `ObjectArg` from `Option<&Gd<U>>`, handling upcast to target type `T`.
53-
pub fn from_option_gd_ref<T, U>(opt: Option<&Gd<U>>) -> Self
54-
where
55-
T: GodotClass,
56-
U: GodotClass + Inherits<T>,
57-
{
58-
match opt {
59-
Some(gd) => unsafe { Self::from_gd(gd) },
43+
/// The referenced `Gd`, if not `None`, must remain valid for the lifetime of this `ObjectArg`.
44+
pub unsafe fn from_option_gd<T: GodotClass>(obj: Option<&Gd<T>>) -> Self {
45+
match obj {
46+
Some(gd) => Self::from_gd(gd),
6047
None => Self::null(),
6148
}
6249
}
@@ -72,11 +59,6 @@ impl ObjectArg {
7259
pub fn is_null(&self) -> bool {
7360
self.object_ptr.is_null()
7461
}
75-
76-
/// Returns the raw object pointer
77-
pub fn raw_ptr(&self) -> sys::GDExtensionObjectPtr {
78-
self.object_ptr
79-
}
8062
}
8163

8264
// #[derive(Clone)] doesn't seem to get bounds right.
@@ -130,6 +112,16 @@ unsafe impl GodotFfi for ObjectArg {
130112
}
131113
}
132114

115+
impl GodotFfiVariant for ObjectArg {
116+
fn ffi_to_variant(&self) -> Variant {
117+
obj::object_ffi_to_variant(self)
118+
}
119+
120+
fn ffi_from_variant(_variant: &Variant) -> Result<Self, ConvertError> {
121+
unreachable!("ObjectArg should only be passed *to* Godot, not *from*.")
122+
}
123+
}
124+
133125
impl GodotNullableFfi for ObjectArg {
134126
fn null() -> Self {
135127
Self::null()

godot-core/src/meta/godot_convert/impls.rs

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use godot_ffi as sys;
99

1010
use crate::builtin::{Array, Variant};
11+
use crate::meta;
1112
use crate::meta::error::{ConvertError, ErrorKind, FromFfiError, FromVariantError};
1213
use crate::meta::{
1314
ArrayElement, ClassName, FromGodot, GodotConvert, GodotNullableFfi, GodotType,
@@ -83,41 +84,49 @@ where
8384
fn godot_type_name() -> String {
8485
T::godot_type_name()
8586
}
87+
88+
// Only relevant for object types T.
89+
unsafe fn as_object_arg(&self) -> meta::ObjectArg {
90+
match self {
91+
Some(inner) => unsafe { inner.as_object_arg() },
92+
None => meta::ObjectArg::null(),
93+
}
94+
}
8695
}
8796

88-
impl<T: GodotConvert> GodotConvert for Option<T>
97+
impl<T> GodotConvert for Option<T>
8998
where
99+
T: GodotConvert,
90100
Option<T::Via>: GodotType,
91101
{
92102
type Via = Option<T::Via>;
93103
}
94104

95-
impl<T: ToGodot> ToGodot for Option<T>
105+
impl<T> ToGodot for Option<T>
96106
where
97-
Option<T::Via>: GodotType,
107+
// Currently limited to holding objects -> needed to establish to_godot() relation T::to_godot() = Option<&T::Via>.
108+
T: ToGodot<Pass = meta::ByObject>,
109+
// Extra Clone bound for to_godot_owned(); might be extracted in the future.
110+
T::Via: Clone,
111+
// T::Via must be a Godot nullable type (to support the None case).
98112
for<'f> T::Via: GodotType<
99113
// Associated types need to be nullable.
100114
Ffi: GodotNullableFfi,
101115
ToFfi<'f>: GodotNullableFfi,
102116
>,
103-
T::Via: Clone,
117+
// Previously used bound, not needed right now but don't remove: Option<T::Via>: GodotType,
104118
{
105-
// Potential optimization: use underlying T::Pass instead of ByValue.
106-
// Problem is that ByRef requires to_godot() -> &Self::Via, which is &Option<T::Via>. We would however need Option<&T::Via>.
107-
// Might need a third ArgPassing impl, or different design.
108-
type Pass = crate::meta::ByValue;
119+
// Basically ByRef, but allows Option<T> -> Option<&T::Via> conversion.
120+
type Pass = meta::ByOption<T::Via>;
109121

110-
fn to_godot(&self) -> Option<T::Via> {
111-
self.as_ref().map(T::to_godot_owned)
122+
fn to_godot(&self) -> Option<&T::Via> {
123+
self.as_ref().map(T::to_godot)
112124
}
113125

114-
fn to_godot_owned(&self) -> Self::Via
126+
fn to_godot_owned(&self) -> Option<T::Via>
115127
where
116128
Self::Via: Clone,
117129
{
118-
// Default implementation calls underlying T::to_godot().clone(), which may be wrong.
119-
// Some to_godot_owned() calls are specialized/overridden, we need to honor that.
120-
121130
self.as_ref().map(T::to_godot_owned)
122131
}
123132

@@ -248,7 +257,7 @@ macro_rules! impl_godot_scalar {
248257
}
249258

250259
impl ToGodot for $T {
251-
type Pass = crate::meta::ByValue;
260+
type Pass = meta::ByValue;
252261

253262
fn to_godot(&self) -> Self::Via {
254263
*self
@@ -264,10 +273,10 @@ macro_rules! impl_godot_scalar {
264273
}
265274

266275
// `GodotType` for these three is implemented in `godot-core/src/builtin/variant/impls.rs`.
267-
crate::meta::impl_godot_as_self!(bool: ByValue);
268-
crate::meta::impl_godot_as_self!(i64: ByValue);
269-
crate::meta::impl_godot_as_self!(f64: ByValue);
270-
crate::meta::impl_godot_as_self!((): ByValue);
276+
meta::impl_godot_as_self!(bool: ByValue);
277+
meta::impl_godot_as_self!(i64: ByValue);
278+
meta::impl_godot_as_self!(f64: ByValue);
279+
meta::impl_godot_as_self!((): ByValue);
271280

272281
// Also implements ArrayElement.
273282
impl_godot_scalar!(
@@ -334,7 +343,7 @@ impl GodotConvert for u64 {
334343
}
335344

336345
impl ToGodot for u64 {
337-
type Pass = crate::meta::ByValue;
346+
type Pass = meta::ByValue;
338347

339348
fn to_godot(&self) -> Self::Via {
340349
*self
@@ -374,7 +383,7 @@ impl<T: ArrayElement> GodotConvert for Vec<T> {
374383
}
375384

376385
impl<T: ArrayElement> ToGodot for Vec<T> {
377-
type Pass = crate::meta::ByValue;
386+
type Pass = meta::ByValue;
378387

379388
fn to_godot(&self) -> Self::Via {
380389
Array::from(self.as_slice())
@@ -392,7 +401,7 @@ impl<T: ArrayElement, const LEN: usize> GodotConvert for [T; LEN] {
392401
}
393402

394403
impl<T: ArrayElement, const LEN: usize> ToGodot for [T; LEN] {
395-
type Pass = crate::meta::ByValue;
404+
type Pass = meta::ByValue;
396405

397406
fn to_godot(&self) -> Self::Via {
398407
Array::from(self)
@@ -432,7 +441,7 @@ impl<T: ArrayElement> GodotConvert for &[T] {
432441
}
433442

434443
impl<T: ArrayElement> ToGodot for &[T] {
435-
type Pass = crate::meta::ByValue;
444+
type Pass = meta::ByValue;
436445

437446
fn to_godot(&self) -> Self::Via {
438447
Array::from(*self)
@@ -453,7 +462,7 @@ macro_rules! impl_pointer_convert {
453462
}
454463

455464
impl ToGodot for $Ptr {
456-
type Pass = crate::meta::ByValue;
465+
type Pass = meta::ByValue;
457466

458467
fn to_godot(&self) -> Self::Via {
459468
*self as i64

godot-core/src/meta/traits.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,25 @@ pub trait GodotType: GodotConvert<Via = Self> + sealed::Sealed + Sized + 'static
140140
fn qualifies_as_special_none(_from_variant: &Variant) -> bool {
141141
false
142142
}
143+
144+
/// Convert to `ObjectArg` for efficient object argument passing.
145+
///
146+
/// Implemented in `GodotType` because Rust has no specialization, and there's no good way to have trait bounds in `ByObject`, but not in
147+
/// other arg-passing strategies `ByValue`/`ByRef`.
148+
///
149+
/// # Panics
150+
/// If `Self` is not an object type (`Gd<T>`, `Option<Gd<T>>`). Note that `DynGd<T>` isn't directly implemented here, but uses `Gd<T>`'s
151+
/// impl on the FFI layer.
152+
///
153+
/// # Safety
154+
/// `self` must be kept alive while return value is in use. Might be addressed with lifetime in the future.
155+
#[doc(hidden)]
156+
unsafe fn as_object_arg(&self) -> crate::meta::ObjectArg {
157+
panic!(
158+
"as_object_arg() called for non-object type: {}",
159+
std::any::type_name::<Self>()
160+
)
161+
}
143162
}
144163

145164
// ----------------------------------------------------------------------------------------------------------------------------------------------

godot-core/src/obj/dyn_gd.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -553,9 +553,10 @@ where
553553
T: GodotClass,
554554
D: ?Sized,
555555
{
556+
// Delegate to Gd<T> passing strategy.
556557
type Pass = <Gd<T> as ToGodot>::Pass;
557558

558-
fn to_godot(&self) -> Self::Via {
559+
fn to_godot(&self) -> &Self::Via {
559560
self.obj.to_godot()
560561
}
561562

0 commit comments

Comments
 (0)