@@ -1605,17 +1605,37 @@ impl<T: FromVariant> FromVariant for Vec<T> {
1605
1605
}
1606
1606
}
1607
1607
1608
+ /// Converts the hash map to a `Dictionary`, wrapped in a `Variant`.
1609
+ ///
1610
+ /// Note that Rust's `HashMap` is non-deterministically ordered for security reasons, meaning that
1611
+ /// the order of the same elements will differ between two program invocations. To provide a
1612
+ /// deterministic output in Godot (e.g. UI elements for properties), the elements are sorted by key.
1608
1613
impl < K : ToVariant + Hash + ToVariantEq , V : ToVariant > ToVariant for HashMap < K , V > {
1609
1614
#[ inline]
1610
1615
fn to_variant ( & self ) -> Variant {
1616
+ // Note: dictionary currently provides neither a sort() function nor random access (or at least bidirectional)
1617
+ // iterators, making it difficult to sort in-place. Workaround: copy to vector
1618
+
1619
+ let mut intermediate: Vec < ( Variant , Variant ) > = self
1620
+ . iter ( )
1621
+ . map ( |( k, v) | ( k. to_variant ( ) , v. to_variant ( ) ) )
1622
+ . collect ( ) ;
1623
+
1624
+ intermediate. sort ( ) ;
1625
+
1611
1626
let dict = Dictionary :: new ( ) ;
1612
- for ( key, value) in self {
1627
+ for ( key, value) in intermediate . into_iter ( ) {
1613
1628
dict. insert ( key, value) ;
1614
1629
}
1630
+
1615
1631
dict. owned_to_variant ( )
1616
1632
}
1617
1633
}
1618
1634
1635
+ /// Expects a `Variant` populated with a `Dictionary` and tries to convert it into a `HashMap`.
1636
+ ///
1637
+ /// Since Rust's `HashMap` is unordered, there is no guarantee about the resulting element order.
1638
+ /// In fact it is possible that two program invocations cause a different output.
1619
1639
impl < K : FromVariant + Hash + Eq , V : FromVariant > FromVariant for HashMap < K , V > {
1620
1640
#[ inline]
1621
1641
fn from_variant ( variant : & Variant ) -> Result < Self , FromVariantError > {
@@ -1624,6 +1644,7 @@ impl<K: FromVariant + Hash + Eq, V: FromVariant> FromVariant for HashMap<K, V> {
1624
1644
. len ( )
1625
1645
. try_into ( )
1626
1646
. expect ( "Dictionary length should fit in usize" ) ;
1647
+
1627
1648
let mut hash_map = HashMap :: with_capacity ( len) ;
1628
1649
for ( key, value) in dictionary. iter ( ) {
1629
1650
hash_map. insert ( K :: from_variant ( & key) ?, V :: from_variant ( & value) ?) ;
@@ -1632,17 +1653,28 @@ impl<K: FromVariant + Hash + Eq, V: FromVariant> FromVariant for HashMap<K, V> {
1632
1653
}
1633
1654
}
1634
1655
1656
+ /// Converts the hash set to a `VariantArray`, wrapped in a `Variant`.
1657
+ ///
1658
+ /// Note that Rust's `HashSet` is non-deterministically ordered for security reasons, meaning that
1659
+ /// the order of the same elements will differ between two program invocations. To provide a
1660
+ /// deterministic output in Godot (e.g. UI elements for properties), the elements are sorted by key.
1635
1661
impl < T : ToVariant > ToVariant for HashSet < T > {
1636
1662
#[ inline]
1637
1663
fn to_variant ( & self ) -> Variant {
1638
1664
let array = VariantArray :: new ( ) ;
1639
1665
for value in self {
1640
1666
array. push ( value. to_variant ( ) ) ;
1641
1667
}
1668
+
1669
+ array. sort ( ) ; // deterministic order in Godot
1642
1670
array. owned_to_variant ( )
1643
1671
}
1644
1672
}
1645
1673
1674
+ /// Expects a `Variant` populated with a `VariantArray` and tries to convert it into a `HashSet`.
1675
+ ///
1676
+ /// Since Rust's `HashSet` is unordered, there is no guarantee about the resulting element order.
1677
+ /// In fact it is possible that two program invocations cause a different output.
1646
1678
impl < T : FromVariant + Eq + Hash > FromVariant for HashSet < T > {
1647
1679
#[ inline]
1648
1680
fn from_variant ( variant : & Variant ) -> Result < Self , FromVariantError > {
@@ -1651,6 +1683,7 @@ impl<T: FromVariant + Eq + Hash> FromVariant for HashSet<T> {
1651
1683
. len ( )
1652
1684
. try_into ( )
1653
1685
. expect ( "VariantArray length should fit in usize" ) ;
1686
+
1654
1687
let mut set = HashSet :: with_capacity ( len) ;
1655
1688
for idx in 0 ..len as i32 {
1656
1689
let item =
0 commit comments