Skip to content

Commit

Permalink
feat(Rust): Support polymorphism (#1795)
Browse files Browse the repository at this point in the history
## What does this PR do?
1. Support polymorphism
Add a class resolver which is used for managing the relationship between
type_id and serializer. The serializer is generated by a generic
function which will be called by dynamic dispatch. When a struct uses
`Box<dyn Any>` as a field type and does not specify the specific type,
we will use the class resolver to load the handler by type_id. In this
situation, there is some performance overhead due to hash lookup, but it
greatly improves convenience.
Use as follow:
```Rust
#[test]
fn any() {
    #[derive(Fury, Debug)]
    struct Animal {
        f3: String,
    }

    #[derive(Fury, Debug)]
    struct Person {
        f1: Box<dyn Any>,
    }

    let person = Person {
        f1: Box::new(Animal {
            f3: String::from("hello"),
        }),
    };

    let mut fury = Fury::default();
    fury.register::<Animal>(999);
    fury.register::<Person>(1000);
    let bin = fury.serialize(&person);
    let obj: Person = fury.deserialize(&bin).expect("");
    assert_eq!(true, obj.f1.is::<Animal>())
}

```
2. Add a register  function for user to register Struct and id
3. Remove tag and hash which were generate by macro before and were
removed in our protocol now.

## TODO
1. Internal types like String、Set and Map should be registered by fury
by default and lookup by pattern match to avoid hash overhead.
2. More unit testcases.
3. Support `Box<dyn customTrait> `
  • Loading branch information
theweipeng authored Aug 8, 2024
1 parent 497fe0a commit e99b46f
Show file tree
Hide file tree
Showing 26 changed files with 437 additions and 171 deletions.
11 changes: 11 additions & 0 deletions rust/fury-core/src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,4 +247,15 @@ impl<'bf> Reader<'bf> {
self.move_next(len);
result
}

pub fn reset_cursor_to_here(&self) -> impl FnOnce(&mut Self) {
let raw_cursor = self.cursor;
move |this: &mut Self| {
this.cursor = raw_cursor;
}
}

pub fn aligned<T>(&self) -> bool {
unsafe { (self.bf.as_ptr().add(self.cursor) as usize) % std::mem::align_of::<T>() == 0 }
}
}
13 changes: 8 additions & 5 deletions rust/fury-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// specific language governing permissions and limitations
// under the License.

use super::types::{FieldType, Language};
use super::types::Language;

#[derive(thiserror::Error, Debug)]
pub enum Error {
Expand All @@ -31,17 +31,17 @@ pub enum Error {
#[error("BadRefFlag")]
BadRefFlag,

#[error("Bad FieldType; expected: {expected:?}, actual: {actial:?}")]
FieldType { expected: FieldType, actial: i16 },
#[error("Bad FieldType; expected: {expected:?}, actual: {actual:?}")]
FieldType { expected: i16, actual: i16 },

#[error("Bad timestamp; out-of-range number of milliseconds")]
NaiveDateTime,

#[error("Bad date; out-of-range")]
NaiveDate,

#[error("Schema is not consistent; expected: {expected:?}, actual: {actial:?}")]
StructHash { expected: u32, actial: u32 },
#[error("Schema is not consistent; expected: {expected:?}, actual: {actual:?}")]
StructHash { expected: u32, actual: u32 },

#[error("Bad Tag Type: {0}")]
TagType(u8),
Expand Down Expand Up @@ -75,4 +75,7 @@ pub enum Error {

#[error("Invalid character value for LOWER_UPPER_DIGIT_SPECIAL decoding: {value:?}")]
InvalidLowerUpperDigitSpecialValue { value: u8 },

#[error("Unregistered type when serializing or deserializing object of Any type: {value:?}")]
UnregisteredType { value: u32 },
}
14 changes: 13 additions & 1 deletion rust/fury-core/src/fury.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,22 @@

use crate::buffer::{Reader, Writer};
use crate::error::Error;
use crate::resolver::class_resolver::{ClassInfo, ClassResolver};
use crate::resolver::context::ReadContext;
use crate::resolver::context::WriteContext;
use crate::serializer::Serializer;
use crate::serializer::{Serializer, StructSerializer};
use crate::types::{config_flags, Language, Mode, SIZE_OF_REF_AND_TYPE};

pub struct Fury {
mode: Mode,
class_resolver: ClassResolver,
}

impl Default for Fury {
fn default() -> Self {
Fury {
mode: Mode::SchemaConsistent,
class_resolver: ClassResolver::default(),
}
}
}
Expand Down Expand Up @@ -82,4 +85,13 @@ impl Fury {
}
writer.dump()
}

pub fn get_class_resolver(&self) -> &ClassResolver {
&self.class_resolver
}

pub fn register<T: 'static + StructSerializer>(&mut self, id: u32) {
let class_info = ClassInfo::new::<T>(self, id);
self.class_resolver.register::<T>(class_info, id);
}
}
21 changes: 13 additions & 8 deletions rust/fury-core/src/meta/type_meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,17 @@ use super::meta_string::MetaStringEncoder;
use crate::buffer::{Reader, Writer};
use crate::error::Error;
use crate::meta::{Encoding, MetaStringDecoder};
use crate::types::FieldType;

