Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add get_property_list #707

Merged
merged 4 commits into from
May 25, 2024
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
28 changes: 28 additions & 0 deletions godot-codegen/src/generator/virtual_traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,34 @@ fn special_virtual_methods(notification_enum_name: &Ident) -> TokenStream {
unimplemented!()
}

/// Called whenever Godot [`get_property_list()`](crate::engine::Object::get_property_list) is called, the returned vector here is
/// appended to the existing list of properties.
///
/// This should mainly be used for advanced purposes, such as dynamically updating the property list in the editor.
///
/// See also in Godot docs:
/// * [`Object::_get_property_list`](https://docs.godotengine.org/en/latest/classes/class_object.html#class-object-private-method-get-property-list)
#[cfg(since_api = "4.3")]
fn get_property_list(&mut self) -> Vec<crate::builtin::meta::PropertyInfo> {
unimplemented!()
}

/// Called by Godot to tell if a property has a custom revert or not.
///
/// Return `None` for no custom revert, and return `Some(value)` to specify the custom revert.
///
/// This is a combination of Godot's [`Object::_property_get_revert`] and [`Object::_property_can_revert`]. This means that this
/// function will usually be called twice by Godot to find the revert.
///
/// Note that this should be a _pure_ function. That is, it should always return the same value for a property as long as `self`
/// remains unchanged. Otherwise this may lead to unexpected (safe) behavior.
///
/// [`Object::_property_get_revert`]: https://docs.godotengine.org/en/latest/classes/class_object.html#class-object-private-method-property-get-revert
/// [`Object::_property_can_revert`]: https://docs.godotengine.org/en/latest/classes/class_object.html#class-object-private-method-property-can-revert
#[doc(alias = "property_can_revert")]
fn property_get_revert(&self, property: StringName) -> Option<Variant> {
unimplemented!()
}
}
}

Expand Down
142 changes: 141 additions & 1 deletion godot-core/src/builtin/meta/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ pub use signature::*;
pub(crate) use godot_convert::convert_error::*;

use crate::builtin::*;
use crate::engine::global;
use crate::engine::global::{self, PropertyHint, PropertyUsageFlags};
use crate::property::Var;
use crate::property::{Export, PropertyHintInfo};
use godot_ffi as sys;
use registration::method::MethodParamOrReturnInfo;
use sys::{GodotFfi, GodotNullableFfi};
Expand Down Expand Up @@ -268,15 +270,119 @@ pub trait ArrayElement: GodotType {}
#[derive(Debug, Clone)]
// Note: is not #[non_exhaustive], so adding fields is a breaking change. Mostly used internally at the moment though.
pub struct PropertyInfo {
/// Which type this property has.
///
/// For objects this should be set to [`VariantType::Object`], and the `class_name` field to the actual name of the class.
///
/// For [`Variant`] this should be set to [`VariantType::Nil`].
pub variant_type: VariantType,

/// Which class this property is.
///
/// This should be set to [`ClassName::none()`] unless the variant type is `Object`. You can use
/// [`GodotClass::class_name()`](crate::obj::GodotClass::class_name()) to get the right name to use here.
pub class_name: ClassName,

/// The name of this property in Godot.
pub property_name: StringName,

/// How the property is meant to be edited. See also [`PropertyHint`] in the Godot docs.
///
/// [`PropertyHint`]: https://docs.godotengine.org/en/latest/classes/class_%40globalscope.html#enum-globalscope-propertyhint
pub hint: global::PropertyHint,

/// Extra information passed to Godot for this property, what this means depends on the `hint` value.
pub hint_string: GString,

/// How this property should be used. See [`PropertyUsageFlags`] in Godot for the meaning.
///
/// [`PropertyUsageFlags`]: https://docs.godotengine.org/en/latest/classes/class_%40globalscope.html#enum-globalscope-propertyusageflags
pub usage: global::PropertyUsageFlags,
}

