Skip to content

Commit edd3d17

Browse files
committed
Allow to use #[func(gd_self)] with Interface methods.
1 parent 4737427 commit edd3d17

File tree

11 files changed

+490
-103
lines changed

11 files changed

+490
-103
lines changed

godot-core/src/obj/traits.rs

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,7 @@ pub mod cap {
656656
use crate::builtin::{StringName, Variant};
657657
use crate::meta::PropertyInfo;
658658
use crate::obj::{Base, Bounds, Gd};
659+
use crate::storage::{IntoVirtualMethodReceiver, VirtualMethodReceiver};
659660

660661
/// Trait for all classes that are default-constructible from the Godot engine.
661662
///
@@ -712,7 +713,9 @@ pub mod cap {
712713
#[doc(hidden)]
713714
pub trait GodotToString: GodotClass {
714715
#[doc(hidden)]
715-
fn __godot_to_string(&self) -> GString;
716+
type Recv: IntoVirtualMethodReceiver<Self>;
717+
#[doc(hidden)]
718+
fn __godot_to_string(this: VirtualMethodReceiver<Self>) -> GString;
716719
}
717720

718721
// TODO Evaluate whether we want this public or not
@@ -732,32 +735,59 @@ pub mod cap {
732735
#[doc(hidden)]
733736
pub trait GodotGet: GodotClass {
734737
#[doc(hidden)]
735-
fn __godot_get_property(&self, property: StringName) -> Option<Variant>;
738+
type Recv: IntoVirtualMethodReceiver<Self>;
739+
#[doc(hidden)]
740+
fn __godot_get_property(
741+
this: VirtualMethodReceiver<Self>,
742+
property: StringName,
743+
) -> Option<Variant>;
736744
}
737745

738746
#[doc(hidden)]
739747
pub trait GodotSet: GodotClass {
740748
#[doc(hidden)]
741-
fn __godot_set_property(&mut self, property: StringName, value: Variant) -> bool;
749+
type Recv: IntoVirtualMethodReceiver<Self>;
750+
#[doc(hidden)]
751+
fn __godot_set_property(
752+
this: VirtualMethodReceiver<Self>,
753+
property: StringName,
754+
value: Variant,
755+
) -> bool;
742756
}
743757

744758
#[doc(hidden)]
745759
pub trait GodotGetPropertyList: GodotClass {
746760
#[doc(hidden)]
747-
fn __godot_get_property_list(&mut self) -> Vec<crate::meta::PropertyInfo>;
761+
type Recv: IntoVirtualMethodReceiver<Self>;
762+
763+
#[doc(hidden)]
764+
fn __godot_get_property_list(
765+
this: VirtualMethodReceiver<Self>,
766+
) -> Vec<crate::meta::PropertyInfo>;
748767
}
749768

750769
#[doc(hidden)]
751770
pub trait GodotPropertyGetRevert: GodotClass {
752771
#[doc(hidden)]
753-
fn __godot_property_get_revert(&self, property: StringName) -> Option<Variant>;
772+
type Recv: IntoVirtualMethodReceiver<Self>;
773+
774+
#[doc(hidden)]
775+
fn __godot_property_get_revert(
776+
this: VirtualMethodReceiver<Self>,
777+
property: StringName,
778+
) -> Option<Variant>;
754779
}
755780

756781
#[doc(hidden)]
757782
#[cfg(since_api = "4.2")]
758783
pub trait GodotValidateProperty: GodotClass {
759784
#[doc(hidden)]
760-
fn __godot_validate_property(&self, property: &mut PropertyInfo);
785+
type Recv: IntoVirtualMethodReceiver<Self>;
786+
#[doc(hidden)]
787+
fn __godot_validate_property(
788+
this: VirtualMethodReceiver<Self>,
789+
property: &mut PropertyInfo,
790+
);
761791
}
762792

763793
/// Auto-implemented for `#[godot_api] impl MyClass` blocks

godot-core/src/private.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ mod reexport_pub {
3333
};
3434
#[cfg(since_api = "4.2")]
3535
pub use crate::registry::signal::priv_re_export::*;
36-
pub use crate::storage::{as_storage, Storage};
36+
pub use crate::storage::{
37+
as_storage, GdRecv, IntoVirtualMethodReceiver, SelfMutRecv, SelfRecv, Storage,
38+
VirtualMethodReceiver,
39+
};
3740
pub use crate::sys::out;
3841
}
3942
pub use reexport_pub::*;

godot-core/src/registry/callbacks.rs

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use crate::builtin::{StringName, Variant};
2121
use crate::classes::Object;
2222
use crate::meta::PropertyInfo;
2323
use crate::obj::{bounds, cap, AsDyn, Base, Bounds, Gd, GodotClass, Inherits, UserClass};
24-
use crate::private::{handle_panic, PanicPayload};
24+
use crate::private::{handle_panic, IntoVirtualMethodReceiver, PanicPayload};
2525
use crate::registry::plugin::ErasedDynGd;
2626
use crate::storage::{as_storage, InstanceStorage, Storage, StorageRefCounted};
2727

@@ -250,8 +250,7 @@ pub unsafe extern "C" fn to_string<T: cap::GodotToString>(
250250
// Note: to_string currently always succeeds, as it is only provided for classes that have a working implementation.
251251

252252
let storage = as_storage::<T>(instance);
253-
let instance = storage.get();
254-
let string = T::__godot_to_string(&*instance);
253+
let string = T::__godot_to_string(T::Recv::instance(storage));
255254

256255
// Transfer ownership to Godot
257256
string.move_into_string_ptr(out_string);
@@ -289,10 +288,10 @@ pub unsafe extern "C" fn get_property<T: cap::GodotGet>(
289288
ret: sys::GDExtensionVariantPtr,
290289
) -> sys::GDExtensionBool {
291290
let storage = as_storage::<T>(instance);
292-
let instance = storage.get();
291+
let instance = T::Recv::instance(storage);
293292
let property = StringName::new_from_string_sys(name);
294293

295-
match T::__godot_get_property(&*instance, property) {
294+
match T::__godot_get_property(instance, property) {
296295
Some(value) => {
297296
value.move_into_var_ptr(ret);
298297
sys::conv::SYS_TRUE
@@ -307,12 +306,12 @@ pub unsafe extern "C" fn set_property<T: cap::GodotSet>(
307306
value: sys::GDExtensionConstVariantPtr,
308307
) -> sys::GDExtensionBool {
309308
let storage = as_storage::<T>(instance);
310-
let mut instance = storage.get_mut();
309+
let instance = T::Recv::instance(storage);
311310

312311
let property = StringName::new_from_string_sys(name);
313312
let value = Variant::new_from_var_sys(value);
314313

315-
sys::conv::bool_to_sys(T::__godot_set_property(&mut *instance, property, value))
314+
sys::conv::bool_to_sys(T::__godot_set_property(instance, property, value))
316315
}
317316

318317
pub unsafe extern "C" fn reference<T: GodotClass>(instance: sys::GDExtensionClassInstancePtr) {
@@ -335,9 +334,9 @@ pub unsafe extern "C" fn get_property_list<T: cap::GodotGetPropertyList>(
335334
) -> *const sys::GDExtensionPropertyInfo {
336335
// SAFETY: Godot provides us with a valid instance pointer to a `T`. And it will live until the end of this function.
337336
let storage = unsafe { as_storage::<T>(instance) };
338-
let mut instance = storage.get_mut();
337+
let instance = T::Recv::instance(storage);
339338

340-
let property_list = T::__godot_get_property_list(&mut *instance);
339+
let property_list = T::__godot_get_property_list(instance);
341340
let property_list_sys: Box<[sys::GDExtensionPropertyInfo]> = property_list
342341
.into_iter()
343342
.map(|prop| prop.into_owned_property_sys())
@@ -398,11 +397,11 @@ unsafe fn raw_property_get_revert<T: cap::GodotPropertyGetRevert>(
398397
) -> Option<Variant> {
399398
// SAFETY: `instance` is a valid `T` instance pointer for the duration of this function call.
400399
let storage = unsafe { as_storage::<T>(instance) };
401-
let instance = storage.get();
400+
let instance = T::Recv::instance(storage);
402401

403402
// SAFETY: `property_name` is a valid `StringName` pointer for the duration of this function call.
404403
let property = unsafe { StringName::borrow_string_sys(property_name) };
405-
T::__godot_property_get_revert(&*instance, property.clone())
404+
T::__godot_property_get_revert(instance, property.clone())
406405
}
407406

408407
/// # Safety
@@ -458,11 +457,11 @@ pub unsafe extern "C" fn validate_property<T: cap::GodotValidateProperty>(
458457
) -> sys::GDExtensionBool {
459458
// SAFETY: `instance` is a valid `T` instance pointer for the duration of this function call.
460459
let storage = unsafe { as_storage::<T>(instance) };
461-
let instance = storage.get();
460+
let instance = T::Recv::instance(storage);
462461

463462
// SAFETY: property_info_ptr must be valid.
464463
let mut property_info = unsafe { PropertyInfo::new_from_sys(property_info_ptr) };
465-
T::__godot_validate_property(&*instance, &mut property_info);
464+
T::__godot_validate_property(instance, &mut property_info);
466465

467466
// SAFETY: property_info_ptr remains valid & unchanged.
468467
unsafe { property_info.move_into_property_info_ptr(property_info_ptr) };

godot-core/src/storage/instance_storage.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
use std::cell::Cell;
9+
use std::ops::{Deref, DerefMut};
910
use std::ptr;
1011

1112
#[cfg(feature = "experimental-threads")]
@@ -169,6 +170,100 @@ const fn _assert_implements_storage<T: Storage + StorageRefCounted>() {}
169170
const _INSTANCE_STORAGE_IMPLEMENTS_STORAGE: () =
170171
_assert_implements_storage::<InstanceStorage<crate::classes::Object>>();
171172

173+
/// Wrapper to handle multiple receivers type, without exposing the Storage itself.
174+
#[doc(hidden)]
175+
pub struct VirtualMethodReceiver<'a, T: GodotClass> {
176+
inner: VirtualMethodReceiverInner<'a, T>,
177+
}
178+
179+
#[allow(clippy::enum_variant_names)] // `Self` is not valid enum variant name.
180+
pub enum VirtualMethodReceiverInner<'a, T: GodotClass> {
181+
/// &self.
182+
SelfRecv(RefGuard<'a, T>),
183+
/// &mut self.
184+
SelfMutRecv(MutGuard<'a, T>),
185+
/// this: Gd<Self>.
186+
GdRecv(Gd<T>),
187+
/// Blank.
188+
Uninit,
189+
}
190+
191+
impl<'a, T: GodotClass> VirtualMethodReceiver<'a, T> {
192+
pub fn recv_gd(mut self) -> Gd<T> {
193+
let mut out = VirtualMethodReceiverInner::Uninit;
194+
std::mem::swap(&mut out, &mut self.inner);
195+
196+
let VirtualMethodReceiverInner::GdRecv(instance) = out else {
197+
panic!("Tried to use Gd<T> receiver for method which doesn't accept it.")
198+
};
199+
200+
instance
201+
}
202+
203+
pub fn recv_self(mut self) -> impl Deref<Target = T> + use<'a, T> {
204+
let mut out = VirtualMethodReceiverInner::Uninit;
205+
std::mem::swap(&mut out, &mut self.inner);
206+
let VirtualMethodReceiverInner::SelfRecv(instance) = out else {
207+
panic!("Tried to use &self receiver for method which doesn't accept it.")
208+
};
209+
210+
instance
211+
}
212+
213+
pub fn recv_self_mut(mut self) -> impl DerefMut<Target = T> + use<'a, T> {
214+
let mut out = VirtualMethodReceiverInner::Uninit;
215+
std::mem::swap(&mut out, &mut self.inner);
216+
let VirtualMethodReceiverInner::SelfMutRecv(instance) = out else {
217+
panic!("Tried to use &mut self receiver for method which doesn't accept it.")
218+
};
219+
220+
instance
221+
}
222+
}
223+
224+
// Marker structs.
225+
// Used to extract proper type from storage and pass it to public API while defined as an associated item on the trait (`T::Recv::instance(storage)`).
226+
227+
#[doc(hidden)]
228+
pub struct SelfRecv;
229+
#[doc(hidden)]
230+
pub struct SelfMutRecv;
231+
#[doc(hidden)]
232+
pub struct GdRecv;
233+
234+
#[doc(hidden)]
235+
pub trait IntoVirtualMethodReceiver<T: GodotClass> {
236+
#[doc(hidden)]
237+
fn instance<'a, 'b: 'a>(storage: &'b InstanceStorage<T>) -> VirtualMethodReceiver<'a, T>;
238+
}
239+
240+
impl<T: GodotClass> IntoVirtualMethodReceiver<T> for SelfRecv {
241+
fn instance<'a, 'b: 'a>(storage: &'b InstanceStorage<T>) -> VirtualMethodReceiver<'a, T> {
242+
VirtualMethodReceiver {
243+
inner: VirtualMethodReceiverInner::SelfRecv(storage.get()),
244+
}
245+
}
246+
}
247+
248+
impl<T: GodotClass> IntoVirtualMethodReceiver<T> for SelfMutRecv {
249+
fn instance<'a, 'b: 'a>(storage: &'b InstanceStorage<T>) -> VirtualMethodReceiver<'a, T> {
250+
VirtualMethodReceiver {
251+
inner: VirtualMethodReceiverInner::SelfMutRecv(storage.get_mut()),
252+
}
253+
}
254+
}
255+
256+
impl<T> IntoVirtualMethodReceiver<T> for GdRecv
257+
where
258+
T: GodotClass + Inherits<<T as GodotClass>::Base>,
259+
{
260+
fn instance<'a, 'b: 'a>(storage: &'b InstanceStorage<T>) -> VirtualMethodReceiver<'a, T> {
261+
VirtualMethodReceiver {
262+
inner: VirtualMethodReceiverInner::GdRecv(storage.get_gd()),
263+
}
264+
}
265+
}
266+
172267
/// Interprets the opaque pointer as pointing to `InstanceStorage<T>`.
173268
///
174269
/// Note: returns reference with unbounded lifetime; intended for local usage

godot-macros/src/class/data_models/func.rs

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use proc_macro2::{Group, Ident, TokenStream, TokenTree};
99
use quote::{format_ident, quote};
1010

1111
use crate::class::RpcAttr;
12-
use crate::util::{bail_fn, ident, safe_ident};
12+
use crate::util::{bail, bail_fn, ident, safe_ident};
1313
use crate::{util, ParseResult};
1414

1515
/// Information used for registering a Rust function with Godot.
@@ -255,7 +255,12 @@ fn make_forwarding_closure(
255255
let before_method_call = match before_kind {
256256
BeforeKind::WithBefore | BeforeKind::OnlyBefore => {
257257
let before_method = format_ident!("__before_{}", method_name);
258-
quote! { instance.#before_method(); }
258+
if let ReceiverType::GdSelf = &signature_info.receiver_type {
259+
// In case of GdSelf receiver use instance only to call the before_method.
260+
quote! { ::godot::private::Storage::get_mut(storage).#before_method(); }
261+
} else {
262+
quote! { instance.#before_method(); }
263+
}
259264
}
260265
BeforeKind::Without => TokenStream::new(),
261266
};
@@ -386,6 +391,12 @@ pub(crate) fn into_signature_info(
386391
};
387392
}
388393
venial::FnParam::Typed(arg) => {
394+
// The first parameter - Receiver - should be removed.
395+
let index = if receiver_type == ReceiverType::GdSelf {
396+
index + 1
397+
} else {
398+
index
399+
};
389400
let ident = maybe_rename_parameter(arg.name, &mut next_unnamed_index);
390401
let ty = match maybe_change_parameter_type(arg.ty, &method_name, index) {
391402
// Parameter type was modified.
@@ -577,3 +588,31 @@ fn make_call_context(class_name_str: &str, method_name_str: &str) -> TokenStream
577588
::godot::meta::CallContext::func(#class_name_str, #method_name_str)
578589
}
579590
}
591+
592+
pub fn bail_attr<R>(attr_name: &Ident, msg: &str, method_name: &Ident) -> ParseResult<R> {
593+
bail!(method_name, "#[{attr_name}]: {msg}")
594+
}
595+
596+
pub fn extract_gd_self(signature: &mut venial::Function, attr_name: &Ident) -> ParseResult<Ident> {
597+
if signature.params.is_empty() {
598+
return bail_attr(
599+
attr_name,
600+
"with attribute key `gd_self`, the method must have a first parameter of type Gd<Self>",
601+
&signature.name,
602+
);
603+
}
604+
605+
// Remove Gd<Self> receiver from signature for further processing.
606+
let param = signature.params.inner.remove(0);
607+
608+
let venial::FnParam::Typed(param) = param.0 else {
609+
return bail_attr(
610+
attr_name,
611+
"with attribute key `gd_self`, the first parameter must be Gd<Self> (not a `self` receiver)",
612+
&signature.name
613+
);
614+
};
615+
616+
// Note: parameter is explicitly NOT renamed (maybe_rename_parameter).
617+
Ok(param.name)
618+
}

0 commit comments

Comments
 (0)