//todo backward/forward compatibility
#[allow(dead_code)]
pub struct FieldInfo {
field_name: String,
field_type: FieldType,
field_id: i16,
}

impl FieldInfo {
pub fn new(field_name: &str, field_type: FieldType) -> FieldInfo {
pub fn new(field_name: &str, field_type: i16) -> FieldInfo {
FieldInfo {
field_name: field_name.to_string(),
field_type,
field_id: field_type,
}
}

Expand Down Expand Up @@ -60,7 +57,7 @@ impl FieldInfo {
.unwrap();
FieldInfo {
field_name,
field_type: FieldType::try_from(type_id).unwrap(),
field_id: type_id,
}
}

Expand All @@ -80,7 +77,7 @@ impl FieldInfo {
header |= (size << 5) as u8;
writer.u8(header);
}
writer.i16(self.field_type as i16);
writer.i16(self.field_id);
writer.bytes(encoded);
Ok(writer.dump())
}
Expand All @@ -99,6 +96,10 @@ impl TypeMetaLayer {
}
}

pub fn get_type_id(&self) -> u32 {
self.type_id
}

pub fn get_field_info(&self) -> &Vec<FieldInfo> {
&self.field_info
}
Expand Down Expand Up @@ -133,6 +134,10 @@ impl TypeMeta {
self.layers.first().unwrap().get_field_info()
}

pub fn get_type_id(&self) -> u32 {
self.layers.first().unwrap().get_type_id()
}

