Skip to content

Commit fa43b85

Browse files
committed
Remove Export impls with surprising behavior
Also added documentation on the rationales behind leaving `Export` unimplemented for standard Rust collections, pointing to alternatives. Close #1009
1 parent 91e1b77 commit fa43b85

File tree

4 files changed

+48
-56
lines changed

4 files changed

+48
-56
lines changed

examples/property-export/GDScriptPrinter.gd

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ func _ready():
44
var rust = get_node("../PropertyExport")
55

66
print("\n-----------------------------------------------------------------")
7-
print("Print from GDScript (note the lexicographically ordered map/set):")
8-
print(" Vec (name):");
7+
print("Print from GDScript:")
8+
print(" PoolArray<GodotString>:");
99
for name in rust.name_vec:
1010
print(" * %s" % name)
1111

12-
print("\n HashMap (string -> color):")
12+
print("\n Dictionary (string -> color):")
1313
for string in rust.color_map:
1414
var color = rust.color_map[string]
1515
print(" * %s -> #%s" % [string, color.to_html(false)]);
1616

17-
print("\n HashSet (ID):")
17+
print("\n PoolArray<i32>:")
1818
for id in rust.id_set:
1919
print(" * %s" % id)
2020

examples/property-export/Main.tscn

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ library = ExtResource( 1 )
1212

1313
[node name="PropertyExport" type="Node" parent="."]
1414
script = SubResource( 1 )
15-
name_vec = [ "Godot", "Godette", "Go ." ]
15+
name_vec = PoolStringArray("Godot", "Godette", "Go .")
1616
color_map = {
1717
"blue": Color( 0.184314, 0.160784, 0.8, 1 ),
1818
"green": Color( 0.0941176, 0.447059, 0.192157, 1 ),
1919
"teal": Color( 0.0941176, 0.423529, 0.564706, 1 )
2020
}
21-
id_set = [ 21, 77, 8, 90 ]
21+
id_set = PoolIntArray(21, 77, 8, 90)
2222

2323
[node name="GDScriptPrinter" type="Node" parent="."]
2424
script = ExtResource( 2 )

examples/property-export/src/lib.rs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
use gdnative::prelude::*;
22

3-
use std::collections::{HashMap, HashSet};
4-
53
#[derive(NativeClass, Default)]
64
#[inherit(Node)]
75
pub struct PropertyExport {
86
#[property]
9-
name_vec: Vec<String>,
7+
name_vec: PoolArray<GodotString>,
108

119
#[property]
12-
color_map: HashMap<GodotString, Color>,
10+
color_map: Dictionary,
1311

1412
#[property]
15-
id_set: HashSet<i64>,
13+
id_set: PoolArray<i32>,
1614
}
1715

1816
#[methods]
@@ -24,19 +22,20 @@ impl PropertyExport {
2422
#[method]
2523
fn _ready(&self) {
2624
godot_print!("------------------------------------------------------------------");
27-
godot_print!("Print from Rust (note the unordered map/set):");
28-
godot_print!(" Vec (name):");
29-
for name in &self.name_vec {
25+
godot_print!("Print from Rust:");
26+
godot_print!(" PoolArray<GodotString>:");
27+
for name in &*self.name_vec.read() {
3028
godot_print!(" * {}", name);
3129
}
3230

33-
godot_print!("\n HashMap (string -> color):");
31+
godot_print!("\n Dictionary (string -> color):");
3432
for (string, color) in &self.color_map {
33+
let color = Color::from_variant(&color).unwrap();
3534
godot_print!(" * {} -> #{}", string, color.to_html(false));
3635
}
3736

38-
godot_print!("\n HashSet (ID):");
39-
for id in &self.id_set {
37+
godot_print!("\n PoolArray<i32>:");
38+
for id in &*self.id_set.read() {
4039
godot_print!(" * {}", id);
4140
}
4241
}

gdnative-core/src/export/property.rs

Lines changed: 32 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,38 @@ mod invalid_accessor;
2323
pub mod hint;
2424

2525
/// Trait for exportable types.
26+
///
27+
/// ## Rust collections
28+
///
29+
/// `Export` is intentionally unimplemented for standard Rust collections, such as [`Vec`] or
30+
/// [`HashMap`][std::collections::HashMap]. The reason is that such types exhibit surprising
31+
/// behavior when used from GDScript, due to how [`ToVariant`]/[`FromVariant`] conversions work
32+
/// for these types.
33+
///
34+
/// Godot has no concept of Rust collections, and cannot operate on them. Whenever a standard
35+
/// collection is converted to [`Variant`] via [`ToVariant`], what actually happens is that:
36+
///
37+
/// - First, a new Godot collection of the corresponding "kind" is allocated.
38+
/// - Then, the Rust collection is iterated over, and each element is converted and inserted into
39+
/// the new collection, possibly triggering many more allocations in the process.
40+
///
41+
/// With properties, this whole process happens anew *with each access to the property*, which
42+
/// means that:
43+
///
44+
/// - Modifying such properties from the remote debugger, or calling methods on the property
45+
/// directly from GDScript (e.g. `thing.exported_vec.append("foo")`) do not produce the desired
46+
/// behavior by the user.
47+
/// - Seemingly innocuous expressions such as
48+
/// `thing.exported_vec[0] + thing.exported_vec[1] + thing.exported_vec[2]` can be much more
49+
/// expensive computationally than what the user would expect.
50+
///
51+
/// As such, we do not allow these types to be exported as properties directly as a precaution.
52+
/// If you wish to export collections to GDScript, consider the following options:
53+
///
54+
/// - Exporting a [`Variant`] collection such as [`VariantArray`] or [`Dictionary`] explicitly,
55+
/// embracing their respective semantics.
56+
/// - Exporting not a property, but methods that have to be explicitly called, to set clear
57+
/// expectations that the return value might be expensive to produce.
2658
pub trait Export: crate::core_types::ToVariant {
2759
/// A type-specific hint type that is valid for the type being exported.
2860
///
@@ -455,8 +487,6 @@ pub struct Property<T> {
455487
}
456488

457489
mod impl_export {
458-
use std::collections::{HashMap, HashSet};
459-
460490
use super::*;
461491

462492
/// Hint type indicating that there are no hints available for the time being.
@@ -615,41 +645,4 @@ mod impl_export {
615645
hint.unwrap_or_default().export_info()
616646
}
617647
}
618-
619-
impl<K, V> Export for HashMap<K, V>
620-
where
621-
K: std::hash::Hash + ToVariantEq + ToVariant,
622-
V: ToVariant,
623-
{
624-
type Hint = NoHint;
625-
626-
#[inline]
627-
fn export_info(_hint: Option<Self::Hint>) -> ExportInfo {
628-
ExportInfo::new(VariantType::Dictionary)
629-
}
630-
}
631-
632-
impl<T> Export for HashSet<T>
633-
where
634-
T: ToVariant,
635-
{
636-
type Hint = NoHint;
637-
638-
#[inline]
639-
fn export_info(_hint: Option<Self::Hint>) -> ExportInfo {
640-
ExportInfo::new(VariantType::VariantArray)
641-
}
642-
}
643-
644-
impl<T> Export for Vec<T>
645-
where
646-
T: ToVariant,
647-
{
648-
type Hint = NoHint;
649-
650-
#[inline]
651-
fn export_info(_hint: Option<Self::Hint>) -> ExportInfo {
652-
ExportInfo::new(VariantType::VariantArray)
653-
}
654-
}
655648
}

0 commit comments

Comments
 (0)