Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 40 additions & 6 deletions godot-core/src/obj/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,7 @@ pub mod cap {
use crate::builtin::{StringName, Variant};
use crate::meta::PropertyInfo;
use crate::obj::{Base, Bounds, Gd};
use crate::storage::{IntoVirtualMethodReceiver, VirtualMethodReceiver};

/// Trait for all classes that are default-constructible from the Godot engine.
///
Expand Down Expand Up @@ -712,7 +713,10 @@ pub mod cap {
#[doc(hidden)]
pub trait GodotToString: GodotClass {
#[doc(hidden)]
fn __godot_to_string(&self) -> GString;
type Recv: IntoVirtualMethodReceiver<Self>;

#[doc(hidden)]
fn __godot_to_string(this: VirtualMethodReceiver<Self>) -> GString;
}

// TODO Evaluate whether we want this public or not
Expand All @@ -732,32 +736,62 @@ pub mod cap {
#[doc(hidden)]
pub trait GodotGet: GodotClass {
#[doc(hidden)]
fn __godot_get_property(&self, property: StringName) -> Option<Variant>;
type Recv: IntoVirtualMethodReceiver<Self>;

#[doc(hidden)]
fn __godot_get_property(
this: VirtualMethodReceiver<Self>,
property: StringName,
) -> Option<Variant>;
}

#[doc(hidden)]
pub trait GodotSet: GodotClass {
#[doc(hidden)]
fn __godot_set_property(&mut self, property: StringName, value: Variant) -> bool;
type Recv: IntoVirtualMethodReceiver<Self>;

#[doc(hidden)]
fn __godot_set_property(
this: VirtualMethodReceiver<Self>,
property: StringName,
value: Variant,
) -> bool;
}

#[doc(hidden)]
pub trait GodotGetPropertyList: GodotClass {
#[doc(hidden)]
fn __godot_get_property_list(&mut self) -> Vec<crate::meta::PropertyInfo>;
type Recv: IntoVirtualMethodReceiver<Self>;

#[doc(hidden)]
fn __godot_get_property_list(
this: VirtualMethodReceiver<Self>,
) -> Vec<crate::meta::PropertyInfo>;
}

#[doc(hidden)]
pub trait GodotPropertyGetRevert: GodotClass {
#[doc(hidden)]
fn __godot_property_get_revert(&self, property: StringName) -> Option<Variant>;
type Recv: IntoVirtualMethodReceiver<Self>;

#[doc(hidden)]
fn __godot_property_get_revert(
this: VirtualMethodReceiver<Self>,
property: StringName,
) -> Option<Variant>;
}

#[doc(hidden)]
#[cfg(since_api = "4.2")]
pub trait GodotValidateProperty: GodotClass {
#[doc(hidden)]
fn __godot_validate_property(&self, property: &mut PropertyInfo);
type Recv: IntoVirtualMethodReceiver<Self>;

#[doc(hidden)]
fn __godot_validate_property(
this: VirtualMethodReceiver<Self>,
property: &mut PropertyInfo,
);
}

/// Auto-implemented for `#[godot_api] impl MyClass` blocks
Expand Down
5 changes: 4 additions & 1 deletion godot-core/src/private.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ mod reexport_pub {
};
#[cfg(since_api = "4.2")]
pub use crate::registry::signal::priv_re_export::*;
pub use crate::storage::{as_storage, Storage};
pub use crate::storage::{
as_storage, IntoVirtualMethodReceiver, RecvGdSelf, RecvMut, RecvRef, Storage,
VirtualMethodReceiver,
};
pub use crate::sys::out;
}
pub use reexport_pub::*;
Expand Down
25 changes: 12 additions & 13 deletions godot-core/src/registry/callbacks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::builtin::{StringName, Variant};
use crate::classes::Object;
use crate::meta::PropertyInfo;
use crate::obj::{bounds, cap, AsDyn, Base, Bounds, Gd, GodotClass, Inherits, UserClass};
use crate::private::{handle_panic, PanicPayload};
use crate::private::{handle_panic, IntoVirtualMethodReceiver, PanicPayload};
use crate::registry::plugin::ErasedDynGd;
use crate::storage::{as_storage, InstanceStorage, Storage, StorageRefCounted};

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

let storage = as_storage::<T>(instance);
let instance = storage.get();
let string = T::__godot_to_string(&*instance);
let string = T::__godot_to_string(T::Recv::instance(storage));

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

match T::__godot_get_property(&*instance, property) {
match T::__godot_get_property(instance, property) {
Some(value) => {
value.move_into_var_ptr(ret);
sys::conv::SYS_TRUE
Expand All @@ -307,12 +306,12 @@ pub unsafe extern "C" fn set_property<T: cap::GodotSet>(
value: sys::GDExtensionConstVariantPtr,
) -> sys::GDExtensionBool {
let storage = as_storage::<T>(instance);
let mut instance = storage.get_mut();
let instance = T::Recv::instance(storage);

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

sys::conv::bool_to_sys(T::__godot_set_property(&mut *instance, property, value))
sys::conv::bool_to_sys(T::__godot_set_property(instance, property, value))
}

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

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

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

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

// SAFETY: property_info_ptr must be valid.
let mut property_info = unsafe { PropertyInfo::new_from_sys(property_info_ptr) };
T::__godot_validate_property(&*instance, &mut property_info);
T::__godot_validate_property(instance, &mut property_info);

// SAFETY: property_info_ptr remains valid & unchanged.
unsafe { property_info.move_into_property_info_ptr(property_info_ptr) };
Expand Down
84 changes: 84 additions & 0 deletions godot-core/src/storage/instance_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

use std::cell::Cell;
use std::ops::{Deref, DerefMut};
use std::ptr;

#[cfg(feature = "experimental-threads")]
Expand Down Expand Up @@ -169,6 +170,89 @@ const fn _assert_implements_storage<T: Storage + StorageRefCounted>() {}
const _INSTANCE_STORAGE_IMPLEMENTS_STORAGE: () =
_assert_implements_storage::<InstanceStorage<crate::classes::Object>>();

/// Wrapper to handle multiple receivers type, without exposing the Storage itself.
#[doc(hidden)]
pub struct VirtualMethodReceiver<'a, T: GodotClass> {
inner: VirtualMethodReceiverInner<'a, T>,
}

enum VirtualMethodReceiverInner<'a, T: GodotClass> {
/// &self.
Ref(RefGuard<'a, T>),
/// &mut self.
Mut(MutGuard<'a, T>),
/// this: Gd<Self>.
GdSelf(Gd<T>),
/// Implementation detail – required to swap the values.
Uninit,
}

impl<'a, T: GodotClass> VirtualMethodReceiver<'a, T> {
pub fn recv_gd(mut self) -> Gd<T> {
match std::mem::replace(&mut self.inner, VirtualMethodReceiverInner::Uninit) {
VirtualMethodReceiverInner::GdSelf(instance) => instance,
_ => panic!("Tried to use Gd<Self> receiver for method which doesn't accept it."),
}
}

pub fn recv_self(mut self) -> impl Deref<Target = T> + use<'a, T> {
match std::mem::replace(&mut self.inner, VirtualMethodReceiverInner::Uninit) {
VirtualMethodReceiverInner::Ref(instance) => instance,
_ => panic!("Tried to use &self receiver for method which doesn't accept it."),
}
}

pub fn recv_self_mut(mut self) -> impl DerefMut<Target = T> + use<'a, T> {
match std::mem::replace(&mut self.inner, VirtualMethodReceiverInner::Uninit) {
VirtualMethodReceiverInner::Mut(instance) => instance,
_ => panic!("Tried to use &mut self receiver for method which doesn't accept it."),
}
}
}

// Marker structs.
// 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)`).

#[doc(hidden)]
pub enum RecvRef {}
#[doc(hidden)]
pub enum RecvMut {}
#[doc(hidden)]
pub enum RecvGdSelf {}

#[doc(hidden)]
pub trait IntoVirtualMethodReceiver<T: GodotClass> {
#[doc(hidden)]
fn instance<'a, 'b: 'a>(storage: &'b InstanceStorage<T>) -> VirtualMethodReceiver<'a, T>;
}

impl<T: GodotClass> IntoVirtualMethodReceiver<T> for RecvRef {
fn instance<'a, 'b: 'a>(storage: &'b InstanceStorage<T>) -> VirtualMethodReceiver<'a, T> {
VirtualMethodReceiver {
inner: VirtualMethodReceiverInner::Ref(storage.get()),
}
}
}