impl PropertyInfo {
/// Create a new `PropertyInfo` representing a property named `property_name` with type `T`.
///
/// This will generate property info equivalent to what a `#[var]` attribute would.
pub fn new_var<T: Var>(property_name: &str) -> Self {
<T as GodotConvert>::Via::property_info(property_name).with_hint_info(T::property_hint())
}

/// Create a new `PropertyInfo` representing an exported property named `property_name` with type `T`.
///
/// This will generate property info equivalent to what an `#[export]` attribute would.
pub fn new_export<T: Export>(property_name: &str) -> Self {
<T as GodotConvert>::Via::property_info(property_name)
.with_hint_info(T::default_export_info())
}

/// Change the `hint` and `hint_string` to be the given `hint_info`.
///
/// See [`export_info_functions`](crate::property::export_info_functions) for functions that return appropriate `PropertyHintInfo`s for
/// various Godot annotations.
///
/// # Examples
///
/// Creating an `@export_range` property.
///
// TODO: Make this nicer to use.
/// ```no_run
/// use godot::register::property::export_info_functions;
/// use godot::builtin::meta::PropertyInfo;
///
/// let property = PropertyInfo::new_export::<f64>("my_range_property")
/// .with_hint_info(export_info_functions::export_range(
/// 0.0,
/// 10.0,
/// Some(0.1),
/// false,
/// false,
/// false,
/// false,
/// false,
/// false,
/// ));
/// ```
pub fn with_hint_info(self, hint_info: PropertyHintInfo) -> Self {
let PropertyHintInfo { hint, hint_string } = hint_info;

Self {
hint,
hint_string,
..self
}
}

/// Create a new `PropertyInfo` representing a group in Godot.
///
/// See [`EditorInspector`](https://docs.godotengine.org/en/latest/classes/class_editorinspector.html#class-editorinspector) in Godot for
/// more information.
pub fn new_group(group_name: &str, group_prefix: &str) -> Self {
Self {
variant_type: VariantType::Nil,
class_name: ClassName::none(),
property_name: group_name.into(),
hint: PropertyHint::NONE,
hint_string: group_prefix.into(),
usage: PropertyUsageFlags::GROUP,
}
}

/// Create a new `PropertyInfo` representing a subgroup in Godot.
///
/// See [`EditorInspector`](https://docs.godotengine.org/en/latest/classes/class_editorinspector.html#class-editorinspector) in Godot for
/// more information.
pub fn new_subgroup(subgroup_name: &str, subgroup_prefix: &str) -> Self {
Self {
variant_type: VariantType::Nil,
class_name: ClassName::none(),
property_name: subgroup_name.into(),
hint: PropertyHint::NONE,
hint_string: subgroup_prefix.into(),
usage: PropertyUsageFlags::SUBGROUP,
}
}

/// Converts to the FFI type. Keep this object allocated while using that!
pub fn property_sys(&self) -> sys::GDExtensionPropertyInfo {
use crate::obj::EngineBitfield as _;
Expand Down Expand Up @@ -305,6 +411,40 @@ impl PropertyInfo {
usage: global::PropertyUsageFlags::NONE.ord() as u32,
}
}

/// Consumes self and turns it into a `sys::GDExtensionPropertyInfo`, should be used together with
/// [`free_owned_property_sys`](Self::free_owned_property_sys).
///
/// This will leak memory unless used together with `free_owned_property_sys`.
pub(crate) fn into_owned_property_sys(self) -> sys::GDExtensionPropertyInfo {
use crate::obj::EngineBitfield as _;
use crate::obj::EngineEnum as _;

sys::GDExtensionPropertyInfo {
type_: self.variant_type.sys(),
name: self.property_name.into_owned_string_sys(),
class_name: sys::SysPtr::force_mut(self.class_name.string_sys()),
hint: u32::try_from(self.hint.ord()).expect("hint.ord()"),
hint_string: self.hint_string.into_owned_string_sys(),
usage: u32::try_from(self.usage.ord()).expect("usage.ord()"),
}
}

/// Properly frees a `sys::GDExtensionPropertyInfo` created by [`into_owned_property_sys`](Self::into_owned_property_sys).
///
/// # Safety
///
/// * Must only be used on a struct returned from a call to `into_owned_property_sys`, without modification.
/// * Must not be called more than once on a `sys::GDExtensionPropertyInfo` struct.
pub(crate) unsafe fn free_owned_property_sys(info: sys::GDExtensionPropertyInfo) {
// SAFETY: This function was called on a pointer returned from `into_owned_property_sys`, thus both `info.name` and
// `info.hint_string` were created from calls to `into_owned_string_sys` on their respective types.
// Additionally this function isn't called more than once on a struct containing the same `name` or `hint_string` pointers.
unsafe {
let _name = StringName::from_owned_string_sys(info.name);
let _hint_string = GString::from_owned_string_sys(info.hint_string);
}
}
}

