@@ -668,26 +668,40 @@ impl Table {
668
668
guard : self . 0 . lua . lock ( ) ,
669
669
table : self ,
670
670
index : 1 ,
671
+ len : None ,
671
672
_phantom : PhantomData ,
672
673
}
673
674
}
674
675
675
676
/// Iterates over the sequence part of the table, invoking the given closure on each value.
677
+ ///
678
+ /// This methods is similar to [`Table::sequence_values`], but optimized for performance.
676
679
#[ doc( hidden) ]
677
- pub fn for_each_value < V > ( & self , mut f : impl FnMut ( V ) -> Result < ( ) > ) -> Result < ( ) >
678
- where
679
- V : FromLua ,
680
- {
680
+ pub fn for_each_value < V : FromLua > ( & self , f : impl FnMut ( V ) -> Result < ( ) > ) -> Result < ( ) > {
681
+ self . for_each_value_by_len ( None , f)
682
+ }
683
+
684
+ fn for_each_value_by_len < V : FromLua > (
685
+ & self ,
686
+ len : impl Into < Option < usize > > ,
687
+ mut f : impl FnMut ( V ) -> Result < ( ) > ,
688
+ ) -> Result < ( ) > {
689
+ let len = len. into ( ) ;
681
690
let lua = self . 0 . lua . lock ( ) ;
682
691
let state = lua. state ( ) ;
683
692
unsafe {
684
693
let _sg = StackGuard :: new ( state) ;
685
694
check_stack ( state, 4 ) ?;
686
695
687
696
lua. push_ref ( & self . 0 ) ;
688
- let len = ffi:: lua_rawlen ( state, -1 ) ;
689
- for i in 1 ..=len {
690
- ffi:: lua_rawgeti ( state, -1 , i as _ ) ;
697
+ for i in 1 .. {
698
+ if len. map ( |len| i > len) . unwrap_or ( false ) {
699
+ break ;
700
+ }
701
+ let t = ffi:: lua_rawgeti ( state, -1 , i as _ ) ;
702
+ if len. is_none ( ) && t == ffi:: LUA_TNIL {
703
+ break ;
704
+ }
691
705
f ( V :: from_stack ( -1 , & lua) ?) ?;
692
706
ffi:: lua_pop ( state, 1 ) ;
693
707
}
@@ -720,8 +734,9 @@ impl Table {
720
734
Ok ( ( ) )
721
735
}
722
736
737
+ /// Checks if the table has the array metatable attached.
723
738
#[ cfg( feature = "serde" ) ]
724
- pub ( crate ) fn is_array ( & self ) -> bool {
739
+ fn has_array_metatable ( & self ) -> bool {
725
740
let lua = self . 0 . lua . lock ( ) ;
726
741
let state = lua. state ( ) ;
727
742
unsafe {
@@ -737,6 +752,70 @@ impl Table {
737
752
}
738
753
}
739
754
755
+ /// If the table is an array, returns the number of non-nil elements and max index.
756
+ ///
757
+ /// Returns `None` if the table is not an array.
758
+ ///
759
+ /// This operation has O(n) complexity.
760
+ #[ cfg( feature = "serde" ) ]
761
+ fn find_array_len ( & self ) -> Option < ( usize , usize ) > {
762
+ let lua = self . 0 . lua . lock ( ) ;
763
+ let ref_thread = lua. ref_thread ( ) ;
764
+ unsafe {
765
+ let _sg = StackGuard :: new ( ref_thread) ;
766
+
767
+ let ( mut count, mut max_index) = ( 0 , 0 ) ;
768
+ ffi:: lua_pushnil ( ref_thread) ;
769
+ while ffi:: lua_next ( ref_thread, self . 0 . index ) != 0 {
770
+ if ffi:: lua_type ( ref_thread, -2 ) != ffi:: LUA_TNUMBER {
771
+ return None ;
772
+ }
773
+
774
+ let k = ffi:: lua_tonumber ( ref_thread, -2 ) ;
775
+ if k. trunc ( ) != k || k < 1.0 {
776
+ return None ;
777
+ }
778
+ max_index = std:: cmp:: max ( max_index, k as usize ) ;
779
+ count += 1 ;
780
+ ffi:: lua_pop ( ref_thread, 1 ) ;
781
+ }
782
+ Some ( ( count, max_index) )
783
+ }
784
+ }
785
+
786
+ /// Determines if the table should be encoded as an array or a map.
787
+ ///
788
+ /// The algorithm is the following:
789
+ /// 1. If `detect_mixed_tables` is enabled, iterate over all keys in the table checking is they
790
+ /// all are positive integers. If non-array key is found, return `None` (encode as map).
791
+ /// Otherwise check the sparsity of the array. Too sparse arrays are encoded as maps.
792
+ ///
793
+ /// 2. If `detect_mixed_tables` is disabled, check if the table has a positive length or has the
794
+ /// array metatable. If so, encode as array. If the table is empty and
795
+ /// `encode_empty_tables_as_array` is enabled, encode as array.
796
+ ///
797
+ /// Returns the length of the array if it should be encoded as an array.
798
+ #[ cfg( feature = "serde" ) ]
799
+ pub ( crate ) fn encode_as_array ( & self , options : crate :: serde:: de:: Options ) -> Option < usize > {
800
+ if options. detect_mixed_tables {
801
+ if let Some ( ( len, max_idx) ) = self . find_array_len ( ) {
802
+ // If the array is too sparse, serialize it as a map instead
803
+ if len < 10 || len * 2 >= max_idx {
804
+ return Some ( max_idx) ;
805
+ }
806
+ }
807
+ } else {
808
+ let len = self . raw_len ( ) ;
809
+ if len > 0 || self . has_array_metatable ( ) {
810
+ return Some ( len) ;
811
+ }
812
+ if options. encode_empty_tables_as_array && self . is_empty ( ) {
813
+ return Some ( 0 ) ;
814
+ }
815
+ }
816
+ None
817
+ }
818
+
740
819
#[ cfg( feature = "luau" ) ]
741
820
#[ inline( always) ]
742
821
fn check_readonly_write ( & self , lua : & RawLua ) -> Result < ( ) > {
@@ -980,6 +1059,15 @@ impl<'a> SerializableTable<'a> {
980
1059
}
981
1060
}
982
1061
1062
+ impl < V > TableSequence < ' _ , V > {
1063
+ /// Sets the length (hint) of the sequence.
1064
+ #[ cfg( feature = "serde" ) ]
1065
+ pub ( crate ) fn with_len ( mut self , len : usize ) -> Self {
1066
+ self . len = Some ( len) ;
1067
+ self
1068
+ }
1069
+ }
1070
+
983
1071
#[ cfg( feature = "serde" ) ]
984
1072
impl Serialize for SerializableTable < ' _ > {
985
1073
fn serialize < S > ( & self , serializer : S ) -> StdResult < S :: Ok , S :: Error >
@@ -1001,14 +1089,10 @@ impl Serialize for SerializableTable<'_> {
1001
1089
let _guard = RecursionGuard :: new ( self . table , visited) ;
1002
1090
1003
1091
// Array
1004
- let len = self . table . raw_len ( ) ;
1005
- if len > 0
1006
- || self . table . is_array ( )
1007
- || ( self . options . encode_empty_tables_as_array && self . table . is_empty ( ) )
1008
- {
1092
+ if let Some ( len) = self . table . encode_as_array ( self . options ) {
1009
1093
let mut seq = serializer. serialize_seq ( Some ( len) ) ?;
1010
1094
let mut serialize_err = None ;
1011
- let res = self . table . for_each_value :: < Value > ( |value| {
1095
+ let res = self . table . for_each_value_by_len :: < Value > ( len , |value| {
1012
1096
let skip = check_value_for_skip ( & value, self . options , visited)
1013
1097
. map_err ( |err| Error :: SerializeError ( err. to_string ( ) ) ) ?;
1014
1098
if skip {
@@ -1132,13 +1216,11 @@ pub struct TableSequence<'a, V> {
1132
1216
guard : LuaGuard ,
1133
1217
table : & ' a Table ,
1134
1218
index : Integer ,
1219
+ len : Option < usize > ,
1135
1220
_phantom : PhantomData < V > ,
1136
1221
}
1137
1222
1138
- impl < V > Iterator for TableSequence < ' _ , V >
1139
- where
1140
- V : FromLua ,
1141
- {
1223
+ impl < V : FromLua > Iterator for TableSequence < ' _ , V > {
1142
1224
type Item = Result < V > ;
1143
1225
1144
1226
fn next ( & mut self ) -> Option < Self :: Item > {
@@ -1152,7 +1234,7 @@ where
1152
1234
1153
1235
lua. push_ref ( & self . table . 0 ) ;
1154
1236
match ffi:: lua_rawgeti ( state, -1 , self . index ) {
1155
- ffi:: LUA_TNIL => None ,
1237
+ ffi:: LUA_TNIL if self . index as usize > self . len . unwrap_or ( 0 ) => None ,
1156
1238
_ => {
1157
1239
self . index += 1 ;
1158
1240
Some ( V :: from_stack ( -1 , lua) )
0 commit comments