Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ cargo clippy --all-targets --all-features -- -D warnings
# Run tests (requires test features)
cargo test --features tests

# run specific test
cargo test -p fory-tests --test $test_file $test_method

# Format code
cargo fmt

Expand Down
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ go test -v fory_xlang_test.go
```bash
cd rust
cargo test
# run test with specific test file and method
cargo test -p fory-tests --test $test_file $test_method
```

### JavaScript
Expand Down
2 changes: 2 additions & 0 deletions docs/guide/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ cd rust
cargo build
# run test
cargo test
# run specific test
cargo test -p fory-tests --test $test_file $test_method
```

#### Environment Requirements
Expand Down
1 change: 1 addition & 0 deletions rust/fory-core/src/fory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ impl Fory {
if bytes_to_skip > 0 {
context.reader.skip(bytes_to_skip as u32);
}
context.ref_reader.resolve_callbacks();
result
}

Expand Down
2 changes: 1 addition & 1 deletion rust/fory-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,4 @@ pub mod util;
// Re-export paste for use in macros
pub use paste;

// Trait object macros are available at crate root due to #[macro_export]
pub use crate::serializer::weak::{ArcWeak, RcWeak};
67 changes: 60 additions & 7 deletions rust/fory-core/src/resolver/ref_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,15 @@ pub struct RefWriter {
next_ref_id: u32,
}

type UpdateCallback = Box<dyn FnOnce(&RefReader)>;

impl RefWriter {
/// Creates a new RefWriter instance.
pub fn new() -> Self {
Self::default()
}

/// Attempt to write a reference for an Rc<T>.
/// Attempt to write a reference for an `Rc<T>`.
///
/// Returns true if a reference was written (indicating this object has been
/// seen before), false if this is the first occurrence and the object should
Expand All @@ -80,12 +82,10 @@ impl RefWriter {
let ptr_addr = Rc::as_ptr(rc) as *const () as usize;

if let Some(&ref_id) = self.refs.get(&ptr_addr) {
// This object has been seen before, write a reference
writer.write_i8(RefFlag::Ref as i8);
writer.write_u32(ref_id);
true
} else {
// First time seeing this object, register it and return false
let ref_id = self.next_ref_id;
self.next_ref_id += 1;
self.refs.insert(ptr_addr, ref_id);
Expand All @@ -94,7 +94,7 @@ impl RefWriter {
}
}

/// Attempt to write a reference for an Arc<T>.
/// Attempt to write a reference for an `Arc<T>`.
///
/// Returns true if a reference was written (indicating this object has been
/// seen before), false if this is the first occurrence and the object should
Expand Down Expand Up @@ -163,6 +163,8 @@ impl RefWriter {
pub struct RefReader {
/// Vector to store boxed objects for reference resolution
refs: Vec<Box<dyn Any>>,
/// Callbacks to execute when references are resolved
callbacks: Vec<UpdateCallback>,
}

impl RefReader {
Expand All @@ -171,7 +173,26 @@ impl RefReader {
Self::default()
}

/// Store an Rc<T> for later reference resolution during deserialization.
/// Reserve a reference ID slot without storing anything yet.
///
/// Returns the reserved reference ID that will be used when storing the object later.
pub fn reserve_ref_id(&mut self) -> u32 {
let ref_id = self.refs.len() as u32;
self.refs.push(Box::new(()));
ref_id
}

/// Store an `Rc<T>` at a previously reserved reference ID.
///
/// # Arguments
///
/// * `ref_id` - The reference ID that was reserved
/// * `rc` - The Rc to store
pub fn store_rc_ref_at<T: 'static + ?Sized>(&mut self, ref_id: u32, rc: Rc<T>) {
self.refs[ref_id as usize] = Box::new(rc);
}

/// Store an `Rc<T>` for later reference resolution during deserialization.
///
/// # Arguments
///
Expand All @@ -186,7 +207,17 @@ impl RefReader {
ref_id
}

/// Store an Arc<T> for later reference resolution during deserialization.
/// Store an `Arc<T>` at a previously reserved reference ID.
///
/// # Arguments
///
/// * `ref_id` - The reference ID that was reserved
/// * `arc` - The Arc to store
pub fn store_arc_ref_at<T: 'static + ?Sized>(&mut self, ref_id: u32, arc: Arc<T>) {
self.refs[ref_id as usize] = Box::new(arc);
}

/// Store an `Arc<T>` for later reference resolution during deserialization.
///
/// # Arguments
///
Expand Down Expand Up @@ -231,6 +262,15 @@ impl RefReader {
any_box.downcast_ref::<Arc<T>>().cloned()
}

/// Add a callback to be executed when weak references are resolved.
///
/// # Arguments
///
/// * `callback` - A closure that takes a reference to the RefReader
pub fn add_callback(&mut self, callback: UpdateCallback) {
self.callbacks.push(callback);
}

/// Read a reference flag and determine what action to take.
///
/// # Arguments
Expand Down Expand Up @@ -268,10 +308,23 @@ impl RefReader {
reader.read_u32()
}

/// Clear all stored references.
/// Execute all pending callbacks to resolve weak pointer references.
///
/// This should be called after deserialization completes to update any weak pointers
/// that referenced objects which were not yet available during deserialization.
pub fn resolve_callbacks(&mut self) {
let callbacks = std::mem::take(&mut self.callbacks);
for callback in callbacks {
callback(self);
}
}

/// Clear all stored references and callbacks.
///
/// This is useful for reusing the RefReader for multiple deserialization operations.
pub fn clear(&mut self) {
self.resolve_callbacks();
self.refs.clear();
self.callbacks.clear();
}
}
46 changes: 33 additions & 13 deletions rust/fory-core/src/serializer/arc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,28 @@ use anyhow::anyhow;
use std::sync::Arc;

impl<T: Serializer + ForyDefault + Send + Sync + 'static> Serializer for Arc<T> {
fn fory_read_data(context: &mut ReadContext, is_field: bool) -> Result<Self, Error> {
fn fory_is_shared_ref() -> bool {
true
}

fn fory_write(&self, context: &mut WriteContext, is_field: bool) {
if !context.ref_writer.try_write_arc_ref(context.writer, self) {
T::fory_write_data(self.as_ref(), context, is_field);
}
}

fn fory_write_data(&self, context: &mut WriteContext, is_field: bool) {
// When Arc is nested inside another shared ref (like Rc<Arc<T>>),
// the outer ref calls fory_write_data on the inner Arc.
// We still need to track the Arc's own references here.
self.fory_write(context, is_field);
}

fn fory_write_type_info(context: &mut WriteContext, is_field: bool) {
T::fory_write_type_info(context, is_field);
}

fn fory_read(context: &mut ReadContext, is_field: bool) -> Result<Self, Error> {
let ref_flag = context.ref_reader.read_ref_flag(&mut context.reader);

match ref_flag {
Expand All @@ -41,30 +62,29 @@ impl<T: Serializer + ForyDefault + Send + Sync + 'static> Serializer for Arc<T>
Ok(Arc::new(inner))
}
RefFlag::RefValue => {
let ref_id = context.ref_reader.reserve_ref_id();
let inner = T::fory_read_data(context, is_field)?;
let arc = Arc::new(inner);
context.ref_reader.store_arc_ref(arc.clone());
context.ref_reader.store_arc_ref_at(ref_id, arc.clone());
Ok(arc)
}
}
}

fn fory_read_type_info(context: &mut ReadContext, is_field: bool) {
T::fory_read_type_info(context, is_field);
}

fn fory_write_data(&self, context: &mut WriteContext, is_field: bool) {
if !context.ref_writer.try_write_arc_ref(context.writer, self) {
T::fory_write_data(self.as_ref(), context, is_field);
}
fn fory_read_data(context: &mut ReadContext, is_field: bool) -> Result<Self, Error> {
// When Arc is nested inside another shared ref, fory_read_data is called.
// Delegate to fory_read which handles ref tracking properly.
Self::fory_read(context, is_field)
}

fn fory_write_type_info(context: &mut WriteContext, is_field: bool) {
T::fory_write_type_info(context, is_field);
fn fory_read_type_info(context: &mut ReadContext, is_field: bool) {
T::fory_read_type_info(context, is_field);
}

fn fory_reserved_space() -> usize {
T::fory_reserved_space()
// Arc is a shared ref, so we just need space for the ref tracking
// We don't recursively compute inner type's space to avoid infinite recursion
4
}

fn fory_get_type_id(fory: &Fory) -> u32 {
Expand Down
9 changes: 5 additions & 4 deletions rust/fory-core/src/serializer/collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,15 @@ pub fn write_collection<'a, T: Serializer + 'a, I: IntoIterator<Item = &'a T>>(
context.writer.write_u8(header);
T::fory_write_type_info(context, is_field);
// context.writer.reserve((T::reserved_space() + SIZE_OF_REF_AND_TYPE) * len);
if T::fory_is_polymorphic() {
if T::fory_is_polymorphic() || T::fory_is_shared_ref() {
// TOTO: make it xlang compatible
for item in &items {
item.fory_write(context, is_field);
}
} else {
// let skip_ref_flag = crate::serializer::get_skip_ref_flag::<T>(context.get_fory());
let skip_ref_flag = is_same_type && !has_null;
for item in &items {
// let skip_ref_flag = crate::serializer::get_skip_ref_flag::<T>(context.get_fory());
let skip_ref_flag = is_same_type && !has_null;
crate::serializer::write_ref_info_data(*item, context, is_field, skip_ref_flag, true);
}
}
Expand Down Expand Up @@ -119,7 +120,7 @@ where
T::fory_read_type_info(context, declared);
let has_null = (header & HAS_NULL) != 0;
let is_same_type = (header & IS_SAME_TYPE) != 0;
if T::fory_is_polymorphic() {
if T::fory_is_polymorphic() || T::fory_is_shared_ref() {
(0..len)
.map(|_| T::fory_read(context, declared))
.collect::<Result<C, Error>>()
Expand Down
4 changes: 2 additions & 2 deletions rust/fory-core/src/serializer/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,12 +137,12 @@ impl<K: Serializer + ForyDefault + Eq + std::hash::Hash, V: Serializer + ForyDef
check_and_write_null(context, is_field, key, value);
continue;
}
if K::fory_is_polymorphic() {
if K::fory_is_polymorphic() || K::fory_is_shared_ref() {
key.fory_write(context, is_field);
} else {
write_ref_info_data(key, context, is_field, skip_key_ref_flag, true);
}
if V::fory_is_polymorphic() {
if V::fory_is_polymorphic() || V::fory_is_shared_ref() {
value.fory_write(context, is_field);
} else {
write_ref_info_data(value, context, is_field, skip_val_ref_flag, true);
Expand Down
10 changes: 10 additions & 0 deletions rust/fory-core/src/serializer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,18 @@ mod datetime;
pub mod enum_;
mod list;
pub mod map;
mod mutex;
mod number;
mod option;
mod primitive_list;
mod rc;
mod refcell;
mod set;
pub mod skip;
mod string;
pub mod struct_;
pub mod trait_object;
pub mod weak;

pub fn write_ref_info_data<T: Serializer + 'static>(
record: &T,
Expand Down Expand Up @@ -167,6 +170,13 @@ pub trait Serializer: 'static {
false
}

fn fory_is_shared_ref() -> bool
where
Self: Sized,
{
false
}

fn fory_get_type_id(fory: &Fory) -> u32
where
Self: Sized,
Expand Down
Loading
Loading