diff --git a/java/fory-core/src/main/java/org/apache/fory/Fory.java b/java/fory-core/src/main/java/org/apache/fory/Fory.java index fc9aee8cb5..e6b1667440 100644 --- a/java/fory-core/src/main/java/org/apache/fory/Fory.java +++ b/java/fory-core/src/main/java/org/apache/fory/Fory.java @@ -590,6 +590,8 @@ public void xwriteData(MemoryBuffer buffer, ClassInfo classInfo, Object obj) { buffer.writeInt16((Short) obj); break; case Types.INT32: + buffer.writeInt32((Integer) obj); + break; case Types.VAR_INT32: // TODO(chaokunyang) support other encoding buffer.writeVarInt32((Integer) obj); @@ -1106,6 +1108,7 @@ public Object xreadNonRef(MemoryBuffer buffer, ClassInfo classInfo) { case Types.INT16: return buffer.readInt16(); case Types.INT32: + return buffer.readInt32(); case Types.VAR_INT32: // TODO(chaokunyang) support other encoding return buffer.readVarInt32(); diff --git a/java/fory-core/src/main/java/org/apache/fory/config/ForyBuilder.java b/java/fory-core/src/main/java/org/apache/fory/config/ForyBuilder.java index 0c610bad5f..3a3d254f54 100644 --- a/java/fory-core/src/main/java/org/apache/fory/config/ForyBuilder.java +++ b/java/fory-core/src/main/java/org/apache/fory/config/ForyBuilder.java @@ -421,7 +421,7 @@ private void finish() { if (language != Language.JAVA) { stringRefIgnored = true; longEncoding = LongEncoding.PVL; - compressInt = true; + // compressInt = true; compressString = true; } if (ENABLE_CLASS_REGISTRATION_FORCIBLY) { diff --git a/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java b/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java index b5451869ff..aba6178043 100644 --- a/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java +++ b/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java @@ -586,7 +586,11 @@ private void registerDefaultTypes() { registerDefaultTypes(Types.BOOL, Boolean.class, boolean.class, AtomicBoolean.class); registerDefaultTypes(Types.INT8, Byte.class, byte.class); registerDefaultTypes(Types.INT16, Short.class, short.class); - registerDefaultTypes(Types.INT32, Integer.class, int.class, AtomicInteger.class); + if (this.fory.compressInt()) { + registerDefaultTypes(Types.VAR_INT32, Integer.class, int.class, AtomicInteger.class); + } else { + registerDefaultTypes(Types.INT32, Integer.class, int.class, AtomicInteger.class); + } registerDefaultTypes(Types.INT64, Long.class, long.class, AtomicLong.class); registerDefaultTypes(Types.FLOAT32, Float.class, float.class); registerDefaultTypes(Types.FLOAT64, Double.class, double.class); diff --git a/java/fory-core/src/main/java/org/apache/fory/type/Types.java b/java/fory-core/src/main/java/org/apache/fory/type/Types.java index c6300b2be2..8a2a0e50f6 100644 --- a/java/fory-core/src/main/java/org/apache/fory/type/Types.java +++ b/java/fory-core/src/main/java/org/apache/fory/type/Types.java @@ -222,6 +222,7 @@ public static boolean isPrimitiveType(int typeId) { case INT8: case INT16: case INT32: + case VAR_INT32: case INT64: case FLOAT32: case FLOAT64: diff --git a/java/fory-core/src/test/java/org/apache/fory/RustXlangTest.java b/java/fory-core/src/test/java/org/apache/fory/RustXlangTest.java index 3b6c289fb7..9f720dd627 100644 --- a/java/fory-core/src/test/java/org/apache/fory/RustXlangTest.java +++ b/java/fory-core/src/test/java/org/apache/fory/RustXlangTest.java @@ -877,6 +877,53 @@ public void testStructVersionCheck() throws java.io.IOException { Assert.assertEquals(fory.deserialize(buffer2), obj); } + @Data + static class IntWrapper { + int f1; + + IntWrapper(int f1) { + this.f1 = f1; + } + } + + @Test + public void testCompressInt() throws java.io.IOException { + String caseName = "test_compress_int"; + List command = setTestCase(caseName); + Fory fory = + Fory.builder() + .withLanguage(Language.XLANG) + .withCompatibleMode(CompatibleMode.SCHEMA_CONSISTENT) + .withCodegen(false) + .withClassVersionCheck(true) + .withIntCompressed(false) + .build(); + Fory foryCompressed = + Fory.builder() + .withLanguage(Language.XLANG) + .withCompatibleMode(CompatibleMode.SCHEMA_CONSISTENT) + .withCodegen(false) + .withClassVersionCheck(true) + .withIntCompressed(true) + .build(); + fory.register(IntWrapper.class, 100); + foryCompressed.register(IntWrapper.class, 101); + + IntWrapper item = new IntWrapper(42); + IntWrapper itemCompressed = new IntWrapper(43); + + MemoryBuffer buffer = MemoryBuffer.newHeapBuffer(32); + fory.serialize(buffer, item); + foryCompressed.serialize(buffer, itemCompressed); + Path dataFile = Files.createTempFile(caseName, "data"); + Pair, File> env_workdir = + setFilePath(dataFile, buffer.getBytes(0, buffer.writerIndex())); + Assert.assertTrue(executeCommand(command, 30, env_workdir.getLeft(), env_workdir.getRight())); + MemoryBuffer buffer2 = MemoryUtils.wrap(Files.readAllBytes(dataFile)); + Assert.assertEquals(fory.deserialize(buffer2), item); + Assert.assertEquals(foryCompressed.deserialize(buffer2), itemCompressed); + } + /** * Execute an external command. * diff --git a/python/pyfory/_fory.py b/python/pyfory/_fory.py index 7d5d406d17..bc4abd8c1a 100644 --- a/python/pyfory/_fory.py +++ b/python/pyfory/_fory.py @@ -165,6 +165,7 @@ class Fory: "depth", "field_nullable", "policy", + "compress_int", ) def __init__( @@ -216,6 +217,12 @@ def __init__( (xlang=False), regardless of Optional annotation. Ignored in cross-language mode. + compress_int: Enable integer compression using varint encoding (default: True). + When enabled, integers are encoded using variable-length encoding + for more efficient serialization of small integers. This reduces + the serialized size but may have minor performance impact on + serialization/deserialization. + Example: >>> # Python-native mode with reference tracking >>> fory = Fory(ref=True) @@ -237,6 +244,10 @@ def __init__( if kwargs.get("require_type_registration") is not None: strict = kwargs.get("require_type_registration") self.strict = _ENABLE_TYPE_REGISTRATION_FORCIBLY or strict + if kwargs.get("compress_int") is not None: + self.compress_int = kwargs.get("compress_int") + else: + self.compress_int = True self.policy = policy or DEFAULT_POLICY self.compatible = compatible self.field_nullable = field_nullable if self.is_py else False diff --git a/python/pyfory/_registry.py b/python/pyfory/_registry.py index 3ecb90f612..cd85693da3 100644 --- a/python/pyfory/_registry.py +++ b/python/pyfory/_registry.py @@ -258,7 +258,10 @@ def _initialize_common(self): register(bool, type_id=TypeId.BOOL, serializer=BooleanSerializer) register(int8, type_id=TypeId.INT8, serializer=ByteSerializer) register(int16, type_id=TypeId.INT16, serializer=Int16Serializer) - register(int32, type_id=TypeId.INT32, serializer=Int32Serializer) + int32_type_id = TypeId.INT32 + if self.fory.compress_int: + int32_type_id = TypeId.VAR_INT32 + register(int32, type_id=int32_type_id, serializer=Int32Serializer) register(int64, type_id=TypeId.INT64, serializer=Int64Serializer) register(int, type_id=TypeId.INT64, serializer=Int64Serializer) register( diff --git a/python/pyfory/_serializer.py b/python/pyfory/_serializer.py index 17c9d9ed2c..ab20784160 100644 --- a/python/pyfory/_serializer.py +++ b/python/pyfory/_serializer.py @@ -122,10 +122,16 @@ def read(self, buffer): class Int32Serializer(XlangCompatibleSerializer): def write(self, buffer, value): - buffer.write_varint32(value) + if self.fory.compress_int: + buffer.write_varint32(value) + else: + buffer.write_int32(value) def read(self, buffer): - return buffer.read_varint32() + if self.fory.compress_int: + return buffer.read_varint32() + else: + return buffer.read_int32() class Int64Serializer(Serializer): diff --git a/python/pyfory/serialization.pyx b/python/pyfory/serialization.pyx index 9b46e940a8..b21efe2ca4 100644 --- a/python/pyfory/serialization.pyx +++ b/python/pyfory/serialization.pyx @@ -902,6 +902,7 @@ cdef class Fory: cdef readonly c_bool compatible cdef readonly c_bool field_nullable cdef readonly object policy + cdef readonly c_bool compress_int cdef readonly MapRefResolver ref_resolver cdef readonly TypeResolver type_resolver cdef readonly MetaStringResolver metastring_resolver @@ -964,6 +965,12 @@ cdef class Fory: (xlang=False), regardless of Optional annotation. Ignored in cross-language mode. + compress_int: Enable integer compression using varint encoding (default: True). + When enabled, integers are encoded using variable-length encoding + for more efficient serialization of small integers. This reduces + the serialized size but may have minor performance impact on + serialization/deserialization. + Example: >>> # Python-native mode with reference tracking >>> fory = Fory(ref=True) @@ -982,6 +989,10 @@ cdef class Fory: self.strict = True else: self.strict = False + if kwargs.get("compress_int") is not None: + self.compress_int = kwargs.get("compress_int") + else: + self.compress_int = True self.policy = policy or DEFAULT_POLICY self.compatible = compatible self.ref_tracking = ref @@ -1719,10 +1730,16 @@ cdef class Int16Serializer(XlangCompatibleSerializer): @cython.final cdef class Int32Serializer(XlangCompatibleSerializer): cpdef inline write(self, Buffer buffer, value): - buffer.write_varint32(value) + if self.fory.compress_int: + buffer.write_varint32(value) + else: + buffer.write_int32(value) cpdef inline read(self, Buffer buffer): - return buffer.read_varint32() + if self.fory.compress_int: + return buffer.read_varint32() + else: + return buffer.read_int32() @cython.final diff --git a/python/pyfory/serializer.py b/python/pyfory/serializer.py index 62235b4c08..60d1db9776 100644 --- a/python/pyfory/serializer.py +++ b/python/pyfory/serializer.py @@ -346,7 +346,10 @@ def _get_write_stmt_for_codegen(self, serializer, buffer, field_value): elif isinstance(serializer, Int16Serializer): return f"{buffer}.write_int16({field_value})" elif isinstance(serializer, Int32Serializer): - return f"{buffer}.write_varint32({field_value})" + if self.fory.compress_int: + return f"{buffer}.write_varint32({field_value})" + else: + return f"{buffer}.write_int32({field_value})" elif isinstance(serializer, Int64Serializer): return f"{buffer}.write_varint64({field_value})" elif isinstance(serializer, Float32Serializer): @@ -367,7 +370,10 @@ def _get_read_stmt_for_codegen(self, serializer, buffer, field_value): elif isinstance(serializer, Int16Serializer): return f"{field_value} = {buffer}.read_int16()" elif isinstance(serializer, Int32Serializer): - return f"{field_value} = {buffer}.read_varint32()" + if self.fory.compress_int: + return f"{field_value} = {buffer}.read_varint32()" + else: + return f"{field_value} = {buffer}.read_int32()" elif isinstance(serializer, Int64Serializer): return f"{field_value} = {buffer}.read_varint64()" elif isinstance(serializer, Float32Serializer): @@ -388,7 +394,10 @@ def _write_non_nullable_field(self, buffer, field_value, serializer): elif isinstance(serializer, Int16Serializer): buffer.write_int16(field_value) elif isinstance(serializer, Int32Serializer): - buffer.write_varint32(field_value) + if self.fory.compress_int: + buffer.write_varint32(field_value) + else: + buffer.write_int32(field_value) elif isinstance(serializer, Int64Serializer): buffer.write_varint64(field_value) elif isinstance(serializer, Float32Serializer): @@ -409,7 +418,10 @@ def _read_non_nullable_field(self, buffer, serializer): elif isinstance(serializer, Int16Serializer): return buffer.read_int16() elif isinstance(serializer, Int32Serializer): - return buffer.read_varint32() + if self.fory.compress_int: + return buffer.read_varint32() + else: + return buffer.read_int32() elif isinstance(serializer, Int64Serializer): return buffer.read_varint64() elif isinstance(serializer, Float32Serializer): diff --git a/python/pyfory/type.py b/python/pyfory/type.py index 349a25cf29..35d749c701 100644 --- a/python/pyfory/type.py +++ b/python/pyfory/type.py @@ -255,6 +255,7 @@ def is_namespaced_type(type_id: int) -> bool: TypeId.INT8, TypeId.INT16, TypeId.INT32, + TypeId.VAR_INT32, TypeId.INT64, TypeId.FLOAT16, TypeId.FLOAT32, diff --git a/rust/fory-core/src/fory.rs b/rust/fory-core/src/fory.rs index d9a046bb36..ef20cb752e 100644 --- a/rust/fory-core/src/fory.rs +++ b/rust/fory-core/src/fory.rs @@ -18,8 +18,7 @@ use crate::buffer::{Reader, Writer}; use crate::ensure; use crate::error::Error; -use crate::resolver::context::WriteContext; -use crate::resolver::context::{Pool, ReadContext}; +use crate::resolver::context::{Pool, ReadContext, WriteContext}; use crate::resolver::type_resolver::TypeResolver; use crate::serializer::ForyDefault; use crate::serializer::{Serializer, StructSerializer}; @@ -179,6 +178,49 @@ impl Fory { self } + /// Enables or disables int32 compression for the `Fory` instance. + /// + /// Int32 compression uses a variable-length encoding to reduce the size of serialized 32-bit integers. + /// + /// # Arguments + /// + /// * `compress_int` - If `true`, int32 values will be encoded in a compact, variable-length format, + /// reducing the serialized payload size. If `false`, int32 values will be serialized using a fixed-width + /// 32-bit representation. + /// + /// # Returns + /// + /// Returns `self` to allow method chaining. + /// + /// # Default + /// + /// The default value is `true`. + /// + /// # Trade-offs + /// + /// - **Enabled** (`true`): Produces smaller payloads at the cost of slightly higher CPU usage during + /// serialization and deserialization. + /// - **Disabled** (`false`): Produces larger payloads but with faster serialization/deserialization. + /// + /// # Error cases + /// + /// If a struct has already been registered with `Fory` and it has a conflicting `compress_int` + /// setting, calling this method with a different value will panic. + /// See the test case [`test_i32_conflict`](crate::tests::test_compress::test_i32_conflict) + /// for an example of this situation. + /// + /// # Examples + /// + /// ```rust + /// use fory_core::Fory; + /// + /// let fory = Fory::default().compress_int(true); + /// ``` + pub fn compress_int(mut self, compress_int: bool) -> Self { + self.type_resolver.set_compress_int(compress_int); + self + } + /// Enables or disables meta string compression. /// /// # Arguments @@ -312,6 +354,15 @@ impl Fory { self.compress_string } + /// Returns whether int32 compression is enabled. + /// + /// # Returns + /// + /// `true` if i32 compression is enabled, `false` otherwise. + pub fn is_compress_int(&self) -> bool { + self.type_resolver.is_compress_int() + } + /// Returns whether metadata sharing is enabled. /// /// # Returns @@ -523,7 +574,6 @@ impl Fory { let type_resolver = self.type_resolver.build_final_type_resolver()?; let compatible = self.compatible; let share_meta = self.share_meta; - let compress_string = self.compress_string; let xlang = self.xlang; let check_struct_version = self.check_struct_version; @@ -532,7 +582,6 @@ impl Fory { type_resolver.clone(), compatible, share_meta, - compress_string, xlang, check_struct_version, )) @@ -552,7 +601,7 @@ impl Fory { context: &mut WriteContext, ) -> Result<(), Error> { let is_none = record.fory_is_none(); - self.write_head::(is_none, &mut context.writer); + self.write_head::(is_none, context); let meta_start_offset = context.writer.len(); if !is_none { if context.is_compatible() { @@ -763,11 +812,13 @@ impl Fory { /// Writes the serialization header to the writer. #[inline(always)] - pub fn write_head(&self, is_none: bool, writer: &mut Writer) { + pub fn write_head(&self, is_none: bool, context: &mut WriteContext) { const HEAD_SIZE: usize = 10; - writer.reserve(T::fory_reserved_space() + SIZE_OF_REF_AND_TYPE + HEAD_SIZE); + context + .writer + .reserve(T::fory_reserved_space() + SIZE_OF_REF_AND_TYPE + HEAD_SIZE); if self.xlang { - writer.write_u16(MAGIC_NUMBER); + context.writer.write_u16(MAGIC_NUMBER); } #[cfg(target_endian = "big")] let mut bitmap = 0; @@ -779,12 +830,12 @@ impl Fory { if is_none { bitmap |= IS_NULL_FLAG; } - writer.write_u8(bitmap); + context.writer.write_u8(bitmap); if is_none { return; } if self.xlang { - writer.write_u8(Language::Rust as u8); + context.writer.write_u8(Language::Rust as u8); } } diff --git a/rust/fory-core/src/resolver/context.rs b/rust/fory-core/src/resolver/context.rs index c116d6666e..cc79188e63 100644 --- a/rust/fory-core/src/resolver/context.rs +++ b/rust/fory-core/src/resolver/context.rs @@ -36,7 +36,6 @@ pub struct WriteContext<'a> { type_resolver: TypeResolver, compatible: bool, share_meta: bool, - compress_string: bool, xlang: bool, check_struct_version: bool, @@ -54,7 +53,6 @@ impl<'a> WriteContext<'a> { type_resolver: TypeResolver, compatible: bool, share_meta: bool, - compress_string: bool, xlang: bool, check_struct_version: bool, ) -> WriteContext<'a> { @@ -62,7 +60,6 @@ impl<'a> WriteContext<'a> { type_resolver, compatible, share_meta, - compress_string, xlang, check_struct_version, default_writer: None, @@ -113,12 +110,6 @@ impl<'a> WriteContext<'a> { self.share_meta } - /// Check if string compression is enabled - #[inline(always)] - pub fn is_compress_string(&self) -> bool { - self.compress_string - } - /// Check if cross-language mode is enabled #[inline(always)] pub fn is_xlang(&self) -> bool { diff --git a/rust/fory-core/src/resolver/type_resolver.rs b/rust/fory-core/src/resolver/type_resolver.rs index 2709cc8379..f55f269c6c 100644 --- a/rust/fory-core/src/resolver/type_resolver.rs +++ b/rust/fory-core/src/resolver/type_resolver.rs @@ -415,6 +415,8 @@ pub struct TypeResolver { // Fast lookup by numeric ID for common types type_id_index: Vec, compatible: bool, + compress_int: bool, + compress_int_opt: Option, } // Safety: TypeResolver instances are only shared through higher-level synchronization that @@ -435,6 +437,8 @@ impl Default for TypeResolver { type_id_index: Vec::new(), partial_type_infos: HashMap::new(), compatible: false, + compress_int: true, + compress_int_opt: None, }; registry.register_builtin_types().unwrap(); registry @@ -442,6 +446,31 @@ impl Default for TypeResolver { } impl TypeResolver { + #[inline(always)] + pub(crate) fn is_compress_int(&self) -> bool { + if self.compress_int_opt.is_none() { + panic!( + "{}", + Error::not_allowed("cannot get is_compress_int at the moment") + ); + } + self.compress_int + } + + pub(crate) fn set_compress_int(&mut self, compress_int: bool) { + if self.compress_int_opt.is_some() { + if compress_int != self.compress_int { + panic!( + "{}", + Error::not_allowed(format!("type with compress_int={} has already been registered, cannot set compress_int={}", self.compress_int, compress_int)) + ); + } + } else { + self.compress_int_opt = Some(compress_int); + self.compress_int = compress_int; + } + } + pub fn get_type_info(&self, type_id: &std::any::TypeId) -> Result, Error> { self.type_info_map .get(type_id) @@ -540,11 +569,20 @@ impl TypeResolver { .map(|info| info.get_type_id()) } + fn register_var_types(&mut self) -> Result<(), Error> { + if self.compress_int { + self.register_internal_serializer::(TypeId::VAR_INT32)?; + } else { + self.register_internal_serializer::(TypeId::INT32)?; + } + Ok(()) + } + fn register_builtin_types(&mut self) -> Result<(), Error> { self.register_internal_serializer::(TypeId::BOOL)?; self.register_internal_serializer::(TypeId::INT8)?; self.register_internal_serializer::(TypeId::INT16)?; - self.register_internal_serializer::(TypeId::INT32)?; + self.register_internal_serializer::(TypeId::INT64)?; self.register_internal_serializer::(TypeId::FLOAT32)?; self.register_internal_serializer::(TypeId::FLOAT64)?; @@ -612,6 +650,15 @@ impl TypeResolver { "Either id must be non-zero for ID registration, or type_name must be non-empty for name registration", )); } + let type_is_compress_int = T::fory_is_compress_int(); + if self.compress_int_opt.is_none() { + self.compress_int_opt = Some(type_is_compress_int); + self.compress_int = type_is_compress_int; + } else if self.compress_int != type_is_compress_int { + return Err(Error::not_allowed( + "inconsistent compress_int setting among registered types", + )); + } let actual_type_id = T::fory_actual_type_id(id, register_by_name, self.compatible); fn write( @@ -999,8 +1046,12 @@ impl TypeResolver { } } } - - Ok(TypeResolver { + let (final_compress_int, final_compress_int_opt) = if self.compress_int_opt.is_some() { + (self.compress_int, self.compress_int_opt) + } else { + (true, Some(true)) + }; + let mut final_type_resolver = TypeResolver { type_info_map_by_id, type_info_map, type_info_map_by_name, @@ -1008,7 +1059,11 @@ impl TypeResolver { partial_type_infos: HashMap::new(), type_id_index, compatible: self.compatible, - }) + compress_int: final_compress_int, + compress_int_opt: final_compress_int_opt, + }; + final_type_resolver.register_var_types()?; + Ok(final_type_resolver) } /// Clones the TypeResolver including any partial type infos. @@ -1040,6 +1095,8 @@ impl TypeResolver { partial_type_infos: HashMap::new(), type_id_index: self.type_id_index.clone(), compatible: self.compatible, + compress_int: self.compress_int, + compress_int_opt: self.compress_int_opt, } } } diff --git a/rust/fory-core/src/serializer/array.rs b/rust/fory-core/src/serializer/array.rs index fc9537115b..1fe54973b6 100644 --- a/rust/fory-core/src/serializer/array.rs +++ b/rust/fory-core/src/serializer/array.rs @@ -16,8 +16,7 @@ // under the License. use crate::error::Error; -use crate::resolver::context::ReadContext; -use crate::resolver::context::WriteContext; +use crate::resolver::context::{ReadContext, WriteContext}; use crate::resolver::type_resolver::TypeResolver; use crate::serializer::primitive_list; use crate::serializer::{ForyDefault, Serializer}; diff --git a/rust/fory-core/src/serializer/bool.rs b/rust/fory-core/src/serializer/bool.rs index 5dac06404f..d18fc2ea66 100644 --- a/rust/fory-core/src/serializer/bool.rs +++ b/rust/fory-core/src/serializer/bool.rs @@ -16,8 +16,7 @@ // under the License. use crate::error::Error; -use crate::resolver::context::ReadContext; -use crate::resolver::context::WriteContext; +use crate::resolver::context::{ReadContext, WriteContext}; use crate::resolver::type_resolver::TypeResolver; use crate::serializer::util::read_basic_type_info; use crate::serializer::{ForyDefault, Serializer}; diff --git a/rust/fory-core/src/serializer/box_.rs b/rust/fory-core/src/serializer/box_.rs index 17c2bf0de8..ce041f455b 100644 --- a/rust/fory-core/src/serializer/box_.rs +++ b/rust/fory-core/src/serializer/box_.rs @@ -16,8 +16,7 @@ // under the License. use crate::error::Error; -use crate::resolver::context::ReadContext; -use crate::resolver::context::WriteContext; +use crate::resolver::context::{ReadContext, WriteContext}; use crate::resolver::type_resolver::TypeResolver; use crate::serializer::{ForyDefault, Serializer}; use crate::types::TypeId; diff --git a/rust/fory-core/src/serializer/core.rs b/rust/fory-core/src/serializer/core.rs index 6351e3127c..ab5f87f9ec 100644 --- a/rust/fory-core/src/serializer/core.rs +++ b/rust/fory-core/src/serializer/core.rs @@ -1447,6 +1447,22 @@ pub trait StructSerializer: Serializer + 'static { ) -> Result where Self: Sized; + + /// Returns whether this type uses integer compression. + /// + /// This method indicates whether the type applies `i32` compression, i.e., whether it + /// serializes integers using variable-length encoding (varint32) to reduce payload size. + /// + /// # Returns + /// + /// Returns `true` if integer compression (varint32) is enabled for this type; `false` otherwise. + /// + /// # Implementation Notes + /// + /// - **Do not implement** for user types with custom serialization (EXT types) + fn fory_is_compress_int() -> bool { + unreachable!() + } } /// Serializes an object implementing `Serializer` to the write context. diff --git a/rust/fory-core/src/serializer/datetime.rs b/rust/fory-core/src/serializer/datetime.rs index a0c0f6ae88..f12c2d6efd 100644 --- a/rust/fory-core/src/serializer/datetime.rs +++ b/rust/fory-core/src/serializer/datetime.rs @@ -16,8 +16,7 @@ // under the License. use crate::error::Error; -use crate::resolver::context::ReadContext; -use crate::resolver::context::WriteContext; +use crate::resolver::context::{ReadContext, WriteContext}; use crate::resolver::type_resolver::TypeResolver; use crate::serializer::util::read_basic_type_info; use crate::serializer::ForyDefault; diff --git a/rust/fory-core/src/serializer/heap.rs b/rust/fory-core/src/serializer/heap.rs index f74b93997d..b9e33e43f2 100644 --- a/rust/fory-core/src/serializer/heap.rs +++ b/rust/fory-core/src/serializer/heap.rs @@ -16,8 +16,7 @@ // under the License. use crate::error::Error; -use crate::resolver::context::ReadContext; -use crate::resolver::context::WriteContext; +use crate::resolver::context::{ReadContext, WriteContext}; use crate::resolver::type_resolver::TypeResolver; use crate::serializer::collection::{ read_collection_data, read_collection_type_info, write_collection_data, diff --git a/rust/fory-core/src/serializer/list.rs b/rust/fory-core/src/serializer/list.rs index 054923a020..05cbe31d3f 100644 --- a/rust/fory-core/src/serializer/list.rs +++ b/rust/fory-core/src/serializer/list.rs @@ -16,8 +16,7 @@ // under the License. use crate::error::Error; -use crate::resolver::context::ReadContext; -use crate::resolver::context::WriteContext; +use crate::resolver::context::{ReadContext, WriteContext}; use crate::resolver::type_resolver::TypeResolver; use crate::serializer::primitive_list; use crate::serializer::{ForyDefault, Serializer}; diff --git a/rust/fory-core/src/serializer/number.rs b/rust/fory-core/src/serializer/number.rs index 4e6a45b0ce..5dc06e6c55 100644 --- a/rust/fory-core/src/serializer/number.rs +++ b/rust/fory-core/src/serializer/number.rs @@ -17,8 +17,7 @@ use crate::buffer::{Reader, Writer}; use crate::error::Error; -use crate::resolver::context::ReadContext; -use crate::resolver::context::WriteContext; +use crate::resolver::context::{ReadContext, WriteContext}; use crate::resolver::type_resolver::TypeResolver; use crate::serializer::util::read_basic_type_info; use crate::serializer::{ForyDefault, Serializer}; @@ -85,12 +84,6 @@ macro_rules! impl_num_serializer { impl_num_serializer!(i8, Writer::write_i8, Reader::read_i8, TypeId::INT8); impl_num_serializer!(i16, Writer::write_i16, Reader::read_i16, TypeId::INT16); -impl_num_serializer!( - i32, - Writer::write_varint32, - Reader::read_varint32, - TypeId::INT32 -); impl_num_serializer!( i64, Writer::write_varint64, @@ -99,3 +92,71 @@ impl_num_serializer!( ); impl_num_serializer!(f32, Writer::write_f32, Reader::read_f32, TypeId::FLOAT32); impl_num_serializer!(f64, Writer::write_f64, Reader::read_f64, TypeId::FLOAT64); + +impl Serializer for i32 { + #[inline(always)] + fn fory_write_data(&self, context: &mut WriteContext) -> Result<(), Error> { + if context.get_type_resolver().is_compress_int() { + Writer::write_varint32(&mut context.writer, *self) + } else { + Writer::write_i32(&mut context.writer, *self) + } + Ok(()) + } + + #[inline(always)] + fn fory_read_data(context: &mut ReadContext) -> Result { + if context.get_type_resolver().is_compress_int() { + Reader::read_varint32(&mut context.reader) + } else { + Reader::read_i32(&mut context.reader) + } + } + + #[inline(always)] + fn fory_reserved_space() -> usize { + std::mem::size_of::() + } + + #[inline(always)] + fn fory_get_type_id(type_resolver: &TypeResolver) -> Result { + if type_resolver.is_compress_int() { + Ok(TypeId::VAR_INT32 as u32) + } else { + Ok(TypeId::INT32 as u32) + } + } + + #[inline(always)] + fn fory_type_id_dyn(&self, type_resolver: &TypeResolver) -> Result { + Self::fory_get_type_id(type_resolver) + } + + #[inline(always)] + fn fory_static_type_id() -> TypeId { + TypeId::INT32 + } + + #[inline(always)] + fn as_any(&self) -> &dyn std::any::Any { + self + } + + #[inline(always)] + fn fory_write_type_info(context: &mut WriteContext) -> Result<(), Error> { + let type_id = Self::fory_get_type_id(context.get_type_resolver())?; + context.writer.write_varuint32(type_id); + Ok(()) + } + + #[inline(always)] + fn fory_read_type_info(context: &mut ReadContext) -> Result<(), Error> { + read_basic_type_info::(context) + } +} +impl ForyDefault for i32 { + #[inline(always)] + fn fory_default() -> Self { + 0 + } +} diff --git a/rust/fory-core/src/serializer/option.rs b/rust/fory-core/src/serializer/option.rs index 01495011d3..bfefd3687f 100644 --- a/rust/fory-core/src/serializer/option.rs +++ b/rust/fory-core/src/serializer/option.rs @@ -16,8 +16,7 @@ // under the License. use crate::error::Error; -use crate::resolver::context::ReadContext; -use crate::resolver::context::WriteContext; +use crate::resolver::context::{ReadContext, WriteContext}; use crate::resolver::type_resolver::TypeResolver; use crate::serializer::{ForyDefault, Serializer}; use crate::types::{RefFlag, TypeId}; diff --git a/rust/fory-core/src/serializer/set.rs b/rust/fory-core/src/serializer/set.rs index a317f0b362..26d9439101 100644 --- a/rust/fory-core/src/serializer/set.rs +++ b/rust/fory-core/src/serializer/set.rs @@ -16,8 +16,7 @@ // under the License. use crate::error::Error; -use crate::resolver::context::ReadContext; -use crate::resolver::context::WriteContext; +use crate::resolver::context::{ReadContext, WriteContext}; use crate::resolver::type_resolver::TypeResolver; use crate::serializer::collection::{ read_collection_data, read_collection_type_info, write_collection_data, diff --git a/rust/fory-core/src/serializer/skip.rs b/rust/fory-core/src/serializer/skip.rs index cb8a23130b..b84807526f 100644 --- a/rust/fory-core/src/serializer/skip.rs +++ b/rust/fory-core/src/serializer/skip.rs @@ -361,7 +361,7 @@ fn skip_value( types::INT16 => { ::fory_read_data(context)?; } - types::INT32 => { + types::INT32 | types::VAR_INT32 => { ::fory_read_data(context)?; } types::INT64 => { diff --git a/rust/fory-core/src/serializer/string.rs b/rust/fory-core/src/serializer/string.rs index 256ed41b6f..766aa00578 100644 --- a/rust/fory-core/src/serializer/string.rs +++ b/rust/fory-core/src/serializer/string.rs @@ -16,8 +16,7 @@ // under the License. use crate::error::Error; -use crate::resolver::context::ReadContext; -use crate::resolver::context::WriteContext; +use crate::resolver::context::{ReadContext, WriteContext}; use crate::resolver::type_resolver::TypeResolver; use crate::serializer::util::read_basic_type_info; use crate::serializer::{ForyDefault, Serializer}; diff --git a/rust/fory-core/src/serializer/unsigned_number.rs b/rust/fory-core/src/serializer/unsigned_number.rs index 4d424fee4d..9e66f60f5e 100644 --- a/rust/fory-core/src/serializer/unsigned_number.rs +++ b/rust/fory-core/src/serializer/unsigned_number.rs @@ -17,8 +17,7 @@ use crate::buffer::{Reader, Writer}; use crate::error::Error; -use crate::resolver::context::ReadContext; -use crate::resolver::context::WriteContext; +use crate::resolver::context::{ReadContext, WriteContext}; use crate::resolver::type_resolver::TypeResolver; use crate::serializer::util::read_basic_type_info; use crate::serializer::{ForyDefault, Serializer}; diff --git a/rust/fory-core/src/serializer/util.rs b/rust/fory-core/src/serializer/util.rs index 8f02a46a1f..e3525e3415 100644 --- a/rust/fory-core/src/serializer/util.rs +++ b/rust/fory-core/src/serializer/util.rs @@ -22,7 +22,7 @@ use crate::serializer::Serializer; use crate::types::TypeId; use crate::types::{ is_user_type, BOOL, ENUM, FLOAT32, FLOAT64, INT16, INT32, INT64, INT8, NAMED_ENUM, U128, U16, - U32, U64, U8, USIZE, + U32, U64, U8, USIZE, VAR_INT32, }; #[inline(always)] @@ -73,6 +73,7 @@ pub const fn field_need_write_ref_into(type_id: u32, nullable: bool) -> bool { BOOL | INT8 | INT16 | INT32 + | VAR_INT32 | INT64 | FLOAT32 | FLOAT64 diff --git a/rust/fory-core/src/types.rs b/rust/fory-core/src/types.rs index f3733bf0cd..d439fc47a0 100644 --- a/rust/fory-core/src/types.rs +++ b/rust/fory-core/src/types.rs @@ -168,11 +168,12 @@ pub fn compute_string_hash(s: &str) -> u32 { hash as u32 } -pub static BASIC_TYPES: [TypeId; 29] = [ +pub static BASIC_TYPES: [TypeId; 30] = [ TypeId::BOOL, TypeId::INT8, TypeId::INT16, TypeId::INT32, + TypeId::VAR_INT32, TypeId::INT64, TypeId::FLOAT32, TypeId::FLOAT64, @@ -280,6 +281,7 @@ pub const fn is_primitive_type_id(type_id: TypeId) -> bool { | TypeId::INT8 | TypeId::INT16 | TypeId::INT32 + | TypeId::VAR_INT32 | TypeId::INT64 | TypeId::FLOAT32 | TypeId::FLOAT64 diff --git a/rust/fory-derive/src/lib.rs b/rust/fory-derive/src/lib.rs index 8ecb4cdbcf..10820acfaf 100644 --- a/rust/fory-derive/src/lib.rs +++ b/rust/fory-derive/src/lib.rs @@ -212,12 +212,11 @@ pub fn proc_macro_derive_fory_object(input: proc_macro::TokenStream) -> TokenStr // Check if this is being applied to a trait (which is not possible with derive macros) // Derive macros can only be applied to structs, enums, and unions - let debug_enabled = match parse_debug_flag(&input.attrs) { - Ok(flag) => flag, + let (debug_enabled, compress_int_enabled) = match parse_attributes(&input.attrs) { + Ok(result) => result, Err(err) => return err.into_compile_error().into(), }; - - object::derive_serializer(&input, debug_enabled) + object::derive_serializer(&input, debug_enabled, compress_int_enabled) } /// Derive macro for row-based serialization. @@ -245,9 +244,9 @@ pub fn proc_macro_derive_fory_row(input: proc_macro::TokenStream) -> TokenStream derive_row(&input) } -fn parse_debug_flag(attrs: &[Attribute]) -> syn::Result { +fn parse_attributes(attrs: &[Attribute]) -> syn::Result<(bool, bool)> { let mut debug_flag: Option = None; - + let mut compress_int_flag: Option = None; for attr in attrs { if attr.path().is_ident("fory") { attr.parse_nested_meta(|meta| { @@ -268,11 +267,34 @@ fn parse_debug_flag(attrs: &[Attribute]) -> syn::Result { Some(_) => debug_flag, None => Some(value), }; + Ok(()) + } else if meta.path.is_ident("compress_int") { + let value = if meta.input.is_empty() { + true + } else { + let lit: LitBool = meta.value()?.parse()?; + lit.value + }; + compress_int_flag = match compress_int_flag { + Some(existing) if existing != value => { + return Err(syn::Error::new( + meta.path.span(), + "conflicting `compress_int` attribute values", + )); + } + Some(_) => compress_int_flag, + None => Some(value), + }; + Ok(()) + } else { + let ident = meta.path.get_ident().unwrap().to_string(); + Err(meta.error(format!("unsupported property {ident}"))) } - Ok(()) })?; } } - - Ok(debug_flag.unwrap_or(false)) + Ok(( + debug_flag.unwrap_or(false), + compress_int_flag.unwrap_or(true), + )) } diff --git a/rust/fory-derive/src/object/read.rs b/rust/fory-derive/src/object/read.rs index 015f2736c4..174d74e00a 100644 --- a/rust/fory-derive/src/object/read.rs +++ b/rust/fory-derive/src/object/read.rs @@ -190,10 +190,21 @@ pub fn gen_read_field(field: &Field, private_ident: &Ident) -> TokenStream { // Check if this is a direct primitive numeric type that can use direct reader calls if is_direct_primitive_numeric_type(ty) { let type_name = extract_type_name(ty); - let reader_method = get_primitive_reader_method(&type_name); - let reader_ident = syn::Ident::new(reader_method, proc_macro2::Span::call_site()); - quote! { - let #private_ident = context.reader.#reader_ident()?; + if type_name == "i32" { + quote! { + let #private_ident = if context.get_type_resolver().is_compress_int() { + context.reader.read_varint32()?; + } else { + context.reader.read_i32()?; + } + } + } else { + let reader_method = get_primitive_reader_method(&type_name); + let reader_ident = + syn::Ident::new(reader_method, proc_macro2::Span::call_site()); + quote! { + let #private_ident = context.reader.#reader_ident()?; + } } } else if skip_type_info { // Known types (primitives, strings, collections) - skip type info at compile time diff --git a/rust/fory-derive/src/object/serializer.rs b/rust/fory-derive/src/object/serializer.rs index ee5fb72018..5322168920 100644 --- a/rust/fory-derive/src/object/serializer.rs +++ b/rust/fory-derive/src/object/serializer.rs @@ -36,10 +36,14 @@ fn has_existing_default(ast: &syn::DeriveInput, trait_name: &str) -> bool { }) } -pub fn derive_serializer(ast: &syn::DeriveInput, debug_enabled: bool) -> TokenStream { +pub fn derive_serializer( + ast: &syn::DeriveInput, + debug_enabled: bool, + compress_int_enabled: bool, +) -> TokenStream { let name = &ast.ident; use crate::object::util::{clear_struct_context, set_struct_context}; - set_struct_context(&name.to_string(), debug_enabled); + set_struct_context(&name.to_string(), debug_enabled, compress_int_enabled); // Check if ForyDefault is already derived/implemented let has_existing_default = has_existing_default(ast, "ForyDefault"); @@ -169,6 +173,11 @@ pub fn derive_serializer(ast: &syn::DeriveInput, debug_enabled: bool) -> TokenSt fn fory_read_compatible(context: &mut fory_core::resolver::context::ReadContext, type_info: std::rc::Rc) -> Result { #read_compatible_ts } + + #[inline(always)] + fn fory_is_compress_int() -> bool { + #compress_int_enabled + } } impl fory_core::Serializer for #name { diff --git a/rust/fory-derive/src/object/util.rs b/rust/fory-derive/src/object/util.rs index 2b50a73935..8a74e2b9d9 100644 --- a/rust/fory-derive/src/object/util.rs +++ b/rust/fory-derive/src/object/util.rs @@ -36,13 +36,15 @@ thread_local! { struct MacroContext { struct_name: String, debug_enabled: bool, + compress_int_enabled: bool, } -pub(super) fn set_struct_context(name: &str, debug_enabled: bool) { +pub(super) fn set_struct_context(name: &str, debug_enabled: bool, compress_int_enabled: bool) { MACRO_CONTEXT.with(|ctx| { *ctx.borrow_mut() = Some(MacroContext { struct_name: name.to_string(), debug_enabled, + compress_int_enabled, }); }); } @@ -66,6 +68,15 @@ pub(super) fn is_debug_enabled() -> bool { }) } +pub(super) fn is_compress_int_enabled() -> bool { + MACRO_CONTEXT.with(|ctx: &RefCell>| { + ctx.borrow() + .as_ref() + .map(|c| c.compress_int_enabled) + .unwrap_or(false) + }) +} + pub(super) fn contains_trait_object(ty: &Type) -> bool { match ty { Type::TraitObject(_) => true, @@ -538,7 +549,13 @@ fn get_primitive_type_id(ty: &str) -> u32 { "bool" => TypeId::BOOL as u32, "i8" => TypeId::INT8 as u32, "i16" => TypeId::INT16 as u32, - "i32" => TypeId::INT32 as u32, + "i32" => { + (if is_compress_int_enabled() { + TypeId::VAR_INT32 + } else { + TypeId::INT32 + }) as u32 + } "i64" => TypeId::INT64 as u32, "f32" => TypeId::FLOAT32 as u32, "f64" => TypeId::FLOAT64 as u32, @@ -562,7 +579,7 @@ static PRIMITIVE_IO_METHODS: &[(&str, &str, &str)] = &[ ("bool", "write_bool", "read_bool"), ("i8", "write_i8", "read_i8"), ("i16", "write_i16", "read_i16"), - ("i32", "write_varint32", "read_varint32"), + // ("i32", "write_varint32", "read_varint32"), ("i64", "write_varint64", "read_varint64"), ("f32", "write_f32", "read_f32"), ("f64", "write_f64", "read_f64"), diff --git a/rust/fory-derive/src/object/write.rs b/rust/fory-derive/src/object/write.rs index 4f95f766e0..9a1a21bf46 100644 --- a/rust/fory-derive/src/object/write.rs +++ b/rust/fory-derive/src/object/write.rs @@ -190,8 +190,6 @@ pub fn gen_write_field(field: &Field, ident: &Ident, use_self: bool) -> TokenStr // Check if this is a direct primitive numeric type that can use direct writer calls if is_direct_primitive_numeric_type(ty) { let type_name = extract_type_name(ty); - let writer_method = get_primitive_writer_method(&type_name); - let writer_ident = syn::Ident::new(writer_method, proc_macro2::Span::call_site()); // For primitives: // - use_self=true: #value_ts is `self.field`, which is T (copy happens automatically) // - use_self=false: #value_ts is `field` from pattern match on &self, which is &T @@ -200,8 +198,21 @@ pub fn gen_write_field(field: &Field, ident: &Ident, use_self: bool) -> TokenStr } else { quote! { *#value_ts } }; - quote! { - context.writer.#writer_ident(#value_expr); + if type_name == "i32" { + quote! { + if context.get_type_resolver().is_compress_int() { + context.writer.write_varint32(#value_expr); + } else { + context.writer.write_i32(#value_expr); + } + } + } else { + let writer_method = get_primitive_writer_method(&type_name); + let writer_ident: Ident = + syn::Ident::new(writer_method, proc_macro2::Span::call_site()); + quote! { + context.writer.#writer_ident(#value_expr); + } } } else if type_id == TypeId::LIST as u32 || type_id == TypeId::SET as u32 diff --git a/rust/tests/tests/test_compress.rs b/rust/tests/tests/test_compress.rs new file mode 100644 index 0000000000..07864c4261 --- /dev/null +++ b/rust/tests/tests/test_compress.rs @@ -0,0 +1,146 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use std::panic::{catch_unwind, AssertUnwindSafe}; + +use fory_core::{Fory, Reader}; +use fory_derive::ForyObject; + +#[test] +pub fn test_i32() { + #[derive(ForyObject, Debug, PartialEq)] + #[fory(compress_int = false)] + struct Item { + f1: i32, + f2: [i32; 1], + } + #[derive(ForyObject, Debug, PartialEq)] + struct ItemCompressed { + f1: i32, + f2: [i32; 1], + } + let f1: i32 = 13; + // `primitive_array` is not related to compression; just for testing. + let primitive_array: [i32; 1] = [100]; + let item = Item { + f1, + f2: primitive_array, + }; + let item_compressed = ItemCompressed { + f1, + f2: primitive_array, + }; + for compress_int in [true, false] { + let mut fory = Fory::default(); + if compress_int { + fory.register::(100).unwrap(); + assert!(fory.is_compress_int()); + } else { + fory.register::(100).unwrap(); + assert!(!fory.is_compress_int()); + }; + let mut buf = Vec::new(); + fory.serialize_to(&f1, &mut buf).unwrap(); + fory.serialize_to(&primitive_array, &mut buf).unwrap(); + if compress_int { + fory.serialize_to(&item_compressed, &mut buf).unwrap(); + } else { + fory.serialize_to(&item, &mut buf).unwrap(); + } + + let mut reader = Reader::new(buf.as_slice()); + let new_f1: i32 = fory.deserialize_from(&mut reader).unwrap(); + assert_eq!(f1, new_f1); + let new_primitive_array: [i32; 1] = fory.deserialize_from(&mut reader).unwrap(); + assert_eq!(primitive_array, new_primitive_array); + if compress_int { + let new_item: ItemCompressed = fory.deserialize_from(&mut reader).unwrap(); + assert_eq!(item_compressed, new_item); + } else { + let new_item: Item = fory.deserialize_from(&mut reader).unwrap(); + assert_eq!(item, new_item); + } + } +} + +#[test] +fn test_inconsistent() { + #[derive(ForyObject)] + #[fory(compress_int = true)] + struct Item1 {} + + #[derive(ForyObject)] + #[fory(compress_int = false)] + struct Item2 {} + + let mut fory = Fory::default(); + fory.register::(100).unwrap(); + assert!(fory.register::(101).is_err()); +} + +#[test] +fn test_inconsistent_with_enum() { + #[derive(ForyObject)] + #[fory(compress_int = true)] + struct Item1 {} + + #[derive(ForyObject)] + #[fory(compress_int = false)] + enum Item2 { + Red, + } + + let mut fory = Fory::default(); + fory.register::(100).unwrap(); + assert!(fory.register::(101).is_err()); +} + +#[test] +pub fn test_i32_no_struct() { + let f1: i32 = 13; + // `primitive_array` is not related to compression; just for testing. + let primitive_array: [i32; 1] = [100]; + for compress_int in [true, false] { + // Without a struct, the `compress_int` of the fory object will not be obtained from the struct’s `compress_int`. + // In this case, users can manually set it using Fory#compress_int(bool) + let fory = Fory::default().compress_int(compress_int); + assert_eq!(fory.is_compress_int(), compress_int); + let mut buf = Vec::new(); + fory.serialize_to(&f1, &mut buf).unwrap(); + fory.serialize_to(&primitive_array, &mut buf).unwrap(); + + let mut reader = Reader::new(buf.as_slice()); + let new_f1: i32 = fory.deserialize_from(&mut reader).unwrap(); + assert_eq!(f1, new_f1); + let new_primitive_array: [i32; 1] = fory.deserialize_from(&mut reader).unwrap(); + assert_eq!(primitive_array, new_primitive_array); + } +} + +#[test] +pub fn test_i32_conflict() { + #[derive(ForyObject)] + #[fory(compress_int = true)] + struct Item1 {} + + let mut fory = Fory::default(); + fory.register::(100).unwrap(); + let result = catch_unwind(AssertUnwindSafe(|| { + fory.compress_int(false); + })); + assert!(result.is_err()); +} diff --git a/rust/tests/tests/test_cross_language.rs b/rust/tests/tests/test_cross_language.rs index fa55ac01d8..a0c90246ef 100644 --- a/rust/tests/tests/test_cross_language.rs +++ b/rust/tests/tests/test_cross_language.rs @@ -737,3 +737,42 @@ fn test_struct_version_check() { assert_eq!(new_local_obj, local_obj); fs::write(&data_file_path, new_bytes).unwrap(); } + +#[test] +#[ignore] +fn test_compress_int() { + let data_file_path = get_data_file(); + let bytes = fs::read(&data_file_path).unwrap(); + let mut reader = Reader::new(bytes.as_slice()); + #[derive(ForyObject, PartialEq, Debug)] + #[fory(compress_int = false, debug = false)] + struct Item { + f1: i32, + } + #[derive(ForyObject, PartialEq, Debug)] + #[fory(compress_int = true, debug)] + struct ItemCompressed { + f1: i32, + } + let mut fory = Fory::default().xlang(true).check_struct_version(true); + fory.register::(100).unwrap(); + let mut fory_compressed = Fory::default().xlang(true).check_struct_version(true); + fory_compressed.register::(101).unwrap(); + + let item = Item { f1: 42 }; + let item_compressed = ItemCompressed { f1: 43 }; + assert_eq!(fory.deserialize_from::(&mut reader).unwrap(), item); + assert_eq!( + fory_compressed + .deserialize_from::(&mut reader) + .unwrap(), + item_compressed + ); + + let mut buf: Vec = Vec::new(); + fory.serialize_to(&item, &mut buf).unwrap(); + fory_compressed + .serialize_to(&item_compressed, &mut buf) + .unwrap(); + fs::write(&data_file_path, buf).unwrap(); +}