godot_core/meta/
property_info.rs

1/*
2 * Copyright (c) godot-rust; Bromeon and contributors.
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 */
7
8use godot_ffi::VariantType;
9
10use crate::builtin::{GString, StringName};
11use crate::global::{PropertyHint, PropertyUsageFlags};
12use crate::meta::{element_godot_type_name, ArrayElement, ClassId, GodotType, PackedArrayElement};
13use crate::obj::{bounds, Bounds, EngineBitfield, EngineEnum, GodotClass};
14use crate::registry::class::get_dyn_property_hint_string;
15use crate::registry::property::{Export, Var};
16use crate::{classes, sys};
17
18/// Describes a property in Godot.
19///
20/// Abstraction of the low-level `sys::GDExtensionPropertyInfo`.
21///
22/// Keeps the actual allocated values (the `sys` equivalent only keeps pointers, which fall out of scope).
23#[derive(Clone, Debug)]
24// Note: is not #[non_exhaustive], so adding fields is a breaking change. Mostly used internally at the moment though.
25// Note: There was an idea of a high-level representation of the following, but it's likely easier and more efficient to use introspection
26// APIs like `is_array_of_elem()`, unless there's a real user-facing need.
27// pub(crate) enum SimplePropertyType {
28//     Variant { ty: VariantType },
29//     Array { elem_ty: VariantType },
30//     Object { class_id: ClassId },
31// }
32pub struct PropertyInfo {
33    /// Which type this property has.
34    ///
35    /// For objects this should be set to [`VariantType::OBJECT`], and the `class_name` field to the actual name of the class.
36    ///
37    /// For [`Variant`][crate::builtin::Variant], this should be set to [`VariantType::NIL`].
38    pub variant_type: VariantType,
39
40    /// Which class this property is.
41    ///
42    /// This should be set to [`ClassId::none()`] unless the variant type is `Object`. You can use
43    /// [`GodotClass::class_id()`](crate::obj::GodotClass::class_name()) to get the right name to use here.
44    pub class_id: ClassId,
45
46    /// The name of this property in Godot.
47    pub property_name: StringName,
48
49    /// Additional type information for this property, e.g. about array types or enum values. Split into `hint` and `hint_string` members.
50    ///
51    /// See also [`PropertyHint`] in the Godot docs.
52    ///
53    /// [`PropertyHint`]: https://docs.godotengine.org/en/latest/classes/class_%40globalscope.html#enum-globalscope-propertyhint
54    pub hint_info: PropertyHintInfo,
55
56    /// How this property should be used. See [`PropertyUsageFlags`] in Godot for the meaning.
57    ///
58    /// [`PropertyUsageFlags`]: https://docs.godotengine.org/en/latest/classes/class_%40globalscope.html#enum-globalscope-propertyusageflags
59    pub usage: PropertyUsageFlags,
60}
61
62impl PropertyInfo {
63    /// Create a new `PropertyInfo` representing a property named `property_name` with type `T`.
64    ///
65    /// This will generate property info equivalent to what a `#[var]` attribute would.
66    pub fn new_var<T: Var>(property_name: &str) -> Self {
67        T::Via::property_info(property_name).with_hint_info(T::var_hint())
68    }
69
70    /// Create a new `PropertyInfo` representing an exported property named `property_name` with type `T`.
71    ///
72    /// This will generate property info equivalent to what an `#[export]` attribute would.
73    pub fn new_export<T: Export>(property_name: &str) -> Self {
74        T::Via::property_info(property_name).with_hint_info(T::export_hint())
75    }
76
77    /// Change the `hint` and `hint_string` to be the given `hint_info`.
78    ///
79    /// See [`export_info_functions`](crate::registry::property::export_info_functions) for functions that return appropriate `PropertyHintInfo`s for
80    /// various Godot annotations.
81    ///
82    /// # Examples
83    ///
84    /// Creating an `@export_range` property.
85    ///
86    // TODO: Make this nicer to use.
87    /// ```no_run
88    /// use godot::register::property::export_info_functions;
89    /// use godot::meta::PropertyInfo;
90    ///
91    /// let property = PropertyInfo::new_export::<f64>("my_range_property")
92    ///     .with_hint_info(export_info_functions::export_range(
93    ///         0.0,
94    ///         10.0,
95    ///         Some(0.1),
96    ///         false,
97    ///         false,
98    ///         false,
99    ///         false,
100    ///         false,
101    ///         false,
102    ///         Some("mm".to_string()),
103    ///     ));
104    /// ```
105    pub fn with_hint_info(self, hint_info: PropertyHintInfo) -> Self {
106        Self { hint_info, ..self }
107    }
108
109    /// Create a new `PropertyInfo` representing a group in Godot.
110    ///
111    /// See [`EditorInspector`](https://docs.godotengine.org/en/latest/classes/class_editorinspector.html#class-editorinspector) in Godot for
112    /// more information.
113    pub fn new_group(group_name: &str, group_prefix: &str) -> Self {
114        Self {
115            variant_type: VariantType::NIL,
116            class_id: ClassId::none(),
117            property_name: group_name.into(),
118            hint_info: PropertyHintInfo {
119                hint: PropertyHint::NONE,
120                hint_string: group_prefix.into(),
121            },
122            usage: PropertyUsageFlags::GROUP,
123        }
124    }
125
126    /// Create a new `PropertyInfo` representing a subgroup in Godot.
127    ///
128    /// See [`EditorInspector`](https://docs.godotengine.org/en/latest/classes/class_editorinspector.html#class-editorinspector) in Godot for
129    /// more information.
130    pub fn new_subgroup(subgroup_name: &str, subgroup_prefix: &str) -> Self {
131        Self {
132            variant_type: VariantType::NIL,
133            class_id: ClassId::none(),
134            property_name: subgroup_name.into(),
135            hint_info: PropertyHintInfo {
136                hint: PropertyHint::NONE,
137                hint_string: subgroup_prefix.into(),
138            },
139            usage: PropertyUsageFlags::SUBGROUP,
140        }
141    }
142
143    // ------------------------------------------------------------------------------------------------------------------------------------------
144    // Introspection API -- could be made public in the future
145
146    pub(crate) fn is_array_of_elem<T>(&self) -> bool
147    where
148        T: ArrayElement,
149    {
150        self.variant_type == VariantType::ARRAY
151            && self.hint_info.hint == PropertyHint::ARRAY_TYPE
152            && self.hint_info.hint_string == GString::from(&T::Via::godot_type_name())
153    }
154
155    // ------------------------------------------------------------------------------------------------------------------------------------------
156    // FFI conversion functions
157
158    /// Converts to the FFI type. Keep this object allocated while using that!
159    pub fn property_sys(&self) -> sys::GDExtensionPropertyInfo {
160        use crate::obj::{EngineBitfield as _, EngineEnum as _};
161
162        sys::GDExtensionPropertyInfo {
163            type_: self.variant_type.sys(),
164            name: sys::SysPtr::force_mut(self.property_name.string_sys()),
165            class_name: sys::SysPtr::force_mut(self.class_id.string_sys()),
166            hint: u32::try_from(self.hint_info.hint.ord()).expect("hint.ord()"),
167            hint_string: sys::SysPtr::force_mut(self.hint_info.hint_string.string_sys()),
168            usage: u32::try_from(self.usage.ord()).expect("usage.ord()"),
169        }
170    }
171
172    pub fn empty_sys() -> sys::GDExtensionPropertyInfo {
173        use crate::obj::{EngineBitfield as _, EngineEnum as _};
174
175        sys::GDExtensionPropertyInfo {
176            type_: VariantType::NIL.sys(),
177            name: std::ptr::null_mut(),
178            class_name: std::ptr::null_mut(),
179            hint: PropertyHint::NONE.ord() as u32,
180            hint_string: std::ptr::null_mut(),
181            usage: PropertyUsageFlags::NONE.ord() as u32,
182        }
183    }
184
185    /// Consumes self and turns it into a `sys::GDExtensionPropertyInfo`, should be used together with
186    /// [`free_owned_property_sys`](Self::free_owned_property_sys).
187    ///
188    /// This will leak memory unless used together with `free_owned_property_sys`.
189    pub(crate) fn into_owned_property_sys(self) -> sys::GDExtensionPropertyInfo {
190        use crate::obj::{EngineBitfield as _, EngineEnum as _};
191
192        sys::GDExtensionPropertyInfo {
193            type_: self.variant_type.sys(),
194            name: self.property_name.into_owned_string_sys(),
195            class_name: sys::SysPtr::force_mut(self.class_id.string_sys()),
196            hint: u32::try_from(self.hint_info.hint.ord()).expect("hint.ord()"),
197            hint_string: self.hint_info.hint_string.into_owned_string_sys(),
198            usage: u32::try_from(self.usage.ord()).expect("usage.ord()"),
199        }
200    }
201
202    /// Properly frees a `sys::GDExtensionPropertyInfo` created by [`into_owned_property_sys`](Self::into_owned_property_sys).
203    ///
204    /// # Safety
205    ///
206    /// * Must only be used on a struct returned from a call to `into_owned_property_sys`, without modification.
207    /// * Must not be called more than once on a `sys::GDExtensionPropertyInfo` struct.
208    pub(crate) unsafe fn free_owned_property_sys(info: sys::GDExtensionPropertyInfo) {
209        // SAFETY: This function was called on a pointer returned from `into_owned_property_sys`, thus both `info.name` and
210        // `info.hint_string` were created from calls to `into_owned_string_sys` on their respective types.
211        // Additionally, this function isn't called more than once on a struct containing the same `name` or `hint_string` pointers.
212        unsafe {
213            let _name = StringName::from_owned_string_sys(info.name);
214            let _hint_string = GString::from_owned_string_sys(info.hint_string);
215        }
216    }
217
218    /// Moves its values into given `GDExtensionPropertyInfo`, dropping previous values if necessary.
219    ///
220    /// # Safety
221    ///
222    /// * `property_info_ptr` must be valid.
223    ///
224    pub(crate) unsafe fn move_into_property_info_ptr(
225        self,
226        property_info_ptr: *mut sys::GDExtensionPropertyInfo,
227    ) {
228        let ptr = &mut *property_info_ptr;
229
230        ptr.usage = u32::try_from(self.usage.ord()).expect("usage.ord()");
231        ptr.hint = u32::try_from(self.hint_info.hint.ord()).expect("hint.ord()");
232        ptr.type_ = self.variant_type.sys();
233
234        *StringName::borrow_string_sys_mut(ptr.name) = self.property_name;
235        *GString::borrow_string_sys_mut(ptr.hint_string) = self.hint_info.hint_string;
236
237        if self.class_id != ClassId::none() {
238            *StringName::borrow_string_sys_mut(ptr.class_name) = self.class_id.to_string_name();
239        }
240    }
241
242    /// Creates copy of given `sys::GDExtensionPropertyInfo`.
243    ///
244    /// # Safety
245    ///
246    /// * `property_info_ptr` must be valid.
247    pub(crate) unsafe fn new_from_sys(
248        property_info_ptr: *mut sys::GDExtensionPropertyInfo,
249    ) -> Self {
250        let ptr = *property_info_ptr;
251
252        Self {
253            variant_type: VariantType::from_sys(ptr.type_),
254            class_id: ClassId::none(),
255            property_name: StringName::new_from_string_sys(ptr.name),
256            hint_info: PropertyHintInfo {
257                hint: PropertyHint::from_ord(ptr.hint.to_owned() as i32),
258                hint_string: GString::new_from_string_sys(ptr.hint_string),
259            },
260            usage: PropertyUsageFlags::from_ord(ptr.usage as u64),
261        }
262    }
263}
264
265// ----------------------------------------------------------------------------------------------------------------------------------------------
266
267/// Info needed by Godot, for how to export a type to the editor.
268#[derive(Clone, Eq, PartialEq, Debug)]
269pub struct PropertyHintInfo {
270    pub hint: PropertyHint,
271    pub hint_string: GString,
272}
273
274impl PropertyHintInfo {
275    /// Create a new `PropertyHintInfo` with a property hint of [`PROPERTY_HINT_NONE`](PropertyHint::NONE), and no hint string.
276    pub fn none() -> Self {
277        Self {
278            hint: PropertyHint::NONE,
279            hint_string: GString::new(),
280        }
281    }
282
283    /// Use [`PROPERTY_HINT_NONE`](PropertyHint::NONE) with `T`'s Godot type name.
284    ///
285    /// Starting with Godot version 4.3, the hint string will always be the empty string. Before that, the hint string is set to
286    /// be the Godot type name of `T`.
287    pub fn type_name<T: GodotType>() -> Self {
288        let type_name = T::godot_type_name();
289        let hint_string = if sys::GdextBuild::since_api("4.3") {
290            GString::new()
291        } else {
292            GString::from(&type_name)
293        };
294
295        Self {
296            hint: PropertyHint::NONE,
297            hint_string,
298        }
299    }
300
301    /// Use for `#[var]` properties -- [`PROPERTY_HINT_ARRAY_TYPE`](PropertyHint::ARRAY_TYPE) with the type name as hint string.
302    pub fn var_array_element<T: ArrayElement>() -> Self {
303        Self {
304            hint: PropertyHint::ARRAY_TYPE,
305            hint_string: GString::from(&element_godot_type_name::<T>()),
306        }
307    }
308
309    /// Use for `#[export]` properties -- [`PROPERTY_HINT_TYPE_STRING`](PropertyHint::TYPE_STRING) with the **element** type string as hint string.
310    pub fn export_array_element<T: ArrayElement>() -> Self {
311        Self {
312            hint: PropertyHint::TYPE_STRING,
313            hint_string: GString::from(&T::element_type_string()),
314        }
315    }
316
317    /// Use for `#[export]` properties -- [`PROPERTY_HINT_TYPE_STRING`](PropertyHint::TYPE_STRING) with the **element** type string as hint string.
318    pub fn export_packed_array_element<T: PackedArrayElement>() -> Self {
319        Self {
320            hint: PropertyHint::TYPE_STRING,
321            hint_string: GString::from(&T::element_type_string()),
322        }
323    }
324
325    pub fn export_gd<T>() -> Self
326    where
327        T: GodotClass + Bounds<Exportable = bounds::Yes>,
328    {
329        let hint = if T::inherits::<classes::Resource>() {
330            PropertyHint::RESOURCE_TYPE
331        } else if T::inherits::<classes::Node>() {
332            PropertyHint::NODE_TYPE
333        } else {
334            unreachable!("classes not inheriting from Resource or Node should not be exportable")
335        };
336
337        // Godot does this by default too; the hint is needed when the class is a resource/node,
338        // but doesn't seem to make a difference otherwise.
339        let hint_string = T::class_id().to_gstring();
340
341        Self { hint, hint_string }
342    }
343
344    pub fn export_dyn_gd<T, D>() -> Self
345    where
346        T: GodotClass + Bounds<Exportable = bounds::Yes>,
347        D: ?Sized + 'static,
348    {
349        PropertyHintInfo {
350            hint_string: GString::from(&get_dyn_property_hint_string::<T, D>()),
351            ..PropertyHintInfo::export_gd::<T>()
352        }
353    }
354
355    #[doc(hidden)]
356    pub fn object_as_node_class<T>() -> Option<ClassId>
357    where
358        T: GodotClass + Bounds<Exportable = bounds::Yes>,
359    {
360        T::inherits::<classes::Node>().then(|| T::class_id())
361    }
362}