@@ -81,33 +81,26 @@ impl ElementType {
81
81
}
82
82
}
83
83
84
- /// Transfer cached element type from source to destination, preserving more specific type info.
84
+ /// Transfer cached element type from source to destination, preserving type info.
85
85
///
86
- /// This is a helper for cloning operations like duplicate(), slice(), etc. where we want to
87
- /// preserve cached type information to avoid redundant FFI calls. Only transfers if the source
88
- /// has more specific information than the destination (typed vs untyped).
86
+ /// Used by clone-like operations like `duplicate()`, `slice()`, etc. where we want to preserve cached type information to avoid
87
+ /// redundant FFI calls. Only transfers if the source has computed info and destination doesn't.
89
88
pub ( crate ) fn transfer_cache (
90
- source_cache : & std:: cell:: Cell < ElementType > ,
91
- dest_cache : & std:: cell:: Cell < ElementType > ,
89
+ source_cache : & std:: cell:: OnceCell < ElementType > ,
90
+ dest_cache : & std:: cell:: OnceCell < ElementType > ,
92
91
) {
93
- let source_value = source_cache. get ( ) ;
94
- let dest_value = dest_cache. get ( ) ;
95
-
96
- // Only transfer if source has more specific info (typed) than destination (untyped)
97
- match ( source_value, dest_value) {
98
- // Source is typed, destination is untyped - transfer the typed info
99
- ( source, ElementType :: Untyped ) if !matches ! ( source, ElementType :: Untyped ) => {
100
- dest_cache. set ( source) ;
101
- }
102
- // All other cases: don't transfer (dest already has same or better info)
103
- _ => { }
92
+ if let Some ( source_value) = source_cache. get ( ) {
93
+ // Ignore result. If dest is already set, that's fine.
94
+ let _ = dest_cache. set ( * source_value) ;
104
95
}
105
96
}
106
97
107
98
/// Get element type from cache or compute it via FFI calls.
108
99
///
109
- /// Common caching and computation pattern for element type computation. Checks cache first,
110
- /// returns cached value if typed, otherwise computes via FFI and caches the result.
100
+ /// Returns cached value if available, otherwise computes via FFI and caches the result.
101
+ ///
102
+ /// In Debug mode, validates cached `Untyped` values to ensure another extension hasn't made an array/dictionary typed via C functions
103
+ /// `set_array_type`/`set_dictionary_type` after caching.
111
104
///
112
105
/// Takes closures for the three FFI operations needed to determine element type:
113
106
/// - `get_builtin_type`: Get the variant type (sys variant type as i64)
@@ -116,42 +109,50 @@ impl ElementType {
116
109
///
117
110
/// Returns the computed `ElementType` and updates the cache.
118
111
pub ( crate ) fn get_or_compute_cached (
119
- cache : & std:: cell:: Cell < ElementType > ,
112
+ cache : & std:: cell:: OnceCell < ElementType > ,
120
113
get_builtin_type : impl Fn ( ) -> i64 ,
121
114
get_class_name : impl Fn ( ) -> crate :: builtin:: StringName ,
122
115
get_script_variant : impl Fn ( ) -> crate :: builtin:: Variant ,
123
116
) -> ElementType {
124
- let cached = cache. get ( ) ;
125
-
126
- // Already cached and typed: won't change anymore.
127
- if !matches ! ( cached, ElementType :: Untyped ) {
128
- return cached;
129
- }
130
-
131
- // Untyped or not queried yet - compute via FFI.
132
- let sys_variant_type = get_builtin_type ( ) ;
133
- let variant_type =
134
- VariantType :: from_sys ( sys_variant_type as crate :: sys:: GDExtensionVariantType ) ;
135
-
136
- let element_type = if variant_type == VariantType :: NIL {
137
- ElementType :: Untyped
138
- } else if variant_type == VariantType :: OBJECT {
139
- let class_name_stringname = get_class_name ( ) ;
140
- let class_name = ClassName :: new_dynamic ( class_name_stringname. to_string ( ) ) ;
141
-
142
- // If there's a script associated, the class is interpreted as the native base class of the script.
143
- let script_variant = get_script_variant ( ) ;
144
- if let Some ( script) = Self :: script_from_variant ( & script_variant) {
145
- ElementType :: ScriptClass ( ElementScript :: new ( script) )
117
+ let cached = * cache. get_or_init ( || {
118
+ let sys_variant_type = get_builtin_type ( ) ;
119
+ let variant_type =
120
+ VariantType :: from_sys ( sys_variant_type as crate :: sys:: GDExtensionVariantType ) ;
121
+
122
+ if variant_type == VariantType :: NIL {
123
+ ElementType :: Untyped
124
+ } else if variant_type == VariantType :: OBJECT {
125
+ let class_name_stringname = get_class_name ( ) ;
126
+ let class_name = ClassName :: new_dynamic ( class_name_stringname. to_string ( ) ) ;
127
+
128
+ // If there's a script associated, the class is interpreted as the native base class of the script.
129
+ let script_variant = get_script_variant ( ) ;
130
+ if let Some ( script) = Self :: script_from_variant ( & script_variant) {
131
+ ElementType :: ScriptClass ( ElementScript :: new ( script) )
132
+ } else {
133
+ ElementType :: Class ( class_name)
134
+ }
146
135
} else {
147
- ElementType :: Class ( class_name )
136
+ ElementType :: Builtin ( variant_type )
148
137
}
149
- } else {
150
- ElementType :: Builtin ( variant_type)
151
- } ;
138
+ } ) ;
139
+
140
+ // Debug validation for cached Untyped values.
141
+ #[ cfg( debug_assertions) ]
142
+ if matches ! ( cached, ElementType :: Untyped ) {
143
+ let sys_variant_type = get_builtin_type ( ) ;
144
+ let variant_type =
145
+ VariantType :: from_sys ( sys_variant_type as crate :: sys:: GDExtensionVariantType ) ;
146
+
147
+ assert_eq ! (
148
+ variant_type,
149
+ VariantType :: NIL ,
150
+ "Array/Dictionary element type validation failed: cached as Untyped but FFI reports {variant_type:?}. \
151
+ This indicates that another extension modified the type after godot-rust cached it.",
152
+ ) ;
153
+ }
152
154
153
- cache. set ( element_type) ;
154
- element_type
155
+ cached
155
156
}
156
157
157
158
/// Convert a script variant to a `Gd<Script>`, or `None` if nil.
0 commit comments