5
5
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
6
*/
7
7
8
+ use std:: cell:: Cell ;
8
9
use std:: marker:: PhantomData ;
9
10
use std:: { cmp, fmt} ;
10
11
@@ -15,7 +16,7 @@ use crate::builtin::*;
15
16
use crate :: meta;
16
17
use crate :: meta:: error:: { ConvertError , FromGodotError , FromVariantError } ;
17
18
use crate :: meta:: {
18
- element_godot_type_name, element_variant_type, ArrayElement , ArrayTypeInfo , AsArg , ClassName ,
19
+ element_godot_type_name, element_variant_type, ArrayElement , AsArg , ClassName , ElementType ,
19
20
ExtVariantType , FromGodot , GodotConvert , GodotFfiVariant , GodotType , PropertyHintInfo , RefArg ,
20
21
ToGodot ,
21
22
} ;
@@ -141,6 +142,12 @@ pub struct Array<T: ArrayElement> {
141
142
// Safety Invariant: The type of all values in `opaque` matches the type `T`.
142
143
opaque : sys:: types:: OpaqueArray ,
143
144
_phantom : PhantomData < T > ,
145
+
146
+ /// Lazily computed and cached element type information.
147
+ ///
148
+ /// `ElementType::Untyped` means either "not yet queried" or "queried but array was untyped". Since GDScript can call
149
+ /// `set_type()` at any time, we must re-query FFI whenever cached value is `Untyped`.
150
+ cached_element_type : Cell < ElementType > ,
144
151
}
145
152
146
153
/// Guard that can only call immutable methods on the array.
@@ -183,6 +190,7 @@ impl<T: ArrayElement> Array<T> {
183
190
Self {
184
191
opaque,
185
192
_phantom : PhantomData ,
193
+ cached_element_type : Cell :: new ( ElementType :: Untyped ) ,
186
194
}
187
195
}
188
196
@@ -502,7 +510,8 @@ impl<T: ArrayElement> Array<T> {
502
510
let duplicate: VariantArray = unsafe { self . as_inner ( ) . duplicate ( false ) } ;
503
511
504
512
// SAFETY: duplicate() returns a typed array with the same type as Self, and all values are taken from `self` so have the right type.
505
- unsafe { duplicate. assume_type ( ) }
513
+ let result = unsafe { duplicate. assume_type ( ) } ;
514
+ result. with_cache ( self )
506
515
}
507
516
508
517
/// Returns a deep copy of the array. All nested arrays and dictionaries are duplicated and
@@ -516,7 +525,8 @@ impl<T: ArrayElement> Array<T> {
516
525
let duplicate: VariantArray = unsafe { self . as_inner ( ) . duplicate ( true ) } ;
517
526
518
527
// SAFETY: duplicate() returns a typed array with the same type as Self, and all values are taken from `self` so have the right type.
519
- unsafe { duplicate. assume_type ( ) }
528
+ let result = unsafe { duplicate. assume_type ( ) } ;
529
+ result. with_cache ( self )
520
530
}
521
531
522
532
/// Returns a sub-range `begin..end`, as a new array.
@@ -571,8 +581,9 @@ impl<T: ArrayElement> Array<T> {
571
581
. slice ( to_i64 ( begin) , to_i64 ( end) , step. try_into ( ) . unwrap ( ) , deep)
572
582
} ;
573
583
574
- // SAFETY: slice() returns a typed array with the same type as Self
575
- unsafe { subarray. assume_type ( ) }
584
+ // SAFETY: slice() returns a typed array with the same type as Self.
585
+ let result = unsafe { subarray. assume_type ( ) } ;
586
+ result. with_cache ( self )
576
587
}
577
588
578
589
/// Returns an iterator over the elements of the `Array`. Note that this takes the array
@@ -956,21 +967,22 @@ impl<T: ArrayElement> Array<T> {
956
967
std:: mem:: transmute :: < & Array < T > , & Array < U > > ( self )
957
968
}
958
969
970
+ /// Validates that all elements in this array can be converted to integers of type `T`.
959
971
#[ cfg( debug_assertions) ]
960
- pub ( crate ) fn debug_validate_elements ( & self ) -> Result < ( ) , ConvertError > {
972
+ pub ( crate ) fn debug_validate_int_elements ( & self ) -> Result < ( ) , ConvertError > {
961
973
// SAFETY: every element is internally represented as Variant.
962
974
let canonical_array = unsafe { self . assume_type_ref :: < Variant > ( ) } ;
963
975
964
976
// If any element is not convertible, this will return an error.
965
977
for elem in canonical_array. iter_shared ( ) {
966
978
elem. try_to :: < T > ( ) . map_err ( |_err| {
967
979
FromGodotError :: BadArrayTypeInt {
968
- expected : self . type_info ( ) ,
980
+ expected_int_type : std :: any :: type_name :: < T > ( ) ,
969
981
value : elem
970
982
. try_to :: < i64 > ( )
971
983
. expect ( "origin must be i64 compatible; this is a bug" ) ,
972
984
}
973
- . into_error ( self . clone ( ) )
985
+ . into_error ( self . clone ( ) ) // Context info about array, not element.
974
986
} ) ?;
975
987
}
976
988
@@ -979,42 +991,51 @@ impl<T: ArrayElement> Array<T> {
979
991
980
992
// No-op in Release. Avoids O(n) conversion checks, but still panics on access.
981
993
#[ cfg( not( debug_assertions) ) ]
982
- pub ( crate ) fn debug_validate_elements ( & self ) -> Result < ( ) , ConvertError > {
994
+ pub ( crate ) fn debug_validate_int_elements ( & self ) -> Result < ( ) , ConvertError > {
983
995
Ok ( ( ) )
984
996
}
985
997
986
- /// Returns the runtime type info of this array.
987
- fn type_info ( & self ) -> ArrayTypeInfo {
988
- let variant_type = VariantType :: from_sys (
989
- self . as_inner ( ) . get_typed_builtin ( ) as sys:: GDExtensionVariantType
990
- ) ;
991
-
992
- let class_name = if variant_type == VariantType :: OBJECT {
993
- Some ( self . as_inner ( ) . get_typed_class_name ( ) )
994
- } else {
995
- None
996
- } ;
997
-
998
- ArrayTypeInfo {
999
- variant_type,
1000
- class_name,
1001
- }
998
+ /// Returns the runtime element type information for this array.
999
+ ///
1000
+ /// The result is cached when the array is typed. If the array is untyped, this method
1001
+ /// will always re-query Godot's FFI since GDScript may call `set_type()` at any time.
1002
+ /// Repeated calls on typed arrays will not result in multiple Godot FFI roundtrips.
1003
+ pub fn element_type ( & self ) -> ElementType {
1004
+ ElementType :: get_or_compute_cached (
1005
+ & self . cached_element_type ,
1006
+ || self . as_inner ( ) . get_typed_builtin ( ) ,
1007
+ || self . as_inner ( ) . get_typed_class_name ( ) ,
1008
+ || self . as_inner ( ) . get_typed_script ( ) ,
1009
+ )
1002
1010
}
1003
1011
1004
1012
/// Checks that the inner array has the correct type set on it for storing elements of type `T`.
1005
1013
fn with_checked_type ( self ) -> Result < Self , ConvertError > {
1006
- let self_ty = self . type_info ( ) ;
1007
- let target_ty = ArrayTypeInfo :: of :: < T > ( ) ;
1014
+ let self_ty = self . element_type ( ) ;
1015
+ let target_ty = ElementType :: of :: < T > ( ) ;
1008
1016
1017
+ // Exact match: check successful.
1009
1018
if self_ty == target_ty {
1010
- Ok ( self )
1011
- } else {
1012
- Err ( FromGodotError :: BadArrayType {
1013
- expected : target_ty,
1014
- actual : self_ty,
1019
+ return Ok ( self ) ;
1020
+ }
1021
+
1022
+ // Check if script class (runtime) matches its native base class (compile-time).
1023
+ // This allows an Array[Enemy] from GDScript to be used as Array<Gd<RefCounted>> in Rust.
1024
+ if let ( ElementType :: ScriptClass ( _) , ElementType :: Class ( expected_class) ) =
1025
+ ( & self_ty, & target_ty)
1026
+ {
1027
+ if let Some ( actual_base_class) = self_ty. class_name ( ) {
1028
+ if actual_base_class == * expected_class {
1029
+ return Ok ( self ) ;
1030
+ }
1015
1031
}
1016
- . into_error ( self ) )
1017
1032
}
1033
+
1034
+ Err ( FromGodotError :: BadArrayType {
1035
+ expected : target_ty,
1036
+ actual : self_ty,
1037
+ }
1038
+ . into_error ( self ) )
1018
1039
}
1019
1040
1020
1041
/// Sets the type of the inner array.
@@ -1024,28 +1045,29 @@ impl<T: ArrayElement> Array<T> {
1024
1045
/// Must only be called once, directly after creation.
1025
1046
unsafe fn init_inner_type ( & mut self ) {
1026
1047
debug_assert ! ( self . is_empty( ) ) ;
1027
- debug_assert ! ( !self . type_info ( ) . is_typed( ) ) ;
1048
+ debug_assert ! ( !self . element_type ( ) . is_typed( ) ) ;
1028
1049
1029
- let type_info = ArrayTypeInfo :: of :: < T > ( ) ;
1030
- if type_info . is_typed ( ) {
1050
+ let elem_ty = ElementType :: of :: < T > ( ) ;
1051
+ if elem_ty . is_typed ( ) {
1031
1052
let script = Variant :: nil ( ) ;
1032
1053
1033
1054
// A bit contrived because empty StringName is lazy-initialized but must also remain valid.
1034
1055
#[ allow( unused_assignments) ]
1035
1056
let mut empty_string_name = None ;
1036
- let class_name = if let Some ( class_name) = & type_info . class_name {
1057
+ let class_name = if let Some ( class_name) = elem_ty . class_name ( ) {
1037
1058
class_name. string_sys ( )
1038
1059
} else {
1039
1060
empty_string_name = Some ( StringName :: default ( ) ) ;
1040
1061
// as_ref() crucial here -- otherwise the StringName is dropped.
1041
1062
empty_string_name. as_ref ( ) . unwrap ( ) . string_sys ( )
1042
1063
} ;
1043
1064
1044
- // SAFETY: The array is a newly created empty untyped array.
1065
+ // SAFETY: Valid pointers are passed in.
1066
+ // Relevant for correctness, not safety: the array is a newly created, empty, untyped array.
1045
1067
unsafe {
1046
1068
interface_fn ! ( array_set_typed) (
1047
1069
self . sys_mut ( ) ,
1048
- type_info . variant_type . sys ( ) ,
1070
+ elem_ty . variant_type ( ) . sys ( ) ,
1049
1071
class_name, // must be empty if variant_type != OBJECT.
1050
1072
script. var_sys ( ) ,
1051
1073
) ;
@@ -1059,11 +1081,12 @@ impl<T: ArrayElement> Array<T> {
1059
1081
/// Should be used only in scenarios where the caller can guarantee that the resulting array will have the correct type,
1060
1082
/// or when an incorrect Rust type is acceptable (passing raw arrays to Godot FFI).
1061
1083
unsafe fn clone_unchecked ( & self ) -> Self {
1062
- Self :: new_with_uninit ( |self_ptr| {
1084
+ let result = Self :: new_with_uninit ( |self_ptr| {
1063
1085
let ctor = sys:: builtin_fn!( array_construct_copy) ;
1064
1086
let args = [ self . sys ( ) ] ;
1065
1087
ctor ( self_ptr, args. as_ptr ( ) ) ;
1066
- } )
1088
+ } ) ;
1089
+ result. with_cache ( self )
1067
1090
}
1068
1091
1069
1092
/// Whether this array is untyped and holds `Variant` elements (compile-time check).
@@ -1073,6 +1096,15 @@ impl<T: ArrayElement> Array<T> {
1073
1096
fn has_variant_t ( ) -> bool {
1074
1097
element_variant_type :: < T > ( ) == VariantType :: NIL
1075
1098
}
1099
+
1100
+ /// Execute a function that creates a new Array, transferring cached element type if available.
1101
+ ///
1102
+ /// This is a convenience helper for methods that create new Array instances and want to preserve
1103
+ /// cached type information to avoid redundant FFI calls.
1104
+ fn with_cache ( self , source : & Self ) -> Self {
1105
+ ElementType :: transfer_cache ( & source. cached_element_type , & self . cached_element_type ) ;
1106
+ self
1107
+ }
1076
1108
}
1077
1109
1078
1110
#[ test]
0 commit comments