@@ -18,6 +18,14 @@ use crate::builtin::{GString, NodePath};
18
18
///
19
19
/// StringNames are immutable strings designed for representing unique names. StringName ensures that only
20
20
/// one instance of a given name exists.
21
+ ///
22
+ /// # Ordering
23
+ ///
24
+ /// In Godot, `StringName`s are **not** ordered lexicographically, and the ordering relation is **not** stable across multiple runs of your
25
+ /// application. Therefore, this type does not implement `PartialOrd` and `Ord`, as it would be very easy to introduce bugs by accidentally
26
+ /// relying on lexicographical ordering.
27
+ ///
28
+ /// Instead, we provide [`transient_ord()`][Self::transient_ord] for ordering relations.
21
29
#[ repr( C ) ]
22
30
pub struct StringName {
23
31
opaque : sys:: types:: OpaqueStringName ,
@@ -90,6 +98,19 @@ impl StringName {
90
98
. expect ( "Godot hashes are uint32_t" )
91
99
}
92
100
101
+ /// O(1), non-lexicographic, non-stable ordering relation.
102
+ ///
103
+ /// The result of the comparison is **not** lexicographic and **not** stable across multiple runs of your application.
104
+ ///
105
+ /// However, it is very fast. It doesn't depend on the length of the strings, but on the memory location of string names.
106
+ /// This can still be useful if you need to establish an ordering relation, but are not interested in the actual order of the strings
107
+ /// (example: binary search).
108
+ ///
109
+ /// For lexicographical ordering, convert to `GString` (significantly slower).
110
+ pub fn transient_ord ( & self ) -> TransientStringNameOrd < ' _ > {
111
+ TransientStringNameOrd ( self )
112
+ }
113
+
93
114
ffi_methods ! {
94
115
type sys:: GDExtensionStringNamePtr = * mut Opaque ;
95
116
@@ -152,8 +173,8 @@ impl_builtin_traits! {
152
173
Clone => string_name_construct_copy;
153
174
Drop => string_name_destroy;
154
175
Eq => string_name_operator_equal;
155
- // currently broken: https://github.com/godotengine/godot/issues/76218
156
- // Ord => string_name_operator_less;
176
+ // Do not provide PartialOrd or Ord. Even though Godot provides a `operator <`, it is non-lexicographic and non-deterministic
177
+ // (based on pointers). See transient_ord() method.
157
178
Hash ;
158
179
}
159
180
}
@@ -248,6 +269,61 @@ impl From<NodePath> for StringName {
248
269
}
249
270
}
250
271
272
+ // ----------------------------------------------------------------------------------------------------------------------------------------------
273
+ // Ordering
274
+
275
+ /// Type that implements `Ord` for `StringNames`.
276
+ ///
277
+ /// See [`StringName::transient_ord()`].
278
+ pub struct TransientStringNameOrd < ' a > ( & ' a StringName ) ;
279
+
280
+ impl < ' a > PartialEq for TransientStringNameOrd < ' a > {
281
+ fn eq ( & self , other : & Self ) -> bool {
282
+ self . 0 == other. 0
283
+ }
284
+ }
285
+
286
+ impl < ' a > Eq for TransientStringNameOrd < ' a > { }
287
+
288
+ impl < ' a > PartialOrd for TransientStringNameOrd < ' a > {
289
+ fn partial_cmp ( & self , other : & Self ) -> Option < std:: cmp:: Ordering > {
290
+ Some ( self . cmp ( other) )
291
+ }
292
+ }
293
+
294
+ // implement Ord like above
295
+ impl < ' a > Ord for TransientStringNameOrd < ' a > {
296
+ fn cmp ( & self , other : & Self ) -> std:: cmp:: Ordering {
297
+ // SAFETY: builtin operator provided by Godot.
298
+ let op_less = |lhs, rhs| unsafe {
299
+ let mut result = false ;
300
+ sys:: builtin_call! {
301
+ string_name_operator_less( lhs, rhs, result. sys_mut( ) )
302
+ }
303
+ result
304
+ } ;
305
+
306
+ let self_ptr = self . 0 . sys ( ) ;
307
+ let other_ptr = other. 0 . sys ( ) ;
308
+
309
+ if op_less ( self_ptr, other_ptr) {
310
+ std:: cmp:: Ordering :: Less
311
+ } else if op_less ( other_ptr, self_ptr) {
312
+ std:: cmp:: Ordering :: Greater
313
+ } else if self . eq ( other) {
314
+ std:: cmp:: Ordering :: Equal
315
+ } else {
316
+ panic ! (
317
+ "Godot provides inconsistent StringName ordering for \" {}\" and \" {}\" " ,
318
+ self . 0 , other. 0
319
+ ) ;
320
+ }
321
+ }
322
+ }
323
+
324
+ // ----------------------------------------------------------------------------------------------------------------------------------------------
325
+ // serde support
326
+
251
327
#[ cfg( feature = "serde" ) ]
252
328
mod serialize {
253
329
use super :: * ;
0 commit comments