5
5
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
6
*/
7
7
8
+ // Result<..., ()> is used. But we don't have more error info. https://rust-lang.github.io/rust-clippy/master/index.html#result_unit_err.
9
+ // We may want to change () to something like godot::meta::IoError, or a domain-specific one, in the future.
10
+ #![ allow( clippy:: result_unit_err) ]
11
+
8
12
use godot_ffi as sys;
9
13
10
14
use crate :: builtin:: * ;
@@ -13,8 +17,11 @@ use std::{fmt, ops, ptr};
13
17
use sys:: types:: * ;
14
18
use sys:: { ffi_methods, interface_fn, GodotFfi } ;
15
19
16
- // FIXME remove dependency on these types
20
+ use crate :: classes :: file_access :: CompressionMode ;
17
21
use crate :: meta;
22
+ use crate :: obj:: EngineEnum ;
23
+
24
+ // FIXME remove dependency on these types.
18
25
use sys:: { __GdextString, __GdextType} ;
19
26
// TODO(bromeon): ensure and test that all element types can be packed.
20
27
// Many builtin types don't have a #[repr] themselves, but they are used in packed arrays, which assumes certain size and alignment.
@@ -112,6 +119,7 @@ macro_rules! impl_packed_array {
112
119
}
113
120
114
121
/// Returns the number of elements in the array. Equivalent of `size()` in Godot.
122
+ #[ doc( alias = "size" ) ]
115
123
pub fn len( & self ) -> usize {
116
124
to_usize( self . as_inner( ) . size( ) )
117
125
}
@@ -310,7 +318,7 @@ macro_rules! impl_packed_array {
310
318
}
311
319
312
320
// Include specific functions in the code only if the Packed*Array provides the function.
313
- impl_specific_packed_array_functions !( $PackedArray) ;
321
+ declare_packed_array_conversion_fns !( $PackedArray) ;
314
322
315
323
/// # Panics
316
324
///
@@ -544,7 +552,7 @@ macro_rules! impl_packed_array {
544
552
}
545
553
546
554
// Helper macro to only include specific functions in the code if the Packed*Array provides the function.
547
- macro_rules! impl_specific_packed_array_functions {
555
+ macro_rules! declare_packed_array_conversion_fns {
548
556
( PackedByteArray ) => {
549
557
/// Returns a copy of the data converted to a `PackedFloat32Array`, where each block of 4 bytes has been converted to a 32-bit float.
550
558
///
@@ -818,10 +826,248 @@ impl_packed_trait_as_into!(Vector3);
818
826
impl_packed_trait_as_into ! ( Vector4 ) ;
819
827
impl_packed_trait_as_into ! ( Color ) ;
820
828
821
- impl < ' r > PackedTraits for crate :: meta:: CowArg < ' r , GString > {
822
- type ArgType = crate :: meta:: CowArg < ' r , GString > ;
829
+ impl < ' r > PackedTraits for meta:: CowArg < ' r , GString > {
830
+ type ArgType = meta:: CowArg < ' r , GString > ;
823
831
824
832
fn into_packed_arg ( self ) -> Self :: ArgType {
825
833
self
826
834
}
827
835
}
836
+
837
+ // ----------------------------------------------------------------------------------------------------------------------------------------------
838
+ // Specific API for PackedByteArray
839
+
840
+ macro_rules! declare_encode_decode {
841
+ // $Via could be inferred, but ensures we have the correct type expectations.
842
+ ( $Ty: ty, $bytes: literal, $encode_fn: ident, $decode_fn: ident, $Via: ty) => {
843
+ #[ doc = concat!( "Encodes `" , stringify!( $Ty) , "` as " , stringify!( $bytes) , " byte(s) at position `byte_offset`." ) ]
844
+ ///
845
+ /// Returns `Err` if there is not enough space left to write the value, and does nothing in that case.
846
+ ///
847
+ /// **Note:** byte order and encoding pattern is an implementation detail. For portable byte representation and faster encoding, use
848
+ /// [`as_mut_slice()`][Self::as_mut_slice] and the various Rust standard APIs such as
849
+ #[ doc = concat!( "[`" , stringify!( $Ty) , "::to_be_bytes()`]." ) ]
850
+ pub fn $encode_fn( & mut self , byte_offset: usize , value: $Ty) -> Result <( ) , ( ) > {
851
+ // sys::static_assert!(std::mem::size_of::<$Ty>() == $bytes); -- used for testing, can't keep enabled due to half-floats.
852
+
853
+ if byte_offset + $bytes > self . len( ) {
854
+ return Err ( ( ) ) ;
855
+ }
856
+
857
+ self . as_inner( )
858
+ . $encode_fn( byte_offset as i64 , value as $Via) ;
859
+ Ok ( ( ) )
860
+ }
861
+
862
+ #[ doc = concat!( "Decodes `" , stringify!( $Ty) , "` from " , stringify!( $bytes) , " byte(s) at position `byte_offset`." ) ]
863
+ ///
864
+ /// Returns `Err` if there is not enough space left to read the value. In case Godot has other error conditions for decoding, it may
865
+ /// return zero and print an error.
866
+ ///
867
+ /// **Note:** byte order and encoding pattern is an implementation detail. For portable byte representation and faster decoding, use
868
+ /// [`as_slice()`][Self::as_slice] and the various Rust standard APIs such as
869
+ #[ doc = concat!( "[`" , stringify!( $Ty) , "::from_be_bytes()`]." ) ]
870
+ pub fn $decode_fn( & self , byte_offset: usize ) -> Result <$Ty, ( ) > {
871
+ if byte_offset + $bytes > self . len( ) {
872
+ return Err ( ( ) ) ;
873
+ }
874
+
875
+ let decoded: $Via = self . as_inner( ) . $decode_fn( byte_offset as i64 ) ;
876
+ Ok ( decoded as $Ty)
877
+ }
878
+ } ;
879
+ }
880
+
881
+ impl PackedByteArray {
882
+ declare_encode_decode ! ( u8 , 1 , encode_u8, decode_u8, i64 ) ;
883
+ declare_encode_decode ! ( i8 , 1 , encode_s8, decode_s8, i64 ) ;
884
+ declare_encode_decode ! ( u16 , 2 , encode_u16, decode_u16, i64 ) ;
885
+ declare_encode_decode ! ( i16 , 2 , encode_s16, decode_s16, i64 ) ;
886
+ declare_encode_decode ! ( u32 , 4 , encode_u32, decode_u32, i64 ) ;
887
+ declare_encode_decode ! ( i32 , 4 , encode_s32, decode_s32, i64 ) ;
888
+ declare_encode_decode ! ( u64 , 8 , encode_u64, decode_u64, i64 ) ;
889
+ declare_encode_decode ! ( i64 , 8 , encode_s64, decode_s64, i64 ) ;
890
+ declare_encode_decode ! ( f32 , 2 , encode_half, decode_half, f64 ) ;
891
+ declare_encode_decode ! ( f32 , 4 , encode_float, decode_float, f64 ) ;
892
+ declare_encode_decode ! ( f64 , 8 , encode_double, decode_double, f64 ) ;
893
+
894
+ /// Encodes a `Variant` as bytes. Returns number of bytes written, or `Err` on encoding failure.
895
+ ///
896
+ /// Sufficient space must be allocated, depending on the encoded variant's size. If `allow_objects` is false, [`VariantType::OBJECT`] values
897
+ /// are not permitted and will instead be serialized as ID-only. You should set `allow_objects` to false by default.
898
+ pub fn encode_var (
899
+ & mut self ,
900
+ byte_offset : usize ,
901
+ value : impl AsArg < Variant > ,
902
+ allow_objects : bool ,
903
+ ) -> Result < usize , ( ) > {
904
+ meta:: arg_into_ref!( value) ;
905
+
906
+ let bytes_written: i64 =
907
+ self . as_inner ( )
908
+ . encode_var ( byte_offset as i64 , value, allow_objects) ;
909
+
910
+ if bytes_written == -1 {
911
+ Err ( ( ) )
912
+ } else {
913
+ Ok ( bytes_written as usize )
914
+ }
915
+ }
916
+
917
+ /// Decodes a `Variant` from bytes and returns it, alongside the number of bytes read.
918
+ ///
919
+ /// Returns `Err` on decoding error. If you store legit `NIL` variants inside the byte array, use
920
+ /// [`decode_var_allow_nil()`][Self::decode_var_allow_nil] instead.
921
+ ///
922
+ /// # API design
923
+ /// Godot offers three separate methods `decode_var()`, `decode_var_size()` and `has_encoded_var()`. That comes with several problems:
924
+ /// - `has_encoded_var()` is practically useless, because it performs the full decoding work and then throws away the variant.
925
+ /// `decode_var()` can do all that and more.
926
+ /// - Both `has_encoded_var()` and `decode_var_size()` are unreliable. They don't tell whether an actual variant has been written at
927
+ /// the location. They interpret garbage as `Variant::nil()` and return `true` or `4`, respectively. This can very easily cause bugs
928
+ /// because surprisingly, some users may expect that `has_encoded_var()` returns _whether a variant has been encoded_.
929
+ /// - The underlying C++ implementation has all the necessary information (whether a variant is there, how big it is and its value) but the
930
+ /// GDExtension API returns only one info at a time, requiring re-decoding on each call.
931
+ ///
932
+ /// godot-rust mitigates this somewhat, with the following design:
933
+ /// - `decode_var()` treats all `NIL`s as errors. This is most often the desired behavior, and if not, `decode_var_allow_nil()` can be used.
934
+ /// It's also the only way to detect errors at all -- once you store legit `NIL` values, you can no longer differentiate them from garbage.
935
+ /// - `decode_var()` returns both the decoded variant and its size. This requires two decoding runs, but only if the variant is actually
936
+ /// valid. Again, in many cases, a user needs the size to know where follow-up data in the buffer starts.
937
+ /// - `decode_var_size()` and `has_encoded_var()` are not exposed.
938
+ ///
939
+ /// # Security
940
+ /// You should set `allow_objects` to `false` unless you have a good reason not to. Decoding objects (e.g. coming from remote sources)
941
+ /// can cause arbitrary code execution.
942
+ #[ doc( alias = "has_encoded_var" , alias = "decode_var_size" ) ]
943
+ #[ inline]
944
+ pub fn decode_var (
945
+ & self ,
946
+ byte_offset : usize ,
947
+ allow_objects : bool ,
948
+ ) -> Result < ( Variant , usize ) , ( ) > {
949
+ let variant = self
950
+ . as_inner ( )
951
+ . decode_var ( byte_offset as i64 , allow_objects) ;
952
+
953
+ if variant. is_nil ( ) {
954
+ return Err ( ( ) ) ;
955
+ }
956
+
957
+ // It's unfortunate that this does another full decoding, but decode_var() is barely useful without also knowing the size, as it won't
958
+ // be possible to know where to start reading any follow-up data. Furthermore, decode_var_size() often returns true when there's in fact
959
+ // no variant written at that place, it just interprets "nil", treats it as valid, and happily returns 4 bytes.
960
+ //
961
+ // So we combine the two calls for the sake of convenience and to avoid accidental usage.
962
+ let size: i64 = self
963
+ . as_inner ( )
964
+ . decode_var_size ( byte_offset as i64 , allow_objects) ;
965
+ debug_assert_ne ! ( size, -1 ) ; // must not happen if we just decoded variant.
966
+
967
+ Ok ( ( variant, size as usize ) )
968
+ }
969
+
970
+ /// Unreliable `Variant` decoding, allowing `NIL`.
971
+ ///
972
+ /// <div class="warning">
973
+ /// <p>This method is highly unreliable and will try to interpret anything into variants, even zeroed memory or random byte patterns.
974
+ /// Only use it if you need a 1:1 equivalent of Godot's <code>decode_var()</code> and <code>decode_var_size()</code> functions.</p>
975
+ ///
976
+ /// <p>In the majority of cases, <a href="struct.PackedByteArray.html#method.decode_var" title="method godot::builtin::PackedByteArray::decode_var">
977
+ /// <code>decode_var()</code></a> is the better choice, as it’s much easier to use correctly. See also its section about the rationale
978
+ /// behind the current API design.</p>
979
+ /// </div>
980
+ ///
981
+ /// Returns a tuple of two elements:
982
+ /// 1. the decoded variant. This is [`Variant::nil()`] if a valid variant can't be decoded, or the value is of type [`VariantType::OBJECT`]
983
+ /// and `allow_objects` is `false`.
984
+ /// 2. The number of bytes the variant occupies. This is `0` if running out of space, but most other failures are not recognized.
985
+ ///
986
+ /// # Security
987
+ /// You should set `allow_objects` to `false` unless you have a good reason not to. Decoding objects (e.g. coming from remote sources)
988
+ /// can cause arbitrary code execution.
989
+ #[ inline]
990
+ pub fn decode_var_allow_nil (
991
+ & self ,
992
+ byte_offset : usize ,
993
+ allow_objects : bool ,
994
+ ) -> ( Variant , usize ) {
995
+ let byte_offset = byte_offset as i64 ;
996
+
997
+ let variant = self . as_inner ( ) . decode_var ( byte_offset, allow_objects) ;
998
+ let decoded_size = self . as_inner ( ) . decode_var_size ( byte_offset, allow_objects) ;
999
+ let decoded_size = decoded_size. try_into ( ) . unwrap_or_else ( |_| {
1000
+ panic ! ( "unexpected value {decoded_size} returned from decode_var_size()" )
1001
+ } ) ;
1002
+
1003
+ ( variant, decoded_size)
1004
+ }
1005
+
1006
+ /// Returns a new `PackedByteArray`, with the data of this array compressed.
1007
+ ///
1008
+ /// On failure, Godot prints an error and this method returns `Err`. (Note that any empty results coming from Godot are mapped to `Err`
1009
+ /// in Rust.)
1010
+ pub fn compress ( & self , compression_mode : CompressionMode ) -> Result < PackedByteArray , ( ) > {
1011
+ let compressed: PackedByteArray = self . as_inner ( ) . compress ( compression_mode. ord ( ) as i64 ) ;
1012
+ populated_or_err ( compressed)
1013
+ }
1014
+
1015
+ /// Returns a new `PackedByteArray`, with the data of this array decompressed.
1016
+ ///
1017
+ /// Set `buffer_size` to the size of the uncompressed data.
1018
+ ///
1019
+ /// On failure, Godot prints an error and this method returns `Err`. (Note that any empty results coming from Godot are mapped to `Err`
1020
+ /// in Rust.)
1021
+ ///
1022
+ /// **Note:** Decompression is not guaranteed to work with data not compressed by Godot, for example if data compressed with the deflate
1023
+ /// compression mode lacks a checksum or header.
1024
+ pub fn decompress (
1025
+ & self ,
1026
+ buffer_size : usize ,
1027
+ compression_mode : CompressionMode ,
1028
+ ) -> Result < PackedByteArray , ( ) > {
1029
+ let decompressed: PackedByteArray = self
1030
+ . as_inner ( )
1031
+ . decompress ( buffer_size as i64 , compression_mode. ord ( ) as i64 ) ;
1032
+
1033
+ populated_or_err ( decompressed)
1034
+ }
1035
+
1036
+ /// Returns a new `PackedByteArray`, with the data of this array decompressed, and without fixed decompression buffer.
1037
+ ///
1038
+ /// This method only accepts `BROTLI`, `GZIP`, and `DEFLATE` compression modes.
1039
+ ///
1040
+ /// This method is potentially slower than [`decompress()`][Self::decompress], as it may have to re-allocate its output buffer multiple
1041
+ /// times while decompressing, whereas `decompress()` knows its output buffer size from the beginning.
1042
+ ///
1043
+ /// GZIP has a maximal compression ratio of 1032:1, meaning it's very possible for a small compressed payload to decompress to a potentially
1044
+ /// very large output. To guard against this, you may provide a maximum size this function is allowed to allocate in bytes via
1045
+ /// `max_output_size`. Passing `None` will allow for unbounded output. If any positive value is passed, and the decompression exceeds that
1046
+ /// amount in bytes, then an error will be returned.
1047
+ ///
1048
+ /// On failure, Godot prints an error and this method returns `Err`. (Note that any empty results coming from Godot are mapped to `Err`
1049
+ /// in Rust.)
1050
+ ///
1051
+ /// **Note:** Decompression is not guaranteed to work with data not compressed by Godot, for example if data compressed with the deflate
1052
+ /// compression mode lacks a checksum or header.
1053
+ pub fn decompress_dynamic (
1054
+ & self ,
1055
+ max_output_size : Option < usize > ,
1056
+ compression_mode : CompressionMode ,
1057
+ ) -> Result < PackedByteArray , ( ) > {
1058
+ let max_output_size = max_output_size. map ( |i| i as i64 ) . unwrap_or ( -1 ) ;
1059
+ let decompressed: PackedByteArray = self
1060
+ . as_inner ( )
1061
+ . decompress_dynamic ( max_output_size, compression_mode. ord ( ) as i64 ) ;
1062
+
1063
+ populated_or_err ( decompressed)
1064
+ }
1065
+ }
1066
+
1067
+ fn populated_or_err ( array : PackedByteArray ) -> Result < PackedByteArray , ( ) > {
1068
+ if array. is_empty ( ) {
1069
+ Err ( ( ) )
1070
+ } else {
1071
+ Ok ( array)
1072
+ }
1073
+ }
0 commit comments