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}