impl<T: GodotClass> IntoVirtualMethodReceiver<T> for RecvMut {
fn instance<'a, 'b: 'a>(storage: &'b InstanceStorage<T>) -> VirtualMethodReceiver<'a, T> {
VirtualMethodReceiver {
inner: VirtualMethodReceiverInner::Mut(storage.get_mut()),
}
}
}

impl<T> IntoVirtualMethodReceiver<T> for RecvGdSelf
where
T: GodotClass + Inherits<<T as GodotClass>::Base>,
{
fn instance<'a, 'b: 'a>(storage: &'b InstanceStorage<T>) -> VirtualMethodReceiver<'a, T> {
VirtualMethodReceiver {
inner: VirtualMethodReceiverInner::GdSelf(storage.get_gd()),
}
}
}

/// Interprets the opaque pointer as pointing to `InstanceStorage<T>`.
///
/// Note: returns reference with unbounded lifetime; intended for local usage
Expand Down
43 changes: 41 additions & 2 deletions godot-macros/src/class/data_models/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use proc_macro2::{Group, Ident, TokenStream, TokenTree};
use quote::{format_ident, quote};

use crate::class::RpcAttr;
use crate::util::{bail_fn, ident, safe_ident};
use crate::util::{bail, bail_fn, ident, safe_ident};
use crate::{util, ParseResult};

