This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git
The following commit(s) were added to refs/heads/main by this push:
new c11d5899e perf(c++): remove shared_ptr from type info to reduce atomic
counter cost (#2951)
c11d5899e is described below
commit c11d5899e0fa211478f65f74ee473e34d2a14b29
Author: Shawn Yang <[email protected]>
AuthorDate: Sun Nov 30 22:46:02 2025 +0800
perf(c++): remove shared_ptr from type info to reduce atomic counter cost
(#2951)
## Why?
Eliminate `shared_ptr<TypeInfo>` overhead from the hot serialization
code path. The atomic reference counting in `shared_ptr` adds
unnecessary overhead when TypeInfo objects are accessed frequently
during serialization/deserialization. By switching to raw pointers with
clear ownership semantics, we can improve performance on the critical
path.
## What does this PR do?
This PR refactors the C++ serialization library's TypeInfo ownership
model:
### TypeInfo Changes
- Changed internal fields (`type_meta`, `encoded_namespace`,
`encoded_type_name`) from `shared_ptr` to `unique_ptr`
- Added `TypeInfo::deep_clone()` method for creating deep copies
- Made `TypeInfo` non-copyable (deleted copy constructor/assignment)
### TypeResolver Storage Changes
- Changed from `map<K, shared_ptr<TypeInfo>>` to primary storage
pattern:
- Primary storage: `vector<unique_ptr<TypeInfo>>` (owns all TypeInfo
objects)
- Lookup maps: Raw pointers (`TypeInfo*`) pointing to primary storage
- Lookup methods now return:
- `const TypeInfo*` for nullable lookups (`get_type_info_by_id`,
`get_type_info_by_name`)
- `Result<const TypeInfo&>` for error-handling lookups (`get_type_info`,
`get_struct_type_info<T>`)
### Context Ownership Changes
- `WriteContext` and `ReadContext` now hold `unique_ptr<TypeResolver>`
instead of `shared_ptr`
- Contexts are created lazily after type resolver finalization with
deep-cloned resolvers
- `Fory` class uses `std::optional` for contexts to support lazy
initialization
### Code Pattern Updates
- Updated all call sites that used `FORY_TRY` with `Result<const
TypeInfo&>` to use explicit error handling (since TypeInfo is now
non-copyable)
- Updated `read_struct_fields_compatible` signature from
`shared_ptr<TypeMeta>` to `const TypeMeta*`
## Related issues
#2906
#2944
## Does this PR introduce any user-facing change?
- [x] Does this PR introduce any public API change?
- Internal API only. TypeResolver lookup methods now return raw pointers
or references instead of shared_ptr. User-facing Fory API remains
unchanged.
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
This change eliminates atomic reference counting overhead on the hot
serialization path:
- `shared_ptr` copy/destruction involves atomic increment/decrement
operations
- Raw pointer access is a simple dereference with no atomic operations
- Deep cloning happens once during context creation, not
per-serialization
Expected improvement: Reduced CPU overhead in tight serialization loops,
especially noticeable when serializing many small objects where TypeInfo
lookups are frequent relative to data size.
---
cpp/fory/serialization/context.cc | 121 ++++---------
cpp/fory/serialization/context.h | 35 ++--
cpp/fory/serialization/fory.h | 57 +++---
cpp/fory/serialization/map_serializer.h | 28 +--
cpp/fory/serialization/serialization_test.cc | 56 ++++--
cpp/fory/serialization/skip.cc | 6 +-
cpp/fory/serialization/struct_serializer.h | 47 ++---
cpp/fory/serialization/type_info.h | 20 ++-
cpp/fory/serialization/type_resolver.cc | 257 ++++++++++++++++-----------
cpp/fory/serialization/type_resolver.h | 252 ++++++++++----------------
10 files changed, 427 insertions(+), 452 deletions(-)
diff --git a/cpp/fory/serialization/context.cc
b/cpp/fory/serialization/context.cc
index 8099ff64c..9a43001ee 100644
--- a/cpp/fory/serialization/context.cc
+++ b/cpp/fory/serialization/context.cc
@@ -51,51 +51,14 @@ static const std::vector<MetaEncoding> kTypeNameEncodings =
{
MetaEncoding::LOWER_UPPER_DIGIT_SPECIAL,
MetaEncoding::FIRST_TO_LOWER_SPECIAL};
-// ============================================================================
-// encode_meta_string - Pre-encode meta strings during registration
-// ============================================================================
-
-Result<std::shared_ptr<CachedMetaString>, Error>
-encode_meta_string(const std::string &value, bool is_namespace) {
- auto result = std::make_shared<CachedMetaString>();
- result->original = value;
-
- if (value.empty()) {
- result->bytes.clear();
- result->encoding = static_cast<uint8_t>(MetaEncoding::UTF8);
- result->hash = 0;
- return result;
- }
-
- // Choose encoder and encodings based on whether this is namespace or type
- // name
- const auto &encoder = is_namespace ? kNamespaceEncoder : kTypeNameEncoder;
- const auto &encodings = is_namespace ? kPkgEncodings : kTypeNameEncodings;
-
- // Encode the string
- FORY_TRY(encoded, encoder.encode(value, encodings));
- result->bytes = std::move(encoded.bytes);
- result->encoding = static_cast<uint8_t>(encoded.encoding);
-
- // Pre-compute hash for large strings (>16 bytes)
- if (result->bytes.size() > kSmallStringThreshold) {
- int64_t hash_out[2] = {0, 0};
- MurmurHash3_x64_128(result->bytes.data(),
- static_cast<int>(result->bytes.size()), 47, hash_out);
- result->hash = hash_out[0];
- } else {
- result->hash = 0;
- }
-
- return result;
-}
+// Note: encode_meta_string is now implemented in type_resolver.cc
// ============================================================================
// WriteContext Implementation
// ============================================================================
WriteContext::WriteContext(const Config &config,
- std::shared_ptr<TypeResolver> type_resolver)
+ std::unique_ptr<TypeResolver> type_resolver)
: buffer_(), config_(&config), type_resolver_(std::move(type_resolver)),
current_dyn_depth_(0) {}
@@ -163,14 +126,7 @@ static void write_encoded_meta_string(Buffer &buffer,
Result<void, Error>
WriteContext::write_enum_typeinfo(const std::type_index &type) {
- auto type_info_result = type_resolver_->get_type_info(type);
- if (!type_info_result.ok()) {
- // Enum not registered, write plain ENUM type id
- buffer_.WriteVarUint32(static_cast<uint32_t>(TypeId::ENUM));
- return Result<void, Error>();
- }
-
- const auto &type_info = type_info_result.value();
+ FORY_TRY(type_info, type_resolver_->get_type_info(type));
uint32_t type_id = type_info->type_id;
uint32_t type_id_low = type_id & 0xff;
@@ -200,9 +156,7 @@ WriteContext::write_enum_typeinfo(const std::type_index
&type) {
Result<void, Error>
WriteContext::write_enum_typeinfo(const TypeInfo *type_info) {
if (!type_info) {
- // Enum not registered, write plain ENUM type id
- buffer_.WriteVarUint32(static_cast<uint32_t>(TypeId::ENUM));
- return Result<void, Error>();
+ return Unexpected(Error::type_error("Enum type not registered"));
}
uint32_t type_id = type_info->type_id;
@@ -237,12 +191,8 @@ WriteContext::write_any_typeinfo(uint32_t fory_type_id,
// Check if it's an internal type
if (is_internal_type(fory_type_id)) {
buffer_.WriteVarUint32(fory_type_id);
- auto type_info = type_resolver_->get_type_info_by_id(fory_type_id);
- if (!type_info) {
- return Unexpected(
- Error::type_error("Type info for internal type not found"));
- }
- return type_info.get();
+ FORY_TRY(type_info, type_resolver_->get_type_info_by_id(fory_type_id));
+ return type_info;
}
// Get type info for the concrete type
@@ -401,7 +351,7 @@ uint32_t WriteContext::get_type_id_for_cache(const
std::type_index &type_idx) {
// ============================================================================
ReadContext::ReadContext(const Config &config,
- std::shared_ptr<TypeResolver> type_resolver)
+ std::unique_ptr<TypeResolver> type_resolver)
: buffer_(nullptr), config_(&config),
type_resolver_(std::move(type_resolver)), current_dyn_depth_(0) {}
@@ -411,14 +361,14 @@ ReadContext::~ReadContext() = default;
static const MetaStringDecoder kNamespaceDecoder('.', '_');
static const MetaStringDecoder kTypeNameDecoder('$', '_');
-Result<std::shared_ptr<TypeInfo>, Error>
+Result<const TypeInfo *, Error>
ReadContext::read_enum_type_info(const std::type_index &type,
uint32_t base_type_id) {
(void)type;
return read_enum_type_info(base_type_id);
}
-Result<std::shared_ptr<TypeInfo>, Error>
+Result<const TypeInfo *, Error>
ReadContext::read_enum_type_info(uint32_t base_type_id) {
FORY_TRY(type_info, read_any_typeinfo());
uint32_t type_id_low = type_info->type_id & 0xff;
@@ -459,18 +409,24 @@ Result<size_t, Error> ReadContext::load_type_meta(int32_t
meta_offset) {
FORY_TRY(parsed_meta,
TypeMeta::from_bytes_with_header(*buffer_, meta_header));
- // Find local TypeInfo to get field_id mapping
- std::shared_ptr<TypeInfo> local_type_info = nullptr;
+ // Find local TypeInfo to get field_id mapping (optional for schema
+ // evolution)
+ const TypeInfo *local_type_info = nullptr;
if (parsed_meta->register_by_name) {
- local_type_info = type_resolver_->get_type_info_by_name(
+ auto result = type_resolver_->get_type_info_by_name(
parsed_meta->namespace_str, parsed_meta->type_name);
+ if (result.ok()) {
+ local_type_info = result.value();
+ }
} else {
- local_type_info =
- type_resolver_->get_type_info_by_id(parsed_meta->type_id);
+ auto result = type_resolver_->get_type_info_by_id(parsed_meta->type_id);
+ if (result.ok()) {
+ local_type_info = result.value();
+ }
}
// Create TypeInfo with field_ids assigned
- std::shared_ptr<TypeInfo> type_info;
+ auto type_info = std::make_unique<TypeInfo>();
if (local_type_info) {
// Have local type - assign field_ids by comparing schemas
// Note: Extension types don't have type_meta (only structs do)
@@ -478,9 +434,8 @@ Result<size_t, Error> ReadContext::load_type_meta(int32_t
meta_offset) {
TypeMeta::assign_field_ids(local_type_info->type_meta.get(),
parsed_meta->field_infos);
}
- type_info = std::make_shared<TypeInfo>();
type_info->type_id = local_type_info->type_id;
- type_info->type_meta = parsed_meta;
+ type_info->type_meta = std::move(parsed_meta);
type_info->type_def = local_type_info->type_def;
// CRITICAL: Copy the harness from the registered type_info
type_info->harness = local_type_info->harness;
@@ -490,17 +445,22 @@ Result<size_t, Error> ReadContext::load_type_meta(int32_t
meta_offset) {
type_info->register_by_name = local_type_info->register_by_name;
} else {
// No local type - create stub TypeInfo with parsed meta
- type_info = std::make_shared<TypeInfo>();
type_info->type_id = parsed_meta->type_id;
- type_info->type_meta = parsed_meta;
+ type_info->type_meta = std::move(parsed_meta);
}
+ // Get raw pointer before moving into storage
+ const TypeInfo *raw_ptr = type_info.get();
+
+ // Store in primary storage
+ owned_reading_type_infos_.push_back(std::move(type_info));
+
// Cache the parsed TypeInfo (with size limit to prevent OOM)
if (parsed_type_infos_.size() < kMaxParsedNumTypeDefs) {
- parsed_type_infos_[meta_header] = type_info;
+ parsed_type_infos_[meta_header] = raw_ptr;
}
- reading_type_infos_.push_back(type_info);
+ reading_type_infos_.push_back(raw_ptr);
}
// Calculate size of meta section
@@ -512,7 +472,7 @@ Result<size_t, Error> ReadContext::load_type_meta(int32_t
meta_offset) {
return meta_section_size;
}
-Result<std::shared_ptr<TypeInfo>, Error>
+Result<const TypeInfo *, Error>
ReadContext::get_type_info_by_index(size_t index) const {
if (index >= reading_type_infos_.size()) {
return Unexpected(Error::invalid(
@@ -522,7 +482,7 @@ ReadContext::get_type_info_by_index(size_t index) const {
return reading_type_infos_[index];
}
-Result<std::shared_ptr<TypeInfo>, Error> ReadContext::read_any_typeinfo() {
+Result<const TypeInfo *, Error> ReadContext::read_any_typeinfo() {
FORY_TRY(type_id, buffer_->ReadVarUint32());
uint32_t type_id_low = type_id & 0xff;
@@ -544,21 +504,13 @@ Result<std::shared_ptr<TypeInfo>, Error>
ReadContext::read_any_typeinfo() {
meta_string_table_.read_string(*buffer_, kNamespaceDecoder));
FORY_TRY(type_name,
meta_string_table_.read_string(*buffer_, kTypeNameDecoder));
- auto type_info =
- type_resolver_->get_type_info_by_name(namespace_str, type_name);
- if (!type_info) {
- return Unexpected(Error::type_error(
- "Name harness not found: " + namespace_str + "." + type_name));
- }
+ FORY_TRY(type_info,
+ type_resolver_->get_type_info_by_name(namespace_str, type_name));
return type_info;
}
default: {
// All types must be registered in type_resolver
- auto type_info = type_resolver_->get_type_info_by_id(type_id);
- if (!type_info) {
- return Unexpected(Error::type_error("Type not found for type_id: " +
- std::to_string(type_id)));
- }
+ FORY_TRY(type_info, type_resolver_->get_type_info_by_id(type_id));
return type_info;
}
}
@@ -568,6 +520,7 @@ void ReadContext::reset() {
ref_reader_.reset();
reading_type_infos_.clear();
parsed_type_infos_.clear();
+ owned_reading_type_infos_.clear();
current_dyn_depth_ = 0;
meta_string_table_.reset();
}
diff --git a/cpp/fory/serialization/context.h b/cpp/fory/serialization/context.h
index 5d94c4b40..9a3d0ddd1 100644
--- a/cpp/fory/serialization/context.h
+++ b/cpp/fory/serialization/context.h
@@ -76,9 +76,10 @@ private:
/// ```
class WriteContext {
public:
- /// Construct write context with configuration and shared type resolver.
+ /// Construct write context with configuration and type resolver.
+ /// Takes ownership of the type resolver.
explicit WriteContext(const Config &config,
- std::shared_ptr<TypeResolver> type_resolver);
+ std::unique_ptr<TypeResolver> type_resolver);
/// Destructor
~WriteContext();
@@ -261,7 +262,7 @@ public:
private:
Buffer buffer_;
const Config *config_;
- std::shared_ptr<TypeResolver> type_resolver_;
+ std::unique_ptr<TypeResolver> type_resolver_;
RefWriter ref_writer_;
uint32_t current_dyn_depth_;
@@ -292,9 +293,10 @@ private:
/// ```
class ReadContext {
public:
- /// Construct read context with configuration and shared type resolver.
+ /// Construct read context with configuration and type resolver.
+ /// Takes ownership of the type resolver.
explicit ReadContext(const Config &config,
- std::shared_ptr<TypeResolver> type_resolver);
+ std::unique_ptr<TypeResolver> type_resolver);
/// Destructor
~ReadContext();
@@ -403,12 +405,11 @@ public:
return buffer().ReadBytes(data, length);
}
- Result<std::shared_ptr<TypeInfo>, Error>
+ Result<const TypeInfo *, Error>
read_enum_type_info(const std::type_index &type, uint32_t base_type_id);
/// Read enum type info without type_index (fast path).
- Result<std::shared_ptr<TypeInfo>, Error>
- read_enum_type_info(uint32_t base_type_id);
+ Result<const TypeInfo *, Error> read_enum_type_info(uint32_t base_type_id);
/// Load all TypeMetas from buffer at the specified offset.
/// After loading, the reader position is restored to where it was before.
@@ -416,8 +417,8 @@ public:
Result<size_t, Error> load_type_meta(int32_t meta_offset);
/// Get TypeInfo by meta index.
- Result<std::shared_ptr<TypeInfo>, Error>
- get_type_info_by_index(size_t index) const;
+ /// @return const pointer to TypeInfo if found, error otherwise
+ Result<const TypeInfo *, Error> get_type_info_by_index(size_t index) const;
/// Read type information dynamically from buffer based on type ID.
/// This mirrors Rust's read_any_typeinfo implementation.
@@ -428,8 +429,8 @@ public:
/// (as raw strings if share_meta is disabled, or meta_index if enabled)
/// - Other types: look up by type_id
///
- /// @return TypeInfo for the read type, or error
- Result<std::shared_ptr<TypeInfo>, Error> read_any_typeinfo();
+ /// @return const pointer to TypeInfo for the read type, or error
+ Result<const TypeInfo *, Error> read_any_typeinfo();
/// Reset context for reuse.
void reset();
@@ -437,13 +438,17 @@ public:
private:
Buffer *buffer_;
const Config *config_;
- std::shared_ptr<TypeResolver> type_resolver_;
+ std::unique_ptr<TypeResolver> type_resolver_;
RefReader ref_reader_;
uint32_t current_dyn_depth_;
// Meta sharing state (for compatible mode)
- std::vector<std::shared_ptr<TypeInfo>> reading_type_infos_;
- absl::flat_hash_map<int64_t, std::shared_ptr<TypeInfo>> parsed_type_infos_;
+ // Primary storage for TypeInfo objects created during deserialization
+ std::vector<std::unique_ptr<TypeInfo>> owned_reading_type_infos_;
+ // Index-based access (pointers to owned_reading_type_infos_ or
type_resolver)
+ std::vector<const TypeInfo *> reading_type_infos_;
+ // Cache by meta_header (pointers to owned_reading_type_infos_)
+ absl::flat_hash_map<int64_t, const TypeInfo *> parsed_type_infos_;
// Dynamic meta strings used for named type/class info.
meta::MetaStringTable meta_string_table_;
diff --git a/cpp/fory/serialization/fory.h b/cpp/fory/serialization/fory.h
index 4621ebf86..34f69ef38 100644
--- a/cpp/fory/serialization/fory.h
+++ b/cpp/fory/serialization/fory.h
@@ -349,8 +349,8 @@ public:
if (FORY_PREDICT_FALSE(!finalized_)) {
ensure_finalized();
}
- WriteContextGuard guard(write_ctx_);
- Buffer &buffer = write_ctx_.buffer();
+ WriteContextGuard guard(*write_ctx_);
+ Buffer &buffer = write_ctx_->buffer();
FORY_RETURN_NOT_OK(serialize_impl(obj, buffer));
@@ -429,8 +429,8 @@ public:
Error::unsupported("Cross-endian deserialization not yet
supported"));
}
- read_ctx_.attach(buffer);
- ReadContextGuard guard(read_ctx_);
+ read_ctx_->attach(buffer);
+ ReadContextGuard guard(*read_ctx_);
return deserialize_impl<T>(buffer);
}
@@ -470,8 +470,8 @@ public:
Error::unsupported("Cross-endian deserialization not yet
supported"));
}
- read_ctx_.attach(buffer);
- ReadContextGuard guard(read_ctx_);
+ read_ctx_->attach(buffer);
+ ReadContextGuard guard(*read_ctx_);
return deserialize_impl<T>(buffer);
}
@@ -483,32 +483,31 @@ public:
///
/// Use this for direct manipulation of the serialization context.
/// Most users should use the serialize() methods instead.
- WriteContext &write_context() { return write_ctx_; }
+ WriteContext &write_context() { return *write_ctx_; }
/// Access the internal ReadContext (for advanced use).
///
/// Use this for direct manipulation of the deserialization context.
/// Most users should use the deserialize() methods instead.
- ReadContext &read_context() { return read_ctx_; }
+ ReadContext &read_context() { return *read_ctx_; }
private:
/// Constructor for ForyBuilder - resolver will be finalized lazily.
explicit Fory(const Config &config, std::shared_ptr<TypeResolver> resolver)
: BaseFory(config, std::move(resolver)), finalized_(false),
precomputed_header_(compute_header(config.xlang)),
- header_length_(config.xlang ? 4 : 3),
- write_ctx_(config_, type_resolver_),
- read_ctx_(config_, type_resolver_) {}
+ header_length_(config.xlang ? 4 : 3) {}
/// Constructor for ThreadSafeFory pool - resolver is already finalized.
struct PreFinalized {};
explicit Fory(const Config &config, std::shared_ptr<TypeResolver> resolver,
PreFinalized)
- : BaseFory(config, std::move(resolver)), finalized_(true),
+ : BaseFory(config, std::move(resolver)), finalized_(false),
precomputed_header_(compute_header(config.xlang)),
- header_length_(config.xlang ? 4 : 3),
- write_ctx_(config_, type_resolver_),
- read_ctx_(config_, type_resolver_) {}
+ header_length_(config.xlang ? 4 : 3) {
+ // Pre-finalized, immediately create contexts
+ ensure_finalized();
+ }
/// Finalize the type resolver on first use.
void ensure_finalized() {
@@ -517,7 +516,13 @@ private:
FORY_CHECK(final_result.ok())
<< "Failed to build finalized TypeResolver: "
<< final_result.error().to_string();
- type_resolver_ = std::move(final_result).value();
+ // Replace with finalized resolver
+ auto finalized_resolver = std::move(final_result).value();
+ // Create contexts with cloned resolvers
+ write_ctx_.emplace(config_, finalized_resolver->clone());
+ read_ctx_.emplace(config_, finalized_resolver->clone());
+ // Store finalized resolver
+ type_resolver_ = std::move(finalized_resolver);
finalized_ = true;
}
}
@@ -553,17 +558,17 @@ private:
// Reserve space for meta offset in compatible mode
size_t meta_start_offset = 0;
- if (write_ctx_.is_compatible()) {
+ if (write_ctx_->is_compatible()) {
meta_start_offset = buffer.writer_index();
buffer.WriteInt32(-1); // Placeholder for meta offset (fixed 4 bytes)
}
// Top-level serialization: YES ref flags, yes type info
- FORY_RETURN_NOT_OK(Serializer<T>::write(obj, write_ctx_, true, true));
+ FORY_RETURN_NOT_OK(Serializer<T>::write(obj, *write_ctx_, true, true));
// Write collected TypeMetas at the end in compatible mode
- if (write_ctx_.is_compatible() && !write_ctx_.meta_empty()) {
- write_ctx_.write_meta(meta_start_offset);
+ if (write_ctx_->is_compatible() && !write_ctx_->meta_empty()) {
+ write_ctx_->write_meta(meta_start_offset);
}
return buffer.writer_index() - start_pos;
@@ -573,21 +578,21 @@ private:
template <typename T> Result<T, Error> deserialize_impl(Buffer &buffer) {
// Load TypeMetas at the beginning in compatible mode
size_t bytes_to_skip = 0;
- if (read_ctx_.is_compatible()) {
+ if (read_ctx_->is_compatible()) {
auto meta_offset_result = buffer.ReadInt32();
FORY_RETURN_IF_ERROR(meta_offset_result);
int32_t meta_offset = meta_offset_result.value();
if (meta_offset != -1) {
- FORY_TRY(meta_size, read_ctx_.load_type_meta(meta_offset));
+ FORY_TRY(meta_size, read_ctx_->load_type_meta(meta_offset));
bytes_to_skip = meta_size;
}
}
// Top-level deserialization: YES ref flags, yes type info
- auto result = Serializer<T>::read(read_ctx_, true, true);
+ auto result = Serializer<T>::read(*read_ctx_, true, true);
if (result.ok()) {
- read_ctx_.ref_reader().resolve_callbacks();
+ read_ctx_->ref_reader().resolve_callbacks();
if (bytes_to_skip > 0) {
buffer.IncreaseReaderIndex(static_cast<uint32_t>(bytes_to_skip));
}
@@ -598,8 +603,8 @@ private:
bool finalized_;
uint32_t precomputed_header_;
uint8_t header_length_;
- WriteContext write_ctx_;
- ReadContext read_ctx_;
+ std::optional<WriteContext> write_ctx_;
+ std::optional<ReadContext> read_ctx_;
friend class ForyBuilder;
friend class ThreadSafeFory;
diff --git a/cpp/fory/serialization/map_serializer.h
b/cpp/fory/serialization/map_serializer.h
index a27c69f3a..32abb9c8e 100644
--- a/cpp/fory/serialization/map_serializer.h
+++ b/cpp/fory/serialization/map_serializer.h
@@ -61,7 +61,7 @@ inline Result<void, Error> read_type_info(ReadContext &ctx) {
return Serializer<T>::read_type_info(ctx);
}
-inline Result<std::shared_ptr<TypeInfo>, Error>
+inline Result<const TypeInfo *, Error>
read_polymorphic_type_info(ReadContext &ctx) {
return ctx.read_any_typeinfo();
}
@@ -343,13 +343,15 @@ write_map_data_slow(const MapType &map, WriteContext
&ctx, bool has_generics) {
uint32_t val_type_id = 0;
if constexpr (key_is_polymorphic) {
auto concrete_type_id = get_concrete_type_id(key);
- FORY_TRY(type_info, ctx.type_resolver().get_type_info(concrete_type_id));
- key_type_id = type_info->type_id;
+ FORY_TRY(key_type_info,
+ ctx.type_resolver().get_type_info(concrete_type_id));
+ key_type_id = key_type_info->type_id;
}
if constexpr (val_is_polymorphic) {
auto concrete_type_id = get_concrete_type_id(value);
- FORY_TRY(type_info, ctx.type_resolver().get_type_info(concrete_type_id));
- val_type_id = type_info->type_id;
+ FORY_TRY(val_type_info,
+ ctx.type_resolver().get_type_info(concrete_type_id));
+ val_type_id = val_type_info->type_id;
}
// Check if we need to start a new chunk due to type changes
@@ -597,11 +599,11 @@ inline Result<MapType, Error>
read_map_data_slow(ReadContext &ctx,
}
// Now read type info if needed
- std::shared_ptr<TypeInfo> value_type_info = nullptr;
+ const TypeInfo *value_type_info = nullptr;
if (!value_declared || val_is_polymorphic) {
if constexpr (val_is_polymorphic) {
FORY_TRY(type_info, read_polymorphic_type_info(ctx));
- value_type_info = std::move(type_info);
+ value_type_info = type_info;
} else {
FORY_RETURN_NOT_OK(read_type_info<V>(ctx));
}
@@ -646,11 +648,11 @@ inline Result<MapType, Error>
read_map_data_slow(ReadContext &ctx,
}
// Now read type info if needed
- std::shared_ptr<TypeInfo> key_type_info = nullptr;
+ const TypeInfo *key_type_info = nullptr;
if (!key_declared || key_is_polymorphic) {
if constexpr (key_is_polymorphic) {
FORY_TRY(type_info, read_polymorphic_type_info(ctx));
- key_type_info = std::move(type_info);
+ key_type_info = type_info;
} else {
FORY_RETURN_NOT_OK(read_type_info<K>(ctx));
}
@@ -681,13 +683,13 @@ inline Result<MapType, Error>
read_map_data_slow(ReadContext &ctx,
bool track_value_ref = (header & TRACKING_VALUE_REF) != 0;
// Read type info if not declared
- std::shared_ptr<TypeInfo> key_type_info = nullptr;
- std::shared_ptr<TypeInfo> value_type_info = nullptr;
+ const TypeInfo *key_type_info = nullptr;
+ const TypeInfo *value_type_info = nullptr;
if (!key_declared || key_is_polymorphic) {
if constexpr (key_is_polymorphic) {
FORY_TRY(type_info, read_polymorphic_type_info(ctx));
- key_type_info = std::move(type_info);
+ key_type_info = type_info;
} else {
FORY_RETURN_NOT_OK(read_type_info<K>(ctx));
}
@@ -695,7 +697,7 @@ inline Result<MapType, Error>
read_map_data_slow(ReadContext &ctx,
if (!value_declared || val_is_polymorphic) {
if constexpr (val_is_polymorphic) {
FORY_TRY(type_info, read_polymorphic_type_info(ctx));
- value_type_info = std::move(type_info);
+ value_type_info = type_info;
} else {
FORY_RETURN_NOT_OK(read_type_info<V>(ctx));
}
diff --git a/cpp/fory/serialization/serialization_test.cc
b/cpp/fory/serialization/serialization_test.cc
index 5c708bf8c..bf9d7e17d 100644
--- a/cpp/fory/serialization/serialization_test.cc
+++ b/cpp/fory/serialization/serialization_test.cc
@@ -91,6 +91,11 @@ inline void register_test_types(Fory &fory) {
fory.register_struct<::SimpleStruct>(type_id++);
fory.register_struct<::ComplexStruct>(type_id++);
fory.register_struct<::NestedStruct>(type_id++);
+
+ // Register all enum types used in tests (register_struct works for enums
too)
+ fory.register_struct<Color>(type_id++);
+ fory.register_struct<LegacyStatus>(type_id++);
+ fory.register_struct<OldStatus>(type_id++);
}
template <typename T>
@@ -195,6 +200,7 @@ TEST(SerializationTest, OldEnumRoundtrip) {
TEST(SerializationTest, EnumSerializesOrdinalValue) {
auto fory = Fory::builder().xlang(true).track_ref(false).build();
+ fory.register_struct<LegacyStatus>(1);
auto bytes_result = fory.serialize(LegacyStatus::LARGE);
ASSERT_TRUE(bytes_result.ok())
@@ -202,48 +208,57 @@ TEST(SerializationTest, EnumSerializesOrdinalValue) {
std::vector<uint8_t> bytes = bytes_result.value();
// Xlang spec: enums are serialized as varuint32, not fixed int32_t
- // Expected: 4 (header) + 1 (ref flag) + 1 (type id) + 1 (ordinal as
- // varuint32) = 7 bytes
- ASSERT_GE(bytes.size(), 4 + 1 + 1 + 1);
+ // With registration, type_id = (1 << 8) + ENUM = 269, which takes 2 bytes as
+ // varuint32 Expected: 4 (header) + 1 (ref flag) + 2 (type id as varuint) + 1
+ // (ordinal as varuint32) = 8 bytes
+ ASSERT_GE(bytes.size(), 4 + 1 + 2 + 1);
size_t offset = 4;
EXPECT_EQ(bytes[offset], static_cast<uint8_t>(NOT_NULL_VALUE_FLAG));
- EXPECT_EQ(bytes[offset + 1], static_cast<uint8_t>(TypeId::ENUM));
+ // Type ID 269 = (1 << 8) + ENUM encoded as varuint32: 0x8D, 0x02
+ EXPECT_EQ(bytes[offset + 1], 0x8D);
+ EXPECT_EQ(bytes[offset + 2], 0x02);
// Ordinal 2 encoded as varuint32 is just 1 byte with value 2
- EXPECT_EQ(bytes[offset + 2], 2);
+ EXPECT_EQ(bytes[offset + 3], 2);
}
TEST(SerializationTest, OldEnumSerializesOrdinalValue) {
auto fory = Fory::builder().xlang(true).track_ref(false).build();
+ fory.register_struct<OldStatus>(1);
auto bytes_result = fory.serialize(OldStatus::OLD_POS);
ASSERT_TRUE(bytes_result.ok())
<< "Serialization failed: " << bytes_result.error().to_string();
std::vector<uint8_t> bytes = bytes_result.value();
- // Xlang spec: enums are serialized as varuint32, not fixed int32_t
- ASSERT_GE(bytes.size(), 4 + 1 + 1 + 1);
+ // With registration, type_id = (1 << 8) + ENUM = 269, which takes 2 bytes
+ ASSERT_GE(bytes.size(), 4 + 1 + 2 + 1);
size_t offset = 4;
EXPECT_EQ(bytes[offset], static_cast<uint8_t>(NOT_NULL_VALUE_FLAG));
- EXPECT_EQ(bytes[offset + 1], static_cast<uint8_t>(TypeId::ENUM));
+ // Type ID 269 encoded as varuint32: 0x8D, 0x02
+ EXPECT_EQ(bytes[offset + 1], 0x8D);
+ EXPECT_EQ(bytes[offset + 2], 0x02);
// Ordinal 2 encoded as varuint32 is just 1 byte with value 2
- EXPECT_EQ(bytes[offset + 2], 2);
+ EXPECT_EQ(bytes[offset + 3], 2);
}
TEST(SerializationTest, EnumOrdinalMappingHandlesNonZeroStart) {
auto fory = Fory::builder().xlang(true).track_ref(false).build();
+ fory.register_struct<LegacyStatus>(1);
auto bytes_result = fory.serialize(LegacyStatus::NEG);
ASSERT_TRUE(bytes_result.ok())
<< "Serialization failed: " << bytes_result.error().to_string();
std::vector<uint8_t> bytes = bytes_result.value();
- // Xlang spec: enums are serialized as varuint32, not fixed int32_t
- ASSERT_GE(bytes.size(), 4 + 1 + 1 + 1);
+ // With registration, type_id = (1 << 8) + ENUM = 269, which takes 2 bytes
+ ASSERT_GE(bytes.size(), 4 + 1 + 2 + 1);
size_t offset = 4;
EXPECT_EQ(bytes[offset], static_cast<uint8_t>(NOT_NULL_VALUE_FLAG));
- EXPECT_EQ(bytes[offset + 1], static_cast<uint8_t>(TypeId::ENUM));
+ // Type ID 269 encoded as varuint32: 0x8D, 0x02
+ EXPECT_EQ(bytes[offset + 1], 0x8D);
+ EXPECT_EQ(bytes[offset + 2], 0x02);
// Ordinal 0 encoded as varuint32 is just 1 byte with value 0
- EXPECT_EQ(bytes[offset + 2], 0);
+ EXPECT_EQ(bytes[offset + 3], 0);
auto roundtrip = fory.deserialize<LegacyStatus>(bytes.data(), bytes.size());
ASSERT_TRUE(roundtrip.ok())
@@ -253,6 +268,7 @@ TEST(SerializationTest,
EnumOrdinalMappingHandlesNonZeroStart) {
TEST(SerializationTest, EnumOrdinalMappingRejectsInvalidOrdinal) {
auto fory = Fory::builder().xlang(true).track_ref(false).build();
+ fory.register_struct<LegacyStatus>(1);
auto bytes_result = fory.serialize(LegacyStatus::NEG);
ASSERT_TRUE(bytes_result.ok())
@@ -260,8 +276,9 @@ TEST(SerializationTest,
EnumOrdinalMappingRejectsInvalidOrdinal) {
std::vector<uint8_t> bytes = bytes_result.value();
size_t offset = 4;
+ // With registration, type_id takes 2 bytes, ordinal is at offset + 3
// Replace the valid ordinal with an invalid one (99 as varuint32)
- bytes[offset + 2] = 99;
+ bytes[offset + 3] = 99;
auto decode = fory.deserialize<LegacyStatus>(bytes.data(), bytes.size());
EXPECT_FALSE(decode.ok());
@@ -269,19 +286,22 @@ TEST(SerializationTest,
EnumOrdinalMappingRejectsInvalidOrdinal) {
TEST(SerializationTest, OldEnumOrdinalMappingHandlesNonZeroStart) {
auto fory = Fory::builder().xlang(true).track_ref(false).build();
+ fory.register_struct<OldStatus>(1);
auto bytes_result = fory.serialize(OldStatus::OLD_NEG);
ASSERT_TRUE(bytes_result.ok())
<< "Serialization failed: " << bytes_result.error().to_string();
std::vector<uint8_t> bytes = bytes_result.value();
- // Xlang spec: enums are serialized as varuint32, not fixed int32_t
- ASSERT_GE(bytes.size(), 4 + 1 + 1 + 1);
+ // With registration, type_id = (1 << 8) + ENUM = 269, which takes 2 bytes
+ ASSERT_GE(bytes.size(), 4 + 1 + 2 + 1);
size_t offset = 4;
EXPECT_EQ(bytes[offset], static_cast<uint8_t>(NOT_NULL_VALUE_FLAG));
- EXPECT_EQ(bytes[offset + 1], static_cast<uint8_t>(TypeId::ENUM));
+ // Type ID 269 encoded as varuint32: 0x8D, 0x02
+ EXPECT_EQ(bytes[offset + 1], 0x8D);
+ EXPECT_EQ(bytes[offset + 2], 0x02);
// Ordinal 0 encoded as varuint32 is just 1 byte with value 0
- EXPECT_EQ(bytes[offset + 2], 0);
+ EXPECT_EQ(bytes[offset + 3], 0);
auto roundtrip = fory.deserialize<OldStatus>(bytes.data(), bytes.size());
ASSERT_TRUE(roundtrip.ok())
diff --git a/cpp/fory/serialization/skip.cc b/cpp/fory/serialization/skip.cc
index 92626cd6b..af073b476 100644
--- a/cpp/fory/serialization/skip.cc
+++ b/cpp/fory/serialization/skip.cc
@@ -223,7 +223,7 @@ Result<void, Error> skip_ext(ReadContext &ctx, const
FieldType &field_type) {
uint32_t low = full_type_id & 0xffu;
TypeId tid = static_cast<TypeId>(low);
- std::shared_ptr<TypeInfo> type_info;
+ const TypeInfo *type_info = nullptr;
if (tid == TypeId::NAMED_EXT) {
// Named ext: read type_id and meta_index
@@ -236,7 +236,9 @@ Result<void, Error> skip_ext(ReadContext &ctx, const
FieldType &field_type) {
} else {
// ID-based ext: look up by full type_id
// The ext fields in TypeMeta store the user type_id (high bits | EXT)
- type_info = ctx.type_resolver().get_type_info_by_id(full_type_id);
+ FORY_TRY(type_info_by_id,
+ ctx.type_resolver().get_type_info_by_id(full_type_id));
+ type_info = type_info_by_id;
}
if (!type_info) {
diff --git a/cpp/fory/serialization/struct_serializer.h
b/cpp/fory/serialization/struct_serializer.h
index bca4e027b..e9b33ee3c 100644
--- a/cpp/fory/serialization/struct_serializer.h
+++ b/cpp/fory/serialization/struct_serializer.h
@@ -1106,7 +1106,7 @@ Result<void, Error> read_struct_fields_impl(T &obj,
ReadContext &ctx,
template <typename T, size_t... Indices>
Result<void, Error>
read_struct_fields_compatible(T &obj, ReadContext &ctx,
- const std::shared_ptr<TypeMeta>
&remote_type_meta,
+ const TypeMeta *remote_type_meta,
std::index_sequence<Indices...>) {
using Helpers = CompileTimeFieldHelpers<T>;
@@ -1184,14 +1184,13 @@ struct Serializer<T,
std::enable_if_t<is_fory_serializable_v<T>>> {
/// This is used by collection serializers to write element type info.
/// Matches Rust's struct_::write_type_info.
static Result<void, Error> write_type_info(WriteContext &ctx) {
- FORY_TRY(type_info, ctx.type_resolver().template
get_struct_type_info<T>());
- FORY_TRY(type_id, ctx.type_resolver().template get_type_id<T>());
- ctx.write_varuint32(type_id);
+ FORY_TRY(type_info, ctx.type_resolver().template get_type_info<T>());
+ ctx.write_varuint32(type_info->type_id);
// In compatible mode, always write meta index (matches Rust behavior)
if (ctx.is_compatible() && type_info->type_meta) {
// Use TypeInfo* overload to avoid type_index creation
- size_t meta_index = ctx.push_meta(type_info.get());
+ size_t meta_index = ctx.push_meta(type_info);
ctx.write_varuint32(static_cast<uint32_t>(meta_index));
}
return Result<void, Error>();
@@ -1215,11 +1214,7 @@ struct Serializer<T,
std::enable_if_t<is_fory_serializable_v<T>>> {
if (write_type) {
// Direct lookup using compile-time type_index<T>() - O(1) hash lookup
- auto info_result = ctx.type_resolver().template
get_struct_type_info<T>();
- if (FORY_PREDICT_FALSE(!info_result.ok())) {
- return Unexpected(info_result.error());
- }
- const TypeInfo *type_info = info_result.value().get();
+ FORY_TRY(type_info, ctx.type_resolver().template get_type_info<T>());
uint32_t tid = type_info->type_id;
// Fast path: check if this is a simple STRUCT type (no meta needed)
@@ -1237,8 +1232,7 @@ struct Serializer<T,
std::enable_if_t<is_fory_serializable_v<T>>> {
static Result<void, Error> write_data(const T &obj, WriteContext &ctx) {
if (ctx.check_struct_version()) {
- FORY_TRY(type_info,
- ctx.type_resolver().template get_struct_type_info<T>());
+ FORY_TRY(type_info, ctx.type_resolver().template get_type_info<T>());
if (!type_info->type_meta) {
return Unexpected(Error::type_error(
"Type metadata not initialized for requested struct"));
@@ -1257,8 +1251,7 @@ struct Serializer<T,
std::enable_if_t<is_fory_serializable_v<T>>> {
static Result<void, Error> write_data_generic(const T &obj, WriteContext
&ctx,
bool has_generics) {
if (ctx.check_struct_version()) {
- FORY_TRY(type_info,
- ctx.type_resolver().template get_struct_type_info<T>());
+ FORY_TRY(type_info, ctx.type_resolver().template get_type_info<T>());
if (!type_info->type_meta) {
return Unexpected(Error::type_error(
"Type metadata not initialized for requested struct"));
@@ -1307,9 +1300,8 @@ struct Serializer<T,
std::enable_if_t<is_fory_serializable_v<T>>> {
// Check LOCAL type to decide if we should read meta_index (matches
// Rust logic)
FORY_TRY(local_type_info,
- ctx.type_resolver().template get_struct_type_info<T>());
- uint32_t local_type_id =
- ctx.type_resolver().get_type_id(*local_type_info);
+ ctx.type_resolver().template get_type_info<T>());
+ uint32_t local_type_id = local_type_info->type_id;
uint8_t local_type_id_low = local_type_id & 0xff;
if (local_type_id_low ==
@@ -1347,11 +1339,8 @@ struct Serializer<T,
std::enable_if_t<is_fory_serializable_v<T>>> {
// the expected static type.
if (read_type) {
// Direct lookup using compile-time type_index<T>() - O(1) hash
lookup
- auto type_id_result = ctx.type_resolver().template get_type_id<T>();
- if (FORY_PREDICT_FALSE(!type_id_result.ok())) {
- return Unexpected(std::move(type_id_result).error());
- }
- uint32_t expected_type_id = type_id_result.value();
+ FORY_TRY(type_info, ctx.type_resolver().template get_type_info<T>());
+ uint32_t expected_type_id = type_info->type_id;
// FAST PATH: For simple numeric type IDs (not named types), we can
// just read the varint and compare directly without hash lookup.
@@ -1395,14 +1384,13 @@ struct Serializer<T,
std::enable_if_t<is_fory_serializable_v<T>>> {
}
}
- static Result<T, Error>
- read_compatible(ReadContext &ctx,
- std::shared_ptr<TypeInfo> remote_type_info) {
+ static Result<T, Error> read_compatible(ReadContext &ctx,
+ const TypeInfo *remote_type_info) {
// Read and verify struct version if enabled (matches write_data behavior)
if (ctx.check_struct_version()) {
FORY_TRY(read_version, ctx.buffer().ReadInt32());
FORY_TRY(local_type_info,
- ctx.type_resolver().template get_struct_type_info<T>());
+ ctx.type_resolver().template get_type_info<T>());
if (!local_type_info->type_meta) {
return Unexpected(Error::type_error(
"Type metadata not initialized for requested struct"));
@@ -1425,7 +1413,7 @@ struct Serializer<T,
std::enable_if_t<is_fory_serializable_v<T>>> {
// Use remote TypeMeta for schema evolution - field IDs already assigned
FORY_RETURN_NOT_OK(detail::read_struct_fields_compatible(
- obj, ctx, remote_type_info->type_meta,
+ obj, ctx, remote_type_info->type_meta.get(),
std::make_index_sequence<field_count>{}));
return obj;
@@ -1435,7 +1423,7 @@ struct Serializer<T,
std::enable_if_t<is_fory_serializable_v<T>>> {
if (ctx.check_struct_version()) {
FORY_TRY(read_version, ctx.buffer().ReadInt32());
FORY_TRY(local_type_info,
- ctx.type_resolver().template get_struct_type_info<T>());
+ ctx.type_resolver().template get_type_info<T>());
if (!local_type_info->type_meta) {
return Unexpected(Error::type_error(
"Type metadata not initialized for requested struct"));
@@ -1469,8 +1457,7 @@ struct Serializer<T,
std::enable_if_t<is_fory_serializable_v<T>>> {
// In compatible mode with type info provided, use schema evolution path
if (ctx.is_compatible() && type_info.type_meta) {
- auto remote_type_info = std::make_shared<TypeInfo>(type_info);
- return read_compatible(ctx, remote_type_info);
+ return read_compatible(ctx, &type_info);
}
// Otherwise use normal read path
diff --git a/cpp/fory/serialization/type_info.h
b/cpp/fory/serialization/type_info.h
index 0715ca763..9f34a8d66 100644
--- a/cpp/fory/serialization/type_info.h
+++ b/cpp/fory/serialization/type_info.h
@@ -106,15 +106,29 @@ struct TypeInfo {
std::string type_name;
bool register_by_name = false;
bool is_external = false;
- std::shared_ptr<TypeMeta> type_meta;
+ std::unique_ptr<TypeMeta> type_meta;
std::vector<size_t> sorted_indices;
absl::flat_hash_map<std::string, size_t> name_to_index;
std::vector<uint8_t> type_def;
Harness harness;
// Pre-encoded meta strings for efficient writing (avoids re-encoding on each
// write)
- std::shared_ptr<CachedMetaString> encoded_namespace;
- std::shared_ptr<CachedMetaString> encoded_type_name;
+ std::unique_ptr<CachedMetaString> encoded_namespace;
+ std::unique_ptr<CachedMetaString> encoded_type_name;
+
+ TypeInfo() = default;
+
+ // Non-copyable due to unique_ptr members
+ TypeInfo(const TypeInfo &) = delete;
+ TypeInfo &operator=(const TypeInfo &) = delete;
+
+ // Movable
+ TypeInfo(TypeInfo &&) = default;
+ TypeInfo &operator=(TypeInfo &&) = default;
+
+ /// Creates a deep clone of this TypeInfo.
+ /// All unique_ptr members are cloned into new instances.
+ std::unique_ptr<TypeInfo> deep_clone() const;
};
} // namespace serialization
diff --git a/cpp/fory/serialization/type_resolver.cc
b/cpp/fory/serialization/type_resolver.cc
index 63babe58c..98f5c1341 100644
--- a/cpp/fory/serialization/type_resolver.cc
+++ b/cpp/fory/serialization/type_resolver.cc
@@ -336,7 +336,7 @@ Result<std::vector<uint8_t>, Error> TypeMeta::to_bytes()
const {
result_buffer.writer_index());
}
-Result<std::shared_ptr<TypeMeta>, Error>
+Result<std::unique_ptr<TypeMeta>, Error>
TypeMeta::from_bytes(Buffer &buffer, const TypeMeta *local_type_info) {
size_t start_pos = buffer.reader_index();
@@ -411,7 +411,7 @@ TypeMeta::from_bytes(Buffer &buffer, const TypeMeta
*local_type_info) {
buffer.IncreaseReaderIndex(remaining);
}
- auto meta = std::make_shared<TypeMeta>();
+ auto meta = std::make_unique<TypeMeta>();
meta->hash = meta_hash;
meta->type_id = type_id;
meta->namespace_str = std::move(namespace_str);
@@ -422,7 +422,7 @@ TypeMeta::from_bytes(Buffer &buffer, const TypeMeta
*local_type_info) {
return meta;
}
-Result<std::shared_ptr<TypeMeta>, Error>
+Result<std::unique_ptr<TypeMeta>, Error>
TypeMeta::from_bytes_with_header(Buffer &buffer, int64_t header) {
int64_t meta_size = header & META_SIZE_MASK;
size_t header_size = 0;
@@ -489,7 +489,7 @@ TypeMeta::from_bytes_with_header(Buffer &buffer, int64_t
header) {
buffer.IncreaseReaderIndex(remaining);
}
- auto meta = std::make_shared<TypeMeta>();
+ auto meta = std::make_unique<TypeMeta>();
meta->hash = meta_hash;
meta->type_id = type_id;
meta->namespace_str = std::move(namespace_str);
@@ -904,75 +904,87 @@ int32_t TypeMeta::compute_struct_version(const TypeMeta
&meta) {
}
// ============================================================================
-// TypeResolver::read_any_typeinfo Implementation
+// TypeInfo::deep_clone Implementation
// ============================================================================
-Result<std::shared_ptr<TypeInfo>, Error>
-TypeResolver::read_any_typeinfo(ReadContext &ctx) {
- return read_any_typeinfo(ctx, nullptr);
+std::unique_ptr<TypeInfo> TypeInfo::deep_clone() const {
+ auto cloned = std::make_unique<TypeInfo>();
+ cloned->type_id = type_id;
+ cloned->namespace_name = namespace_name;
+ cloned->type_name = type_name;
+ cloned->register_by_name = register_by_name;
+ cloned->is_external = is_external;
+ cloned->sorted_indices = sorted_indices;
+ cloned->name_to_index = name_to_index;
+ cloned->type_def = type_def;
+ cloned->harness = harness;
+
+ // Deep clone unique_ptr members
+ if (type_meta) {
+ cloned->type_meta = std::make_unique<TypeMeta>(*type_meta);
+ }
+ if (encoded_namespace) {
+ cloned->encoded_namespace = std::make_unique<CachedMetaString>();
+ cloned->encoded_namespace->original = encoded_namespace->original;
+ cloned->encoded_namespace->bytes = encoded_namespace->bytes;
+ cloned->encoded_namespace->encoding = encoded_namespace->encoding;
+ cloned->encoded_namespace->hash = encoded_namespace->hash;
+ }
+ if (encoded_type_name) {
+ cloned->encoded_type_name = std::make_unique<CachedMetaString>();
+ cloned->encoded_type_name->original = encoded_type_name->original;
+ cloned->encoded_type_name->bytes = encoded_type_name->bytes;
+ cloned->encoded_type_name->encoding = encoded_type_name->encoding;
+ cloned->encoded_type_name->hash = encoded_type_name->hash;
+ }
+
+ return cloned;
}
-Result<std::shared_ptr<TypeInfo>, Error>
-TypeResolver::read_any_typeinfo(ReadContext &ctx,
- const TypeMeta *local_type_meta) {
- FORY_TRY(fory_type_id, ctx.read_varuint32());
- uint32_t type_id_low = fory_type_id & 0xff;
-
- // Handle compatible struct types (with embedded TypeMeta)
- if (type_id_low == static_cast<uint32_t>(TypeId::NAMED_COMPATIBLE_STRUCT) ||
- type_id_low == static_cast<uint32_t>(TypeId::COMPATIBLE_STRUCT)) {
- // Use provided local_type_meta if available, otherwise try to get from
- // registry
- if (local_type_meta == nullptr) {
- auto local_type_info = get_type_info_by_id(fory_type_id);
- if (local_type_info && local_type_info->type_meta) {
- local_type_meta = local_type_info->type_meta.get();
- }
- }
+// ============================================================================
+// encode_meta_string Implementation
+// ============================================================================
- // Read the embedded TypeMeta from stream
- // Pass local_type_meta so that assign_field_ids is called during parsing
- FORY_TRY(remote_meta, TypeMeta::from_bytes(ctx.buffer(), local_type_meta));
+Result<std::unique_ptr<CachedMetaString>, Error>
+encode_meta_string(const std::string &value, bool is_namespace) {
+ auto cached = std::make_unique<CachedMetaString>();
+ cached->original = value;
+
+ if (value.empty()) {
+ // For empty strings, use a minimal encoding
+ cached->encoding = 0; // UTF8
+ cached->bytes.clear();
+ cached->hash = 0;
+ return cached;
+ }
- // Create a temporary TypeInfo with the remote TypeMeta
- auto type_info = std::make_shared<TypeInfo>();
- type_info->type_id = fory_type_id;
- type_info->type_meta = remote_meta;
- // Note: We don't have type_def here since this is remote schema
+ // Use MetaStringEncoder to encode the string
+ static const MetaStringEncoder kNamespaceEncoder('.', '_');
+ static const MetaStringEncoder kTypeNameEncoder('$', '_');
- return type_info;
- }
+ static const std::vector<MetaEncoding> kNamespaceEncodings = {
+ MetaEncoding::UTF8, MetaEncoding::ALL_TO_LOWER_SPECIAL,
+ MetaEncoding::LOWER_UPPER_DIGIT_SPECIAL};
- // Handle named types (namespace + type name)
- if (type_id_low == static_cast<uint32_t>(TypeId::NAMED_ENUM) ||
- type_id_low == static_cast<uint32_t>(TypeId::NAMED_EXT) ||
- type_id_low == static_cast<uint32_t>(TypeId::NAMED_STRUCT)) {
- // TODO: If share_meta is enabled, read meta_index instead
- // For now, read namespace and type name
- FORY_TRY(ns_len, ctx.read_varuint32());
- std::string namespace_str(ns_len, '\0');
- FORY_RETURN_NOT_OK(ctx.read_bytes(namespace_str.data(), ns_len));
-
- FORY_TRY(name_len, ctx.read_varuint32());
- std::string type_name(name_len, '\0');
- FORY_RETURN_NOT_OK(ctx.read_bytes(type_name.data(), name_len));
-
- auto type_info = get_type_info_by_name(namespace_str, type_name);
- if (type_info) {
- return type_info;
- }
- return Unexpected(Error::type_error("Type info not found for namespace '" +
- namespace_str + "' and type name '" +
- type_name + "'"));
- }
+ static const std::vector<MetaEncoding> kTypeNameEncodings = {
+ MetaEncoding::UTF8, MetaEncoding::ALL_TO_LOWER_SPECIAL,
+ MetaEncoding::LOWER_UPPER_DIGIT_SPECIAL,
+ MetaEncoding::FIRST_TO_LOWER_SPECIAL};
- // Handle other types by ID lookup
- auto type_info = get_type_info_by_id(fory_type_id);
- if (type_info) {
- return type_info;
+ if (is_namespace) {
+ FORY_TRY(result, kNamespaceEncoder.encode(value, kNamespaceEncodings));
+ cached->encoding = static_cast<uint8_t>(result.encoding);
+ cached->bytes = std::move(result.bytes);
+ } else {
+ FORY_TRY(result, kTypeNameEncoder.encode(value, kTypeNameEncodings));
+ cached->encoding = static_cast<uint8_t>(result.encoding);
+ cached->bytes = std::move(result.bytes);
}
- return Unexpected(Error::type_error("Type info not found for type_id: " +
- std::to_string(fory_type_id)));
+
+ // Compute hash if needed (for now, just use 0)
+ cached->hash = 0;
+
+ return cached;
}
Result<const TypeInfo *, Error>
@@ -982,12 +994,12 @@ TypeResolver::get_type_info(const std::type_index
&type_index) const {
if (it == type_info_by_runtime_type_.end()) {
return Unexpected(Error::type_error("TypeInfo not found for type_index"));
}
- return it->second.get();
+ return it->second;
}
-Result<std::shared_ptr<TypeResolver>, Error>
+Result<std::unique_ptr<TypeResolver>, Error>
TypeResolver::build_final_type_resolver() {
- auto final_resolver = std::make_shared<TypeResolver>();
+ auto final_resolver = std::make_unique<TypeResolver>();
// Copy configuration
final_resolver->compatible_ = compatible_;
@@ -996,16 +1008,40 @@ TypeResolver::build_final_type_resolver() {
final_resolver->track_ref_ = track_ref_;
final_resolver->finalized_ = true;
- // Copy all existing type info maps
- final_resolver->type_info_by_ctid_ = type_info_by_ctid_;
- final_resolver->type_info_by_id_ = type_info_by_id_;
- final_resolver->type_info_by_name_ = type_info_by_name_;
- final_resolver->type_info_by_runtime_type_ = type_info_by_runtime_type_;
+ // Build mapping from old pointers to new pointers for rebuilding lookup maps
+ absl::flat_hash_map<const TypeInfo *, TypeInfo *> ptr_map;
+
+ // Deep clone all existing TypeInfo objects
+ for (const auto &info : type_infos_) {
+ auto cloned = info->deep_clone();
+ TypeInfo *new_ptr = cloned.get();
+ ptr_map[info.get()] = new_ptr;
+ final_resolver->type_infos_.push_back(std::move(cloned));
+ }
+
+ // Rebuild lookup maps with new pointers
+ for (const auto &[key, old_ptr] : type_info_by_ctid_) {
+ final_resolver->type_info_by_ctid_[key] = ptr_map[old_ptr];
+ }
+ for (const auto &[key, old_ptr] : type_info_by_id_) {
+ final_resolver->type_info_by_id_[key] = ptr_map[old_ptr];
+ }
+ for (const auto &[key, old_ptr] : type_info_by_name_) {
+ final_resolver->type_info_by_name_[key] = ptr_map[old_ptr];
+ }
+ for (const auto &[key, old_ptr] : type_info_by_runtime_type_) {
+ final_resolver->type_info_by_runtime_type_[key] = ptr_map[old_ptr];
+ }
+ for (const auto &[key, old_ptr] : partial_type_infos_) {
+ final_resolver->partial_type_infos_[key] = ptr_map[old_ptr];
+ }
// Process all partial type infos to build complete type metadata
- for (const auto &[rust_type_id, partial_info] : partial_type_infos_) {
+ for (const auto &[rust_type_id, partial_ptr] :
+ final_resolver->partial_type_infos_) {
// Call the harness's sorted_field_infos function to get complete field
info
- auto fields_result = partial_info->harness.sorted_field_infos_fn(*this);
+ auto fields_result =
+ partial_ptr->harness.sorted_field_infos_fn(*final_resolver);
if (!fields_result.ok()) {
return Unexpected(fields_result.error());
}
@@ -1013,8 +1049,8 @@ TypeResolver::build_final_type_resolver() {
// Build complete TypeMeta
TypeMeta meta = TypeMeta::from_fields(
- partial_info->type_id, partial_info->namespace_name,
- partial_info->type_name, partial_info->register_by_name,
+ partial_ptr->type_id, partial_ptr->namespace_name,
+ partial_ptr->type_name, partial_ptr->register_by_name,
std::move(sorted_fields));
// Serialize TypeMeta to bytes
@@ -1023,32 +1059,18 @@ TypeResolver::build_final_type_resolver() {
return Unexpected(type_def_result.error());
}
- // Create complete TypeInfo
- auto complete_info = std::make_shared<TypeInfo>(*partial_info);
- complete_info->type_def = std::move(type_def_result).value();
+ // Update the TypeInfo in place
+ partial_ptr->type_def = std::move(type_def_result).value();
- // Parse the serialized TypeMeta back to create shared_ptr<TypeMeta>
- Buffer buffer(complete_info->type_def.data(),
- static_cast<uint32_t>(complete_info->type_def.size()),
false);
- buffer.WriterIndex(static_cast<uint32_t>(complete_info->type_def.size()));
+ // Parse the serialized TypeMeta back to create unique_ptr<TypeMeta>
+ Buffer buffer(partial_ptr->type_def.data(),
+ static_cast<uint32_t>(partial_ptr->type_def.size()), false);
+ buffer.WriterIndex(static_cast<uint32_t>(partial_ptr->type_def.size()));
auto parsed_meta_result = TypeMeta::from_bytes(buffer, nullptr);
if (!parsed_meta_result.ok()) {
return Unexpected(parsed_meta_result.error());
}
- complete_info->type_meta = std::move(parsed_meta_result).value();
-
- // Update all maps with complete info
- final_resolver->type_info_by_ctid_[rust_type_id] = complete_info;
-
- if (complete_info->type_id != 0) {
- final_resolver->type_info_by_id_[complete_info->type_id] = complete_info;
- }
-
- if (complete_info->register_by_name) {
- auto key = make_name_key(complete_info->namespace_name,
- complete_info->type_name);
- final_resolver->type_info_by_name_[key] = complete_info;
- }
+ partial_ptr->type_meta = std::move(parsed_meta_result).value();
}
// Clear partial_type_infos in the final resolver since they're all completed
@@ -1057,8 +1079,8 @@ TypeResolver::build_final_type_resolver() {
return final_resolver;
}
-std::shared_ptr<TypeResolver> TypeResolver::clone() const {
- auto cloned = std::make_shared<TypeResolver>();
+std::unique_ptr<TypeResolver> TypeResolver::clone() const {
+ auto cloned = std::make_unique<TypeResolver>();
// Copy configuration
cloned->compatible_ = compatible_;
@@ -1067,13 +1089,32 @@ std::shared_ptr<TypeResolver> TypeResolver::clone()
const {
cloned->track_ref_ = track_ref_;
cloned->finalized_ = finalized_;
- // Shallow copy all maps (shared_ptr sharing)
- cloned->type_info_by_ctid_ = type_info_by_ctid_;
- cloned->type_info_by_id_ = type_info_by_id_;
- cloned->type_info_by_name_ = type_info_by_name_;
- cloned->type_info_by_runtime_type_ = type_info_by_runtime_type_;
- // Don't copy partial_type_infos_ - clone should only be used on finalized
- // resolvers
+ // Build mapping from old pointers to new pointers
+ absl::flat_hash_map<const TypeInfo *, TypeInfo *> ptr_map;
+
+ // Deep clone all TypeInfo objects
+ for (const auto &info : type_infos_) {
+ auto cloned_info = info->deep_clone();
+ TypeInfo *new_ptr = cloned_info.get();
+ ptr_map[info.get()] = new_ptr;
+ cloned->type_infos_.push_back(std::move(cloned_info));
+ }
+
+ // Rebuild lookup maps with new pointers
+ for (const auto &[key, old_ptr] : type_info_by_ctid_) {
+ cloned->type_info_by_ctid_[key] = ptr_map[old_ptr];
+ }
+ for (const auto &[key, old_ptr] : type_info_by_id_) {
+ cloned->type_info_by_id_[key] = ptr_map[old_ptr];
+ }
+ for (const auto &[key, old_ptr] : type_info_by_name_) {
+ cloned->type_info_by_name_[key] = ptr_map[old_ptr];
+ }
+ for (const auto &[key, old_ptr] : type_info_by_runtime_type_) {
+ cloned->type_info_by_runtime_type_[key] = ptr_map[old_ptr];
+ }
+ // Note: Don't copy partial_type_infos_ - clone should only be used on
+ // finalized resolvers
return cloned;
}
@@ -1082,11 +1123,13 @@ void TypeResolver::register_builtin_types() {
// Register internal type IDs without harnesses (deserialization is static)
// These are needed so read_any_typeinfo can find them by type_id
auto register_type_id_only = [this](TypeId type_id) {
- auto info = std::make_shared<TypeInfo>();
+ auto info = std::make_unique<TypeInfo>();
info->type_id = static_cast<uint32_t>(type_id);
info->register_by_name = false;
info->is_external = false;
- type_info_by_id_[info->type_id] = info;
+ TypeInfo *raw_ptr = info.get();
+ type_infos_.push_back(std::move(info));
+ type_info_by_id_[raw_ptr->type_id] = raw_ptr;
};
// Primitive types
diff --git a/cpp/fory/serialization/type_resolver.h
b/cpp/fory/serialization/type_resolver.h
index 733e46cfd..0ba949de4 100644
--- a/cpp/fory/serialization/type_resolver.h
+++ b/cpp/fory/serialization/type_resolver.h
@@ -183,13 +183,13 @@ public:
/// Read type meta from buffer (for deserialization)
/// @param buffer Source buffer
/// @param local_type_info Local type information (for field ID assignment)
- static Result<std::shared_ptr<TypeMeta>, Error>
+ static Result<std::unique_ptr<TypeMeta>, Error>
from_bytes(Buffer &buffer, const TypeMeta *local_type_info);
/// Read type meta from buffer with pre-read header
/// @param buffer Source buffer (positioned after header)
/// @param header Pre-read 8-byte header
- static Result<std::shared_ptr<TypeMeta>, Error>
+ static Result<std::unique_ptr<TypeMeta>, Error>
from_bytes_with_header(Buffer &buffer, int64_t header);
/// Skip type meta in buffer without parsing
@@ -416,7 +416,7 @@ std::vector<FieldInfo>
build_field_infos(std::index_sequence<Indices...>) {
/// Encode a meta string for namespace or type_name using the appropriate
/// encoder. This is called during registration to pre-compute the encoded
form.
-Result<std::shared_ptr<CachedMetaString>, Error>
+Result<std::unique_ptr<CachedMetaString>, Error>
encode_meta_string(const std::string &value, bool is_namespace);
// ============================================================================
@@ -441,37 +441,16 @@ public:
template <typename T>
const absl::flat_hash_map<std::string, size_t> &field_name_to_index();
- template <typename T>
- Result<std::shared_ptr<TypeInfo>, Error> get_struct_type_info();
-
- uint32_t get_type_id(const TypeInfo &info) const;
-
- template <typename T> Result<uint32_t, Error> get_type_id();
-
/// Get type info by type ID (for non-namespaced types)
- std::shared_ptr<TypeInfo> get_type_info_by_id(uint32_t type_id) const;
+ /// @return const pointer to TypeInfo if found, error otherwise
+ Result<const TypeInfo *, Error> get_type_info_by_id(uint32_t type_id) const;
/// Get type info by namespace and type name (for namespaced types)
- std::shared_ptr<TypeInfo>
+ /// @return const pointer to TypeInfo if found, error otherwise
+ Result<const TypeInfo *, Error>
get_type_info_by_name(const std::string &ns,
const std::string &type_name) const;
- /// Read type information dynamically from ReadContext based on type ID.
- ///
- /// This method handles reading type info for various type categories:
- /// - COMPATIBLE_STRUCT/NAMED_COMPATIBLE_STRUCT: reads meta index
- /// - NAMED_ENUM/NAMED_STRUCT/NAMED_EXT: reads namespace and type name (if
not
- /// sharing meta)
- /// - Other types: looks up by type ID
- ///
- /// @return TypeInfo pointer if found, error otherwise
- Result<std::shared_ptr<TypeInfo>, Error> read_any_typeinfo(ReadContext &ctx);
-
- /// Read type info from stream with explicit local TypeMeta for field_id
- /// assignment
- Result<std::shared_ptr<TypeInfo>, Error>
- read_any_typeinfo(ReadContext &ctx, const TypeMeta *local_type_meta);
-
/// Get TypeInfo by type_index (used for looking up registered types)
/// @return const pointer to TypeInfo if found, error otherwise
Result<const TypeInfo *, Error>
@@ -479,8 +458,8 @@ public:
/// Get TypeInfo by compile-time type ID (fast path for template types)
/// Works for enums, structs, and any registered type.
- /// @return shared_ptr to TypeInfo if found, nullptr otherwise
- template <typename T> std::shared_ptr<TypeInfo> get_type_info() const;
+ /// @return const pointer to TypeInfo if found, error otherwise
+ template <typename T> Result<const TypeInfo *, Error> get_type_info() const;
/// Builds the final TypeResolver by completing all partial type infos
/// created during registration.
@@ -497,15 +476,16 @@ public:
///
/// @return A new TypeResolver with all type infos fully initialized and
ready
/// for use.
- Result<std::shared_ptr<TypeResolver>, Error> build_final_type_resolver();
+ Result<std::unique_ptr<TypeResolver>, Error> build_final_type_resolver();
- /// Clones the TypeResolver for use in a new context.
+ /// Deep clones the TypeResolver for use in a new context.
///
- /// This method creates a shallow clone of the TypeResolver. The clone shares
- /// the same TypeInfo objects as the original but is a separate instance.
+ /// This method creates a deep clone of the TypeResolver. All TypeInfo
+ /// objects are cloned into new instances owned by the new TypeResolver.
+ /// This ensures thread safety when cloning for use in different contexts.
///
- /// @return A new TypeResolver instance
- std::shared_ptr<TypeResolver> clone() const;
+ /// @return A new TypeResolver instance with cloned TypeInfo objects
+ std::unique_ptr<TypeResolver> clone() const;
private:
friend class BaseFory;
@@ -525,17 +505,17 @@ private:
const std::string &type_name);
template <typename T>
- static Result<std::shared_ptr<TypeInfo>, Error>
+ static Result<std::unique_ptr<TypeInfo>, Error>
build_struct_type_info(uint32_t type_id, std::string ns,
std::string type_name, bool register_by_name);
template <typename T>
- static Result<std::shared_ptr<TypeInfo>, Error>
+ static Result<std::unique_ptr<TypeInfo>, Error>
build_enum_type_info(uint32_t type_id, std::string ns, std::string type_name,
bool register_by_name);
template <typename T>
- static Result<std::shared_ptr<TypeInfo>, Error>
+ static Result<std::unique_ptr<TypeInfo>, Error>
build_ext_type_info(uint32_t type_id, std::string ns, std::string type_name,
bool register_by_name);
@@ -573,13 +553,14 @@ private:
static std::string make_name_key(const std::string &ns,
const std::string &name);
- Result<void, Error> register_type_internal(uint64_t ctid,
- std::shared_ptr<TypeInfo> info);
+ /// Register a TypeInfo, taking ownership and storing in primary storage.
+ /// Returns pointer to the stored TypeInfo (owned by TypeResolver).
+ Result<TypeInfo *, Error>
+ register_type_internal(uint64_t ctid, std::unique_ptr<TypeInfo> info);
// For runtime polymorphic lookups (smart pointers with dynamic types)
- Result<void, Error>
- register_type_internal_runtime(const std::type_index &type_index,
- std::shared_ptr<TypeInfo> info);
+ void register_type_internal_runtime(const std::type_index &type_index,
+ TypeInfo *info);
void check_registration_thread();
@@ -593,17 +574,18 @@ private:
std::thread::id registration_thread_id_;
bool finalized_;
- // Primary map using compile-time type ID (uint64_t) - fast for template
- // lookups
- absl::flat_hash_map<uint64_t, std::shared_ptr<TypeInfo>> type_info_by_ctid_;
- absl::flat_hash_map<uint32_t, std::shared_ptr<TypeInfo>> type_info_by_id_;
- absl::flat_hash_map<std::string, std::shared_ptr<TypeInfo>>
- type_info_by_name_;
- absl::flat_hash_map<uint64_t, std::shared_ptr<TypeInfo>> partial_type_infos_;
+ // Primary storage - owns all TypeInfo objects
+ std::vector<std::unique_ptr<TypeInfo>> type_infos_;
+
+ // Lookup maps - store raw pointers (borrowed from primary storage)
+ // Using compile-time type ID (uint64_t) - fast for template lookups
+ absl::flat_hash_map<uint64_t, TypeInfo *> type_info_by_ctid_;
+ absl::flat_hash_map<uint32_t, TypeInfo *> type_info_by_id_;
+ absl::flat_hash_map<std::string, TypeInfo *> type_info_by_name_;
+ absl::flat_hash_map<uint64_t, TypeInfo *> partial_type_infos_;
// For runtime polymorphic lookups (smart pointers) - uses std::type_index
- absl::flat_hash_map<std::type_index, std::shared_ptr<TypeInfo>>
- type_info_by_runtime_type_;
+ absl::flat_hash_map<std::type_index, TypeInfo *> type_info_by_runtime_type_;
};
// Alias for backward compatibility (already defined above as top-level)
@@ -671,59 +653,14 @@ TypeResolver::field_name_to_index() {
}
template <typename T>
-Result<std::shared_ptr<TypeInfo>, Error> TypeResolver::get_struct_type_info() {
- static_assert(is_fory_serializable_v<T>,
- "get_struct_type_info requires FORY_STRUCT types");
+Result<const TypeInfo *, Error> TypeResolver::get_type_info() const {
// Use compile-time type ID (uint64_t key) for fast lookup
constexpr uint64_t ctid = type_index<T>();
auto it = type_info_by_ctid_.find(ctid);
if (FORY_PREDICT_TRUE(it != type_info_by_ctid_.end())) {
return it->second;
}
- return Unexpected(Error::type_error(
- "Type not registered. All struct/enum/ext types must be explicitly "
- "registered before serialization."));
-}
-
-template <typename T>
-std::shared_ptr<TypeInfo> TypeResolver::get_type_info() const {
- // Use compile-time type ID (uint64_t key) for fast lookup
- constexpr uint64_t ctid = type_index<T>();
- auto it = type_info_by_ctid_.find(ctid);
- if (FORY_PREDICT_TRUE(it != type_info_by_ctid_.end())) {
- return it->second;
- }
- return nullptr;
-}
-
-inline uint32_t TypeResolver::get_type_id(const TypeInfo &info) const {
- // In the xlang spec the numeric type id used on the wire for
- // structs is the "actual" type id computed by the resolver
- // (see Rust's `struct_::actual_type_id`). That value is already
- // stored in `info.type_id` for both id-based and name-based
- // registrations. Unlike the previous implementation we must not
- // apply another layer of shifting/tagging here, otherwise the
- // local type id will no longer match the id written by Java/Rust
- // and struct reads will fail with `TypeMismatch`.
- //
- // Rust equivalent:
- // let type_id = T::fory_get_type_id(type_resolver)?;
- // context.writer.write_varuint32(type_id);
- // and on read:
- // ensure!(local_type_id == remote_type_id, Error::type_mismatch(...));
- //
- // So we simply return the stored actual type id.
- (void)this; // suppress unused warning in some builds
- return info.type_id;
-}
-
-template <typename T> Result<uint32_t, Error> TypeResolver::get_type_id() {
- FORY_TRY(info, get_struct_type_info<T>());
- if (!info->type_meta) {
- return Unexpected(Error::type_error(
- "Type metadata not initialized for requested struct"));
- }
- return get_type_id(*info);
+ return Unexpected(Error::type_error("Type not registered"));
}
template <typename T>
@@ -749,10 +686,12 @@ Result<void, Error> TypeResolver::register_by_id(uint32_t
type_id) {
Error::invalid("Harness for registered type is incomplete"));
}
- partial_type_infos_[ctid] = info;
- // Also register for runtime polymorphic lookups (smart pointers)
- type_info_by_runtime_type_[std::type_index(typeid(T))] = info;
- return register_type_internal(ctid, std::move(info));
+ // Register and get back the stored pointer
+ FORY_TRY(stored_ptr, register_type_internal(ctid, std::move(info)));
+ // Also register for runtime polymorphic lookups and partial type infos
+ partial_type_infos_[ctid] = stored_ptr;
+ register_type_internal_runtime(std::type_index(typeid(T)), stored_ptr);
+ return Result<void, Error>();
} else if constexpr (std::is_enum_v<T>) {
uint32_t actual_type_id =
(type_id << 8) + static_cast<uint32_t>(TypeId::ENUM);
@@ -763,9 +702,10 @@ Result<void, Error> TypeResolver::register_by_id(uint32_t
type_id) {
Error::invalid("Harness for registered enum type is incomplete"));
}
- partial_type_infos_[ctid] = info;
- type_info_by_runtime_type_[std::type_index(typeid(T))] = info;
- return register_type_internal(ctid, std::move(info));
+ FORY_TRY(stored_ptr, register_type_internal(ctid, std::move(info)));
+ partial_type_infos_[ctid] = stored_ptr;
+ register_type_internal_runtime(std::type_index(typeid(T)), stored_ptr);
+ return Result<void, Error>();
} else {
static_assert(is_fory_serializable_v<T>,
"register_by_id requires a type declared with FORY_STRUCT "
@@ -798,9 +738,10 @@ TypeResolver::register_by_name(const std::string &ns,
Error::invalid("Harness for registered type is incomplete"));
}
- partial_type_infos_[ctid] = info;
- type_info_by_runtime_type_[std::type_index(typeid(T))] = info;
- return register_type_internal(ctid, std::move(info));
+ FORY_TRY(stored_ptr, register_type_internal(ctid, std::move(info)));
+ partial_type_infos_[ctid] = stored_ptr;
+ register_type_internal_runtime(std::type_index(typeid(T)), stored_ptr);
+ return Result<void, Error>();
} else if constexpr (std::is_enum_v<T>) {
uint32_t actual_type_id = static_cast<uint32_t>(TypeId::NAMED_ENUM);
FORY_TRY(info,
@@ -810,9 +751,10 @@ TypeResolver::register_by_name(const std::string &ns,
Error::invalid("Harness for registered enum type is incomplete"));
}
- partial_type_infos_[ctid] = info;
- type_info_by_runtime_type_[std::type_index(typeid(T))] = info;
- return register_type_internal(ctid, std::move(info));
+ FORY_TRY(stored_ptr, register_type_internal(ctid, std::move(info)));
+ partial_type_infos_[ctid] = stored_ptr;
+ register_type_internal_runtime(std::type_index(typeid(T)), stored_ptr);
+ return Result<void, Error>();
} else {
static_assert(is_fory_serializable_v<T>,
"register_by_name requires a type declared with FORY_STRUCT "
@@ -836,9 +778,10 @@ Result<void, Error>
TypeResolver::register_ext_type_by_id(uint32_t type_id) {
FORY_TRY(info, build_ext_type_info<T>(actual_type_id, "", "", false));
- partial_type_infos_[ctid] = info;
- type_info_by_runtime_type_[std::type_index(typeid(T))] = info;
- return register_type_internal(ctid, std::move(info));
+ FORY_TRY(stored_ptr, register_type_internal(ctid, std::move(info)));
+ partial_type_infos_[ctid] = stored_ptr;
+ register_type_internal_runtime(std::type_index(typeid(T)), stored_ptr);
+ return Result<void, Error>();
}
template <typename T>
@@ -856,13 +799,14 @@ TypeResolver::register_ext_type_by_name(const std::string
&ns,
uint32_t actual_type_id = static_cast<uint32_t>(TypeId::NAMED_EXT);
FORY_TRY(info, build_ext_type_info<T>(actual_type_id, ns, type_name, true));
- partial_type_infos_[ctid] = info;
- type_info_by_runtime_type_[std::type_index(typeid(T))] = info;
- return register_type_internal(ctid, std::move(info));
+ FORY_TRY(stored_ptr, register_type_internal(ctid, std::move(info)));
+ partial_type_infos_[ctid] = stored_ptr;
+ register_type_internal_runtime(std::type_index(typeid(T)), stored_ptr);
+ return Result<void, Error>();
}
template <typename T>
-Result<std::shared_ptr<TypeInfo>, Error>
+Result<std::unique_ptr<TypeInfo>, Error>
TypeResolver::build_struct_type_info(uint32_t type_id, std::string ns,
std::string type_name,
bool register_by_name) {
@@ -873,7 +817,7 @@ TypeResolver::build_struct_type_info(uint32_t type_id,
std::string ns,
type_id = static_cast<uint32_t>(TypeId::STRUCT);
}
- auto entry = std::make_shared<TypeInfo>();
+ auto entry = std::make_unique<TypeInfo>();
entry->type_id = type_id;
entry->namespace_name = std::move(ns);
entry->register_by_name = register_by_name;
@@ -936,7 +880,7 @@ TypeResolver::build_struct_type_info(uint32_t type_id,
std::string ns,
}
template <typename T>
-Result<std::shared_ptr<TypeInfo>, Error>
+Result<std::unique_ptr<TypeInfo>, Error>
TypeResolver::build_enum_type_info(uint32_t type_id, std::string ns,
std::string type_name,
bool register_by_name) {
@@ -947,7 +891,7 @@ TypeResolver::build_enum_type_info(uint32_t type_id,
std::string ns,
"type_name must be non-empty when register_by_name is true"));
}
- auto entry = std::make_shared<TypeInfo>();
+ auto entry = std::make_unique<TypeInfo>();
entry->type_id = type_id;
entry->namespace_name = std::move(ns);
entry->register_by_name = register_by_name;
@@ -979,11 +923,11 @@ TypeResolver::build_enum_type_info(uint32_t type_id,
std::string ns,
}
template <typename T>
-Result<std::shared_ptr<TypeInfo>, Error>
+Result<std::unique_ptr<TypeInfo>, Error>
TypeResolver::build_ext_type_info(uint32_t type_id, std::string ns,
std::string type_name,
bool register_by_name) {
- auto entry = std::make_shared<TypeInfo>();
+ auto entry = std::make_unique<TypeInfo>();
entry->type_id = type_id;
entry->namespace_name = ns;
entry->type_name = type_name;
@@ -1094,57 +1038,61 @@ inline std::string TypeResolver::make_name_key(const
std::string &ns,
return key;
}
-inline Result<void, Error>
+inline Result<TypeInfo *, Error>
TypeResolver::register_type_internal(uint64_t ctid,
- std::shared_ptr<TypeInfo> info) {
+ std::unique_ptr<TypeInfo> info) {
if (!info || !info->harness.valid()) {
return Unexpected(
Error::invalid("TypeInfo or harness is invalid during registration"));
}
- type_info_by_ctid_[ctid] = info;
+ // Store in primary storage and get raw pointer
+ TypeInfo *raw_ptr = info.get();
+ type_infos_.push_back(std::move(info));
+
+ type_info_by_ctid_[ctid] = raw_ptr;
- if (info->type_id != 0 && !info->register_by_name) {
- auto it = type_info_by_id_.find(info->type_id);
- if (it != type_info_by_id_.end() && it->second.get() != info.get()) {
+ if (raw_ptr->type_id != 0 && !raw_ptr->register_by_name) {
+ auto it = type_info_by_id_.find(raw_ptr->type_id);
+ if (it != type_info_by_id_.end() && it->second != raw_ptr) {
return Unexpected(Error::invalid("Type id already registered: " +
- std::to_string(info->type_id)));
+ std::to_string(raw_ptr->type_id)));
}
- type_info_by_id_[info->type_id] = info;
+ type_info_by_id_[raw_ptr->type_id] = raw_ptr;
}
- if (info->register_by_name) {
- auto key = make_name_key(info->namespace_name, info->type_name);
+ if (raw_ptr->register_by_name) {
+ auto key = make_name_key(raw_ptr->namespace_name, raw_ptr->type_name);
auto it = type_info_by_name_.find(key);
- if (it != type_info_by_name_.end() && it->second.get() != info.get()) {
+ if (it != type_info_by_name_.end() && it->second != raw_ptr) {
return Unexpected(Error::invalid(
- "Type already registered for namespace '" + info->namespace_name +
- "' and name '" + info->type_name + "'"));
+ "Type already registered for namespace '" + raw_ptr->namespace_name +
+ "' and name '" + raw_ptr->type_name + "'"));
}
- type_info_by_name_[key] = info;
+ type_info_by_name_[key] = raw_ptr;
}
- return Result<void, Error>();
+ return raw_ptr;
}
-inline Result<void, Error>
+inline void
TypeResolver::register_type_internal_runtime(const std::type_index &type_index,
- std::shared_ptr<TypeInfo> info) {
+ TypeInfo *info) {
// For runtime polymorphic lookups (smart pointers)
type_info_by_runtime_type_[type_index] = info;
- return Result<void, Error>();
}
-inline std::shared_ptr<TypeInfo>
+inline Result<const TypeInfo *, Error>
TypeResolver::get_type_info_by_id(uint32_t type_id) const {
auto it = type_info_by_id_.find(type_id);
if (it != type_info_by_id_.end()) {
return it->second;
}
- return nullptr;
+ return Unexpected(Error::type_error("TypeInfo not found for type_id: " +
+ std::to_string(type_id)));
}
-inline std::shared_ptr<TypeInfo>
+inline Result<const TypeInfo *, Error>
TypeResolver::get_type_info_by_name(const std::string &ns,
const std::string &type_name) const {
auto key = make_name_key(ns, type_name);
@@ -1152,7 +1100,8 @@ TypeResolver::get_type_info_by_name(const std::string &ns,
if (it != type_info_by_name_.end()) {
return it->second;
}
- return nullptr;
+ return Unexpected(Error::type_error("TypeInfo not found for type: " + ns +
+ "." + type_name));
}
// ============================================================================
@@ -1161,13 +1110,8 @@ TypeResolver::get_type_info_by_name(const std::string
&ns,
// ============================================================================
template <typename E> Result<void, Error> WriteContext::write_enum_typeinfo() {
- auto type_info = type_resolver_->get_type_info<E>();
- if (type_info) {
- return write_enum_typeinfo(type_info.get());
- }
- // Enum not registered, write plain ENUM type id
- buffer_.WriteVarUint32(static_cast<uint32_t>(TypeId::ENUM));
- return Result<void, Error>();
+ FORY_TRY(type_info, type_resolver_->get_type_info<E>());
+ return write_enum_typeinfo(type_info);
}
} // namespace serialization
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]