Skip to content

Commit 8990464

Browse files
bors[bot]lilizoey
andauthored
Merge #241
241: Properly export arrays, nodes, and resources by default r=lilizoey a=lilizoey Adds in support for each type to define what `hint` and `hint_string` is used for that type when exported as a property. This is used here to make arrays get properly exported, as well as nodes and resources have their type restricted to the actual type they can hold. (see #230) So now if you export a `Gd<Mesh>` or `Option<Gd<Mesh>>`, you wont get a list of every single resource in existence, but rather only resources that inherit from `Mesh`. And if you export a specific node-type, like `Node3D` you can only assign nodes that inherit from `Node3D` to it and not other nodes. Also i couldn't find an easy way to conditionally implement some of this stuff for `Gd` based on whether they inherited from `Resource`, `Node`, or not. Orphan rules prevents me from doing so based on `Inherits<T>`. So i added an `inherits` method to check at runtime. It shouldn't have a big impact on performance since afaik each property will only call this once to register the property. I'm not sure if this is the best solution, see my comment below for a major drawback. But overall it works fairly well. closes #230 Co-authored-by: Lili Zoey <[email protected]>
2 parents 04dc3e3 + 34faefa commit 8990464

File tree

10 files changed

+295
-104
lines changed

10 files changed

+295
-104
lines changed

godot-core/src/builtin/array.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ use godot_ffi as sys;
88

99
use crate::builtin::meta::VariantMetadata;
1010
use crate::builtin::*;
11-
use crate::obj::{Export, Share};
11+
use crate::export::{Export, ExportInfo, TypeStringHint};
12+
use crate::obj::Share;
1213
use std::fmt;
1314
use std::marker::PhantomData;
1415
use sys::{ffi_methods, interface_fn, GodotFfi};
@@ -635,10 +636,34 @@ impl<T: VariantMetadata> Share for Array<T> {
635636
}
636637
}
637638