/// Information used for registering a Rust function with Godot.
Expand Down Expand Up @@ -255,7 +255,12 @@ fn make_forwarding_closure(
let before_method_call = match before_kind {
BeforeKind::WithBefore | BeforeKind::OnlyBefore => {
let before_method = format_ident!("__before_{}", method_name);
quote! { instance.#before_method(); }
if let ReceiverType::GdSelf = signature_info.receiver_type {
// In case of GdSelf receiver use instance only to call the before_method.
quote! { ::godot::private::Storage::get_mut(storage).#before_method(); }
} else {
quote! { instance.#before_method(); }
}
}
BeforeKind::Without => TokenStream::new(),
};
Expand Down Expand Up @@ -386,6 +391,12 @@ pub(crate) fn into_signature_info(
};
}
venial::FnParam::Typed(arg) => {
// The first parameter - Receiver - should be removed.
let index = if receiver_type == ReceiverType::GdSelf {
index + 1
} else {
index
};
let ident = maybe_rename_parameter(arg.name, &mut next_unnamed_index);
let ty = match maybe_change_parameter_type(arg.ty, &method_name, index) {
// Parameter type was modified.
Expand Down Expand Up @@ -577,3 +588,31 @@ fn make_call_context(class_name_str: &str, method_name_str: &str) -> TokenStream
::godot::meta::CallContext::func(#class_name_str, #method_name_str)
}
}

pub fn bail_attr<R>(attr_name: &Ident, msg: &str, method_name: &Ident) -> ParseResult<R> {
bail!(method_name, "#[{attr_name}]: {msg}")
}

pub fn extract_gd_self(signature: &mut venial::Function, attr_name: &Ident) -> ParseResult<Ident> {
if signature.params.is_empty() {
return bail_attr(
attr_name,
"with attribute key `gd_self`, the method must have a first parameter of type Gd<Self>",
&signature.name,
);
}

// Remove Gd<Self> receiver from signature for further processing.
let param = signature.params.inner.remove(0);

let venial::FnParam::Typed(param) = param.0 else {
return bail_attr(
attr_name,
"with attribute key `gd_self`, the first parameter must be Gd<Self> (not a `self` receiver)",
&signature.name
);
};

// Note: parameter is explicitly NOT renamed (maybe_rename_parameter).
Ok(param.name)
}
Loading
Loading