// ----------------------------------------------------------------------------------------------------------------------------------------------
Expand Down
28 changes: 28 additions & 0 deletions godot-core/src/builtin/string/gstring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,34 @@ impl GString {
fn string_sys_mut = sys_mut;
}

/// Consumes self and turns it into a sys-ptr, should be used together with [`from_owned_string_sys`](Self::from_owned_string_sys).
///
/// This will leak memory unless `from_owned_string_sys` is called on the returned pointer.
pub(crate) fn into_owned_string_sys(self) -> sys::GDExtensionStringPtr {
sys::static_assert_eq_size_align!(StringName, sys::types::OpaqueString);

let leaked = Box::into_raw(Box::new(self));
leaked.cast()
}

/// Creates a `GString` from a sys-ptr without incrementing the refcount.
///
/// # Safety
///
/// * Must only be used on a pointer returned from a call to [`into_owned_string_sys`](Self::into_owned_string_sys).
/// * Must not be called more than once on the same pointer.
#[deny(unsafe_op_in_unsafe_fn)]
pub(crate) unsafe fn from_owned_string_sys(ptr: sys::GDExtensionStringPtr) -> Self {
sys::static_assert_eq_size_align!(StringName, sys::types::OpaqueString);

let ptr = ptr.cast::<Self>();

// SAFETY: `ptr` was returned from a call to `into_owned_string_sys`, which means it was created by a call to
// `Box::into_raw`, thus we can use `Box::from_raw` here. Additionally this is only called once on this pointer.
let boxed = unsafe { Box::from_raw(ptr) };
*boxed
}

/// Moves this string into a string sys pointer. This is the same as using [`GodotFfi::move_return_ptr`].
///
/// # Safety
Expand Down
28 changes: 28 additions & 0 deletions godot-core/src/builtin/string/string_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,34 @@ impl StringName {
fn string_sys_mut = sys_mut;
}

/// Consumes self and turns it into a sys-ptr, should be used together with [`from_owned_string_sys`](Self::from_owned_string_sys).
///
/// This will leak memory unless `from_owned_string_sys` is called on the returned pointer.
pub(crate) fn into_owned_string_sys(self) -> sys::GDExtensionStringNamePtr {
sys::static_assert_eq_size_align!(StringName, sys::types::OpaqueStringName);

let leaked = Box::into_raw(Box::new(self));
leaked.cast()
}

/// Creates a `StringName` from a sys-ptr without incrementing the refcount.
///
/// # Safety
///
/// * Must only be used on a pointer returned from a call to [`into_owned_string_sys`](Self::into_owned_string_sys).
/// * Must not be called more than once on the same pointer.
#[deny(unsafe_op_in_unsafe_fn)]
pub(crate) unsafe fn from_owned_string_sys(ptr: sys::GDExtensionStringNamePtr) -> Self {
sys::static_assert_eq_size_align!(StringName, sys::types::OpaqueStringName);

let ptr = ptr.cast::<Self>();

// SAFETY: `ptr` was returned from a call to `into_owned_string_sys`, which means it was created by a call to
// `Box::into_raw`, thus we can use `Box::from_raw` here. Additionally this is only called once on this pointer.
let boxed = unsafe { Box::from_raw(ptr) };
*boxed
}

/// Convert a `StringName` sys pointer to a reference with unbounded lifetime.
///
/// # Safety
Expand Down
12 changes: 12 additions & 0 deletions godot-core/src/obj/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,18 @@ pub mod cap {
fn __godot_set_property(&mut self, property: StringName, value: Variant) -> bool;
}

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

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

/// Auto-implemented for `#[godot_api] impl MyClass` blocks
pub trait ImplementsGodotApi: GodotClass {
#[doc(hidden)]
Expand Down
Loading
Loading