5
5
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
6
*/
7
7
8
+ use std:: cell:: OnceCell ;
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,9 @@ 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
+ cached_element_type : OnceCell < ElementType > ,
144
148
}
145
149
146
150
/// Guard that can only call immutable methods on the array.
@@ -183,6 +187,7 @@ impl<T: ArrayElement> Array<T> {
183
187
Self {
184
188
opaque,
185
189
_phantom : PhantomData ,
190
+ cached_element_type : OnceCell :: new ( ) ,
186
191
}
187
192
}
188
193
@@ -502,7 +507,8 @@ impl<T: ArrayElement> Array<T> {
502
507
let duplicate: VariantArray = unsafe { self . as_inner ( ) . duplicate ( false ) } ;
503
508
504
509
// 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 ( ) }
510
+ let result = unsafe { duplicate. assume_type ( ) } ;
511
+ result. with_cache ( self )
506
512
}
507
513
508
514
/// Returns a deep copy of the array. All nested arrays and dictionaries are duplicated and
@@ -516,7 +522,8 @@ impl<T: ArrayElement> Array<T> {
516
522
let duplicate: VariantArray = unsafe { self . as_inner ( ) . duplicate ( true ) } ;
517
523
518
524
// 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 ( ) }
525
+ let result = unsafe { duplicate. assume_type ( ) } ;
526
+ result. with_cache ( self )
520
527
}
521
528
522
529
/// Returns a sub-range `begin..end`, as a new array.
@@ -571,8 +578,9 @@ impl<T: ArrayElement> Array<T> {
571
578
. slice ( to_i64 ( begin) , to_i64 ( end) , step. try_into ( ) . unwrap ( ) , deep)
572
579
} ;
573
580
574
- // SAFETY: slice() returns a typed array with the same type as Self
575
- unsafe { subarray. assume_type ( ) }
581
+ // SAFETY: slice() returns a typed array with the same type as Self.
582
+ let result = unsafe { subarray. assume_type ( ) } ;
583
+ result. with_cache ( self )
576
584
}
577
585
578
586
/// Returns an iterator over the elements of the `Array`. Note that this takes the array
@@ -956,21 +964,22 @@ impl<T: ArrayElement> Array<T> {
956
964
std:: mem:: transmute :: < & Array < T > , & Array < U > > ( self )
957
965
}
958
966
967
+ /// Validates that all elements in this array can be converted to integers of type `T`.
959
968
#[ cfg( debug_assertions) ]
960
- pub ( crate ) fn debug_validate_elements ( & self ) -> Result < ( ) , ConvertError > {
969
+ pub ( crate ) fn debug_validate_int_elements ( & self ) -> Result < ( ) , ConvertError > {
961
970
// SAFETY: every element is internally represented as Variant.
962
971
let canonical_array = unsafe { self . assume_type_ref :: < Variant > ( ) } ;
963
972
964
973
// If any element is not convertible, this will return an error.
965
974
for elem in canonical_array. iter_shared ( ) {
966
975
elem. try_to :: < T > ( ) . map_err ( |_err| {
967
976
FromGodotError :: BadArrayTypeInt {
968
- expected : self . type_info ( ) ,
977
+ expected_int_type : std :: any :: type_name :: < T > ( ) ,
969
978
value : elem
970
979
. try_to :: < i64 > ( )
971
980
. expect ( "origin must be i64 compatible; this is a bug" ) ,
972
981
}
973
- . into_error ( self . clone ( ) )
982
+ . into_error ( self . clone ( ) ) // Context info about array, not element.
974
983
} ) ?;
975
984
}
976
985
@@ -979,73 +988,91 @@ impl<T: ArrayElement> Array<T> {
979
988
980
989
// No-op in Release. Avoids O(n) conversion checks, but still panics on access.
981
990
#[ cfg( not( debug_assertions) ) ]
982
- pub ( crate ) fn debug_validate_elements ( & self ) -> Result < ( ) , ConvertError > {
991
+ pub ( crate ) fn debug_validate_int_elements ( & self ) -> Result < ( ) , ConvertError > {
983
992
Ok ( ( ) )
984
993
}
985
994
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
- }
995
+ /// Returns the runtime element type information for this array.
996
+ ///
997
+ /// The result is generally cached, so feel free to call this method repeatedly.
998
+ ///
999
+ /// # Panics (Debug)
1000
+ /// In the astronomically rare case where another extension in Godot modifies an array's type (which godot-rust already cached as `Untyped`)
1001
+ /// via C function `array_set_typed`, thus leading to incorrect cache values. Such bad practice of not typing arrays immediately on
1002
+ /// construction is not supported, and will not be checked in Release mode.
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.
1021
1042
///
1022
1043
/// # Safety
1023
- ///
1024
1044
/// Must only be called once, directly after creation.
1025
1045
unsafe fn init_inner_type ( & mut self ) {
1026
1046
debug_assert ! ( self . is_empty( ) ) ;
1027
- debug_assert ! ( !self . type_info( ) . is_typed( ) ) ;
1047
+ debug_assert ! (
1048
+ self . cached_element_type. get( ) . is_none( ) ,
1049
+ "init_inner_type() called twice"
1050
+ ) ;
1051
+
1052
+ // Immediately set cache to static type.
1053
+ let elem_ty = ElementType :: of :: < T > ( ) ;
1054
+ let _ = self . cached_element_type . set ( elem_ty) ;
1028
1055
1029
- let type_info = ArrayTypeInfo :: of :: < T > ( ) ;
1030
- if type_info. is_typed ( ) {
1056
+ if elem_ty. is_typed ( ) {
1031
1057
let script = Variant :: nil ( ) ;
1032
1058
1033
1059
// A bit contrived because empty StringName is lazy-initialized but must also remain valid.
1034
1060
#[ allow( unused_assignments) ]
1035
1061
let mut empty_string_name = None ;
1036
- let class_name = if let Some ( class_name) = & type_info . class_name {
1062
+ let class_name = if let Some ( class_name) = elem_ty . class_name ( ) {
1037
1063
class_name. string_sys ( )
1038
1064
} else {
1039
1065
empty_string_name = Some ( StringName :: default ( ) ) ;
1040
1066
// as_ref() crucial here -- otherwise the StringName is dropped.
1041
1067
empty_string_name. as_ref ( ) . unwrap ( ) . string_sys ( )
1042
1068
} ;
1043
1069
1044
- // SAFETY: The array is a newly created empty untyped array.
1070
+ // SAFETY: Valid pointers are passed in.
1071
+ // Relevant for correctness, not safety: the array is a newly created, empty, untyped array.
1045
1072
unsafe {
1046
1073
interface_fn ! ( array_set_typed) (
1047
1074
self . sys_mut ( ) ,
1048
- type_info . variant_type . sys ( ) ,
1075
+ elem_ty . variant_type ( ) . sys ( ) ,
1049
1076
class_name, // must be empty if variant_type != OBJECT.
1050
1077
script. var_sys ( ) ,
1051
1078
) ;
@@ -1059,11 +1086,12 @@ impl<T: ArrayElement> Array<T> {
1059
1086
/// Should be used only in scenarios where the caller can guarantee that the resulting array will have the correct type,
1060
1087
/// or when an incorrect Rust type is acceptable (passing raw arrays to Godot FFI).
1061
1088
unsafe fn clone_unchecked ( & self ) -> Self {
1062
- Self :: new_with_uninit ( |self_ptr| {
1089
+ let result = Self :: new_with_uninit ( |self_ptr| {
1063
1090
let ctor = sys:: builtin_fn!( array_construct_copy) ;
1064
1091
let args = [ self . sys ( ) ] ;
1065
1092
ctor ( self_ptr, args. as_ptr ( ) ) ;
1066
- } )
1093
+ } ) ;
1094
+ result. with_cache ( self )
1067
1095
}
1068
1096
1069
1097
/// Whether this array is untyped and holds `Variant` elements (compile-time check).
@@ -1073,6 +1101,15 @@ impl<T: ArrayElement> Array<T> {
1073
1101
fn has_variant_t ( ) -> bool {
1074
1102
element_variant_type :: < T > ( ) == VariantType :: NIL
1075
1103
}
1104
+
1105
+ /// Execute a function that creates a new Array, transferring cached element type if available.
1106
+ ///
1107
+ /// This is a convenience helper for methods that create new Array instances and want to preserve
1108
+ /// cached type information to avoid redundant FFI calls.
1109
+ fn with_cache ( self , source : & Self ) -> Self {
1110
+ ElementType :: transfer_cache ( & source. cached_element_type , & self . cached_element_type ) ;
1111
+ self
1112
+ }
1076
1113
}
1077
1114
1078
1115
#[ test]
0 commit comments