638-
impl<T: VariantMetadata> Export for Array<T> {
639+
impl<T: VariantMetadata + TypeStringHint> TypeStringHint for Array<T> {
640+
fn type_string() -> String {
641+
format!("{}:{}", sys::VariantType::Array as i32, T::type_string())
642+
}
643+
}
644+
645+
impl<T: VariantMetadata + TypeStringHint> Export for Array<T> {
639646
fn export(&self) -> Self {
640647
self.share()
641648
}
649+
650+
fn default_export_info() -> ExportInfo {
651+
ExportInfo {
652+
variant_type: Self::variant_type(),
653+
hint: crate::engine::global::PropertyHint::PROPERTY_HINT_TYPE_STRING,
654+
hint_string: T::type_string().into(),
655+
}
656+
}
657+
}
658+
659+
impl Export for Array<Variant> {
660+
fn export(&self) -> Self {
661+
self.share()
662+
}
663+
664+
fn default_export_info() -> ExportInfo {
665+
ExportInfo::with_hint_none(Self::variant_type())
666+
}
642667
}
643668

644669
impl<T: VariantMetadata> Default for Array<T> {

godot-core/src/builtin/dictionary.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66

77
use godot_ffi as sys;
88

9+
use crate::builtin::meta::VariantMetadata;
910
use crate::builtin::{inner, FromVariant, ToVariant, Variant};
10-
use crate::obj::{Export, Share};
11+
use crate::export::{Export, ExportInfo};
12+
use crate::obj::Share;
1113
use std::fmt;
1214
use std::marker::PhantomData;
1315
use std::ptr::addr_of_mut;
@@ -310,6 +312,10 @@ impl Export for Dictionary {
310312
fn export(&self) -> Self {
311313
self.share()
312314
}
315+
316+
fn default_export_info() -> ExportInfo {
317+
ExportInfo::with_hint_none(Self::variant_type())
318+
}
313319
}
314320

315321
// ----------------------------------------------------------------------------------------------------------------------------------------------

godot-core/src/builtin/meta/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ impl<T: VariantMetadata> VariantMetadata for Option<T> {
4747
fn variant_type() -> VariantType {
4848
T::variant_type()
4949
}
50+
51+
fn class_name() -> ClassName {
52+
T::class_name()
53+
}
5054
}
5155

5256
// ----------------------------------------------------------------------------------------------------------------------------------------------

godot-core/src/builtin/mod.rs

Lines changed: 0 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -340,74 +340,6 @@ pub enum RectSide {
340340

341341
// ----------------------------------------------------------------------------------------------------------------------------------------------
342342

343-
/// Implementations of the `Export` trait for types where it can be done trivially.
344-
mod export {
345-
use crate::builtin::*;
346-
use crate::obj::Export;
347-
348-
macro_rules! impl_export_by_clone {
349-
($ty:path) => {
350-
impl Export for $ty {
351-
fn export(&self) -> Self {
352-
// If `Self` does not implement `Clone`, this gives a clearer error message
353-
// than simply `self.clone()`.
354-
Clone::clone(self)
355-
}
356-
}
357-
};
358-
}
359-
360-
impl_export_by_clone!(bool);
361-
impl_export_by_clone!(isize);
362-
impl_export_by_clone!(usize);
363-
impl_export_by_clone!(i8);
364-
impl_export_by_clone!(i16);
365-
impl_export_by_clone!(i32);
366-
impl_export_by_clone!(i64);
367-
impl_export_by_clone!(u8);
368-
impl_export_by_clone!(u16);
369-
impl_export_by_clone!(u32);
370-
impl_export_by_clone!(u64);
371-
impl_export_by_clone!(f32);
372-
impl_export_by_clone!(f64);
373-
374-
impl_export_by_clone!(Aabb);
375-
impl_export_by_clone!(Basis);
376-
impl_export_by_clone!(Color);
377-
impl_export_by_clone!(GodotString);
378-
impl_export_by_clone!(NodePath);
379-
impl_export_by_clone!(PackedByteArray);
380-
impl_export_by_clone!(PackedColorArray);
381-
impl_export_by_clone!(PackedFloat32Array);
382-
impl_export_by_clone!(PackedFloat64Array);
383-
impl_export_by_clone!(PackedInt32Array);
384-
impl_export_by_clone!(PackedInt64Array);
385-
impl_export_by_clone!(PackedStringArray);
386-
impl_export_by_clone!(PackedVector2Array);
387-
impl_export_by_clone!(PackedVector3Array);
388-
impl_export_by_clone!(Plane);
389-
impl_export_by_clone!(Projection);
390-
impl_export_by_clone!(Quaternion);
391-
impl_export_by_clone!(Rect2);
392-
impl_export_by_clone!(Rect2i);
393-
impl_export_by_clone!(Rid);
394-
impl_export_by_clone!(StringName);
395-
impl_export_by_clone!(Transform2D);
396-
impl_export_by_clone!(Transform3D);
397-
impl_export_by_clone!(Vector2);
398-
impl_export_by_clone!(Vector2i);
399-
impl_export_by_clone!(Vector3);
400-
impl_export_by_clone!(Vector3i);
401-
impl_export_by_clone!(Vector4);
402-
403-
// Callables can be exported, however you can't do anything with them in the editor.
404-
// But we do need to be able to export them since we can't make something a property without exporting.
405-
// And it should be possible to access Callables by property from for instance GDScript.
406-
impl_export_by_clone!(Callable);
407-
// TODO investigate whether Signal should impl Export at all, and if so, how
408-
// impl_export_by_clone!(Signal);
409-
}
410-
411343
#[cfg(all(test, feature = "serde"))]
412344
pub(crate) mod test_utils {
413345
use serde::{Deserialize, Serialize};

godot-core/src/export.rs

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*/
6+
7+
use crate::builtin::meta::{ClassName, PropertyInfo};
8+
use crate::builtin::{GodotString, StringName};
9+
use crate::engine::global::{PropertyHint, PropertyUsageFlags};
10+
use crate::obj::GodotClass;
11+
12+
use godot_ffi as sys;
13+
use sys::VariantType;
14+
15+
/// Trait implemented for types that can be used as `#[export]` fields. This creates a copy of the
16+
/// value, for some type-specific definition of "copy". For example, `Array` and `Gd` are returned
17+
/// via `Share::share()` instead of copying the actual data.
18+
pub trait Export {
19+
/// Creates a copy to be returned from a getter.
20+
fn export(&self) -> Self;
21+
22+
/// The export info to use for an exported field of this type, if no other export info is specified.
23+
fn default_export_info() -> ExportInfo;
24+
}
25+
26+
/// Info needed for godot to understand how to export a type to the editor.
27+
#[derive(Clone, Eq, PartialEq, Debug)]
28+
pub struct ExportInfo {
29+
pub variant_type: VariantType,
30+
pub hint: PropertyHint,
31+
pub hint_string: GodotString,
32+
}
33+
34+
impl ExportInfo {
35+
/// Create a new `ExportInfo` with a property hint of
36+
/// [`PROPERTY_HINT_NONE`](PropertyHint::PROPERTY_HINT_NONE).
37+
pub fn with_hint_none(variant_type: VariantType) -> Self {
38+
Self {
39+
variant_type,
40+
hint: PropertyHint::PROPERTY_HINT_NONE,
41+
hint_string: GodotString::new(),
42+
}
43+
}
44+
45+
/// Create a `PropertyInfo` from this export info, using the given property_name and usage, as well as the class name of `C`.
46+
pub fn to_property_info<C: GodotClass>(
47+
self,
48+
property_name: StringName,
49+
usage: PropertyUsageFlags,
50+
) -> PropertyInfo {
51+
let Self {
52+
variant_type,
53+
hint,
54+
hint_string,
55+
} = self;
56+
57+
PropertyInfo {
58+
variant_type,
59+
class_name: ClassName::of::<C>(),
60+
property_name,
61+
hint,
62+
hint_string,
63+
usage,
64+
}
65+
}
66+
}
67+
68+
impl<T: Export> Export for Option<T> {
69+
fn export(&self) -> Self {
70+
self.as_ref().map(Export::export)
71+
}
72+
73+
fn default_export_info() -> ExportInfo {
74+
T::default_export_info()
75+
}
76+
}
77+
78+
/// Trait for types that can be represented as a type string for use with
79+
/// [`PropertyHint::PROPERTY_HINT_TYPE_STRING`].
80+
pub trait TypeStringHint {
81+
/// Returns the representation of this type as a type string.
82+
///
83+
/// See [`PropertyHint.PROPERTY_HINT_TYPE_STRING`](
84+
/// https://docs.godotengine.org/en/stable/classes/class_%40globalscope.html#enum-globalscope-propertyhint
85+
/// ).
86+
fn type_string() -> String;
87+
}
88+
89+
mod export_impls {
90+
use super::*;
91+
use crate::builtin::meta::VariantMetadata;
92+
use crate::builtin::*;
93+
94+
macro_rules! impl_export_by_clone {
95+
($Ty:ty => $variant_type:ident) => {
96+
impl Export for $Ty {
97+
fn export(&self) -> Self {
98+
// If `Self` does not implement `Clone`, this gives a clearer error message
99+
// than simply `self.clone()`.
100+
Clone::clone(self)
101+
}
102+
103+
fn default_export_info() -> ExportInfo {
104+
ExportInfo::with_hint_none(Self::variant_type())
105+
}
106+
}
107+
108+
impl TypeStringHint for $Ty {
109+
fn type_string() -> String {
110+
format!("{}:", sys::VariantType::$variant_type as i32)
111+
}
112+
}
113+
};
114+
}
115+
116+
impl_export_by_clone!(Aabb => Aabb);
117+
impl_export_by_clone!(bool => Bool);
118+
impl_export_by_clone!(Basis => Basis);
119+
impl_export_by_clone!(Vector2 => Vector2);
120+
impl_export_by_clone!(Vector3 => Vector3);
121+
impl_export_by_clone!(Vector4 => Vector4);
122+
impl_export_by_clone!(Vector2i => Vector2i);
123+
impl_export_by_clone!(Vector3i => Vector3i);
124+
impl_export_by_clone!(Quaternion => Quaternion);
125+
impl_export_by_clone!(Color => Color);
126+
impl_export_by_clone!(GodotString => String);
127+
impl_export_by_clone!(StringName => StringName);
128+
impl_export_by_clone!(NodePath => NodePath);
129+
impl_export_by_clone!(PackedByteArray => PackedByteArray);
130+
impl_export_by_clone!(PackedInt32Array => PackedInt32Array);
131+
impl_export_by_clone!(PackedInt64Array => PackedInt64Array);
132+
impl_export_by_clone!(PackedFloat32Array => PackedFloat32Array);
133+
impl_export_by_clone!(PackedFloat64Array => PackedFloat64Array);
134+
impl_export_by_clone!(PackedStringArray => PackedStringArray);
135+
impl_export_by_clone!(PackedVector2Array => PackedVector2Array);
136+
impl_export_by_clone!(PackedVector3Array => PackedVector3Array);
137+
impl_export_by_clone!(PackedColorArray => PackedColorArray);
138+
impl_export_by_clone!(Plane => Plane);
139+
impl_export_by_clone!(Projection => Projection);
140+
impl_export_by_clone!(Rid => Rid);
141+
impl_export_by_clone!(Rect2 => Rect2);
142+
impl_export_by_clone!(Rect2i => Rect2i);
143+
impl_export_by_clone!(Transform2D => Transform2D);
144+
impl_export_by_clone!(Transform3D => Transform3D);
145+
impl_export_by_clone!(f64 => Float);
146+
impl_export_by_clone!(i64 => Int);
147+
148+
// Godot uses f64 internally for floats, and if Godot tries to pass an invalid f32 into a rust property
149+
// then the property will just round the value or become inf.
150+
impl_export_by_clone!(f32 => Float);
151+
152+
// Godot uses i64 internally for integers, and if Godot tries to pass an invalid integer into a property
153+
// accepting one of the below values then rust will panic. In the editor this will appear as the property
154+
// failing to be set to a value and an error printed in the console. During runtime this will crash the
155+
// program and print the panic from rust stating that the property cannot store the value.
156+
impl_export_by_clone!(i32 => Int);
157+
impl_export_by_clone!(i16 => Int);
158+
impl_export_by_clone!(i8 => Int);
159+
impl_export_by_clone!(u32 => Int);
160+
impl_export_by_clone!(u16 => Int);
161+
impl_export_by_clone!(u8 => Int);
162+
163+
// Callables can be exported, however you can't do anything with them in the editor.
164+
// But we do need to be able to export them since we can't make something a property without exporting.
165+
// And it should be possible to access Callables by property from for instance GDScript.
166+
// TODO:
167+
// Remove export impl when we can create properties without exporting them.
168+
impl_export_by_clone!(Callable => Callable);
169+
170+
// impl_export_by_clone!(Signal => Signal);
171+
}

godot-core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ mod storage;
99

1010
pub mod builder;
1111
pub mod builtin;
12+
pub mod export;
1213
pub mod init;
1314
pub mod log;
1415
pub mod macros;

godot-core/src/obj/gd.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ use crate::builtin::meta::{ClassName, VariantMetadata};
1818
use crate::builtin::{
1919
Callable, FromVariant, GodotString, StringName, ToVariant, Variant, VariantConversionError,
2020
};
21-
use crate::engine::Object;
21+
use crate::engine::{Node, Object, Resource};
22+
use crate::export::{Export, ExportInfo, TypeStringHint};
2223
use crate::obj::dom::Domain as _;
2324
use crate::obj::mem::Memory as _;
24-
use crate::obj::{cap, dom, mem, Export, GodotClass, Inherits, Share};
25+
use crate::obj::{cap, dom, mem, EngineEnum, GodotClass, Inherits, Share};
2526
use crate::obj::{GdMut, GdRef, InstanceId};
2627
use crate::storage::InstanceStorage;
2728
use crate::{callbacks, engine, out};
@@ -652,10 +653,49 @@ impl<T: GodotClass> Share for Gd<T> {
652653
}
653654
}
654655

656+
impl<T: GodotClass> TypeStringHint for Gd<T> {
657+
fn type_string() -> String {
658+
use engine::global::PropertyHint;
659+
660+
match Self::default_export_info().hint {
661+
hint @ (PropertyHint::PROPERTY_HINT_RESOURCE_TYPE
662+
| PropertyHint::PROPERTY_HINT_NODE_TYPE) => {
663+
format!(
664+
"{}/{}:{}",
665+
VariantType::Object as i32,
666+
hint.ord(),
667+
T::CLASS_NAME
668+
)
669+
}
670+
_ => format!("{}:", VariantType::Object as i32),
671+
}
672+
}
673+
}
674+
655675
impl<T: GodotClass> Export for Gd<T> {
656676
fn export(&self) -> Self {
657677
self.share()
658678
}
679+
680+
fn default_export_info() -> ExportInfo {
681+
let hint = if T::inherits::<Resource>() {
682+
engine::global::PropertyHint::PROPERTY_HINT_RESOURCE_TYPE
683+
} else if T::inherits::<Node>() {
684+
engine::global::PropertyHint::PROPERTY_HINT_NODE_TYPE
685+
} else {
686+
engine::global::PropertyHint::PROPERTY_HINT_NONE
687+
};
688+
689+
// Godot does this by default too, it doesn't seem to make a difference when not a resource/node
690+
// but is needed when it is a resource/node.
691+
let hint_string = T::CLASS_NAME.into();
692+
693+
ExportInfo {
694+
variant_type: Self::variant_type(),
695+
hint,
696+
hint_string,
697+
}
698+
}
659699
}
660700

661701
// ----------------------------------------------------------------------------------------------------------------------------------------------

0 commit comments

Comments
 (0)