pub fn from_fields(type_id: u32, field_info: Vec<FieldInfo>) -> TypeMeta {
TypeMeta {
hash: 0,
Expand Down
116 changes: 116 additions & 0 deletions rust/fury-core/src/resolver/class_resolver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// 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 super::context::{ReadContext, WriteContext};
use crate::error::Error;
use crate::fury::Fury;
use crate::serializer::StructSerializer;
use std::any::TypeId;
use std::{any::Any, collections::HashMap};

pub struct Harness {
serializer: fn(&dyn Any, &mut WriteContext),
deserializer: fn(&mut ReadContext) -> Result<Box<dyn Any>, Error>,
}

impl Harness {
pub fn new(
serializer: fn(&dyn Any, &mut WriteContext),
deserializer: fn(&mut ReadContext) -> Result<Box<dyn Any>, Error>,
) -> Harness {
Harness {
serializer,
deserializer,
}
}

pub fn get_serializer(&self) -> fn(&dyn Any, &mut WriteContext) {
self.serializer
}

pub fn get_deserializer(&self) -> fn(&mut ReadContext) -> Result<Box<dyn Any>, Error> {
self.deserializer
}
}

pub struct ClassInfo {
type_def: Vec<u8>,
type_id: u32,
}

impl ClassInfo {
pub fn new<T: StructSerializer>(fury: &Fury, type_id: u32) -> ClassInfo {
ClassInfo {
type_def: T::type_def(fury),
type_id,
}
}

pub fn get_type_id(&self) -> u32 {
self.type_id
}

pub fn get_type_def(&self) -> &Vec<u8> {
&self.type_def
}
}

#[derive(Default)]
pub struct ClassResolver {
serialize_map: HashMap<u32, Harness>,
type_id_map: HashMap<TypeId, u32>,
class_info_map: HashMap<TypeId, ClassInfo>,
}

impl ClassResolver {
pub fn get_class_info(&self, type_id: TypeId) -> &ClassInfo {
self.class_info_map.get(&type_id).unwrap()
}

pub fn register<T: StructSerializer>(&mut self, class_info: ClassInfo, id: u32) {
fn serializer<T2: 'static + StructSerializer>(this: &dyn Any, context: &mut WriteContext) {
let this = this.downcast_ref::<T2>();
match this {
Some(v) => {
T2::serialize(v, context);
}
None => todo!(),
}
}

fn deserializer<T2: 'static + StructSerializer>(
context: &mut ReadContext,
) -> Result<Box<dyn Any>, Error> {
match T2::deserialize(context) {
Ok(v) => Ok(Box::new(v)),
Err(e) => Err(e),
}
}
self.type_id_map.insert(TypeId::of::<T>(), id);
self.serialize_map
.insert(id, Harness::new(serializer::<T>, deserializer::<T>));
self.class_info_map.insert(TypeId::of::<T>(), class_info);
}

pub fn get_harness_by_type(&self, type_id: TypeId) -> Option<&Harness> {
self.get_harness(*self.type_id_map.get(&type_id).unwrap())
}

pub fn get_harness(&self, id: u32) -> Option<&Harness> {
self.serialize_map.get(&id)
}
}
4 changes: 2 additions & 2 deletions rust/fury-core/src/resolver/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ impl<'se> WriteContext<'se> {
}
}

pub fn push_meta(&mut self, type_id: TypeId, type_def: &'static [u8]) -> usize {
self.meta_resolver.push(type_id, type_def)
pub fn push_meta(&mut self, type_id: TypeId) -> usize {
self.meta_resolver.push(type_id, self.fury)
}

pub fn write_meta(&mut self, offset: usize) {
Expand Down
11 changes: 8 additions & 3 deletions rust/fury-core/src/resolver/meta_resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

use crate::buffer::{Reader, Writer};
use crate::error::Error;
use crate::fury::Fury;
use crate::meta::TypeMeta;
use std::any::TypeId;
use std::collections::HashMap;
Expand Down Expand Up @@ -44,17 +45,21 @@ impl MetaReaderResolver {

#[derive(Default)]
pub struct MetaWriterResolver<'a> {
type_defs: Vec<&'a [u8]>,
type_defs: Vec<&'a Vec<u8>>,
type_id_index_map: HashMap<TypeId, usize>,
}

#[allow(dead_code)]
impl<'a> MetaWriterResolver<'a> {
pub fn push<'b: 'a>(&mut self, type_id: TypeId, type_meta_bytes: &'b [u8]) -> usize {
pub fn push<'b: 'a>(&mut self, type_id: TypeId, fury: &'a Fury) -> usize {
match self.type_id_index_map.get(&type_id) {
None => {
let index = self.type_defs.len();
self.type_defs.push(type_meta_bytes);
self.type_defs.push(
fury.get_class_resolver()
.get_class_info(type_id)
.get_type_def(),
);
self.type_id_index_map.insert(type_id, index);
index
}
Expand Down
1 change: 1 addition & 0 deletions rust/fury-core/src/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@
// specific language governing permissions and limitations
// under the License.

pub mod class_resolver;
pub mod context;
pub mod meta_resolver;
Loading

0 comments on commit e99b46f

Please sign in to comment.