You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fory.apache.org by ch...@apache.org on 2026/03/10 08:07:27 UTC
(fory) branch main updated: perf(swift): optimize swift performance (#3457)
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 24aaacba3 perf(swift): optimize swift performance (#3457)
24aaacba3 is described below
commit 24aaacba3a7aa3a24777b6394a13a53d819f9c4d
Author: Shawn Yang <sh...@gmail.com>
AuthorDate: Tue Mar 10 16:07:24 2026 +0800
perf(swift): optimize swift performance (#3457)
## Why?
## What does this PR do?
## Related issues
#3355
## Does this PR introduce any user-facing change?
- [ ] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
| Datatype | Operation | Fory TPS | Protobuf TPS | Msgpack TPS | Fastest
|
| --- | --- | ---: | ---: | ---: | --- |
| Struct | Serialize | 9,727,950 | 6,572,406 | 141,248 | fory (1.48x) |
| Struct | Deserialize | 11,889,570 | 8,584,510 | 99,792 | fory (1.39x)
|
| Sample | Serialize | 3,496,305 | 1,281,983 | 17,188 | fory (2.73x) |
| Sample | Deserialize | 1,045,018 | 765,706 | 12,767 | fory (1.36x) |
| MediaContent | Serialize | 1,425,354 | 678,542 | 29,048 | fory (2.10x)
|
| MediaContent | Deserialize | 614,447 | 478,298 | 12,711 | fory (1.28x)
|
| StructList | Serialize | 3,307,962 | 1,028,210 | 24,781 | fory (3.22x)
|
| StructList | Deserialize | 2,788,200 | 708,596 | 8,160 | fory (3.93x)
|
| SampleList | Serialize | 715,734 | 205,380 | 3,361 | fory (3.48x) |
| SampleList | Deserialize | 199,317 | 133,425 | 1,498 | fory (1.49x) |
| MediaContentList | Serialize | 364,097 | 103,721 | 5,538 | fory
(3.51x) |
| MediaContentList | Deserialize | 103,421 | 86,331 | 1,529 | fory
(1.20x) |
---
.gitignore | 3 +-
AGENTS.md | 1 +
README.md | 8 +
.../Sources/SwiftBenchmark/BenchmarkModels.swift | 52 +
.../Sources/SwiftBenchmark/BenchmarkRunner.swift | 10 +-
benchmarks/swift/benchmark_report.py | 2 +-
docs/benchmarks/swift/README.md | 46 +
docs/benchmarks/swift/throughput.png | Bin 0 -> 84759 bytes
docs/guide/swift/configuration.md | 5 +
swift/README.md | 4 +
swift/Sources/Fory/AnySerializer.swift | 95 +-
swift/Sources/Fory/ByteBuffer.swift | 452 ++++---
swift/Sources/Fory/CollectionSerializers.swift | 273 ++--
swift/Sources/Fory/CollectionUtil.swift | 78 ++
swift/Sources/Fory/Context.swift | 830 ------------
swift/Sources/Fory/DateTimeSerializers.swift | 32 +
swift/Sources/Fory/FieldSkipper.swift | 271 ++--
swift/Sources/Fory/FlatMap.swift | 273 ++++
swift/Sources/Fory/Fory.swift | 387 ++----
swift/Sources/Fory/MacroDeclarations.swift | 13 +-
swift/Sources/Fory/MetaString.swift | 138 +-
swift/Sources/Fory/OptionalSerializer.swift | 29 +-
swift/Sources/Fory/PrimitiveSerializers.swift | 194 ++-
swift/Sources/Fory/ReadContext.swift | 633 +++++++++
swift/Sources/Fory/RefResolver.swift | 8 +-
swift/Sources/Fory/Serializer.swift | 564 ++------
swift/Sources/Fory/TypeMeta.swift | 512 +++++---
swift/Sources/Fory/TypeResolver.swift | 821 +++++++-----
swift/Sources/Fory/UnsafeUtil.swift | 1383 ++++++++++++++++++++
swift/Sources/Fory/WriteContext.swift | 288 ++++
swift/Sources/ForyMacro/ForyObjectMacro.swift | 625 +++------
.../ForyObjectMacroPrimitiveFastPath.swift | 371 ++++++
.../ForyMacro/ForyObjectMacroReadGeneration.swift | 500 +++++++
swift/Tests/ForyTests/ForySwiftTests.swift | 109 +-
swift/Tests/ForyTests/ObjectIdMapTests.swift | 84 ++
swift/Tests/ForyXlangTests/main.swift | 13 +-
36 files changed, 5981 insertions(+), 3126 deletions(-)
diff --git a/.gitignore b/.gitignore
index 4c4b03068..fbc9ee0e2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -103,10 +103,11 @@ examples/cpp/cmake_example/build
**/benchmark_*.png
**/results/
benchmarks/**/report/
-ignored/**
+**/ignored/**
ci-logs/**
**/*.log
swift/.build
+.build-*
integration_tests/idl_tests/swift/.build
integration_tests/idl_tests/swift/idl_package/.build
diff --git a/AGENTS.md b/AGENTS.md
index f2d997653..f205ad2d4 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -623,6 +623,7 @@ Fory rust provides macro-based serialization and deserialization. Fory rust cons
- **Performance First**: Never introduce code that reduces performance without explicit justification
- **Benchmark Required After Perf Optimizations**: For every code change expected to improve performance, run the relevant benchmark immediately after applying the change and report the measured results (command + before/after numbers) in your response/PR.
+- **Performance Trace Log**: For every performance-optimization round, append the hypothesis, code change, benchmark command, before/after numbers, and keep/revert decision to `tasks/perf_optimization_rounds.md` before moving to the next round.
- **Zero-Copy**: Leverage zero-copy techniques when possible
- **JIT Compilation**: Consider JIT compilation opportunities
- **Memory Layout**: Optimize for cache-friendly memory access patterns
diff --git a/README.md b/README.md
index 4e1379b07..79bcab751 100644
--- a/README.md
+++ b/README.md
@@ -129,6 +129,14 @@ Fory C# demonstrates excellent performance compared to protobuf-net and MessageP
For more detailed benchmarks and methodology, see [C# Benchmarks](docs/benchmarks/csharp).
+### Swift Serialization Performance
+
+<p align="center">
+<img src="docs/benchmarks/swift/throughput.png" width="95%">
+</p>
+
+For more detailed benchmarks and methodology, see [SwiftBenchmarks](docs/benchmarks/swift).
+
## Installation
**Java**:
diff --git a/benchmarks/swift/Sources/SwiftBenchmark/BenchmarkModels.swift b/benchmarks/swift/Sources/SwiftBenchmark/BenchmarkModels.swift
index f2bbd74ee..17fc06411 100644
--- a/benchmarks/swift/Sources/SwiftBenchmark/BenchmarkModels.swift
+++ b/benchmarks/swift/Sources/SwiftBenchmark/BenchmarkModels.swift
@@ -65,39 +65,69 @@ enum OperationKind: String, CaseIterable {
@ForyObject
struct NumericStruct: Codable, Equatable {
+ @ForyField(id: 1)
var f1: Int32 = 0
+ @ForyField(id: 2)
var f2: Int32 = 0
+ @ForyField(id: 3)
var f3: Int32 = 0
+ @ForyField(id: 4)
var f4: Int32 = 0
+ @ForyField(id: 5)
var f5: Int32 = 0
+ @ForyField(id: 6)
var f6: Int32 = 0
+ @ForyField(id: 7)
var f7: Int32 = 0
+ @ForyField(id: 8)
var f8: Int32 = 0
}
@ForyObject
struct Sample: Codable, Equatable {
+ @ForyField(id: 1)
var intValue: Int32 = 0
+ @ForyField(id: 2)
var longValue: Int64 = 0
+ @ForyField(id: 3)
var floatValue: Float = 0
+ @ForyField(id: 4)
var doubleValue: Double = 0
+ @ForyField(id: 5)
var shortValue: Int32 = 0
+ @ForyField(id: 6)
var charValue: Int32 = 0
+ @ForyField(id: 7)
var booleanValue: Bool = false
+ @ForyField(id: 8)
var intValueBoxed: Int32 = 0
+ @ForyField(id: 9)
var longValueBoxed: Int64 = 0
+ @ForyField(id: 10)
var floatValueBoxed: Float = 0
+ @ForyField(id: 11)
var doubleValueBoxed: Double = 0
+ @ForyField(id: 12)
var shortValueBoxed: Int32 = 0
+ @ForyField(id: 13)
var charValueBoxed: Int32 = 0
+ @ForyField(id: 14)
var booleanValueBoxed: Bool = false
+ @ForyField(id: 15)
var intArray: [Int32] = []
+ @ForyField(id: 16)
var longArray: [Int64] = []
+ @ForyField(id: 17)
var floatArray: [Float] = []
+ @ForyField(id: 18)
var doubleArray: [Double] = []
+ @ForyField(id: 19)
var shortArray: [Int32] = []
+ @ForyField(id: 20)
var charArray: [Int32] = []
+ @ForyField(id: 21)
var booleanArray: [Bool] = []
+ @ForyField(id: 22)
var string: String = ""
}
@@ -115,47 +145,69 @@ enum Size: Int32, Codable, Equatable {
@ForyObject
struct Media: Codable, Equatable {
+ @ForyField(id: 1)
var uri: String = ""
+ @ForyField(id: 2)
var title: String = ""
+ @ForyField(id: 3)
var width: Int32 = 0
+ @ForyField(id: 4)
var height: Int32 = 0
+ @ForyField(id: 5)
var format: String = ""
+ @ForyField(id: 6)
var duration: Int64 = 0
+ @ForyField(id: 7)
var size: Int64 = 0
+ @ForyField(id: 8)
var bitrate: Int32 = 0
+ @ForyField(id: 9)
var hasBitrate: Bool = false
+ @ForyField(id: 10)
var persons: [String] = []
+ @ForyField(id: 11)
var player: Player = .java
+ @ForyField(id: 12)
var copyright: String = ""
}
@ForyObject
struct Image: Codable, Equatable {
+ @ForyField(id: 1)
var uri: String = ""
+ @ForyField(id: 2)
var title: String = ""
+ @ForyField(id: 3)
var width: Int32 = 0
+ @ForyField(id: 4)
var height: Int32 = 0
+ @ForyField(id: 5)
var size: Size = .small
}
@ForyObject
struct MediaContent: Codable, Equatable {
+ @ForyField(id: 1)
var media: Media = .init()
+ @ForyField(id: 2)
var images: [Image] = []
}
@ForyObject
struct StructList: Codable, Equatable {
+ @ForyField(id: 1)
var structList: [NumericStruct] = []
}
@ForyObject
struct SampleList: Codable, Equatable {
+ @ForyField(id: 1)
var sampleList: [Sample] = []
}
@ForyObject
struct MediaContentList: Codable, Equatable {
+ @ForyField(id: 1)
var mediaContentList: [MediaContent] = []
}
diff --git a/benchmarks/swift/Sources/SwiftBenchmark/BenchmarkRunner.swift b/benchmarks/swift/Sources/SwiftBenchmark/BenchmarkRunner.swift
index 4b7043795..5f6d09a6c 100644
--- a/benchmarks/swift/Sources/SwiftBenchmark/BenchmarkRunner.swift
+++ b/benchmarks/swift/Sources/SwiftBenchmark/BenchmarkRunner.swift
@@ -68,7 +68,7 @@ final class BenchmarkSuite {
init(config: BenchmarkConfig) {
self.config = config
- self.fory = Fory(xlang: false, trackRef: false, compatible: false)
+ self.fory = Fory(xlang: false, trackRef: false, compatible: true)
registerTypes()
}
@@ -129,11 +129,9 @@ final class BenchmarkSuite {
fory.register(Media.self, id: 3)
fory.register(Image.self, id: 4)
fory.register(MediaContent.self, id: 5)
- fory.register(Player.self, id: 6)
- fory.register(Size.self, id: 7)
- fory.register(StructList.self, id: 8)
- fory.register(SampleList.self, id: 9)
- fory.register(MediaContentList.self, id: 10)
+ fory.register(StructList.self, id: 6)
+ fory.register(SampleList.self, id: 7)
+ fory.register(MediaContentList.self, id: 8)
}
private func shouldRun(_ dataKind: DataKind, _ serializer: SerializerKind) -> Bool {
diff --git a/benchmarks/swift/benchmark_report.py b/benchmarks/swift/benchmark_report.py
index 517a1b5f9..a9f7d58bb 100755
--- a/benchmarks/swift/benchmark_report.py
+++ b/benchmarks/swift/benchmark_report.py
@@ -253,7 +253,7 @@ def write_report(
else:
image_path = plot_name
lines.append('<p align="center">')
- lines.append(f'<img src="{image_path}" width="95%">')
+ lines.append(f'<img src="{image_path}" width="95%" />')
lines.append("</p>")
lines.append("")
lines.append(
diff --git a/docs/benchmarks/swift/README.md b/docs/benchmarks/swift/README.md
new file mode 100644
index 000000000..58b6c032a
--- /dev/null
+++ b/docs/benchmarks/swift/README.md
@@ -0,0 +1,46 @@
+# Fory Swift Benchmark
+
+This benchmark compares serialization and deserialization throughput for Apache Fory, Protocol Buffers, and MessagePack in Swift.
+
+## Hardware and Runtime Info
+
+| Key | Value |
+| --------------------- | ----------------------------- |
+| Timestamp | 2026-03-10T06:25:16Z |
+| OS | Version 15.7.2 (Build 24G325) |
+| Host | macbook-pro.local |
+| CPU Cores (Logical) | 12 |
+| Memory (GB) | 48.00 |
+| Duration per case (s) | 3 |
+
+## Throughput Results
+
+<p align="center">
+<img src="throughput.png" width="95%" />
+</p>
+
+| Datatype | Operation | Fory TPS | Protobuf TPS | Msgpack TPS | Fastest |
+| ---------------- | ----------- | ---------: | -----------: | ----------: | ------------ |
+| Struct | Serialize | 9,727,950 | 6,572,406 | 141,248 | fory (1.48x) |
+| Struct | Deserialize | 11,889,570 | 8,584,510 | 99,792 | fory (1.39x) |
+| Sample | Serialize | 3,496,305 | 1,281,983 | 17,188 | fory (2.73x) |
+| Sample | Deserialize | 1,045,018 | 765,706 | 12,767 | fory (1.36x) |
+| MediaContent | Serialize | 1,425,354 | 678,542 | 29,048 | fory (2.10x) |
+| MediaContent | Deserialize | 614,447 | 478,298 | 12,711 | fory (1.28x) |
+| StructList | Serialize | 3,307,962 | 1,028,210 | 24,781 | fory (3.22x) |
+| StructList | Deserialize | 2,788,200 | 708,596 | 8,160 | fory (3.93x) |
+| SampleList | Serialize | 715,734 | 205,380 | 3,361 | fory (3.48x) |
+| SampleList | Deserialize | 199,317 | 133,425 | 1,498 | fory (1.49x) |
+| MediaContentList | Serialize | 364,097 | 103,721 | 5,538 | fory (3.51x) |
+| MediaContentList | Deserialize | 103,421 | 86,331 | 1,529 | fory (1.20x) |
+
+## Serialized Size (bytes)
+
+| Datatype | Fory | Protobuf | Msgpack |
+| ---------------- | ---: | -------: | ------: |
+| MediaContent | 365 | 301 | 524 |
+| MediaContentList | 1535 | 1520 | 2639 |
+| Sample | 446 | 375 | 737 |
+| SampleList | 1980 | 1890 | 3698 |
+| Struct | 58 | 61 | 65 |
+| StructList | 184 | 315 | 338 |
diff --git a/docs/benchmarks/swift/throughput.png b/docs/benchmarks/swift/throughput.png
new file mode 100644
index 000000000..291dc8145
Binary files /dev/null and b/docs/benchmarks/swift/throughput.png differ
diff --git a/docs/guide/swift/configuration.md b/docs/guide/swift/configuration.md
index 4b90cc906..0e464807a 100644
--- a/docs/guide/swift/configuration.md
+++ b/docs/guide/swift/configuration.md
@@ -39,6 +39,11 @@ Default configuration:
let fory = Fory() // xlang=true, trackRef=false, compatible=false
```
+## Threading
+
+`Fory` is single-threaded and optimized to reuse one read/write context pair on the calling thread.
+Reuse one instance per thread and do not use the same instance concurrently.
+
## Options
### `xlang`
diff --git a/swift/README.md b/swift/README.md
index c28a0b931..7f51298ee 100644
--- a/swift/README.md
+++ b/swift/README.md
@@ -76,6 +76,10 @@ let output2: User = try fory.deserialize(from: buffer)
assert(output2 == input)
```
+### 4. Threading
+
+`Fory` is the fastest option for single-threaded reuse. Keep one instance per thread.
+
## 📚 Core Features
### 1. Object Graph Serialization
diff --git a/swift/Sources/Fory/AnySerializer.swift b/swift/Sources/Fory/AnySerializer.swift
index 6765ee32b..ea263e5fd 100644
--- a/swift/Sources/Fory/AnySerializer.swift
+++ b/swift/Sources/Fory/AnySerializer.swift
@@ -28,6 +28,14 @@ public struct ForyAnyNullValue: Serializer {
.none
}
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public var foryIsNone: Bool {
true
}
@@ -57,17 +65,22 @@ extension AnyHashable: Serializer {
}
public static func foryReadData(_ context: ReadContext) throws -> AnyHashable {
- guard let typeInfo = context.dynamicTypeInfo(for: Self.self) else {
- throw ForyError.invalidData("dynamic AnyHashable key requires type info")
- }
- if typeInfo.wireTypeID == .none {
+ _ = context
+ throw ForyError.invalidData(
+ "dynamic AnyHashable key read requires type info; foryReadData should not be called directly"
+ )
+ }
+
+ public static func foryReadCompatibleData(_ context: ReadContext, remoteTypeInfo: TypeInfo) throws -> AnyHashable {
+ let typeInfo = remoteTypeInfo
+ if typeInfo.typeID == .none {
throw ForyError.invalidData("dynamic AnyHashable key cannot be null")
}
- let decoded = try context.typeResolver.readDynamicValue(typeInfo: typeInfo, context: context)
+ let decoded = try context.readAnyValue(typeInfo: typeInfo)
return try toAnyHashableKey(decoded)
}
- public static func foryWriteTypeInfo(_ context: WriteContext) throws {
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
_ = context
throw ForyError.invalidData("dynamic AnyHashable key type info is runtime-only")
}
@@ -76,9 +89,8 @@ extension AnyHashable: Serializer {
try writeAnyTypeInfo(base, context: context)
}
- public static func foryReadTypeInfo(_ context: ReadContext) throws {
- let typeInfo = try context.typeResolver.readDynamicTypeInfo(context: context)
- context.setDynamicTypeInfo(for: Self.self, typeInfo)
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try context.readTypeInfo()
}
public func foryWrite(
@@ -105,7 +117,7 @@ extension Optional: OptionalTypeMarker {
static var noneValue: Wrapped? { nil }
}
-private struct DynamicAnyValue: Serializer {
+struct DynamicAnyValue: Serializer {
var value: Any = ForyAnyNullValue()
init(_ value: Any) {
@@ -124,7 +136,7 @@ private struct DynamicAnyValue: Serializer {
true
}
- static var isReferenceTrackableType: Bool {
+ static var isRefType: Bool {
true
}
@@ -161,16 +173,21 @@ private struct DynamicAnyValue: Serializer {
}
static func foryReadData(_ context: ReadContext) throws -> DynamicAnyValue {
- guard let typeInfo = context.dynamicTypeInfo(for: Self.self) else {
- throw ForyError.invalidData("dynamic Any value requires type info")
- }
- if typeInfo.wireTypeID == .none {
+ _ = context
+ throw ForyError.invalidData(
+ "dynamic Any read requires type info; foryReadData should not be called directly"
+ )
+ }
+
+ static func foryReadCompatibleData(_ context: ReadContext, remoteTypeInfo: TypeInfo) throws -> DynamicAnyValue {
+ let typeInfo = remoteTypeInfo
+ if typeInfo.typeID == .none {
return .foryDefault()
}
- return DynamicAnyValue(try context.typeResolver.readDynamicValue(typeInfo: typeInfo, context: context))
+ return DynamicAnyValue(try context.readAnyValue(typeInfo: typeInfo))
}
- static func foryWriteTypeInfo(_ context: WriteContext) throws {
+ static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
_ = context
throw ForyError.invalidData("dynamic Any value type info is runtime-only")
}
@@ -183,9 +200,8 @@ private struct DynamicAnyValue: Serializer {
try writeAnyTypeInfo(value, context: context)
}
- static func foryReadTypeInfo(_ context: ReadContext) throws {
- let typeInfo = try context.typeResolver.readDynamicTypeInfo(context: context)
- context.setDynamicTypeInfo(for: Self.self, typeInfo)
+ static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try context.readTypeInfo()
}
func foryWrite(
@@ -199,8 +215,8 @@ private struct DynamicAnyValue: Serializer {
context.buffer.writeInt8(RefFlag.null.rawValue)
return
}
- if refMode == .tracking, anyValueIsReferenceTrackable(value), let object = value as AnyObject? {
- if context.refWriter.tryWriteReference(buffer: context.buffer, object: object) {
+ if refMode == .tracking, anyValueIsRefType(value), let object = value as AnyObject? {
+ if context.refWriter.tryWriteRef(buffer: context.buffer, object: object) {
return
}
} else {
@@ -219,6 +235,20 @@ private struct DynamicAnyValue: Serializer {
refMode: RefMode,
readTypeInfo: Bool
) throws -> DynamicAnyValue {
+ @inline(__always)
+ func requireDynamicTypeInfo() throws -> TypeInfo {
+ if readTypeInfo {
+ guard let remoteTypeInfo = try foryReadTypeInfo(context) else {
+ throw ForyError.invalidData("dynamic Any value requires type info")
+ }
+ return remoteTypeInfo
+ }
+ guard let remoteTypeInfo = context.getTypeInfo(for: Self.self) else {
+ throw ForyError.invalidData("dynamic Any value requires type info")
+ }
+ return remoteTypeInfo
+ }
+
if refMode != .none {
let rawFlag = try context.buffer.readInt8()
guard let flag = RefFlag(rawValue: rawFlag) else {
@@ -239,24 +269,19 @@ private struct DynamicAnyValue: Serializer {
}
return DynamicAnyValue(referenced)
case .refValue:
- let reservedRefID = context.refReader.reserveRefID()
- context.pushPendingReference(reservedRefID)
- if readTypeInfo {
- try foryReadTypeInfo(context)
+ let reservedRefID = context.trackRef ? context.refReader.reserveRefID() : nil
+ let remoteTypeInfo = try requireDynamicTypeInfo()
+ let value = try foryReadCompatibleData(context, remoteTypeInfo: remoteTypeInfo)
+ if let reservedRefID {
+ context.refReader.storeRef(value, at: reservedRefID)
}
- let value = try foryReadData(context)
- context.finishPendingReferenceIfNeeded(value)
- context.popPendingReference()
return value
case .notNullValue:
break
}
}
- if readTypeInfo {
- try foryReadTypeInfo(context)
- }
- return try foryReadData(context)
+ return try foryReadCompatibleData(context, remoteTypeInfo: requireDynamicTypeInfo())
}
}
@@ -271,11 +296,11 @@ private func unwrapOptionalAny(_ value: Any) -> Any? {
return child
}
-private func anyValueIsReferenceTrackable(_ value: Any) -> Bool {
+private func anyValueIsRefType(_ value: Any) -> Bool {
guard let serializer = value as? any Serializer else {
return false
}
- return type(of: serializer).isReferenceTrackableType
+ return type(of: serializer).isRefType
}
private func toAnyHashableKey(_ value: Any) throws -> AnyHashable {
diff --git a/swift/Sources/Fory/ByteBuffer.swift b/swift/Sources/Fory/ByteBuffer.swift
index 214c22c5d..0ddc18028 100644
--- a/swift/Sources/Fory/ByteBuffer.swift
+++ b/swift/Sources/Fory/ByteBuffer.swift
@@ -24,6 +24,8 @@ public final class ByteBuffer {
@usableFromInline
internal var cursor: Int
+ private var dataBridge = Data()
+
@inlinable
public init(capacity: Int = 256) {
storage = []
@@ -53,6 +55,49 @@ public final class ByteBuffer {
storage.count - cursor
}
+ @usableFromInline
+ @inline(__always)
+ internal var readableCount: Int {
+ storage.count
+ }
+
+ @usableFromInline
+ @inline(__always)
+ internal func byte(at index: Int) -> UInt8 {
+ return storage[index]
+ }
+
+ @usableFromInline
+ @inline(__always)
+ internal func copyBytes(start: Int, end: Int) -> [UInt8] {
+ let length = end - start
+ guard length > 0 else {
+ return []
+ }
+ return Array(storage[start..<end])
+ }
+
+ @usableFromInline
+ @inline(__always)
+ internal func matchesBytes(start: Int, bytes: [UInt8]) -> Bool {
+ var index = 0
+ while index < bytes.count {
+ if storage[start + index] != bytes[index] {
+ return false
+ }
+ index += 1
+ }
+ return true
+ }
+
+ @usableFromInline
+ @inline(__always)
+ internal func withUnsafeReadableBytes<R>(
+ _ body: (UnsafeBufferPointer<UInt8>) throws -> R
+ ) rethrows -> R {
+ return try storage.withUnsafeBufferPointer(body)
+ }
+
@inlinable
public func reserve(_ additional: Int) {
storage.reserveCapacity(storage.count + additional)
@@ -90,14 +135,71 @@ public final class ByteBuffer {
if dataCount > 0 {
data.withUnsafeBytes { source in
- storage.withUnsafeMutableBytes { destination in
- destination.copyBytes(from: source)
+ storage.withUnsafeMutableBufferPointer { destination in
+ guard let sourceBase = source.baseAddress, let destinationBase = destination.baseAddress else {
+ return
+ }
+ UnsafeMutableRawPointer(destinationBase).copyMemory(
+ from: sourceBase,
+ byteCount: dataCount
+ )
}
}
}
cursor = 0
}
+ @usableFromInline
+ @inline(__always)
+ internal func swapState(with other: ByteBuffer) {
+ swap(&storage, &other.storage)
+ swap(&cursor, &other.cursor)
+ swap(&dataBridge, &other.dataBridge)
+ }
+
+ @usableFromInline
+ @inline(__always)
+ internal func copyToData() -> Data {
+ let byteCount = storage.count
+ if dataBridge.count != byteCount {
+ dataBridge.count = byteCount
+ }
+ if byteCount > 0 {
+ dataBridge.withUnsafeMutableBytes { destination in
+ guard let destinationBase = destination.baseAddress else {
+ return
+ }
+ storage.withUnsafeBytes { source in
+ guard let sourceBase = source.baseAddress else {
+ return
+ }
+ destinationBase.copyMemory(from: sourceBase, byteCount: byteCount)
+ }
+ }
+ }
+ return dataBridge
+ }
+
+ @usableFromInline
+ @inline(__always)
+ internal func materializeData(
+ byteCount: Int,
+ _ body: (UnsafeMutablePointer<UInt8>) -> Void
+ ) -> Data {
+ if dataBridge.count != byteCount {
+ dataBridge.count = byteCount
+ }
+ if byteCount > 0 {
+ dataBridge.withUnsafeMutableBytes { destination in
+ guard let base = destination.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
+ return
+ }
+ body(base)
+ }
+ }
+ return dataBridge
+ }
+
@inlinable
public func getCursor() -> Int {
cursor
@@ -117,22 +219,6 @@ public final class ByteBuffer {
}
}
- @inlinable
- @inline(__always)
- internal func appendUninitializedBytes(
- count: Int,
- _ body: (UnsafeMutablePointer<UInt8>) -> Void
- ) {
- guard count > 0 else {
- return
- }
- let start = storage.count
- storage.append(contentsOf: repeatElement(0, count: count))
- storage.withUnsafeMutableBufferPointer { buffer in
- body(buffer.baseAddress!.advanced(by: start))
- }
- }
-
@inlinable
@inline(__always)
public func writeUInt8(_ value: UInt8) {
@@ -186,51 +272,29 @@ public final class ByteBuffer {
return
}
if value < 0x4000 {
- let u1 = (UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80
- let u2 = UInt8(truncatingIfNeeded: value >> 7)
- appendUninitializedBytes(count: 2) { dst in
- dst[0] = u1
- dst[1] = u2
- }
+ storage.append((UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80)
+ storage.append(UInt8(truncatingIfNeeded: value >> 7))
return
}
if value < 0x20_0000 {
- let u1 = (UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80
- let u2 = (UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80
- let u3 = UInt8(truncatingIfNeeded: value >> 14)
- appendUninitializedBytes(count: 3) { dst in
- dst[0] = u1
- dst[1] = u2
- dst[2] = u3
- }
+ storage.append((UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80)
+ storage.append(UInt8(truncatingIfNeeded: value >> 14))
return
}
if value < 0x1000_0000 {
- let u1 = (UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80
- let u2 = (UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80
- let u3 = (UInt8(truncatingIfNeeded: value >> 14) & 0x7F) | 0x80
- let u4 = UInt8(truncatingIfNeeded: value >> 21)
- appendUninitializedBytes(count: 4) { dst in
- dst[0] = u1
- dst[1] = u2
- dst[2] = u3
- dst[3] = u4
- }
+ storage.append((UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 14) & 0x7F) | 0x80)
+ storage.append(UInt8(truncatingIfNeeded: value >> 21))
return
}
- let u1 = (UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80
- let u2 = (UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80
- let u3 = (UInt8(truncatingIfNeeded: value >> 14) & 0x7F) | 0x80
- let u4 = (UInt8(truncatingIfNeeded: value >> 21) & 0x7F) | 0x80
- let u5 = UInt8(truncatingIfNeeded: value >> 28)
- appendUninitializedBytes(count: 5) { dst in
- dst[0] = u1
- dst[1] = u2
- dst[2] = u3
- dst[3] = u4
- dst[4] = u5
- }
+ storage.append((UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 14) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 21) & 0x7F) | 0x80)
+ storage.append(UInt8(truncatingIfNeeded: value >> 28))
}
@inlinable
@@ -241,131 +305,71 @@ public final class ByteBuffer {
return
}
if value < 0x4000 {
- let u1 = (UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80
- let u2 = UInt8(truncatingIfNeeded: value >> 7)
- appendUninitializedBytes(count: 2) { dst in
- dst[0] = u1
- dst[1] = u2
- }
+ storage.append((UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80)
+ storage.append(UInt8(truncatingIfNeeded: value >> 7))
return
}
if value < 0x20_0000 {
- let u1 = (UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80
- let u2 = (UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80
- let u3 = UInt8(truncatingIfNeeded: value >> 14)
- appendUninitializedBytes(count: 3) { dst in
- dst[0] = u1
- dst[1] = u2
- dst[2] = u3
- }
+ storage.append((UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80)
+ storage.append(UInt8(truncatingIfNeeded: value >> 14))
return
}
if value < 0x1000_0000 {
- let u1 = (UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80
- let u2 = (UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80
- let u3 = (UInt8(truncatingIfNeeded: value >> 14) & 0x7F) | 0x80
- let u4 = UInt8(truncatingIfNeeded: value >> 21)
- appendUninitializedBytes(count: 4) { dst in
- dst[0] = u1
- dst[1] = u2
- dst[2] = u3
- dst[3] = u4
- }
+ storage.append((UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 14) & 0x7F) | 0x80)
+ storage.append(UInt8(truncatingIfNeeded: value >> 21))
return
}
if value < 0x8_0000_0000 {
- let u1 = (UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80
- let u2 = (UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80
- let u3 = (UInt8(truncatingIfNeeded: value >> 14) & 0x7F) | 0x80
- let u4 = (UInt8(truncatingIfNeeded: value >> 21) & 0x7F) | 0x80
- let u5 = UInt8(truncatingIfNeeded: value >> 28)
- appendUninitializedBytes(count: 5) { dst in
- dst[0] = u1
- dst[1] = u2
- dst[2] = u3
- dst[3] = u4
- dst[4] = u5
- }
+ storage.append((UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 14) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 21) & 0x7F) | 0x80)
+ storage.append(UInt8(truncatingIfNeeded: value >> 28))
return
}
if value < 0x400_0000_0000 {
- let u1 = (UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80
- let u2 = (UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80
- let u3 = (UInt8(truncatingIfNeeded: value >> 14) & 0x7F) | 0x80
- let u4 = (UInt8(truncatingIfNeeded: value >> 21) & 0x7F) | 0x80
- let u5 = (UInt8(truncatingIfNeeded: value >> 28) & 0x7F) | 0x80
- let u6 = UInt8(truncatingIfNeeded: value >> 35)
- appendUninitializedBytes(count: 6) { dst in
- dst[0] = u1
- dst[1] = u2
- dst[2] = u3
- dst[3] = u4
- dst[4] = u5
- dst[5] = u6
- }
+ storage.append((UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 14) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 21) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 28) & 0x7F) | 0x80)
+ storage.append(UInt8(truncatingIfNeeded: value >> 35))
return
}
if value < 0x2_0000_0000_0000 {
- let u1 = (UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80
- let u2 = (UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80
- let u3 = (UInt8(truncatingIfNeeded: value >> 14) & 0x7F) | 0x80
- let u4 = (UInt8(truncatingIfNeeded: value >> 21) & 0x7F) | 0x80
- let u5 = (UInt8(truncatingIfNeeded: value >> 28) & 0x7F) | 0x80
- let u6 = (UInt8(truncatingIfNeeded: value >> 35) & 0x7F) | 0x80
- let u7 = UInt8(truncatingIfNeeded: value >> 42)
- appendUninitializedBytes(count: 7) { dst in
- dst[0] = u1
- dst[1] = u2
- dst[2] = u3
- dst[3] = u4
- dst[4] = u5
- dst[5] = u6
- dst[6] = u7
- }
+ storage.append((UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 14) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 21) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 28) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 35) & 0x7F) | 0x80)
+ storage.append(UInt8(truncatingIfNeeded: value >> 42))
return
}
if value < 0x100_0000_0000_0000 {
- let u1 = (UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80
- let u2 = (UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80
- let u3 = (UInt8(truncatingIfNeeded: value >> 14) & 0x7F) | 0x80
- let u4 = (UInt8(truncatingIfNeeded: value >> 21) & 0x7F) | 0x80
- let u5 = (UInt8(truncatingIfNeeded: value >> 28) & 0x7F) | 0x80
- let u6 = (UInt8(truncatingIfNeeded: value >> 35) & 0x7F) | 0x80
- let u7 = (UInt8(truncatingIfNeeded: value >> 42) & 0x7F) | 0x80
- let u8 = UInt8(truncatingIfNeeded: value >> 49)
- appendUninitializedBytes(count: 8) { dst in
- dst[0] = u1
- dst[1] = u2
- dst[2] = u3
- dst[3] = u4
- dst[4] = u5
- dst[5] = u6
- dst[6] = u7
- dst[7] = u8
- }
+ storage.append((UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 14) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 21) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 28) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 35) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 42) & 0x7F) | 0x80)
+ storage.append(UInt8(truncatingIfNeeded: value >> 49))
return
}
- let u1 = (UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80
- let u2 = (UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80
- let u3 = (UInt8(truncatingIfNeeded: value >> 14) & 0x7F) | 0x80
- let u4 = (UInt8(truncatingIfNeeded: value >> 21) & 0x7F) | 0x80
- let u5 = (UInt8(truncatingIfNeeded: value >> 28) & 0x7F) | 0x80
- let u6 = (UInt8(truncatingIfNeeded: value >> 35) & 0x7F) | 0x80
- let u7 = (UInt8(truncatingIfNeeded: value >> 42) & 0x7F) | 0x80
- let u8 = (UInt8(truncatingIfNeeded: value >> 49) & 0x7F) | 0x80
- let u9 = UInt8(truncatingIfNeeded: value >> 56)
- appendUninitializedBytes(count: 9) { dst in
- dst[0] = u1
- dst[1] = u2
- dst[2] = u3
- dst[3] = u4
- dst[4] = u5
- dst[5] = u6
- dst[6] = u7
- dst[7] = u8
- dst[8] = u9
- }
+ storage.append((UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 14) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 21) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 28) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 35) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 42) & 0x7F) | 0x80)
+ storage.append((UInt8(truncatingIfNeeded: value >> 49) & 0x7F) | 0x80)
+ storage.append(UInt8(truncatingIfNeeded: value >> 56))
}
@inlinable
@@ -423,6 +427,12 @@ public final class ByteBuffer {
storage.append(contentsOf: bytes)
}
+ @inlinable
+ @inline(__always)
+ public func writeBytes(_ bytes: [UInt8]) {
+ storage.append(contentsOf: bytes)
+ }
+
@inlinable
public func writeBytes(_ bytes: UnsafeRawBufferPointer) {
storage.append(contentsOf: bytes)
@@ -450,16 +460,23 @@ public final class ByteBuffer {
@inlinable
@inline(__always)
public func checkBound(_ need: Int) throws {
- if cursor + need > storage.count {
- throw ForyError.outOfBounds(cursor: cursor, need: need, length: storage.count)
+ let length = readableCount
+ if cursor + need > length {
+ throw ForyError.outOfBounds(cursor: cursor, need: need, length: length)
}
}
@inlinable
public func readBytes(into destination: UnsafeMutableRawBufferPointer) throws {
try checkBound(destination.count)
- storage.withUnsafeBytes { rawBytes in
- destination.copyBytes(from: UnsafeRawBufferPointer(rebasing: rawBytes[cursor..<(cursor + destination.count)]))
+ guard destination.count > 0, let destinationBase = destination.baseAddress else {
+ return
+ }
+ withUnsafeReadableBytes { rawBytes in
+ guard let sourceBase = rawBytes.baseAddress else {
+ return
+ }
+ destinationBase.copyMemory(from: sourceBase.advanced(by: cursor), byteCount: destination.count)
}
cursor += destination.count
}
@@ -469,10 +486,11 @@ public final class ByteBuffer {
public func readUInt8() throws -> UInt8 {
try checkBound(1)
defer { cursor += 1 }
- return storage[cursor]
+ return byte(at: cursor)
}
@inlinable
+ @inline(__always)
public func readInt8() throws -> Int8 {
Int8(bitPattern: try readUInt8())
}
@@ -481,8 +499,8 @@ public final class ByteBuffer {
@inline(__always)
public func readUInt16() throws -> UInt16 {
try checkBound(2)
- let b0 = UInt16(storage[cursor])
- let b1 = UInt16(storage[cursor + 1]) << 8
+ let b0 = UInt16(byte(at: cursor))
+ let b1 = UInt16(byte(at: cursor + 1)) << 8
cursor += 2
return b0 | b1
}
@@ -496,10 +514,10 @@ public final class ByteBuffer {
@inline(__always)
public func readUInt32() throws -> UInt32 {
try checkBound(4)
- let b0 = UInt32(storage[cursor])
- let b1 = UInt32(storage[cursor + 1]) << 8
- let b2 = UInt32(storage[cursor + 2]) << 16
- let b3 = UInt32(storage[cursor + 3]) << 24
+ let b0 = UInt32(byte(at: cursor))
+ let b1 = UInt32(byte(at: cursor + 1)) << 8
+ let b2 = UInt32(byte(at: cursor + 2)) << 16
+ let b3 = UInt32(byte(at: cursor + 3)) << 24
cursor += 4
return b0 | b1 | b2 | b3
}
@@ -513,14 +531,14 @@ public final class ByteBuffer {
@inline(__always)
public func readUInt64() throws -> UInt64 {
try checkBound(8)
- let b0 = UInt64(storage[cursor])
- let b1 = UInt64(storage[cursor + 1]) << 8
- let b2 = UInt64(storage[cursor + 2]) << 16
- let b3 = UInt64(storage[cursor + 3]) << 24
- let b4 = UInt64(storage[cursor + 4]) << 32
- let b5 = UInt64(storage[cursor + 5]) << 40
- let b6 = UInt64(storage[cursor + 6]) << 48
- let b7 = UInt64(storage[cursor + 7]) << 56
+ let b0 = UInt64(byte(at: cursor))
+ let b1 = UInt64(byte(at: cursor + 1)) << 8
+ let b2 = UInt64(byte(at: cursor + 2)) << 16
+ let b3 = UInt64(byte(at: cursor + 3)) << 24
+ let b4 = UInt64(byte(at: cursor + 4)) << 32
+ let b5 = UInt64(byte(at: cursor + 5)) << 40
+ let b6 = UInt64(byte(at: cursor + 6)) << 48
+ let b7 = UInt64(byte(at: cursor + 7)) << 56
cursor += 8
return b0 | b1 | b2 | b3 | b4 | b5 | b6 | b7
}
@@ -533,28 +551,28 @@ public final class ByteBuffer {
@inlinable
@inline(__always)
public func readVarUInt32() throws -> UInt32 {
- let available = storage.count - cursor
+ let available = readableCount - cursor
if available >= 5 {
let offset = cursor
- let b0 = storage[offset]
+ let b0 = byte(at: offset)
if b0 < 0x80 {
cursor = offset + 1
return UInt32(b0)
}
- let b1 = storage[offset + 1]
+ let b1 = byte(at: offset + 1)
if b1 < 0x80 {
cursor = offset + 2
return UInt32(b0 & 0x7F) | (UInt32(b1) << 7)
}
- let b2 = storage[offset + 2]
+ let b2 = byte(at: offset + 2)
if b2 < 0x80 {
cursor = offset + 3
return UInt32(b0 & 0x7F) | (UInt32(b1 & 0x7F) << 7) | (UInt32(b2) << 14)
}
- let b3 = storage[offset + 3]
+ let b3 = byte(at: offset + 3)
if b3 < 0x80 {
cursor = offset + 4
return UInt32(b0 & 0x7F) |
@@ -563,7 +581,7 @@ public final class ByteBuffer {
(UInt32(b3) << 21)
}
- let b4 = storage[offset + 4]
+ let b4 = byte(at: offset + 4)
if b4 >= 0x80 {
throw ForyError.encodingError("varuint32 overflow")
}
@@ -576,28 +594,28 @@ public final class ByteBuffer {
}
try checkBound(1)
- let b0 = storage[cursor]
+ let b0 = byte(at: cursor)
if b0 < 0x80 {
cursor += 1
return UInt32(b0)
}
try checkBound(2)
- let b1 = storage[cursor + 1]
+ let b1 = byte(at: cursor + 1)
if b1 < 0x80 {
cursor += 2
return UInt32(b0 & 0x7F) | (UInt32(b1) << 7)
}
try checkBound(3)
- let b2 = storage[cursor + 2]
+ let b2 = byte(at: cursor + 2)
if b2 < 0x80 {
cursor += 3
return UInt32(b0 & 0x7F) | (UInt32(b1 & 0x7F) << 7) | (UInt32(b2) << 14)
}
try checkBound(4)
- let b3 = storage[cursor + 3]
+ let b3 = byte(at: cursor + 3)
if b3 < 0x80 {
cursor += 4
return UInt32(b0 & 0x7F) |
@@ -607,7 +625,7 @@ public final class ByteBuffer {
}
try checkBound(5)
- let b4 = storage[cursor + 4]
+ let b4 = byte(at: cursor + 4)
if b4 >= 0x80 {
throw ForyError.encodingError("varuint32 overflow")
}
@@ -622,22 +640,22 @@ public final class ByteBuffer {
@inlinable
@inline(__always)
public func readVarUInt64() throws -> UInt64 {
- let available = storage.count - cursor
+ let available = readableCount - cursor
if available >= 9 {
let offset = cursor
- let b0 = storage[offset]
+ let b0 = byte(at: offset)
if b0 < 0x80 {
cursor = offset + 1
return UInt64(b0)
}
- let b1 = storage[offset + 1]
+ let b1 = byte(at: offset + 1)
if b1 < 0x80 {
cursor = offset + 2
return UInt64(b0 & 0x7F) | (UInt64(b1) << 7)
}
- let b2 = storage[offset + 2]
+ let b2 = byte(at: offset + 2)
if b2 < 0x80 {
cursor = offset + 3
return UInt64(b0 & 0x7F) |
@@ -645,7 +663,7 @@ public final class ByteBuffer {
(UInt64(b2) << 14)
}
- let b3 = storage[offset + 3]
+ let b3 = byte(at: offset + 3)
if b3 < 0x80 {
cursor = offset + 4
return UInt64(b0 & 0x7F) |
@@ -654,7 +672,7 @@ public final class ByteBuffer {
(UInt64(b3) << 21)
}
- let b4 = storage[offset + 4]
+ let b4 = byte(at: offset + 4)
if b4 < 0x80 {
cursor = offset + 5
return UInt64(b0 & 0x7F) |
@@ -664,7 +682,7 @@ public final class ByteBuffer {
(UInt64(b4) << 28)
}
- let b5 = storage[offset + 5]
+ let b5 = byte(at: offset + 5)
if b5 < 0x80 {
cursor = offset + 6
return UInt64(b0 & 0x7F) |
@@ -675,7 +693,7 @@ public final class ByteBuffer {
(UInt64(b5) << 35)
}
- let b6 = storage[offset + 6]
+ let b6 = byte(at: offset + 6)
if b6 < 0x80 {
cursor = offset + 7
return UInt64(b0 & 0x7F) |
@@ -687,7 +705,7 @@ public final class ByteBuffer {
(UInt64(b6) << 42)
}
- let b7 = storage[offset + 7]
+ let b7 = byte(at: offset + 7)
if b7 < 0x80 {
cursor = offset + 8
return UInt64(b0 & 0x7F) |
@@ -700,7 +718,7 @@ public final class ByteBuffer {
(UInt64(b7) << 49)
}
- let b8 = storage[offset + 8]
+ let b8 = byte(at: offset + 8)
cursor = offset + 9
let low = UInt64(b0 & 0x7F) |
(UInt64(b1 & 0x7F) << 7) |
@@ -715,21 +733,21 @@ public final class ByteBuffer {
}
try checkBound(1)
- let b0 = storage[cursor]
+ let b0 = byte(at: cursor)
if b0 < 0x80 {
cursor += 1
return UInt64(b0)
}
try checkBound(2)
- let b1 = storage[cursor + 1]
+ let b1 = byte(at: cursor + 1)
if b1 < 0x80 {
cursor += 2
return UInt64(b0 & 0x7F) | (UInt64(b1) << 7)
}
try checkBound(3)
- let b2 = storage[cursor + 2]
+ let b2 = byte(at: cursor + 2)
if b2 < 0x80 {
cursor += 3
return UInt64(b0 & 0x7F) |
@@ -738,7 +756,7 @@ public final class ByteBuffer {
}
try checkBound(4)
- let b3 = storage[cursor + 3]
+ let b3 = byte(at: cursor + 3)
if b3 < 0x80 {
cursor += 4
return UInt64(b0 & 0x7F) |
@@ -748,7 +766,7 @@ public final class ByteBuffer {
}
try checkBound(5)
- let b4 = storage[cursor + 4]
+ let b4 = byte(at: cursor + 4)
if b4 < 0x80 {
cursor += 5
return UInt64(b0 & 0x7F) |
@@ -759,7 +777,7 @@ public final class ByteBuffer {
}
try checkBound(6)
- let b5 = storage[cursor + 5]
+ let b5 = byte(at: cursor + 5)
if b5 < 0x80 {
cursor += 6
return UInt64(b0 & 0x7F) |
@@ -771,7 +789,7 @@ public final class ByteBuffer {
}
try checkBound(7)
- let b6 = storage[cursor + 6]
+ let b6 = byte(at: cursor + 6)
if b6 < 0x80 {
cursor += 7
return UInt64(b0 & 0x7F) |
@@ -784,7 +802,7 @@ public final class ByteBuffer {
}
try checkBound(8)
- let b7 = storage[cursor + 7]
+ let b7 = byte(at: cursor + 7)
if b7 < 0x80 {
cursor += 8
return UInt64(b0 & 0x7F) |
@@ -798,7 +816,7 @@ public final class ByteBuffer {
}
try checkBound(9)
- let b8 = storage[cursor + 8]
+ let b8 = byte(at: cursor + 8)
cursor += 9
let low = UInt64(b0 & 0x7F) |
(UInt64(b1 & 0x7F) << 7) |
@@ -880,11 +898,20 @@ public final class ByteBuffer {
@inline(__always)
public func readUTF8String(count: Int) throws -> String {
try checkBound(count)
+ if count == 0 {
+ return ""
+ }
let start = cursor
let end = start + count
cursor = end
- let utf8Bytes = storage[start..<end]
- guard let decoded = String(bytes: utf8Bytes, encoding: .utf8) else {
+ let decoded = withUnsafeReadableBytes { buffer -> String? in
+ guard let base = buffer.baseAddress else {
+ return nil
+ }
+ let utf8Bytes = UnsafeBufferPointer(start: base.advanced(by: start), count: count)
+ return String(bytes: utf8Bytes, encoding: .utf8)
+ }
+ guard let decoded else {
throw ForyError.invalidData("invalid UTF-8 sequence")
}
return decoded
@@ -898,6 +925,11 @@ public final class ByteBuffer {
@inlinable
public func toData() -> Data {
- Data(storage)
+ return storage.withUnsafeBytes { rawBytes in
+ guard let baseAddress = rawBytes.baseAddress else {
+ return Data()
+ }
+ return Data(bytes: baseAddress, count: rawBytes.count)
+ }
}
}
diff --git a/swift/Sources/Fory/CollectionSerializers.swift b/swift/Sources/Fory/CollectionSerializers.swift
index b95a71b64..032436a58 100644
--- a/swift/Sources/Fory/CollectionSerializers.swift
+++ b/swift/Sources/Fory/CollectionSerializers.swift
@@ -236,7 +236,7 @@ private func writePrimitiveArray<Element: Serializer>(_ value: [Element], contex
private func readPrimitiveArray<Element: Serializer>(_ context: ReadContext) throws -> [Element] {
let payloadSize = Int(try context.buffer.readVarUInt32())
- try context.ensureRemainingBytes(payloadSize, label: "\(Element.self)_array_payload")
+ try context.ensureRemainingBytes(payloadSize, label: "primitive_array_payload")
if Element.self == UInt8.self {
try context.ensureCollectionLength(payloadSize, label: "uint8_array")
@@ -448,7 +448,7 @@ extension Array: Serializer where Element: Serializer {
primitiveArrayTypeID(for: Element.self) ?? .list
}
- public static func foryWriteTypeInfo(_ context: WriteContext) throws {
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
if Element.self == UInt8.self, context.compatible {
context.buffer.writeUInt8(UInt8(truncatingIfNeeded: TypeId.binary.rawValue))
return
@@ -456,7 +456,7 @@ extension Array: Serializer where Element: Serializer {
context.buffer.writeUInt8(UInt8(truncatingIfNeeded: staticTypeId.rawValue))
}
- public static func foryReadTypeInfo(_ context: ReadContext) throws {
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
let rawTypeID = try context.buffer.readVarUInt32()
guard let actualTypeID = TypeId(rawValue: rawTypeID) else {
throw ForyError.invalidData("unknown type id \(rawTypeID)")
@@ -464,7 +464,7 @@ extension Array: Serializer where Element: Serializer {
if Element.self == UInt8.self {
if actualTypeID == .uint8Array || actualTypeID == .binary {
- return
+ return nil
}
throw ForyError.typeMismatch(expected: TypeId.uint8Array.rawValue, actual: rawTypeID)
}
@@ -473,6 +473,7 @@ extension Array: Serializer where Element: Serializer {
if actualTypeID != expectedTypeID {
throw ForyError.typeMismatch(expected: expectedTypeID.rawValue, actual: rawTypeID)
}
+ return nil
}
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
@@ -488,7 +489,7 @@ extension Array: Serializer where Element: Serializer {
}
let hasNull = Element.isNullableType && self.contains(where: { $0.foryIsNone })
- let trackRef = context.trackRef && Element.isReferenceTrackableType
+ let trackRef = context.trackRef && Element.isRefType
let declaredElementType = hasGenerics && !TypeId.needsTypeInfoForField(Element.staticTypeId)
let dynamicElementType = Element.staticTypeId == .unknown
@@ -505,7 +506,7 @@ extension Array: Serializer where Element: Serializer {
buffer.writeUInt8(header)
if !dynamicElementType && !declaredElementType {
- try Element.foryWriteTypeInfo(context)
+ try Element.foryWriteStaticTypeInfo(context)
}
if dynamicElementType {
@@ -560,8 +561,6 @@ extension Array: Serializer where Element: Serializer {
let hasNull = (header & CollectionHeader.hasNull) != 0
let declared = (header & CollectionHeader.declaredElementType) != 0
let sameType = (header & CollectionHeader.sameType) != 0
- let canonicalizeElements = context.trackRef && !trackRef && Element.isReferenceTrackableType
-
if !sameType {
if trackRef {
return try readArrayUninitialized(count: length) { destination in
@@ -580,18 +579,9 @@ extension Array: Serializer where Element: Serializer {
if refFlag == RefFlag.null.rawValue {
destination.advanced(by: index).initialize(to: Element.foryDefault())
} else if refFlag == RefFlag.notNullValue.rawValue {
- if canonicalizeElements {
- let start = buffer.getCursor()
- let value = try Element.foryRead(context, refMode: .none, readTypeInfo: true)
- let end = buffer.getCursor()
- destination.advanced(by: index).initialize(
- to: context.canonicalizeNonTrackingReference(value, start: start, end: end)
- )
- } else {
- destination.advanced(by: index).initialize(
- to: try Element.foryRead(context, refMode: .none, readTypeInfo: true)
- )
- }
+ destination.advanced(by: index).initialize(
+ to: try Element.foryRead(context, refMode: .none, readTypeInfo: true)
+ )
} else {
throw ForyError.refError("invalid nullability flag \(refFlag)")
}
@@ -601,84 +591,50 @@ extension Array: Serializer where Element: Serializer {
return try readArrayUninitialized(count: length) { destination in
for index in 0..<length {
- if canonicalizeElements {
- let start = buffer.getCursor()
- let value = try Element.foryRead(context, refMode: .none, readTypeInfo: true)
- let end = buffer.getCursor()
- destination.advanced(by: index).initialize(
- to: context.canonicalizeNonTrackingReference(value, start: start, end: end)
- )
- } else {
- destination.advanced(by: index).initialize(
- to: try Element.foryRead(context, refMode: .none, readTypeInfo: true)
- )
- }
+ destination.advanced(by: index).initialize(
+ to: try Element.foryRead(context, refMode: .none, readTypeInfo: true)
+ )
}
}
}
- if !declared {
- try Element.foryReadTypeInfo(context)
- }
-
- if trackRef {
- let values = try readArrayUninitialized(count: length) { destination in
- for index in 0..<length {
- destination.advanced(by: index).initialize(
- to: try Element.foryRead(context, refMode: .tracking, readTypeInfo: false)
- )
+ let elementTypeInfo = declared ? nil : try Element.foryReadTypeInfo(context)
+ return try context.withTypeInfo(elementTypeInfo, for: Element.self) {
+ if trackRef {
+ return try readArrayUninitialized(count: length) { destination in
+ for index in 0..<length {
+ destination.advanced(by: index).initialize(
+ to: try Element.foryRead(context, refMode: .tracking, readTypeInfo: false)
+ )
+ }
}
}
- if !declared {
- context.clearDynamicTypeInfo(for: Element.self)
- }
- return values
- }
- if hasNull {
- let values = try readArrayUninitialized(count: length) { destination in
- for index in 0..<length {
- let refFlag = try buffer.readInt8()
- if refFlag == RefFlag.null.rawValue {
- destination.advanced(by: index).initialize(to: Element.foryDefault())
- } else {
- if canonicalizeElements {
- let start = buffer.getCursor()
- let value = try Element.foryReadData(context)
- let end = buffer.getCursor()
+ if hasNull {
+ return try readArrayUninitialized(count: length) { destination in
+ for index in 0..<length {
+ let refFlag = try buffer.readInt8()
+ if refFlag == RefFlag.null.rawValue {
+ destination.advanced(by: index).initialize(to: Element.foryDefault())
+ } else if refFlag == RefFlag.notNullValue.rawValue {
destination.advanced(by: index).initialize(
- to: context.canonicalizeNonTrackingReference(value, start: start, end: end)
+ to: try Element.foryRead(context, refMode: .none, readTypeInfo: false)
)
} else {
- destination.advanced(by: index).initialize(to: try Element.foryReadData(context))
+ throw ForyError.refError("invalid nullability flag \(refFlag)")
}
}
}
}
- if !declared {
- context.clearDynamicTypeInfo(for: Element.self)
- }
- return values
- }
- let values = try readArrayUninitialized(count: length) { destination in
- for index in 0..<length {
- if canonicalizeElements {
- let start = buffer.getCursor()
- let value = try Element.foryReadData(context)
- let end = buffer.getCursor()
+ return try readArrayUninitialized(count: length) { destination in
+ for index in 0..<length {
destination.advanced(by: index).initialize(
- to: context.canonicalizeNonTrackingReference(value, start: start, end: end)
+ to: try Element.foryRead(context, refMode: .none, readTypeInfo: false)
)
- } else {
- destination.advanced(by: index).initialize(to: try Element.foryReadData(context))
}
}
}
- if !declared {
- context.clearDynamicTypeInfo(for: Element.self)
- }
- return values
}
}
@@ -687,6 +643,14 @@ extension Set: Serializer where Element: Serializer & Hashable {
public static var staticTypeId: TypeId { .set }
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
try Array(self).foryWriteData(context, hasGenerics: hasGenerics)
}
@@ -701,14 +665,22 @@ extension Dictionary: Serializer where Key: Serializer & Hashable, Value: Serial
public static var staticTypeId: TypeId { .map }
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
context.buffer.writeVarUInt32(UInt32(self.count))
if self.isEmpty {
return
}
- let trackKeyRef = context.trackRef && Key.isReferenceTrackableType
- let trackValueRef = context.trackRef && Value.isReferenceTrackableType
+ let trackKeyRef = context.trackRef && Key.isRefType
+ let trackValueRef = context.trackRef && Value.isRefType
let keyDeclared = hasGenerics && !TypeId.needsTypeInfoForField(Key.staticTypeId)
let valueDeclared = hasGenerics && !TypeId.needsTypeInfoForField(Value.staticTypeId)
let keyDynamicType = Key.staticTypeId == .unknown
@@ -745,7 +717,7 @@ extension Dictionary: Serializer where Key: Serializer & Hashable, Value: Serial
if valueDynamicType {
try pair.value.foryWriteTypeInfo(context)
} else {
- try Value.foryWriteTypeInfo(context)
+ try Value.foryWriteStaticTypeInfo(context)
}
}
if trackValueRef {
@@ -766,7 +738,7 @@ extension Dictionary: Serializer where Key: Serializer & Hashable, Value: Serial
if keyDynamicType {
try pair.key.foryWriteTypeInfo(context)
} else {
- try Key.foryWriteTypeInfo(context)
+ try Key.foryWriteStaticTypeInfo(context)
}
}
if trackKeyRef {
@@ -788,14 +760,14 @@ extension Dictionary: Serializer where Key: Serializer & Hashable, Value: Serial
if keyDynamicType {
try pair.key.foryWriteTypeInfo(context)
} else {
- try Key.foryWriteTypeInfo(context)
+ try Key.foryWriteStaticTypeInfo(context)
}
}
if !valueDeclared {
if valueDynamicType {
try pair.value.foryWriteTypeInfo(context)
} else {
- try Value.foryWriteTypeInfo(context)
+ try Value.foryWriteStaticTypeInfo(context)
}
}
@@ -846,7 +818,7 @@ extension Dictionary: Serializer where Key: Serializer & Hashable, Value: Serial
context.buffer.writeUInt8(header)
if !keyIsNil {
if !keyDeclared {
- try Key.foryWriteTypeInfo(context)
+ try Key.foryWriteStaticTypeInfo(context)
}
if trackKeyRef {
try pair.key.foryWrite(context, refMode: .tracking, writeTypeInfo: false, hasGenerics: hasGenerics)
@@ -856,7 +828,7 @@ extension Dictionary: Serializer where Key: Serializer & Hashable, Value: Serial
}
if !valueIsNil {
if !valueDeclared {
- try Value.foryWriteTypeInfo(context)
+ try Value.foryWriteStaticTypeInfo(context)
}
if trackValueRef {
try pair.value.foryWrite(context, refMode: .tracking, writeTypeInfo: false, hasGenerics: hasGenerics)
@@ -879,10 +851,10 @@ extension Dictionary: Serializer where Key: Serializer & Hashable, Value: Serial
context.buffer.writeUInt8(0)
if !keyDeclared {
- try Key.foryWriteTypeInfo(context)
+ try Key.foryWriteStaticTypeInfo(context)
}
if !valueDeclared {
- try Value.foryWriteTypeInfo(context)
+ try Value.foryWriteStaticTypeInfo(context)
}
var chunkSize: UInt8 = 0
@@ -918,8 +890,6 @@ extension Dictionary: Serializer where Key: Serializer & Hashable, Value: Serial
map.reserveCapacity(Swift.min(totalLength, context.buffer.remaining))
let keyDynamicType = Key.staticTypeId == .unknown
let valueDynamicType = Value.staticTypeId == .unknown
- let canonicalizeValues = context.trackRef && Value.isReferenceTrackableType
-
if keyDynamicType || valueDynamicType {
var dynamicReadCount = 0
while dynamicReadCount < totalLength {
@@ -939,27 +909,12 @@ extension Dictionary: Serializer where Key: Serializer & Hashable, Value: Serial
}
if keyNull {
- if trackValueRef || !canonicalizeValues {
- let value = try Value.foryRead(
- context,
- refMode: trackValueRef ? .tracking : .none,
- readTypeInfo: valueDynamicType || !valueDeclared
- )
- map[Key.foryDefault()] = value
- } else {
- let start = context.buffer.getCursor()
- let value = try Value.foryRead(
- context,
- refMode: .none,
- readTypeInfo: valueDynamicType || !valueDeclared
- )
- let end = context.buffer.getCursor()
- map[Key.foryDefault()] = context.canonicalizeNonTrackingReference(
- value,
- start: start,
- end: end
- )
- }
+ let value = try Value.foryRead(
+ context,
+ refMode: trackValueRef ? .tracking : .none,
+ readTypeInfo: valueDynamicType || !valueDeclared
+ )
+ map[Key.foryDefault()] = value
dynamicReadCount += 1
continue
}
@@ -979,41 +934,24 @@ extension Dictionary: Serializer where Key: Serializer & Hashable, Value: Serial
if chunkSize > (totalLength - dynamicReadCount) {
throw ForyError.invalidData("map dynamic chunk size exceeds remaining entries")
}
- if !keyDeclared {
- try Key.foryReadTypeInfo(context)
- }
- if !valueDeclared {
- try Value.foryReadTypeInfo(context)
- }
+ let keyTypeInfo = keyDeclared ? nil : try Key.foryReadTypeInfo(context)
+ let valueTypeInfo = valueDeclared ? nil : try Value.foryReadTypeInfo(context)
for _ in 0..<chunkSize {
- let key = try Key.foryRead(
- context,
- refMode: trackKeyRef ? .tracking : .none,
- readTypeInfo: false
- )
- if trackValueRef || !canonicalizeValues {
- let value = try Value.foryRead(
+ let key = try context.withTypeInfo(keyTypeInfo, for: Key.self) {
+ try Key.foryRead(
context,
- refMode: trackValueRef ? .tracking : .none,
+ refMode: trackKeyRef ? .tracking : .none,
readTypeInfo: false
)
- map[key] = value
- } else {
- let start = context.buffer.getCursor()
- let value = try Value.foryRead(
+ }
+ let value = try context.withTypeInfo(valueTypeInfo, for: Value.self) {
+ try Value.foryRead(
context,
- refMode: .none,
+ refMode: trackValueRef ? .tracking : .none,
readTypeInfo: false
)
- let end = context.buffer.getCursor()
- map[key] = context.canonicalizeNonTrackingReference(value, start: start, end: end)
}
- }
- if !keyDeclared {
- context.clearDynamicTypeInfo(for: Key.self)
- }
- if !valueDeclared {
- context.clearDynamicTypeInfo(for: Value.self)
+ map[key] = value
}
dynamicReadCount += chunkSize
}
@@ -1038,27 +976,12 @@ extension Dictionary: Serializer where Key: Serializer & Hashable, Value: Serial
}
if keyNull {
- if trackValueRef || !canonicalizeValues {
- let value = try Value.foryRead(
- context,
- refMode: trackValueRef ? .tracking : .none,
- readTypeInfo: !valueDeclared
- )
- map[Key.foryDefault()] = value
- } else {
- let start = context.buffer.getCursor()
- let value = try Value.foryRead(
- context,
- refMode: .none,
- readTypeInfo: !valueDeclared
- )
- let end = context.buffer.getCursor()
- map[Key.foryDefault()] = context.canonicalizeNonTrackingReference(
- value,
- start: start,
- end: end
- )
- }
+ let value = try Value.foryRead(
+ context,
+ refMode: trackValueRef ? .tracking : .none,
+ readTypeInfo: !valueDeclared
+ )
+ map[Key.foryDefault()] = value
readCount += 1
continue
}
@@ -1078,36 +1001,24 @@ extension Dictionary: Serializer where Key: Serializer & Hashable, Value: Serial
if chunkSize > (totalLength - readCount) {
throw ForyError.invalidData("map chunk size exceeds remaining entries")
}
- if !keyDeclared {
- try Key.foryReadTypeInfo(context)
- }
- if !valueDeclared {
- try Value.foryReadTypeInfo(context)
- }
-
+ let keyTypeInfo = keyDeclared ? nil : try Key.foryReadTypeInfo(context)
+ let valueTypeInfo = valueDeclared ? nil : try Value.foryReadTypeInfo(context)
for _ in 0..<chunkSize {
- let key = try Key.foryRead(
- context,
- refMode: trackKeyRef ? .tracking : .none,
- readTypeInfo: false
- )
- if trackValueRef || !canonicalizeValues {
- let value = try Value.foryRead(
+ let key = try context.withTypeInfo(keyTypeInfo, for: Key.self) {
+ try Key.foryRead(
context,
- refMode: trackValueRef ? .tracking : .none,
+ refMode: trackKeyRef ? .tracking : .none,
readTypeInfo: false
)
- map[key] = value
- } else {
- let start = context.buffer.getCursor()
- let value = try Value.foryRead(
+ }
+ let value = try context.withTypeInfo(valueTypeInfo, for: Value.self) {
+ try Value.foryRead(
context,
- refMode: .none,
+ refMode: trackValueRef ? .tracking : .none,
readTypeInfo: false
)
- let end = context.buffer.getCursor()
- map[key] = context.canonicalizeNonTrackingReference(value, start: start, end: end)
}
+ map[key] = value
}
readCount += chunkSize
}
diff --git a/swift/Sources/Fory/CollectionUtil.swift b/swift/Sources/Fory/CollectionUtil.swift
new file mode 100644
index 000000000..889d1cb40
--- /dev/null
+++ b/swift/Sources/Fory/CollectionUtil.swift
@@ -0,0 +1,78 @@
+// 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.
+
+final class ReusableArray<Element> {
+ private var storage: UnsafeMutablePointer<Element>
+ private var capacity: Int
+ private let defaultValue: Element
+ private(set) var used = 0
+
+ init(defaultValue: Element, reserve: Int = 2) {
+ self.defaultValue = defaultValue
+ capacity = max(reserve, 2)
+ storage = UnsafeMutablePointer<Element>.allocate(capacity: capacity)
+ storage.initialize(repeating: defaultValue, count: capacity)
+ }
+
+ deinit {
+ storage.deinitialize(count: capacity)
+ storage.deallocate()
+ }
+
+ /// Reset logical usage to zero in O(1) without clearing backing slots.
+ ///
+ /// This is intentional for hot-path reuse: existing slot values stay in
+ /// storage until they are overwritten by later `push` calls or released by
+ /// `deinit`.
+ @inline(__always)
+ func reset() {
+ used = 0
+ }
+
+ @inline(__always)
+ func push(_ value: Element) {
+ if used == capacity {
+ grow()
+ }
+ storage.advanced(by: used).pointee = value
+ used += 1
+ }
+
+ @inline(__always)
+ func get(_ index: Int) -> Element {
+ guard index >= 0, index < used else {
+ return defaultValue
+ }
+ return storage.advanced(by: index).pointee
+ }
+
+ @inline(__always)
+ var isEmpty: Bool { used == 0 }
+
+ private func grow() {
+ let newCapacity = capacity << 1
+ let newStorage = UnsafeMutablePointer<Element>.allocate(capacity: newCapacity)
+ newStorage.initialize(repeating: defaultValue, count: newCapacity)
+ for index in 0 ..< used {
+ newStorage.advanced(by: index).pointee = storage.advanced(by: index).pointee
+ }
+ storage.deinitialize(count: capacity)
+ storage.deallocate()
+ storage = newStorage
+ capacity = newCapacity
+ }
+}
diff --git a/swift/Sources/Fory/Context.swift b/swift/Sources/Fory/Context.swift
deleted file mode 100644
index 733789bf0..000000000
--- a/swift/Sources/Fory/Context.swift
+++ /dev/null
@@ -1,830 +0,0 @@
-// 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.
-
-import Foundation
-
-public final class CompatibleTypeDefWriteState {
- private var typeIndexBySwiftType: [ObjectIdentifier: UInt32] = [:]
- private var nextIndex: UInt32 = 0
-
- public init() {}
-
- public func lookupIndex(for typeID: ObjectIdentifier) -> UInt32? {
- typeIndexBySwiftType[typeID]
- }
-
- public func assignIndexIfAbsent(for typeID: ObjectIdentifier) -> (index: UInt32, isNew: Bool) {
- if let existing = typeIndexBySwiftType[typeID] {
- return (existing, false)
- }
- let index = nextIndex
- nextIndex &+= 1
- typeIndexBySwiftType[typeID] = index
- return (index, true)
- }
-
- public func reset() {
- if !typeIndexBySwiftType.isEmpty {
- typeIndexBySwiftType.removeAll(keepingCapacity: true)
- }
- if nextIndex != 0 {
- nextIndex = 0
- }
- }
-}
-
-public final class CompatibleTypeDefReadState {
- private var typeMetas: [TypeMeta] = []
-
- public init() {}
-
- public func typeMeta(at index: Int) -> TypeMeta? {
- guard index >= 0, index < typeMetas.count else {
- return nil
- }
- return typeMetas[index]
- }
-
- public func storeTypeMeta(_ typeMeta: TypeMeta, at index: Int) throws {
- if index < 0 {
- throw ForyError.invalidData("negative compatible type definition index")
- }
- if index == typeMetas.count {
- typeMetas.append(typeMeta)
- return
- }
- if index < typeMetas.count {
- typeMetas[index] = typeMeta
- return
- }
- throw ForyError.invalidData(
- "compatible type definition index gap: index=\(index), count=\(typeMetas.count)"
- )
- }
-
- public func reset() {
- if !typeMetas.isEmpty {
- typeMetas.removeAll(keepingCapacity: true)
- }
- }
-}
-
-private struct MetaStringCacheKey: Hashable {
- let encoding: MetaStringEncoding
- let bytes: [UInt8]
-}
-
-private struct CanonicalReferenceSignature: Hashable {
- let typeID: ObjectIdentifier
- let hashLo: UInt64
- let hashHi: UInt64
- let length: Int
-}
-
-private struct CanonicalReferenceEntry {
- let bytes: [UInt8]
- let object: AnyObject
-}
-
-public final class MetaStringWriteState {
- private var stringIndexByKey: [MetaStringCacheKey: UInt32] = [:]
- private var nextIndex: UInt32 = 0
-
- public init() {}
-
- public func index(for value: MetaString) -> UInt32? {
- stringIndexByKey[MetaStringCacheKey(encoding: value.encoding, bytes: value.bytes)]
- }
-
- public func assignIndexIfAbsent(for value: MetaString) -> (index: UInt32, isNew: Bool) {
- let key = MetaStringCacheKey(encoding: value.encoding, bytes: value.bytes)
- if let existing = stringIndexByKey[key] {
- return (existing, false)
- }
- let index = nextIndex
- nextIndex &+= 1
- stringIndexByKey[key] = index
- return (index, true)
- }
-
- public func reset() {
- if !stringIndexByKey.isEmpty {
- stringIndexByKey.removeAll(keepingCapacity: true)
- }
- if nextIndex != 0 {
- nextIndex = 0
- }
- }
-}
-
-public final class MetaStringReadState {
- private var values: [MetaString] = []
-
- public init() {}
-
- public func value(at index: Int) -> MetaString? {
- guard index >= 0, index < values.count else {
- return nil
- }
- return values[index]
- }
-
- public func append(_ value: MetaString) {
- values.append(value)
- }
-
- public func reset() {
- if !values.isEmpty {
- values.removeAll(keepingCapacity: true)
- }
- }
-}
-
-public struct DynamicTypeInfo {
- public let wireTypeID: TypeId
- public let userTypeID: UInt32?
- public let namespace: MetaString?
- public let typeName: MetaString?
- public let compatibleTypeMeta: TypeMeta?
-
- public init(
- wireTypeID: TypeId,
- userTypeID: UInt32?,
- namespace: MetaString?,
- typeName: MetaString?,
- compatibleTypeMeta: TypeMeta?
- ) {
- self.wireTypeID = wireTypeID
- self.userTypeID = userTypeID
- self.namespace = namespace
- self.typeName = typeName
- self.compatibleTypeMeta = compatibleTypeMeta
- }
-}
-
-public final class WriteContext {
- public let buffer: ByteBuffer
- public let typeResolver: TypeResolver
- public let trackRef: Bool
- public let compatible: Bool
- public let checkClassVersion: Bool
- public let maxDepth: Int
- public let refWriter: RefWriter
- public let compatibleTypeDefState: CompatibleTypeDefWriteState
- public let metaStringWriteState: MetaStringWriteState
- private var compatibleTypeDefStateUsed = false
- private var metaStringWriteStateUsed = false
- private var dynamicAnyDepth = 0
-
- public init(
- buffer: ByteBuffer,
- typeResolver: TypeResolver,
- trackRef: Bool,
- compatible: Bool = false,
- checkClassVersion: Bool = true,
- maxDepth: Int = 5,
- compatibleTypeDefState: CompatibleTypeDefWriteState = CompatibleTypeDefWriteState(),
- metaStringWriteState: MetaStringWriteState = MetaStringWriteState()
- ) {
- self.buffer = buffer
- self.typeResolver = typeResolver
- self.trackRef = trackRef
- self.compatible = compatible
- self.checkClassVersion = checkClassVersion
- self.maxDepth = maxDepth
- self.refWriter = RefWriter()
- self.compatibleTypeDefState = compatibleTypeDefState
- self.metaStringWriteState = metaStringWriteState
- }
-
- @inline(__always)
- public func enterDynamicAnyDepth() throws {
- if maxDepth < 0 {
- throw ForyError.invalidData("configured maxDepth \(maxDepth) is negative")
- }
- let nextDepth = dynamicAnyDepth + 1
- if nextDepth > maxDepth {
- throw ForyError.invalidData(
- "dynamic Any nesting depth \(nextDepth) exceeds configured maxDepth \(maxDepth)"
- )
- }
- dynamicAnyDepth = nextDepth
- }
-
- @inline(__always)
- public func leaveDynamicAnyDepth() {
- if dynamicAnyDepth > 0 {
- dynamicAnyDepth -= 1
- }
- }
-
- public func writeCompatibleTypeMeta<T: Serializer>(
- for type: T.Type,
- typeMeta: TypeMeta
- ) throws {
- try writeCompatibleTypeMeta(
- for: type,
- encodedTypeMeta: typeMeta.encode()
- )
- }
-
- public func writeCompatibleTypeMeta<T: Serializer>(
- for type: T.Type,
- encodedTypeMeta: [UInt8]
- ) throws {
- compatibleTypeDefStateUsed = true
- let typeID = ObjectIdentifier(type)
- let assignment = compatibleTypeDefState.assignIndexIfAbsent(for: typeID)
- if assignment.isNew {
- buffer.writeVarUInt32(assignment.index << 1)
- buffer.writeBytes(encodedTypeMeta)
- } else {
- buffer.writeVarUInt32((assignment.index << 1) | 1)
- }
- }
-
- public func resetObjectState() {
- if dynamicAnyDepth != 0 {
- dynamicAnyDepth = 0
- }
- if trackRef {
- refWriter.reset()
- }
- }
-
- @inline(__always)
- public func markMetaStringWriteStateUsed() {
- metaStringWriteStateUsed = true
- }
-
- public func reset() {
- resetObjectState()
- if compatibleTypeDefStateUsed {
- compatibleTypeDefState.reset()
- compatibleTypeDefStateUsed = false
- }
- if metaStringWriteStateUsed {
- metaStringWriteState.reset()
- metaStringWriteStateUsed = false
- }
- }
-}
-
-private struct PendingRefSlot {
- var refID: UInt32
- var bound: Bool
-}
-
-public final class ReadContext {
- public let buffer: ByteBuffer
- public let typeResolver: TypeResolver
- public let trackRef: Bool
- public let compatible: Bool
- public let checkClassVersion: Bool
- public let maxCollectionSize: Int
- public let maxBinarySize: Int
- public let maxDepth: Int
- public let refReader: RefReader
- public let compatibleTypeDefState: CompatibleTypeDefReadState
- public let metaStringReadState: MetaStringReadState
- private var compatibleTypeDefStateUsed = false
- private var metaStringReadStateUsed = false
- private var dynamicAnyDepth = 0
-
- private var pendingRefStack: [PendingRefSlot] = []
- private var pendingCompatibleTypeMeta: [ObjectIdentifier: [TypeMeta]] = [:]
- private var pendingDynamicTypeInfo: [ObjectIdentifier: DynamicTypeInfo] = [:]
- private var canonicalReferenceCache: [CanonicalReferenceSignature: [CanonicalReferenceEntry]] = [:]
-
- public init(
- buffer: ByteBuffer,
- typeResolver: TypeResolver,
- trackRef: Bool,
- compatible: Bool = false,
- checkClassVersion: Bool = true,
- maxCollectionSize: Int = 1_000_000,
- maxBinarySize: Int = 64 * 1024 * 1024,
- maxDepth: Int = 5,
- compatibleTypeDefState: CompatibleTypeDefReadState = CompatibleTypeDefReadState(),
- metaStringReadState: MetaStringReadState = MetaStringReadState()
- ) {
- self.buffer = buffer
- self.typeResolver = typeResolver
- self.trackRef = trackRef
- self.compatible = compatible
- self.checkClassVersion = checkClassVersion
- self.maxCollectionSize = maxCollectionSize
- self.maxBinarySize = maxBinarySize
- self.maxDepth = maxDepth
- self.refReader = RefReader()
- self.compatibleTypeDefState = compatibleTypeDefState
- self.metaStringReadState = metaStringReadState
- }
-
- @inline(__always)
- public func enterDynamicAnyDepth() throws {
- if maxDepth < 0 {
- throw ForyError.invalidData("configured maxDepth \(maxDepth) is negative")
- }
- let nextDepth = dynamicAnyDepth + 1
- if nextDepth > maxDepth {
- throw ForyError.invalidData(
- "dynamic Any nesting depth \(nextDepth) exceeds configured maxDepth \(maxDepth)"
- )
- }
- dynamicAnyDepth = nextDepth
- }
-
- @inline(__always)
- public func leaveDynamicAnyDepth() {
- if dynamicAnyDepth > 0 {
- dynamicAnyDepth -= 1
- }
- }
-
- @inline(__always)
- public func ensureCollectionLength(_ length: Int, label: String) throws {
- if length < 0 {
- throw ForyError.invalidData("\(label) length is negative")
- }
- if length > maxCollectionSize {
- throw ForyError.invalidData(
- "\(label) length \(length) exceeds configured maxCollectionSize \(maxCollectionSize)"
- )
- }
- }
-
- @inline(__always)
- public func ensureBinaryLength(_ length: Int, label: String) throws {
- if length < 0 {
- throw ForyError.invalidData("\(label) size is negative")
- }
- if length > maxBinarySize {
- throw ForyError.invalidData(
- "\(label) size \(length) exceeds configured maxBinarySize \(maxBinarySize)"
- )
- }
- }
-
- @inline(__always)
- public func ensureRemainingBytes(_ byteCount: Int, label: String) throws {
- if byteCount < 0 {
- throw ForyError.invalidData("\(label) size is negative")
- }
- let remainingBytes = buffer.remaining
- if byteCount > remainingBytes {
- throw ForyError.invalidData(
- "\(label) requires \(byteCount) bytes but only \(remainingBytes) remain in buffer"
- )
- }
- }
-
- public func pushPendingReference(_ refID: UInt32) {
- pendingRefStack.append(PendingRefSlot(refID: refID, bound: false))
- }
-
- public func bindPendingReference(_ value: Any) {
- guard var last = pendingRefStack.popLast() else {
- return
- }
- last.bound = true
- refReader.storeRef(value, at: last.refID)
- pendingRefStack.append(last)
- }
-
- public func finishPendingReferenceIfNeeded(_ value: Any) {
- guard var last = pendingRefStack.popLast() else {
- return
- }
- if !last.bound {
- refReader.storeRef(value, at: last.refID)
- last.bound = true
- }
- }
-
- public func popPendingReference() {
- _ = pendingRefStack.popLast()
- }
-
- public func readCompatibleTypeMeta() throws -> TypeMeta {
- compatibleTypeDefStateUsed = true
- let indexMarker = try buffer.readVarUInt32()
- let isRef = (indexMarker & 1) == 1
- let index = Int(indexMarker >> 1)
- if isRef {
- guard let typeMeta = compatibleTypeDefState.typeMeta(at: index) else {
- throw ForyError.invalidData("unknown compatible type definition ref index \(index)")
- }
- return typeMeta
- }
- let typeMeta = try TypeMeta.decode(buffer)
- try compatibleTypeDefState.storeTypeMeta(typeMeta, at: index)
- return typeMeta
- }
-
- public func pushCompatibleTypeMeta<T: Serializer>(for type: T.Type, _ typeMeta: TypeMeta) {
- let typeID = ObjectIdentifier(type)
- pendingCompatibleTypeMeta[typeID] = [typeMeta]
- }
-
- public func consumeCompatibleTypeMeta<T: Serializer>(for type: T.Type) throws -> TypeMeta {
- let typeID = ObjectIdentifier(type)
- guard let stack = pendingCompatibleTypeMeta[typeID], let last = stack.last else {
- throw ForyError.invalidData("missing compatible type metadata for \(type)")
- }
- return last
- }
-
- @inline(__always)
- public func consumeCompatibleTypeMetaIfPresent<T: Serializer>(for type: T.Type) -> TypeMeta? {
- let typeID = ObjectIdentifier(type)
- return pendingCompatibleTypeMeta[typeID]?.last
- }
-
- public func setDynamicTypeInfo<T: Serializer>(for type: T.Type, _ typeInfo: DynamicTypeInfo) {
- pendingDynamicTypeInfo[ObjectIdentifier(type)] = typeInfo
- }
-
- public func dynamicTypeInfo<T: Serializer>(for type: T.Type) -> DynamicTypeInfo? {
- pendingDynamicTypeInfo[ObjectIdentifier(type)]
- }
-
- public func clearDynamicTypeInfo<T: Serializer>(for type: T.Type) {
- pendingDynamicTypeInfo.removeValue(forKey: ObjectIdentifier(type))
- }
-
- public func canonicalizeNonTrackingReference<T>(
- _ value: T,
- start: Int,
- end: Int
- ) -> T {
- guard trackRef else {
- return value
- }
- guard end > start else {
- return value
- }
- guard let object = value as AnyObject? else {
- return value
- }
-
- let bytes = Array(buffer.storage[start..<end])
- let (hashLo, hashHi) = MurmurHash3.x64_128(bytes, seed: 47)
- let signature = CanonicalReferenceSignature(
- typeID: ObjectIdentifier(type(of: object)),
- hashLo: hashLo,
- hashHi: hashHi,
- length: bytes.count
- )
-
- if var bucket = canonicalReferenceCache[signature] {
- for entry in bucket where entry.bytes == bytes {
- if let shared = entry.object as? T {
- return shared
- }
- }
- bucket.append(CanonicalReferenceEntry(bytes: bytes, object: object))
- canonicalReferenceCache[signature] = bucket
- return value
- }
-
- canonicalReferenceCache[signature] = [CanonicalReferenceEntry(bytes: bytes, object: object)]
- return value
- }
-
- @inline(__always)
- public func markMetaStringReadStateUsed() {
- metaStringReadStateUsed = true
- }
-
- public func resetObjectState() {
- if dynamicAnyDepth != 0 {
- dynamicAnyDepth = 0
- }
- if trackRef {
- refReader.reset()
- if !pendingRefStack.isEmpty {
- pendingRefStack.removeAll(keepingCapacity: true)
- }
- }
- if compatible, !pendingCompatibleTypeMeta.isEmpty {
- pendingCompatibleTypeMeta.removeAll(keepingCapacity: true)
- }
- if !pendingDynamicTypeInfo.isEmpty {
- pendingDynamicTypeInfo.removeAll(keepingCapacity: true)
- }
- if trackRef, !canonicalReferenceCache.isEmpty {
- canonicalReferenceCache.removeAll(keepingCapacity: true)
- }
- }
-
- public func reset() {
- resetObjectState()
- if compatibleTypeDefStateUsed {
- compatibleTypeDefState.reset()
- compatibleTypeDefStateUsed = false
- }
- if metaStringReadStateUsed {
- metaStringReadState.reset()
- metaStringReadStateUsed = false
- }
- }
-}
-
-@inline(__always)
-private func writeAnyGlobal(
- _ value: Any?,
- context: WriteContext,
- refMode: RefMode,
- writeTypeInfo: Bool,
- hasGenerics: Bool
-) throws {
- try writeAny(
- value,
- context: context,
- refMode: refMode,
- writeTypeInfo: writeTypeInfo,
- hasGenerics: hasGenerics
- )
-}
-
-@inline(__always)
-private func writeAnyListGlobal(
- _ value: [Any]?,
- context: WriteContext,
- refMode: RefMode,
- writeTypeInfo: Bool,
- hasGenerics: Bool
-) throws {
- try writeAnyList(
- value,
- context: context,
- refMode: refMode,
- writeTypeInfo: writeTypeInfo,
- hasGenerics: hasGenerics
- )
-}
-
-@inline(__always)
-private func writeStringAnyMapGlobal(
- _ value: [String: Any]?,
- context: WriteContext,
- refMode: RefMode,
- writeTypeInfo: Bool,
- hasGenerics: Bool
-) throws {
- try writeStringAnyMap(
- value,
- context: context,
- refMode: refMode,
- writeTypeInfo: writeTypeInfo,
- hasGenerics: hasGenerics
- )
-}
-
-@inline(__always)
-private func writeInt32AnyMapGlobal(
- _ value: [Int32: Any]?,
- context: WriteContext,
- refMode: RefMode,
- writeTypeInfo: Bool,
- hasGenerics: Bool
-) throws {
- try writeInt32AnyMap(
- value,
- context: context,
- refMode: refMode,
- writeTypeInfo: writeTypeInfo,
- hasGenerics: hasGenerics
- )
-}
-
-@inline(__always)
-private func writeAnyHashableAnyMapGlobal(
- _ value: [AnyHashable: Any]?,
- context: WriteContext,
- refMode: RefMode,
- writeTypeInfo: Bool,
- hasGenerics: Bool
-) throws {
- try writeAnyHashableAnyMap(
- value,
- context: context,
- refMode: refMode,
- writeTypeInfo: writeTypeInfo,
- hasGenerics: hasGenerics
- )
-}
-
-@inline(__always)
-private func readAnyGlobal(
- context: ReadContext,
- refMode: RefMode,
- readTypeInfo: Bool
-) throws -> Any? {
- try readAny(
- context: context,
- refMode: refMode,
- readTypeInfo: readTypeInfo
- )
-}
-
-@inline(__always)
-private func readAnyListGlobal(
- context: ReadContext,
- refMode: RefMode,
- readTypeInfo: Bool
-) throws -> [Any]? {
- try readAnyList(
- context: context,
- refMode: refMode,
- readTypeInfo: readTypeInfo
- )
-}
-
-@inline(__always)
-private func readStringAnyMapGlobal(
- context: ReadContext,
- refMode: RefMode,
- readTypeInfo: Bool
-) throws -> [String: Any]? {
- try readStringAnyMap(
- context: context,
- refMode: refMode,
- readTypeInfo: readTypeInfo
- )
-}
-
-@inline(__always)
-private func readInt32AnyMapGlobal(
- context: ReadContext,
- refMode: RefMode,
- readTypeInfo: Bool
-) throws -> [Int32: Any]? {
- try readInt32AnyMap(
- context: context,
- refMode: refMode,
- readTypeInfo: readTypeInfo
- )
-}
-
-@inline(__always)
-private func readAnyHashableAnyMapGlobal(
- context: ReadContext,
- refMode: RefMode,
- readTypeInfo: Bool
-) throws -> [AnyHashable: Any]? {
- try readAnyHashableAnyMap(
- context: context,
- refMode: refMode,
- readTypeInfo: readTypeInfo
- )
-}
-
-public extension WriteContext {
- func writeAny(
- _ value: Any?,
- refMode: RefMode,
- writeTypeInfo: Bool = true,
- hasGenerics: Bool = false
- ) throws {
- try writeAnyGlobal(
- value,
- context: self,
- refMode: refMode,
- writeTypeInfo: writeTypeInfo,
- hasGenerics: hasGenerics
- )
- }
-
- func writeAnyList(
- _ value: [Any]?,
- refMode: RefMode,
- writeTypeInfo: Bool = false,
- hasGenerics: Bool = true
- ) throws {
- try writeAnyListGlobal(
- value,
- context: self,
- refMode: refMode,
- writeTypeInfo: writeTypeInfo,
- hasGenerics: hasGenerics
- )
- }
-
- func writeStringAnyMap(
- _ value: [String: Any]?,
- refMode: RefMode,
- writeTypeInfo: Bool = false,
- hasGenerics: Bool = true
- ) throws {
- try writeStringAnyMapGlobal(
- value,
- context: self,
- refMode: refMode,
- writeTypeInfo: writeTypeInfo,
- hasGenerics: hasGenerics
- )
- }
-
- func writeInt32AnyMap(
- _ value: [Int32: Any]?,
- refMode: RefMode,
- writeTypeInfo: Bool = false,
- hasGenerics: Bool = true
- ) throws {
- try writeInt32AnyMapGlobal(
- value,
- context: self,
- refMode: refMode,
- writeTypeInfo: writeTypeInfo,
- hasGenerics: hasGenerics
- )
- }
-
- func writeAnyHashableAnyMap(
- _ value: [AnyHashable: Any]?,
- refMode: RefMode,
- writeTypeInfo: Bool = false,
- hasGenerics: Bool = true
- ) throws {
- try writeAnyHashableAnyMapGlobal(
- value,
- context: self,
- refMode: refMode,
- writeTypeInfo: writeTypeInfo,
- hasGenerics: hasGenerics
- )
- }
-}
-
-public extension ReadContext {
- func readAny(
- refMode: RefMode,
- readTypeInfo: Bool = true
- ) throws -> Any? {
- try readAnyGlobal(
- context: self,
- refMode: refMode,
- readTypeInfo: readTypeInfo
- )
- }
-
- func readAnyList(
- refMode: RefMode,
- readTypeInfo: Bool = false
- ) throws -> [Any]? {
- try readAnyListGlobal(
- context: self,
- refMode: refMode,
- readTypeInfo: readTypeInfo
- )
- }
-
- func readStringAnyMap(
- refMode: RefMode,
- readTypeInfo: Bool = false
- ) throws -> [String: Any]? {
- try readStringAnyMapGlobal(
- context: self,
- refMode: refMode,
- readTypeInfo: readTypeInfo
- )
- }
-
- func readInt32AnyMap(
- refMode: RefMode,
- readTypeInfo: Bool = false
- ) throws -> [Int32: Any]? {
- try readInt32AnyMapGlobal(
- context: self,
- refMode: refMode,
- readTypeInfo: readTypeInfo
- )
- }
-
- func readAnyHashableAnyMap(
- refMode: RefMode,
- readTypeInfo: Bool = false
- ) throws -> [AnyHashable: Any]? {
- try readAnyHashableAnyMapGlobal(
- context: self,
- refMode: refMode,
- readTypeInfo: readTypeInfo
- )
- }
-}
diff --git a/swift/Sources/Fory/DateTimeSerializers.swift b/swift/Sources/Fory/DateTimeSerializers.swift
index 3fa502c15..ddd2ebd52 100644
--- a/swift/Sources/Fory/DateTimeSerializers.swift
+++ b/swift/Sources/Fory/DateTimeSerializers.swift
@@ -32,6 +32,14 @@ public struct ForyDate: Serializer, Equatable, Hashable {
.date
}
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
_ = hasGenerics
context.buffer.writeInt32(daysSinceEpoch)
@@ -60,6 +68,14 @@ public struct ForyTimestamp: Serializer, Equatable, Hashable {
.timestamp
}
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
_ = hasGenerics
context.buffer.writeInt64(seconds)
@@ -103,6 +119,14 @@ extension Duration: Serializer {
.duration
}
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
_ = hasGenerics
let components = self.components
@@ -131,6 +155,14 @@ extension Date: Serializer {
.timestamp
}
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
_ = hasGenerics
let ts = ForyTimestamp(date: self)
diff --git a/swift/Sources/Fory/FieldSkipper.swift b/swift/Sources/Fory/FieldSkipper.swift
index 12efc5a34..d61c6462d 100644
--- a/swift/Sources/Fory/FieldSkipper.swift
+++ b/swift/Sources/Fory/FieldSkipper.swift
@@ -17,72 +17,63 @@
import Foundation
-public enum FieldSkipper {
- public static func skipFieldValue(
- context: ReadContext,
- fieldType: TypeMetaFieldType
- ) throws {
- _ = try readFieldValue(
- context: context,
+public extension ReadContext {
+ func skipFieldValue(_ fieldType: TypeMeta.FieldType) throws {
+ _ = try readSkippedFieldValue(
fieldType: fieldType,
- readTypeInfo: needsTypeInfoForField(fieldType.typeID)
+ readTypeInfo: needsTypeInfoForSkippedField(fieldType.typeID)
)
}
- private static func needsTypeInfoForField(_ typeID: UInt32) -> Bool {
+ private func needsTypeInfoForSkippedField(_ typeID: UInt32) -> Bool {
guard let resolved = TypeId(rawValue: typeID) else {
return true
}
return TypeId.needsTypeInfoForField(resolved)
}
- private static func readFieldValue(
- context: ReadContext,
- fieldType: TypeMetaFieldType,
- runtimeTypeInfo: DynamicTypeInfo? = nil,
+ private func readSkippedFieldValue(
+ fieldType: TypeMeta.FieldType,
+ typeInfo: TypeInfo? = nil,
readTypeInfo: Bool
) throws -> Any? {
let refMode = RefMode.from(nullable: fieldType.nullable, trackRef: fieldType.trackRef)
- return try readValueWithRefMode(
- context: context,
+ return try readSkippedValue(
fieldType: fieldType,
- runtimeTypeInfo: runtimeTypeInfo,
+ typeInfo: typeInfo,
refMode: refMode,
readTypeInfo: readTypeInfo
)
}
- private static func readValueWithRefMode(
- context: ReadContext,
- fieldType: TypeMetaFieldType,
- runtimeTypeInfo: DynamicTypeInfo?,
+ private func readSkippedValue(
+ fieldType: TypeMeta.FieldType,
+ typeInfo: TypeInfo?,
refMode: RefMode,
readTypeInfo: Bool
) throws -> Any? {
switch refMode {
case .none:
- return try readFieldPayload(
- context: context,
+ return try readSkippedFieldPayload(
fieldType: fieldType,
- runtimeTypeInfo: runtimeTypeInfo,
+ typeInfo: typeInfo,
readTypeInfo: readTypeInfo
)
case .nullOnly:
- let flag = try context.buffer.readInt8()
+ let flag = try buffer.readInt8()
if flag == RefFlag.null.rawValue {
return nil
}
guard flag == RefFlag.notNullValue.rawValue else {
throw ForyError.invalidData("unexpected nullOnly flag \(flag)")
}
- return try readFieldPayload(
- context: context,
+ return try readSkippedFieldPayload(
fieldType: fieldType,
- runtimeTypeInfo: runtimeTypeInfo,
+ typeInfo: typeInfo,
readTypeInfo: readTypeInfo
)
case .tracking:
- let rawFlag = try context.buffer.readInt8()
+ let rawFlag = try buffer.readInt8()
guard let flag = RefFlag(rawValue: rawFlag) else {
throw ForyError.invalidData("unexpected tracking flag \(rawFlag)")
}
@@ -91,41 +82,38 @@ public enum FieldSkipper {
case .null:
return nil
case .ref:
- let refID = try context.buffer.readVarUInt32()
- return try context.refReader.readRefValue(refID)
+ let refID = try buffer.readVarUInt32()
+ return try refReader.readRefValue(refID)
case .refValue:
- let refID = context.refReader.reserveRefID()
- let value = try readFieldPayload(
- context: context,
+ let refID = refReader.reserveRefID()
+ let value = try readSkippedFieldPayload(
fieldType: fieldType,
- runtimeTypeInfo: runtimeTypeInfo,
+ typeInfo: typeInfo,
readTypeInfo: readTypeInfo
)
- context.refReader.storeRef(value, at: refID)
+ refReader.storeRef(value, at: refID)
return value
case .notNullValue:
- return try readFieldPayload(
- context: context,
+ return try readSkippedFieldPayload(
fieldType: fieldType,
- runtimeTypeInfo: runtimeTypeInfo,
+ typeInfo: typeInfo,
readTypeInfo: readTypeInfo
)
}
}
}
- private static func readFieldPayload(
- context: ReadContext,
- fieldType: TypeMetaFieldType,
- runtimeTypeInfo: DynamicTypeInfo?,
+ private func readSkippedFieldPayload(
+ fieldType: TypeMeta.FieldType,
+ typeInfo: TypeInfo?,
readTypeInfo: Bool
) throws -> Any {
- if let runtimeTypeInfo {
- return try context.typeResolver.readDynamicValue(typeInfo: runtimeTypeInfo, context: context)
+ if let typeInfo {
+ return try readAnyValue(typeInfo: typeInfo)
}
if readTypeInfo {
- let typeInfo = try context.typeResolver.readDynamicTypeInfo(context: context)
- return try context.typeResolver.readDynamicValue(typeInfo: typeInfo, context: context)
+ let typeInfo = try self.readTypeInfo()
+ return try readAnyValue(typeInfo: typeInfo)
}
guard let resolvedTypeID = TypeId(rawValue: fieldType.typeID) else {
@@ -136,144 +124,140 @@ public enum FieldSkipper {
case .none:
return ForyAnyNullValue()
case .bool:
- return try Bool.foryRead(context, refMode: .none, readTypeInfo: false)
+ return try Bool.foryRead(self, refMode: .none, readTypeInfo: false)
case .int8:
- return try Int8.foryRead(context, refMode: .none, readTypeInfo: false)
+ return try Int8.foryRead(self, refMode: .none, readTypeInfo: false)
case .int16:
- return try Int16.foryRead(context, refMode: .none, readTypeInfo: false)
+ return try Int16.foryRead(self, refMode: .none, readTypeInfo: false)
case .int32:
- return try ForyInt32Fixed.foryRead(context, refMode: .none, readTypeInfo: false)
+ return try ForyInt32Fixed.foryRead(self, refMode: .none, readTypeInfo: false)
case .varint32:
- return try Int32.foryRead(context, refMode: .none, readTypeInfo: false)
+ return try Int32.foryRead(self, refMode: .none, readTypeInfo: false)
case .int64:
- return try ForyInt64Fixed.foryRead(context, refMode: .none, readTypeInfo: false)
+ return try ForyInt64Fixed.foryRead(self, refMode: .none, readTypeInfo: false)
case .varint64:
- return try Int64.foryRead(context, refMode: .none, readTypeInfo: false)
+ return try Int64.foryRead(self, refMode: .none, readTypeInfo: false)
case .taggedInt64:
- return try ForyInt64Tagged.foryRead(context, refMode: .none, readTypeInfo: false)
+ return try ForyInt64Tagged.foryRead(self, refMode: .none, readTypeInfo: false)
case .uint8:
- return try UInt8.foryRead(context, refMode: .none, readTypeInfo: false)
+ return try UInt8.foryRead(self, refMode: .none, readTypeInfo: false)
case .uint16:
- return try UInt16.foryRead(context, refMode: .none, readTypeInfo: false)
+ return try UInt16.foryRead(self, refMode: .none, readTypeInfo: false)
case .uint32:
- return try ForyUInt32Fixed.foryRead(context, refMode: .none, readTypeInfo: false)
+ return try ForyUInt32Fixed.foryRead(self, refMode: .none, readTypeInfo: false)
case .varUInt32:
- return try UInt32.foryRead(context, refMode: .none, readTypeInfo: false)
+ return try UInt32.foryRead(self, refMode: .none, readTypeInfo: false)
case .uint64:
- return try ForyUInt64Fixed.foryRead(context, refMode: .none, readTypeInfo: false)
+ return try ForyUInt64Fixed.foryRead(self, refMode: .none, readTypeInfo: false)
case .varUInt64:
- return try UInt64.foryRead(context, refMode: .none, readTypeInfo: false)
+ return try UInt64.foryRead(self, refMode: .none, readTypeInfo: false)
case .taggedUInt64:
- return try ForyUInt64Tagged.foryRead(context, refMode: .none, readTypeInfo: false)
+ return try ForyUInt64Tagged.foryRead(self, refMode: .none, readTypeInfo: false)
case .float16:
- return try Float16.foryRead(context, refMode: .none, readTypeInfo: false)
+ return try Float16.foryRead(self, refMode: .none, readTypeInfo: false)
case .bfloat16:
- return try BFloat16.foryRead(context, refMode: .none, readTypeInfo: false)
+ return try BFloat16.foryRead(self, refMode: .none, readTypeInfo: false)
case .float32:
- return try Float.foryRead(context, refMode: .none, readTypeInfo: false)
+ return try Float.foryRead(self, refMode: .none, readTypeInfo: false)
case .float64:
- return try Double.foryRead(context, refMode: .none, readTypeInfo: false)
+ return try Double.foryRead(self, refMode: .none, readTypeInfo: false)
case .string:
- return try String.foryRead(context, refMode: .none, readTypeInfo: false)
+ return try String.foryRead(self, refMode: .none, readTypeInfo: false)
case .duration:
- return try Duration.foryRead(context, refMode: .none, readTypeInfo: false)
+ return try Duration.foryRead(self, refMode: .none, readTypeInfo: false)
case .timestamp:
- return try Date.foryRead(context, refMode: .none, readTypeInfo: false)
+ return try Date.foryRead(self, refMode: .none, readTypeInfo: false)
case .date:
- return try ForyDate.foryRead(context, refMode: .none, readTypeInfo: false)
+ return try ForyDate.foryRead(self, refMode: .none, readTypeInfo: false)
case .binary, .uint8Array:
- return try Data.foryRead(context, refMode: .none, readTypeInfo: false)
+ return try Data.foryRead(self, refMode: .none, readTypeInfo: false)
case .boolArray:
- return try [Bool].foryRead(context, refMode: .none, readTypeInfo: false)
+ return try [Bool].foryRead(self, refMode: .none, readTypeInfo: false)
case .int8Array:
- return try [Int8].foryRead(context, refMode: .none, readTypeInfo: false)
+ return try [Int8].foryRead(self, refMode: .none, readTypeInfo: false)
case .int16Array:
- return try [Int16].foryRead(context, refMode: .none, readTypeInfo: false)
+ return try [Int16].foryRead(self, refMode: .none, readTypeInfo: false)
case .int32Array:
- return try [Int32].foryRead(context, refMode: .none, readTypeInfo: false)
+ return try [Int32].foryRead(self, refMode: .none, readTypeInfo: false)
case .int64Array:
- return try [Int64].foryRead(context, refMode: .none, readTypeInfo: false)
+ return try [Int64].foryRead(self, refMode: .none, readTypeInfo: false)
case .uint16Array:
- return try [UInt16].foryRead(context, refMode: .none, readTypeInfo: false)
+ return try [UInt16].foryRead(self, refMode: .none, readTypeInfo: false)
case .uint32Array:
- return try [UInt32].foryRead(context, refMode: .none, readTypeInfo: false)
+ return try [UInt32].foryRead(self, refMode: .none, readTypeInfo: false)
case .uint64Array:
- return try [UInt64].foryRead(context, refMode: .none, readTypeInfo: false)
+ return try [UInt64].foryRead(self, refMode: .none, readTypeInfo: false)
case .float16Array:
- return try [Float16].foryRead(context, refMode: .none, readTypeInfo: false)
+ return try [Float16].foryRead(self, refMode: .none, readTypeInfo: false)
case .bfloat16Array:
- return try [BFloat16].foryRead(context, refMode: .none, readTypeInfo: false)
+ return try [BFloat16].foryRead(self, refMode: .none, readTypeInfo: false)
case .float32Array:
- return try [Float].foryRead(context, refMode: .none, readTypeInfo: false)
+ return try [Float].foryRead(self, refMode: .none, readTypeInfo: false)
case .float64Array:
- return try [Double].foryRead(context, refMode: .none, readTypeInfo: false)
+ return try [Double].foryRead(self, refMode: .none, readTypeInfo: false)
case .array, .list:
- return try readCollection(context: context, fieldType: fieldType)
+ return try readSkippedCollection(fieldType: fieldType)
case .set:
- return try readSet(context: context, fieldType: fieldType)
+ return try readSkippedSet(fieldType: fieldType)
case .map:
- return try readMap(context: context, fieldType: fieldType)
+ return try readSkippedMap(fieldType: fieldType)
case .union, .typedUnion, .namedUnion:
- return try readUnion(context: context)
+ return try readSkippedUnion()
case .enumType, .namedEnum:
- return try context.buffer.readVarUInt32()
+ return try buffer.readVarUInt32()
default:
throw ForyError.invalidData("unsupported compatible field type id \(fieldType.typeID)")
}
}
- private static func readCollection(
- context: ReadContext,
- fieldType: TypeMetaFieldType
+ private func readSkippedCollection(
+ fieldType: TypeMeta.FieldType
) throws -> [Any] {
let elementFieldType = fieldType.generics.first
- ?? TypeMetaFieldType(typeID: TypeId.unknown.rawValue, nullable: true)
- let length = Int(try context.buffer.readVarUInt32())
- try context.ensureCollectionLength(length, label: "compatible_collection")
+ ?? TypeMeta.FieldType(typeID: TypeId.unknown.rawValue, nullable: true)
+ let length = Int(try buffer.readVarUInt32())
+ try ensureCollectionLength(length, label: "compatible_collection")
if length == 0 {
return []
}
- let header = try context.buffer.readUInt8()
+ let header = try buffer.readUInt8()
let trackRef = (header & 0b0000_0001) != 0
let hasNull = (header & 0b0000_0010) != 0
let declared = (header & 0b0000_0100) != 0
let sameType = (header & 0b0000_1000) != 0
- var runtimeTypeInfo: DynamicTypeInfo?
+ var typeInfo: TypeInfo?
if sameType, !declared {
- runtimeTypeInfo = try context.typeResolver.readDynamicTypeInfo(context: context)
+ typeInfo = try self.readTypeInfo()
}
for _ in 0..<length {
if sameType {
if trackRef {
- _ = try readValueWithRefMode(
- context: context,
+ _ = try readSkippedValue(
fieldType: elementFieldType,
- runtimeTypeInfo: runtimeTypeInfo,
+ typeInfo: typeInfo,
refMode: .tracking,
readTypeInfo: false
)
} else if hasNull {
- let refFlag = try context.buffer.readInt8()
+ let refFlag = try buffer.readInt8()
if refFlag == RefFlag.null.rawValue {
continue
}
if refFlag != RefFlag.notNullValue.rawValue {
throw ForyError.invalidData("invalid collection nullability flag \(refFlag)")
}
- _ = try readFieldPayload(
- context: context,
+ _ = try readSkippedFieldPayload(
fieldType: elementFieldType,
- runtimeTypeInfo: runtimeTypeInfo,
+ typeInfo: typeInfo,
readTypeInfo: false
)
} else {
- _ = try readFieldPayload(
- context: context,
+ _ = try readSkippedFieldPayload(
fieldType: elementFieldType,
- runtimeTypeInfo: runtimeTypeInfo,
+ typeInfo: typeInfo,
readTypeInfo: false
)
}
@@ -281,32 +265,29 @@ public enum FieldSkipper {
}
if trackRef {
- _ = try readValueWithRefMode(
- context: context,
+ _ = try readSkippedValue(
fieldType: elementFieldType,
- runtimeTypeInfo: nil,
+ typeInfo: nil,
refMode: .tracking,
readTypeInfo: true
)
} else if hasNull {
- let refFlag = try context.buffer.readInt8()
+ let refFlag = try buffer.readInt8()
if refFlag == RefFlag.null.rawValue {
continue
}
if refFlag != RefFlag.notNullValue.rawValue {
throw ForyError.invalidData("invalid collection nullability flag \(refFlag)")
}
- _ = try readFieldPayload(
- context: context,
+ _ = try readSkippedFieldPayload(
fieldType: elementFieldType,
- runtimeTypeInfo: nil,
+ typeInfo: nil,
readTypeInfo: true
)
} else {
- _ = try readFieldPayload(
- context: context,
+ _ = try readSkippedFieldPayload(
fieldType: elementFieldType,
- runtimeTypeInfo: nil,
+ typeInfo: nil,
readTypeInfo: true
)
}
@@ -315,32 +296,30 @@ public enum FieldSkipper {
return []
}
- private static func readSet(
- context: ReadContext,
- fieldType: TypeMetaFieldType
+ private func readSkippedSet(
+ fieldType: TypeMeta.FieldType
) throws -> Set<AnyHashable> {
- _ = try readCollection(context: context, fieldType: fieldType)
+ _ = try readSkippedCollection(fieldType: fieldType)
return []
}
- private static func readMap(
- context: ReadContext,
- fieldType: TypeMetaFieldType
+ private func readSkippedMap(
+ fieldType: TypeMeta.FieldType
) throws -> [AnyHashable: Any] {
let keyType = fieldType.generics.first
- ?? TypeMetaFieldType(typeID: TypeId.unknown.rawValue, nullable: true)
+ ?? TypeMeta.FieldType(typeID: TypeId.unknown.rawValue, nullable: true)
let valueType = fieldType.generics.dropFirst().first
- ?? TypeMetaFieldType(typeID: TypeId.unknown.rawValue, nullable: true)
+ ?? TypeMeta.FieldType(typeID: TypeId.unknown.rawValue, nullable: true)
- let totalLength = Int(try context.buffer.readVarUInt32())
- try context.ensureCollectionLength(totalLength, label: "compatible_map")
+ let totalLength = Int(try buffer.readVarUInt32())
+ try ensureCollectionLength(totalLength, label: "compatible_map")
if totalLength == 0 {
return [:]
}
var readCount = 0
while readCount < totalLength {
- let header = try context.buffer.readUInt8()
+ let header = try buffer.readUInt8()
let trackKeyRef = (header & 0b0000_0001) != 0
let keyNull = (header & 0b0000_0010) != 0
let keyDeclared = (header & 0b0000_0100) != 0
@@ -355,11 +334,10 @@ public enum FieldSkipper {
}
if keyNull {
- let valueRuntimeType = valueDeclared ? nil : try context.typeResolver.readDynamicTypeInfo(context: context)
- _ = try readValueWithRefMode(
- context: context,
+ let valueTypeInfo = valueDeclared ? nil : try self.readTypeInfo()
+ _ = try readSkippedValue(
fieldType: valueType,
- runtimeTypeInfo: valueRuntimeType,
+ typeInfo: valueTypeInfo,
refMode: trackValueRef ? .tracking : .none,
readTypeInfo: false
)
@@ -368,11 +346,10 @@ public enum FieldSkipper {
}
if valueNull {
- let keyRuntimeType = keyDeclared ? nil : try context.typeResolver.readDynamicTypeInfo(context: context)
- _ = try readValueWithRefMode(
- context: context,
+ let keyTypeInfo = keyDeclared ? nil : try self.readTypeInfo()
+ _ = try readSkippedValue(
fieldType: keyType,
- runtimeTypeInfo: keyRuntimeType,
+ typeInfo: keyTypeInfo,
refMode: trackKeyRef ? .tracking : .none,
readTypeInfo: false
)
@@ -380,7 +357,7 @@ public enum FieldSkipper {
continue
}
- let chunkSize = Int(try context.buffer.readUInt8())
+ let chunkSize = Int(try buffer.readUInt8())
if chunkSize <= 0 {
throw ForyError.invalidData("invalid map chunk size \(chunkSize)")
}
@@ -388,21 +365,19 @@ public enum FieldSkipper {
throw ForyError.invalidData("map chunk size exceeds remaining entries")
}
- let keyRuntimeType = keyDeclared ? nil : try context.typeResolver.readDynamicTypeInfo(context: context)
- let valueRuntimeType = valueDeclared ? nil : try context.typeResolver.readDynamicTypeInfo(context: context)
+ let keyTypeInfo = keyDeclared ? nil : try self.readTypeInfo()
+ let valueTypeInfo = valueDeclared ? nil : try self.readTypeInfo()
for _ in 0..<chunkSize {
- _ = try readValueWithRefMode(
- context: context,
+ _ = try readSkippedValue(
fieldType: keyType,
- runtimeTypeInfo: keyRuntimeType,
+ typeInfo: keyTypeInfo,
refMode: trackKeyRef ? .tracking : .none,
readTypeInfo: false
)
- _ = try readValueWithRefMode(
- context: context,
+ _ = try readSkippedValue(
fieldType: valueType,
- runtimeTypeInfo: valueRuntimeType,
+ typeInfo: valueTypeInfo,
refMode: trackValueRef ? .tracking : .none,
readTypeInfo: false
)
@@ -413,8 +388,8 @@ public enum FieldSkipper {
return [:]
}
- private static func readUnion(context: ReadContext) throws -> Any {
- _ = try context.buffer.readVarUInt32()
- return try context.readAny(refMode: .tracking, readTypeInfo: true) ?? ForyAnyNullValue()
+ private func readSkippedUnion() throws -> Any {
+ _ = try buffer.readVarUInt32()
+ return try readAny(refMode: .tracking, readTypeInfo: true) ?? ForyAnyNullValue()
}
}
diff --git a/swift/Sources/Fory/FlatMap.swift b/swift/Sources/Fory/FlatMap.swift
new file mode 100644
index 000000000..aa32c4566
--- /dev/null
+++ b/swift/Sources/Fory/FlatMap.swift
@@ -0,0 +1,273 @@
+// 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.
+
+private let uint64MapEmptyKey = UInt64.max
+private let uint64MapDefaultLoadFactor = 0.5
+private let uint64MapGoldenRatio: UInt64 = 0x9E3779B97F4A7C15
+
+/// Internal open-addressed UInt64-key map for runtime caches in Fory Swift
+/// (for example type metadata caches in resolver/read/write contexts).
+/// This is not part of the public API surface.
+final class UInt64Map<Value> {
+ private struct Slot {
+ var key: UInt64
+ var value: Value?
+ }
+
+ private var entries: UnsafeMutablePointer<Slot>
+ private var tableCapacity: Int
+ private var mask: Int
+ private var shift: Int
+ private var size = 0
+ private let loadFactor: Double
+ private var growThreshold: Int
+
+ private var hasMaxKey = false
+ private var maxKeyValue: Value?
+
+ init(initialCapacity: Int = 2, loadFactor: Double = uint64MapDefaultLoadFactor) {
+ self.loadFactor = loadFactor
+ let capacity = Self.nextPowerOfTwo(max(initialCapacity, 2))
+ tableCapacity = capacity
+ entries = UnsafeMutablePointer<Slot>.allocate(capacity: capacity)
+ entries.initialize(
+ repeating: Slot(key: uint64MapEmptyKey, value: nil),
+ count: capacity
+ )
+ mask = capacity - 1
+ shift = UInt64.bitWidth - UInt64(capacity).trailingZeroBitCount
+ growThreshold = Int(Double(capacity) * loadFactor)
+ }
+
+ deinit {
+ entries.deinitialize(count: tableCapacity)
+ entries.deallocate()
+ }
+
+ var count: Int {
+ size
+ }
+
+ var capacity: Int {
+ tableCapacity
+ }
+
+ var isEmpty: Bool {
+ size == 0
+ }
+
+ @inline(__always)
+ func value(for key: UInt64) -> Value? {
+ if key == uint64MapEmptyKey {
+ return hasMaxKey ? maxKeyValue : nil
+ }
+
+ var index = place(key)
+ while true {
+ let slot = entries[index]
+ if slot.key == key {
+ return slot.value
+ }
+ if slot.key == uint64MapEmptyKey {
+ return nil
+ }
+ index = (index + 1) & mask
+ }
+ }
+
+ @inline(__always)
+ func set(_ value: Value, for key: UInt64) {
+ if key == uint64MapEmptyKey {
+ if !hasMaxKey {
+ hasMaxKey = true
+ size += 1
+ }
+ maxKeyValue = value
+ return
+ }
+
+ if size >= growThreshold {
+ grow()
+ }
+
+ let index = findSlotForInsert(key)
+ if entries[index].key == uint64MapEmptyKey {
+ entries[index].key = key
+ size += 1
+ }
+ entries[index].value = value
+ }
+
+ @inline(__always)
+ func putIfAbsent(_ value: Value, for key: UInt64) -> (value: Value, inserted: Bool) {
+ if key == uint64MapEmptyKey {
+ if hasMaxKey, let maxKeyValue {
+ return (maxKeyValue, false)
+ }
+ hasMaxKey = true
+ maxKeyValue = value
+ size += 1
+ return (value, true)
+ }
+
+ if size >= growThreshold {
+ grow()
+ }
+
+ let index = findSlotForInsert(key)
+ if entries[index].key == uint64MapEmptyKey {
+ entries[index].key = key
+ entries[index].value = value
+ size += 1
+ return (value, true)
+ }
+
+ guard let existingValue = entries[index].value else {
+ fatalError("corrupted UInt64Map state: occupied slot has nil value")
+ }
+ return (existingValue, false)
+ }
+
+ @discardableResult
+ func removeValue(for key: UInt64) -> Value? {
+ if key == uint64MapEmptyKey {
+ guard hasMaxKey else {
+ return nil
+ }
+ hasMaxKey = false
+ size -= 1
+ let removed = maxKeyValue
+ maxKeyValue = nil
+ return removed
+ }
+
+ var index = place(key)
+ while true {
+ let slot = entries[index]
+ if slot.key == key {
+ let removed = slot.value
+ removeEntry(at: index)
+ return removed
+ }
+ if slot.key == uint64MapEmptyKey {
+ return nil
+ }
+ index = (index + 1) & mask
+ }
+ }
+
+ func clear() {
+ if !isEmpty {
+ for index in 0 ..< tableCapacity {
+ entries[index].key = uint64MapEmptyKey
+ entries[index].value = nil
+ }
+ hasMaxKey = false
+ maxKeyValue = nil
+ size = 0
+ }
+ }
+
+ @inline(__always)
+ private func findSlotForInsert(_ key: UInt64) -> Int {
+ var index = place(key)
+ while true {
+ let slotKey = entries[index].key
+ if slotKey == uint64MapEmptyKey || slotKey == key {
+ return index
+ }
+ index = (index + 1) & mask
+ }
+ }
+
+ @inline(__always)
+ private func place(_ key: UInt64) -> Int {
+ Int((key &* uint64MapGoldenRatio) >> shift)
+ }
+
+ private func grow() {
+ let oldEntries = entries
+ let oldCapacity = tableCapacity
+ let newCapacity = oldCapacity << 1
+ let newEntries = UnsafeMutablePointer<Slot>.allocate(capacity: newCapacity)
+ newEntries.initialize(
+ repeating: Slot(key: uint64MapEmptyKey, value: nil),
+ count: newCapacity
+ )
+
+ entries = newEntries
+ tableCapacity = newCapacity
+ mask = newCapacity - 1
+ shift = UInt64.bitWidth - UInt64(newCapacity).trailingZeroBitCount
+ growThreshold = Int(Double(newCapacity) * loadFactor)
+ size = hasMaxKey ? 1 : 0
+
+ for index in 0 ..< oldCapacity {
+ let oldSlot = oldEntries[index]
+ if oldSlot.key != uint64MapEmptyKey {
+ let newIndex = findSlotForInsert(oldSlot.key)
+ entries[newIndex] = oldSlot
+ size += 1
+ }
+ }
+
+ oldEntries.deinitialize(count: oldCapacity)
+ oldEntries.deallocate()
+ }
+
+ private func removeEntry(at index: Int) {
+ var hole = index
+ var cursor = (hole + 1) & mask
+
+ while entries[cursor].key != uint64MapEmptyKey {
+ let idealSlot = place(entries[cursor].key)
+ if shouldMoveEntry(idealSlot: idealSlot, candidateSlot: cursor, emptySlot: hole) {
+ entries[hole] = entries[cursor]
+ hole = cursor
+ }
+ cursor = (cursor + 1) & mask
+ }
+
+ entries[hole].key = uint64MapEmptyKey
+ entries[hole].value = nil
+ size -= 1
+ }
+
+ @inline(__always)
+ private func shouldMoveEntry(idealSlot: Int, candidateSlot: Int, emptySlot: Int) -> Bool {
+ if emptySlot <= candidateSlot {
+ return idealSlot <= emptySlot || idealSlot > candidateSlot
+ }
+ return idealSlot <= emptySlot && idealSlot > candidateSlot
+ }
+
+ private static func nextPowerOfTwo(_ value: Int) -> Int {
+ if value <= 1 {
+ return 1
+ }
+ var valueBits = UInt64(value - 1)
+ valueBits |= valueBits >> 1
+ valueBits |= valueBits >> 2
+ valueBits |= valueBits >> 4
+ valueBits |= valueBits >> 8
+ valueBits |= valueBits >> 16
+ if UInt64.bitWidth > 32 {
+ valueBits |= valueBits >> 32
+ }
+ return Int(valueBits + 1)
+ }
+}
diff --git a/swift/Sources/Fory/Fory.swift b/swift/Sources/Fory/Fory.swift
index 8d7e90f17..6f7a7b6b8 100644
--- a/swift/Sources/Fory/Fory.swift
+++ b/swift/Sources/Fory/Fory.swift
@@ -43,109 +43,39 @@ public struct ForyConfig {
self.maxBinarySize = maxBinarySize
self.maxDepth = maxDepth
}
-}
-
-private final class ForyRuntimeContext {
- let writeBuffer: ByteBuffer
- let writeContext: WriteContext
- let readBuffer: ByteBuffer
- let readContext: ReadContext
-
- var writeInUse = false
- var readInUse = false
-
- var lastReadDataAddress: UnsafeRawPointer?
- var lastReadDataCount: Int = -1
-
- init(typeResolver: TypeResolver, config: ForyConfig) {
- writeBuffer = ByteBuffer()
- writeContext = WriteContext(
- buffer: writeBuffer,
- typeResolver: typeResolver,
- trackRef: config.trackRef,
- compatible: config.compatible,
- checkClassVersion: config.checkClassVersion,
- maxDepth: config.maxDepth,
- compatibleTypeDefState: CompatibleTypeDefWriteState(),
- metaStringWriteState: MetaStringWriteState()
- )
- readBuffer = ByteBuffer()
- readContext = ReadContext(
- buffer: readBuffer,
- typeResolver: typeResolver,
- trackRef: config.trackRef,
- compatible: config.compatible,
- checkClassVersion: config.checkClassVersion,
- maxCollectionSize: config.maxCollectionSize,
- maxBinarySize: config.maxBinarySize,
- maxDepth: config.maxDepth,
- compatibleTypeDefState: CompatibleTypeDefReadState(),
- metaStringReadState: MetaStringReadState()
+ static func resolved(
+ xlang: Bool = true,
+ trackRef: Bool = false,
+ compatible: Bool = false,
+ checkClassVersion: Bool? = nil,
+ maxCollectionSize: Int = 1_000_000,
+ maxBinarySize: Int = 64 * 1024 * 1024,
+ maxDepth: Int = 5
+ ) -> ForyConfig {
+ let effectiveCheckClassVersion = checkClassVersion ?? (xlang && !compatible)
+ return ForyConfig(
+ xlang: xlang,
+ trackRef: trackRef,
+ compatible: compatible,
+ checkClassVersion: effectiveCheckClassVersion,
+ maxCollectionSize: maxCollectionSize,
+ maxBinarySize: maxBinarySize,
+ maxDepth: maxDepth
)
}
}
-private final class ForyThreadContextCache {
- private var cachedID: UInt64 = .max
- private var cachedContext: ForyRuntimeContext?
- private var others: [UInt64: ForyRuntimeContext] = [:]
-
- @inline(__always)
- func getOrCreate(id: UInt64, create: () -> ForyRuntimeContext) -> ForyRuntimeContext {
- if cachedID == id, let cachedContext {
- return cachedContext
- }
-
- if let cachedContext {
- others[cachedID] = cachedContext
- }
-
- let context = others.removeValue(forKey: id) ?? create()
- cachedID = id
- cachedContext = context
- return context
- }
-}
-
-private final class ForyThreadContextStore: @unchecked Sendable {
- private let key: pthread_key_t
-
- init() {
- var localKey = pthread_key_t()
- let createResult = pthread_key_create(&localKey) { rawPointer in
- Unmanaged<ForyThreadContextCache>.fromOpaque(rawPointer).release()
- }
- precondition(createResult == 0, "failed to create pthread TLS key")
- key = localKey
- }
-
- deinit {
- pthread_key_delete(key)
- }
-
- @inline(__always)
- func get() -> ForyThreadContextCache {
- if let rawPointer = pthread_getspecific(key) {
- return Unmanaged<ForyThreadContextCache>.fromOpaque(rawPointer).takeUnretainedValue()
- }
-
- let cache = ForyThreadContextCache()
- let setResult = pthread_setspecific(key, Unmanaged.passRetained(cache).toOpaque())
- precondition(setResult == 0, "failed to set pthread TLS value")
- return cache
- }
-}
-
+/// Single-threaded Fory runtime.
+///
+/// Reuse one `Fory` per thread for the fastest path. The runtime keeps one
+/// reusable read/write context pair and must not be used concurrently from
+/// multiple threads.
public final class Fory {
public let config: ForyConfig
- public let typeResolver: TypeResolver
-
- private let instanceID: UInt64
-
- private static let threadContextStore = ForyThreadContextStore()
- private static let instanceIDLock = NSLock()
- nonisolated(unsafe) private static var nextInstanceID: UInt64 = 0
+ let typeResolver: TypeResolver
+ private let writeContext: WriteContext
+ private let readContext: ReadContext
public init(
xlang: Bool = true,
@@ -156,18 +86,35 @@ public final class Fory {
maxBinarySize: Int = 64 * 1024 * 1024,
maxDepth: Int = 5
) {
- let effectiveCheckClassVersion = checkClassVersion ?? (xlang && !compatible)
- self.config = ForyConfig(
+ self.config = ForyConfig.resolved(
xlang: xlang,
trackRef: trackRef,
compatible: compatible,
- checkClassVersion: effectiveCheckClassVersion,
+ checkClassVersion: checkClassVersion,
maxCollectionSize: maxCollectionSize,
maxBinarySize: maxBinarySize,
maxDepth: maxDepth
)
- self.typeResolver = TypeResolver()
- self.instanceID = Self.allocateInstanceID()
+ self.typeResolver = TypeResolver(trackRef: self.config.trackRef)
+ self.writeContext = WriteContext(
+ buffer: ByteBuffer(),
+ typeResolver: typeResolver,
+ trackRef: self.config.trackRef,
+ compatible: self.config.compatible,
+ checkClassVersion: self.config.checkClassVersion,
+ maxDepth: self.config.maxDepth,
+ metaStringWriteState: MetaStringWriteState()
+ )
+ self.readContext = ReadContext(
+ buffer: ByteBuffer(),
+ typeResolver: typeResolver,
+ trackRef: self.config.trackRef,
+ compatible: self.config.compatible,
+ checkClassVersion: self.config.checkClassVersion,
+ maxCollectionSize: self.config.maxCollectionSize,
+ maxBinarySize: self.config.maxBinarySize,
+ maxDepth: self.config.maxDepth
+ )
}
public convenience init(config: ForyConfig) {
@@ -195,35 +142,44 @@ public final class Fory {
}
public func serialize<T: Serializer>(_ value: T) throws -> Data {
- try serializeRoot(isNone: value.foryIsNone) { context in
- if useRootDataFastPath {
- try value.foryWriteData(context, hasGenerics: false)
- } else {
- try value.foryWrite(context, refMode: rootRefMode, writeTypeInfo: shouldWriteRootTypeInfo, hasGenerics: false)
- }
+ let context = writeContext
+ context.buffer.clear()
+ defer {
+ context.reset()
+ }
+
+ writeHead(buffer: context.buffer, isNone: value.foryIsNone)
+ if !value.foryIsNone {
+ try writeRootTypedValue(value, context: context)
}
+ return context.buffer.copyToData()
}
public func deserialize<T: Serializer>(_ data: Data, as _: T.Type = T.self) throws -> T {
- try deserializeRoot(
- data: data,
- nilValue: T.foryDefault()
- ) { context in
- if useRootDataFastPath {
- return try T.foryReadData(context)
+ try withReusableReadContext(data: data) { context in
+ if try readHead(buffer: context.buffer) {
+ return T.foryDefault()
}
- return try T.foryRead(context, refMode: rootRefMode, readTypeInfo: shouldWriteRootTypeInfo)
+ let value: T = try readRootTypedValue(context: context)
+ if context.buffer.remaining != 0 {
+ throw ForyError.invalidData("unexpected trailing bytes at root: \(context.buffer.remaining)")
+ }
+ return value
}
}
public func serialize<T: Serializer>(_ value: T, to buffer: inout Data) throws {
- try appendSerializedRoot(to: &buffer, isNone: value.foryIsNone) { context in
- if useRootDataFastPath {
- try value.foryWriteData(context, hasGenerics: false)
- } else {
- try value.foryWrite(context, refMode: rootRefMode, writeTypeInfo: shouldWriteRootTypeInfo, hasGenerics: false)
- }
+ let context = writeContext
+ context.buffer.clear()
+ defer {
+ context.reset()
+ }
+
+ writeHead(buffer: context.buffer, isNone: value.foryIsNone)
+ if !value.foryIsNone {
+ try writeRootTypedValue(value, context: context)
}
+ buffer.append(contentsOf: context.buffer.storage.prefix(context.buffer.count))
}
public func deserialize<T: Serializer>(from buffer: ByteBuffer, as _: T.Type = T.self) throws -> T {
@@ -231,10 +187,7 @@ public final class Fory {
from: buffer,
nilValue: T.foryDefault()
) { context in
- if useRootDataFastPath {
- return try T.foryReadData(context)
- }
- return try T.foryRead(context, refMode: rootRefMode, readTypeInfo: shouldWriteRootTypeInfo)
+ try readRootTypedValue(context: context)
}
}
@@ -497,7 +450,9 @@ public final class Fory {
}
}
- public func writeHead(buffer: ByteBuffer, isNone: Bool) {
+ @inlinable
+ @inline(__always)
+ func writeHead(buffer: ByteBuffer, isNone: Bool) {
var bitmap: UInt8 = 0
if config.xlang {
bitmap |= ForyHeaderFlag.isXlang
@@ -508,7 +463,9 @@ public final class Fory {
buffer.writeUInt8(bitmap)
}
- public func readHead(buffer: ByteBuffer) throws -> Bool {
+ @inlinable
+ @inline(__always)
+ func readHead(buffer: ByteBuffer) throws -> Bool {
let bitmap = try buffer.readUInt8()
let peerIsXlang = (bitmap & ForyHeaderFlag.isXlang) != 0
if peerIsXlang != config.xlang {
@@ -517,137 +474,46 @@ public final class Fory {
return (bitmap & ForyHeaderFlag.isNull) != 0
}
- private static func allocateInstanceID() -> UInt64 {
- instanceIDLock.lock()
- defer { instanceIDLock.unlock() }
- let id = nextInstanceID
- nextInstanceID &+= 1
- return id
- }
-
@inline(__always)
private var refMode: RefMode {
config.trackRef ? .tracking : .nullOnly
}
- @inline(__always)
- private var shouldWriteRootTypeInfo: Bool {
- config.xlang || config.compatible
- }
-
- @inline(__always)
- private var rootRefMode: RefMode {
- if config.trackRef {
- return .tracking
- }
- return shouldWriteRootTypeInfo ? .nullOnly : .none
- }
-
- @inline(__always)
- private var useRootDataFastPath: Bool {
- !shouldWriteRootTypeInfo && rootRefMode == .none
- }
-
- @inline(__always)
- private func makeWriteContext(buffer: ByteBuffer) -> WriteContext {
- WriteContext(
- buffer: buffer,
- typeResolver: typeResolver,
- trackRef: config.trackRef,
- compatible: config.compatible,
- checkClassVersion: config.checkClassVersion,
- maxDepth: config.maxDepth,
- compatibleTypeDefState: CompatibleTypeDefWriteState(),
- metaStringWriteState: MetaStringWriteState()
+ private func writeRootTypedValue<T: Serializer>(
+ _ value: T,
+ context: WriteContext
+ ) throws {
+ let writeTypeInfo = config.xlang || config.compatible
+ try value.foryWrite(
+ context,
+ refMode: config.trackRef ? .tracking : (writeTypeInfo ? .nullOnly : .none),
+ writeTypeInfo: writeTypeInfo,
+ hasGenerics: false
)
}
@inline(__always)
- private func makeReadContext(buffer: ByteBuffer) -> ReadContext {
- ReadContext(
- buffer: buffer,
- typeResolver: typeResolver,
- trackRef: config.trackRef,
- compatible: config.compatible,
- checkClassVersion: config.checkClassVersion,
- maxCollectionSize: config.maxCollectionSize,
- maxBinarySize: config.maxBinarySize,
- maxDepth: config.maxDepth,
- compatibleTypeDefState: CompatibleTypeDefReadState(),
- metaStringReadState: MetaStringReadState()
+ private func readRootTypedValue<T: Serializer>(
+ context: ReadContext
+ ) throws -> T {
+ let readTypeInfo = config.xlang || config.compatible
+ return try T.foryRead(
+ context,
+ refMode: config.trackRef ? .tracking : (readTypeInfo ? .nullOnly : .none),
+ readTypeInfo: readTypeInfo
)
}
@inline(__always)
- private func runtimeContext() -> ForyRuntimeContext {
- let cache = threadContextCache()
- return cache.getOrCreate(id: instanceID) {
- ForyRuntimeContext(typeResolver: typeResolver, config: config)
- }
- }
-
- @inline(__always)
- private func threadContextCache() -> ForyThreadContextCache {
- Self.threadContextStore.get()
- }
-
- @inline(__always)
- private func withReusableWriteContext<R>(
- _ body: (WriteContext) throws -> R
- ) rethrows -> R {
- let runtimeContext = runtimeContext()
-
- if runtimeContext.writeInUse {
- let temporaryBuffer = ByteBuffer()
- let temporaryContext = makeWriteContext(buffer: temporaryBuffer)
- defer { temporaryContext.reset() }
- return try body(temporaryContext)
- }
-
- runtimeContext.writeInUse = true
- runtimeContext.writeBuffer.clear()
- defer {
- runtimeContext.writeContext.reset()
- runtimeContext.writeInUse = false
- }
- return try body(runtimeContext.writeContext)
- }
-
- @inline(__always)
- private func withReusableReadContext<R>(
+ func withReusableReadContext<R>(
data: Data,
_ body: (ReadContext) throws -> R
) rethrows -> R {
- let runtimeContext = runtimeContext()
-
- if runtimeContext.readInUse {
- let temporaryBuffer = ByteBuffer(data: data)
- let temporaryContext = makeReadContext(buffer: temporaryBuffer)
- defer { temporaryContext.reset() }
- return try body(temporaryContext)
- }
-
- runtimeContext.readInUse = true
- runtimeContext.readBuffer.replace(with: data)
- data.withUnsafeBytes { rawBytes in
- runtimeContext.lastReadDataAddress = rawBytes.baseAddress
- runtimeContext.lastReadDataCount = rawBytes.count
- }
+ readContext.buffer.replace(with: data)
defer {
- runtimeContext.readContext.reset()
- runtimeContext.readInUse = false
+ readContext.reset()
}
- return try body(runtimeContext.readContext)
- }
-
- @inline(__always)
- private func withTemporaryReadContext<R>(
- buffer: ByteBuffer,
- _ body: (ReadContext) throws -> R
- ) rethrows -> R {
- let context = makeReadContext(buffer: buffer)
- defer { context.reset() }
- return try body(context)
+ return try body(readContext)
}
@inline(__always)
@@ -655,13 +521,16 @@ public final class Fory {
isNone: Bool,
_ body: (WriteContext) throws -> Void
) throws -> Data {
- try withReusableWriteContext { context in
- writeHead(buffer: context.buffer, isNone: isNone)
- if !isNone {
- try body(context)
- }
- return context.buffer.toData()
+ let context = writeContext
+ context.buffer.clear()
+ defer {
+ context.reset()
}
+ writeHead(buffer: context.buffer, isNone: isNone)
+ if !isNone {
+ try body(context)
+ }
+ return context.buffer.copyToData()
}
@inline(__always)
@@ -670,13 +539,16 @@ public final class Fory {
isNone: Bool,
_ body: (WriteContext) throws -> Void
) throws {
- try withReusableWriteContext { context in
- writeHead(buffer: context.buffer, isNone: isNone)
- if !isNone {
- try body(context)
- }
- output.append(contentsOf: context.buffer.storage)
+ let context = writeContext
+ context.buffer.clear()
+ defer {
+ context.reset()
+ }
+ writeHead(buffer: context.buffer, isNone: isNone)
+ if !isNone {
+ try body(context)
}
+ output.append(contentsOf: context.buffer.storage.prefix(context.buffer.count))
}
@inline(__always)
@@ -703,11 +575,14 @@ public final class Fory {
nilValue: @autoclosure () -> R,
_ body: (ReadContext) throws -> R
) throws -> R {
- try withTemporaryReadContext(buffer: buffer) { context in
- if try readHead(buffer: buffer) {
- return nilValue()
- }
- return try body(context)
+ readContext.buffer.swapState(with: buffer)
+ defer {
+ readContext.buffer.swapState(with: buffer)
+ readContext.reset()
+ }
+ if try readHead(buffer: readContext.buffer) {
+ return nilValue()
}
+ return try body(readContext)
}
}
diff --git a/swift/Sources/Fory/MacroDeclarations.swift b/swift/Sources/Fory/MacroDeclarations.swift
index 5a705bc3a..19b979cb9 100644
--- a/swift/Sources/Fory/MacroDeclarations.swift
+++ b/swift/Sources/Fory/MacroDeclarations.swift
@@ -18,18 +18,21 @@
@attached(
member,
names: named(staticTypeId),
- named(isReferenceTrackableType),
+ named(isRefType),
named(__forySchemaHash),
named(__forySchemaHashTrackRefDisabled),
named(__forySchemaHashTrackRefEnabled),
- named(foryCompatibleTypeMetaFields),
- named(__foryCompatibleTypeMetaFieldsTrackRefDisabled),
- named(__foryCompatibleTypeMetaFieldsTrackRefEnabled),
+ named(foryFieldsInfo),
+ named(__foryFieldsInfoTrackRefDisabled),
+ named(__foryFieldsInfoTrackRefEnabled),
named(foryDefault),
named(foryWrite),
named(foryRead),
named(foryWriteData),
- named(foryReadData)
+ named(foryReadData),
+ named(foryReadCompatibleData),
+ named(__foryReadDataImpl),
+ named(__foryReadCompatibleDataImpl)
)
@attached(extension, conformances: Serializer)
public macro ForyObject() = #externalMacro(module: "ForyMacro", type: "ForyObjectMacro")
diff --git a/swift/Sources/Fory/MetaString.swift b/swift/Sources/Fory/MetaString.swift
index 72dabec5e..3a1b61361 100644
--- a/swift/Sources/Fory/MetaString.swift
+++ b/swift/Sources/Fory/MetaString.swift
@@ -27,7 +27,7 @@ public enum MetaStringEncoding: UInt8, CaseIterable, Sendable {
case allToLowerSpecial = 4
}
-public struct MetaString: Equatable, Hashable, Sendable {
+public final class MetaString: Equatable, Hashable, @unchecked Sendable {
public let value: String
public let encoding: MetaStringEncoding
public let specialChar1: Character
@@ -68,6 +68,24 @@ public struct MetaString: Equatable, Hashable, Sendable {
}
return emptyMetaString
}
+
+ public static func == (lhs: MetaString, rhs: MetaString) -> Bool {
+ lhs.value == rhs.value &&
+ lhs.encoding == rhs.encoding &&
+ lhs.specialChar1 == rhs.specialChar1 &&
+ lhs.specialChar2 == rhs.specialChar2 &&
+ lhs.bytes == rhs.bytes &&
+ lhs.stripLastChar == rhs.stripLastChar
+ }
+
+ public func hash(into hasher: inout Hasher) {
+ hasher.combine(value)
+ hasher.combine(encoding)
+ hasher.combine(specialChar1)
+ hasher.combine(specialChar2)
+ hasher.combine(bytes)
+ hasher.combine(stripLastChar)
+ }
}
public struct MetaStringEncoder: Sendable {
@@ -447,3 +465,121 @@ public struct MetaStringDecoder: Sendable {
return out
}
}
+
+@inline(__always)
+func writeMetaString(
+ context: WriteContext,
+ value: MetaString,
+ encodings: [MetaStringEncoding],
+ encoder: MetaStringEncoder
+) throws {
+ let normalized: MetaString
+ if encodings.contains(value.encoding) {
+ normalized = value
+ } else {
+ normalized = try encoder.encode(value.value, allowedEncodings: encodings)
+ }
+
+ guard encodings.contains(normalized.encoding) else {
+ throw ForyError.encodingError("failed to normalize meta string encoding")
+ }
+
+ context.markMetaStringWriteStateUsed()
+ let bytes = normalized.bytes
+ let assignment = context.metaStringWriteState.assignIndexIfAbsent(for: normalized)
+ if assignment.isNew {
+ context.buffer.writeVarUInt32(UInt32(bytes.count) << 1)
+ if bytes.count > 16 {
+ context.buffer.writeInt64(Int64(bitPattern: metaStringHash(normalized)))
+ } else if !bytes.isEmpty {
+ context.buffer.writeUInt8(normalized.encoding.rawValue)
+ }
+ context.buffer.writeBytes(bytes)
+ } else {
+ context.buffer.writeVarUInt32(((assignment.index + 1) << 1) | 1)
+ }
+}
+
+@inline(__always)
+func readMetaString(
+ context: ReadContext,
+ decoder: MetaStringDecoder,
+ encodings: [MetaStringEncoding]
+) throws -> MetaString {
+ let header = try context.buffer.readVarUInt32()
+ let length = Int(header >> 1)
+ let isRef = (header & 1) == 1
+ if isRef {
+ let index = length - 1
+ guard let cached = context.getReadMetaString(at: index) else {
+ throw ForyError.invalidData("unknown meta string ref index \(index)")
+ }
+ return cached
+ }
+
+ let value: MetaString
+ if length == 0 {
+ value = MetaString.empty(
+ specialChar1: decoder.specialChar1,
+ specialChar2: decoder.specialChar2
+ )
+ } else {
+ let encoding: MetaStringEncoding
+ if length > 16 {
+ let hash = try context.buffer.readInt64()
+ let rawEncoding = UInt8(truncatingIfNeeded: hash & 0xFF)
+ guard let resolved = MetaStringEncoding(rawValue: rawEncoding) else {
+ throw ForyError.invalidData("invalid meta string encoding \(rawEncoding)")
+ }
+ encoding = resolved
+ } else {
+ let rawEncoding = try context.buffer.readUInt8()
+ guard let resolved = MetaStringEncoding(rawValue: rawEncoding) else {
+ throw ForyError.invalidData("invalid meta string encoding \(rawEncoding)")
+ }
+ encoding = resolved
+ }
+ guard encodings.contains(encoding) else {
+ throw ForyError.invalidData("meta string encoding \(encoding) not allowed in this context")
+ }
+ let bytes = try context.buffer.readBytes(count: length)
+ value = try decoder.decode(bytes: bytes, encoding: encoding)
+ }
+ context.appendReadMetaString(value)
+ return value
+}
+
+@inline(__always)
+func readMetaString(
+ buffer: ByteBuffer,
+ decoder: MetaStringDecoder,
+ encodings: [MetaStringEncoding]
+) throws -> MetaString {
+ let header = try buffer.readUInt8()
+ let encodingIndex = Int(header & 0b11)
+ guard encodingIndex < encodings.count else {
+ throw ForyError.invalidData("invalid meta string encoding index")
+ }
+
+ var length = Int(header >> 2)
+ if length >= 0b11_1111 {
+ length = 0b11_1111 + Int(try buffer.readVarUInt32())
+ }
+ let bytes = try buffer.readBytes(count: length)
+ return try decoder.decode(bytes: bytes, encoding: encodings[encodingIndex])
+}
+
+@inline(__always)
+func metaStringHash(_ metaString: MetaString) -> UInt64 {
+ var hash = Int64(bitPattern: MurmurHash3.x64_128(metaString.bytes, seed: 47).0)
+ if hash != Int64.min {
+ hash = Swift.abs(hash)
+ }
+ var result = UInt64(bitPattern: hash)
+ if result == 0 {
+ result &+= 256
+ }
+ result &= 0xffffffffffffff00
+ result |= UInt64(metaString.encoding.rawValue & 0xFF)
+ return result
+}
diff --git a/swift/Sources/Fory/OptionalSerializer.swift b/swift/Sources/Fory/OptionalSerializer.swift
index 70f38bcf7..9a504da84 100644
--- a/swift/Sources/Fory/OptionalSerializer.swift
+++ b/swift/Sources/Fory/OptionalSerializer.swift
@@ -30,8 +30,8 @@ extension Optional: Serializer where Wrapped: Serializer {
true
}
- public static var isReferenceTrackableType: Bool {
- Wrapped.isReferenceTrackableType
+ public static var isRefType: Bool {
+ Wrapped.isRefType
}
public var foryIsNone: Bool {
@@ -49,11 +49,11 @@ extension Optional: Serializer where Wrapped: Serializer {
.some(try Wrapped.foryReadData(context))
}
- public static func foryWriteTypeInfo(_ context: WriteContext) throws {
- try Wrapped.foryWriteTypeInfo(context)
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ try Wrapped.foryWriteStaticTypeInfo(context)
}
- public static func foryReadTypeInfo(_ context: ReadContext) throws {
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
try Wrapped.foryReadTypeInfo(context)
}
@@ -90,22 +90,35 @@ extension Optional: Serializer where Wrapped: Serializer {
refMode: RefMode,
readTypeInfo: Bool
) throws -> Wrapped? {
+ let typeInfo = readTypeInfo ? nil : context.getTypeInfo(for: Self.self)
switch refMode {
case .none:
- return .some(try Wrapped.foryRead(context, refMode: .none, readTypeInfo: readTypeInfo))
+ return .some(
+ try context.withTypeInfo(typeInfo, for: Wrapped.self) {
+ try Wrapped.foryRead(context, refMode: .none, readTypeInfo: readTypeInfo)
+ }
+ )
case .nullOnly:
let refFlag = try context.buffer.readInt8()
if refFlag == RefFlag.null.rawValue {
return nil
}
- return .some(try Wrapped.foryRead(context, refMode: .none, readTypeInfo: readTypeInfo))
+ return .some(
+ try context.withTypeInfo(typeInfo, for: Wrapped.self) {
+ try Wrapped.foryRead(context, refMode: .none, readTypeInfo: readTypeInfo)
+ }
+ )
case .tracking:
let refFlag = try context.buffer.readInt8()
if refFlag == RefFlag.null.rawValue {
return nil
}
context.buffer.moveBack(1)
- return .some(try Wrapped.foryRead(context, refMode: .tracking, readTypeInfo: readTypeInfo))
+ return .some(
+ try context.withTypeInfo(typeInfo, for: Wrapped.self) {
+ try Wrapped.foryRead(context, refMode: .tracking, readTypeInfo: readTypeInfo)
+ }
+ )
}
}
}
diff --git a/swift/Sources/Fory/PrimitiveSerializers.swift b/swift/Sources/Fory/PrimitiveSerializers.swift
index 3a6ef3fb4..162d340fd 100644
--- a/swift/Sources/Fory/PrimitiveSerializers.swift
+++ b/swift/Sources/Fory/PrimitiveSerializers.swift
@@ -22,6 +22,14 @@ extension Bool: Serializer {
public static func foryDefault() -> Bool { false }
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
context.buffer.writeUInt8(self ? 1 : 0)
}
@@ -36,6 +44,14 @@ extension Int8: Serializer {
public static func foryDefault() -> Int8 { 0 }
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
context.buffer.writeInt8(self)
}
@@ -50,6 +66,14 @@ extension Int16: Serializer {
public static func foryDefault() -> Int16 { 0 }
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
context.buffer.writeInt16(self)
}
@@ -64,6 +88,14 @@ extension Int32: Serializer {
public static func foryDefault() -> Int32 { 0 }
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
context.buffer.writeVarInt32(self)
}
@@ -78,6 +110,14 @@ extension Int64: Serializer {
public static func foryDefault() -> Int64 { 0 }
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
context.buffer.writeVarInt64(self)
}
@@ -92,6 +132,14 @@ extension UInt8: Serializer {
public static func foryDefault() -> UInt8 { 0 }
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
context.buffer.writeUInt8(self)
}
@@ -106,6 +154,14 @@ extension UInt16: Serializer {
public static func foryDefault() -> UInt16 { 0 }
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
context.buffer.writeUInt16(self)
}
@@ -120,6 +176,14 @@ extension UInt32: Serializer {
public static func foryDefault() -> UInt32 { 0 }
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
context.buffer.writeVarUInt32(self)
}
@@ -134,6 +198,14 @@ extension UInt64: Serializer {
public static func foryDefault() -> UInt64 { 0 }
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
context.buffer.writeVarUInt64(self)
}
@@ -152,6 +224,14 @@ public struct ForyInt32Fixed: Serializer, Equatable {
public static func foryDefault() -> ForyInt32Fixed { .init() }
public static var staticTypeId: TypeId { .int32 }
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
_ = hasGenerics
context.buffer.writeInt32(rawValue)
@@ -171,6 +251,14 @@ public struct ForyInt64Fixed: Serializer, Equatable {
public static func foryDefault() -> ForyInt64Fixed { .init() }
public static var staticTypeId: TypeId { .int64 }
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
_ = hasGenerics
context.buffer.writeInt64(rawValue)
@@ -190,6 +278,14 @@ public struct ForyInt64Tagged: Serializer, Equatable {
public static func foryDefault() -> ForyInt64Tagged { .init() }
public static var staticTypeId: TypeId { .taggedInt64 }
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
_ = hasGenerics
context.buffer.writeTaggedInt64(rawValue)
@@ -209,6 +305,14 @@ public struct ForyUInt32Fixed: Serializer, Equatable {
public static func foryDefault() -> ForyUInt32Fixed { .init() }
public static var staticTypeId: TypeId { .uint32 }
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
_ = hasGenerics
context.buffer.writeUInt32(rawValue)
@@ -228,6 +332,14 @@ public struct ForyUInt64Fixed: Serializer, Equatable {
public static func foryDefault() -> ForyUInt64Fixed { .init() }
public static var staticTypeId: TypeId { .uint64 }
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
_ = hasGenerics
context.buffer.writeUInt64(rawValue)
@@ -247,6 +359,14 @@ public struct ForyUInt64Tagged: Serializer, Equatable {
public static func foryDefault() -> ForyUInt64Tagged { .init() }
public static var staticTypeId: TypeId { .taggedUInt64 }
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
_ = hasGenerics
context.buffer.writeTaggedUInt64(rawValue)
@@ -263,6 +383,14 @@ extension Int: Serializer {
public static func foryDefault() -> Int { 0 }
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
context.buffer.writeVarInt64(Int64(self))
}
@@ -277,6 +405,14 @@ extension UInt: Serializer {
public static func foryDefault() -> UInt { 0 }
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
context.buffer.writeVarUInt64(UInt64(self))
}
@@ -292,6 +428,14 @@ extension Float: Serializer {
public static func foryDefault() -> Float { 0 }
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
context.buffer.writeFloat32(self)
}
@@ -306,6 +450,14 @@ extension Double: Serializer {
public static func foryDefault() -> Double { 0 }
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
context.buffer.writeFloat64(self)
}
@@ -325,6 +477,14 @@ public struct BFloat16: Serializer, Equatable, Hashable, Sendable {
public static func foryDefault() -> BFloat16 { .init() }
public static var staticTypeId: TypeId { .bfloat16 }
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
_ = hasGenerics
context.buffer.writeUInt16(rawValue)
@@ -340,6 +500,14 @@ extension Float16: Serializer {
public static func foryDefault() -> Float16 { 0 }
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
_ = hasGenerics
context.buffer.writeUInt16(bitPattern)
@@ -370,9 +538,28 @@ extension String: Serializer {
public static func foryDefault() -> String { "" }
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try readBuiltinTypeInfo(context, staticTypeId)
+ }
+
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
let utf8Bytes = self.utf8
let header = (UInt64(utf8Bytes.count) << 2) | StringEncoding.utf8.rawValue
+ if utf8Bytes.withContiguousStorageIfAvailable({ contiguousBytes in
+ let totalBytes = UnsafeUtil.varUInt64Size(header) + contiguousBytes.count
+ UnsafeUtil.writeRegion(buffer: context.buffer, exactCount: totalBytes) { base in
+ var index = UnsafeUtil.writeVarUInt64(header, to: base, index: 0)
+ index = UnsafeUtil.copyBytes(contiguousBytes, to: base, index: index)
+ assert(index == totalBytes)
+ }
+ return true
+ }) != nil {
+ return
+ }
context.buffer.writeVarUInt36Small(header)
context.buffer.writeBytes(utf8Bytes)
}
@@ -414,7 +601,11 @@ extension Data: Serializer {
public static func foryDefault() -> Data { Data() }
- public static func foryReadTypeInfo(_ context: ReadContext) throws {
+ public static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
+ writeBuiltinTypeInfo(context, staticTypeId)
+ }
+
+ public static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
let rawTypeID = try context.buffer.readVarUInt32()
guard let typeID = TypeId(rawValue: rawTypeID) else {
throw ForyError.invalidData("unknown type id \(rawTypeID)")
@@ -422,6 +613,7 @@ extension Data: Serializer {
if typeID != .binary && typeID != .uint8Array {
throw ForyError.typeMismatch(expected: TypeId.binary.rawValue, actual: rawTypeID)
}
+ return nil
}
public func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
diff --git a/swift/Sources/Fory/ReadContext.swift b/swift/Sources/Fory/ReadContext.swift
new file mode 100644
index 000000000..dd6188678
--- /dev/null
+++ b/swift/Sources/Fory/ReadContext.swift
@@ -0,0 +1,633 @@
+// 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.
+
+import Foundation
+
+private let typeMetaSizeMask = 0xFF
+
+public final class ReadContext {
+ public let buffer: ByteBuffer
+ let typeResolver: TypeResolver
+ public let trackRef: Bool
+ public let compatible: Bool
+ public let checkClassVersion: Bool
+ public let maxCollectionSize: Int
+ public let maxBinarySize: Int
+ public let maxDepth: Int
+ public let refReader: RefReader
+ private let compatibleTypeDefTypeInfos = ReusableArray<TypeInfo?>(defaultValue: nil, reserve: 2)
+ private let metaStrings = ReusableArray<MetaString?>(defaultValue: nil, reserve: 16)
+ private var dynamicAnyDepth = 0
+
+ private var typeInfoStack = UInt64Map<TypeInfo>(initialCapacity: 8)
+ private var typeInfoScopeStack: [(typeKey: UInt64, previousTypeInfo: TypeInfo?)] = []
+ private var lastTypeInfo = TypeInfo.uncached
+
+ init(
+ buffer: ByteBuffer,
+ typeResolver: TypeResolver,
+ trackRef: Bool,
+ compatible: Bool = false,
+ checkClassVersion: Bool = true,
+ maxCollectionSize: Int = 1_000_000,
+ maxBinarySize: Int = 64 * 1024 * 1024,
+ maxDepth: Int = 5
+ ) {
+ self.buffer = buffer
+ self.typeResolver = typeResolver
+ self.trackRef = trackRef
+ self.compatible = compatible
+ self.checkClassVersion = checkClassVersion
+ self.maxCollectionSize = maxCollectionSize
+ self.maxBinarySize = maxBinarySize
+ self.maxDepth = maxDepth
+ self.refReader = RefReader()
+ }
+
+ @inline(__always)
+ func enterDynamicAnyDepth() throws {
+ if maxDepth < 0 {
+ throw ForyError.invalidData("configured maxDepth \(maxDepth) is negative")
+ }
+ let nextDepth = dynamicAnyDepth + 1
+ if nextDepth > maxDepth {
+ throw ForyError.invalidData(
+ "dynamic Any nesting depth \(nextDepth) exceeds configured maxDepth \(maxDepth)"
+ )
+ }
+ dynamicAnyDepth = nextDepth
+ }
+
+ @inline(__always)
+ func leaveDynamicAnyDepth() {
+ if dynamicAnyDepth > 0 {
+ dynamicAnyDepth -= 1
+ }
+ }
+
+ @inline(__always)
+ func ensureCollectionLength(_ length: Int, label: String) throws {
+ if length < 0 {
+ throw ForyError.invalidData("\(label) length is negative")
+ }
+ if length > maxCollectionSize {
+ throw ForyError.invalidData(
+ "\(label) length \(length) exceeds configured maxCollectionSize \(maxCollectionSize)"
+ )
+ }
+ }
+
+ @inline(__always)
+ func ensureBinaryLength(_ length: Int, label: String) throws {
+ if length < 0 {
+ throw ForyError.invalidData("\(label) size is negative")
+ }
+ if length > maxBinarySize {
+ throw ForyError.invalidData(
+ "\(label) size \(length) exceeds configured maxBinarySize \(maxBinarySize)"
+ )
+ }
+ }
+
+ @inline(__always)
+ func ensureRemainingBytes(_ byteCount: Int, label: String) throws {
+ if byteCount < 0 {
+ throw ForyError.invalidData("\(label) size is negative")
+ }
+ let remainingBytes = buffer.remaining
+ if byteCount > remainingBytes {
+ throw ForyError.invalidData(
+ "\(label) requires \(byteCount) bytes but only \(remainingBytes) remain in buffer"
+ )
+ }
+ }
+
+ @inline(__always)
+ func typeInfo<T: Serializer>(for type: T.Type) throws -> TypeInfo {
+ let typeID = ObjectIdentifier(type)
+ if lastTypeInfo.swiftTypeID == typeID {
+ return lastTypeInfo
+ }
+ let info = try typeResolver.requireTypeInfo(for: type)
+ lastTypeInfo = info
+ return info
+ }
+
+ @inline(__always)
+ func readStaticTypeInfo(_ typeID: TypeId) throws -> TypeInfo? {
+ let rawTypeID = UInt32(try buffer.readUInt8())
+ guard let actualTypeID = TypeId(rawValue: rawTypeID) else {
+ throw ForyError.invalidData("unknown type id \(rawTypeID)")
+ }
+ if actualTypeID != typeID {
+ throw ForyError.typeMismatch(expected: typeID.rawValue, actual: rawTypeID)
+ }
+ return nil
+ }
+
+ func readTypeInfo() throws -> TypeInfo {
+ let rawTypeID = UInt32(try buffer.readUInt8())
+ guard let wireTypeID = TypeId(rawValue: rawTypeID) else {
+ throw ForyError.invalidData("unknown dynamic type id \(rawTypeID)")
+ }
+
+ switch wireTypeID {
+ case .compatibleStruct, .namedCompatibleStruct:
+ return try readCompatibleTypeInfo()
+ case .namedStruct, .namedEnum, .namedExt, .namedUnion:
+ if compatible {
+ return try readCompatibleTypeInfo()
+ }
+ let namespace = try readMetaString(
+ context: self,
+ decoder: .namespace,
+ encodings: namespaceMetaStringEncodings
+ )
+ let typeName = try readMetaString(
+ context: self,
+ decoder: .typeName,
+ encodings: typeNameMetaStringEncodings
+ )
+ return try typeResolver.requireTypeInfo(namespace: namespace.value, typeName: typeName.value)
+ case .structType, .enumType, .ext, .typedUnion, .union:
+ let userTypeID = try buffer.readVarUInt32()
+ return try typeResolver.requireTypeInfo(userTypeID: userTypeID)
+ default:
+ return typeResolver.builtinTypeInfo(for: wireTypeID)
+ }
+ }
+
+ func readTypeInfo<T: Serializer>(for type: T.Type) throws -> TypeInfo? {
+ let rawTypeID = UInt32(try buffer.readUInt8())
+ guard let typeID = TypeId(rawValue: rawTypeID) else {
+ throw ForyError.invalidData("unknown type id \(rawTypeID)")
+ }
+
+ guard T.staticTypeId.isUserTypeKind else {
+ if typeID != T.staticTypeId {
+ throw ForyError.typeMismatch(expected: T.staticTypeId.rawValue, actual: rawTypeID)
+ }
+ return nil
+ }
+
+ let localTypeInfo = try typeInfo(for: type)
+ let expectedWireTypeID = localTypeInfo.wireTypeID(compatible: compatible)
+ if !isAllowedRegisteredWireTypeID(
+ typeID,
+ declaredTypeID: localTypeInfo.typeID,
+ registerByName: localTypeInfo.registerByName,
+ compatible: compatible
+ ) {
+ throw ForyError.typeMismatch(expected: expectedWireTypeID.rawValue, actual: rawTypeID)
+ }
+
+ switch typeID {
+ case .compatibleStruct, .namedCompatibleStruct:
+ return try readCompatibleTypeInfoIfNeeded(
+ for: localTypeInfo,
+ wireTypeID: typeID
+ )
+ case .namedEnum, .namedStruct, .namedExt, .namedUnion:
+ if compatible {
+ let remoteTypeInfo = try readCompatibleTypeInfoIfNeeded(
+ for: localTypeInfo,
+ wireTypeID: typeID
+ )
+ if typeID == .namedStruct, let remoteTypeInfo {
+ return remoteTypeInfo
+ }
+ } else {
+ let namespace = try readMetaString(
+ context: self,
+ decoder: .namespace,
+ encodings: namespaceMetaStringEncodings
+ )
+ let typeName = try readMetaString(
+ context: self,
+ decoder: .typeName,
+ encodings: typeNameMetaStringEncodings
+ )
+ guard localTypeInfo.registerByName else {
+ throw ForyError.invalidData("received name-registered type info for id-registered local type")
+ }
+ if namespace.value != localTypeInfo.namespace.value ||
+ typeName.value != localTypeInfo.typeName.value {
+ let expectedTypeName = "\(localTypeInfo.namespace.value)::\(localTypeInfo.typeName.value)"
+ let actualTypeName = "\(namespace.value)::\(typeName.value)"
+ throw ForyError.invalidData(
+ "type name mismatch: expected \(expectedTypeName), got \(actualTypeName)"
+ )
+ }
+ }
+ default:
+ if !localTypeInfo.registerByName && registeredWireTypeNeedsUserTypeID(typeID) {
+ guard let localUserTypeID = localTypeInfo.userTypeID else {
+ throw ForyError.invalidData("missing user type id for id-registered type")
+ }
+ let remoteUserTypeID = try buffer.readVarUInt32()
+ if remoteUserTypeID != localUserTypeID {
+ throw ForyError.typeMismatch(expected: localUserTypeID, actual: remoteUserTypeID)
+ }
+ }
+ }
+ return nil
+ }
+
+ @inline(__always)
+ private func readCompatibleTypeInfoIfNeeded(
+ for localTypeInfo: TypeInfo,
+ wireTypeID: TypeId
+ ) throws -> TypeInfo? {
+ let buffer = self.buffer
+ let compatibleTypeDefTypeInfos = self.compatibleTypeDefTypeInfos
+ if !checkClassVersion,
+ compatibleTypeDefTypeInfos.isEmpty,
+ !localTypeInfo.typeDefHasUserTypeFields,
+ let localTypeDefHeader = localTypeInfo.typeDefHeader {
+ let typeMetaStart = buffer.getCursor()
+ let indexMarker = try buffer.readVarUInt32()
+ if indexMarker == 0 {
+ let header = try buffer.readUInt64()
+ var bodySize = Int(header & UInt64(typeMetaSizeMask))
+ if bodySize == typeMetaSizeMask {
+ bodySize += Int(try buffer.readVarUInt32())
+ }
+ if header == localTypeDefHeader {
+ compatibleTypeDefTypeInfos.push(localTypeInfo)
+ try buffer.skip(bodySize)
+ return nil
+ }
+ }
+ buffer.setCursor(typeMetaStart)
+ }
+ return try readCompatibleTypeInfo(
+ for: localTypeInfo,
+ wireTypeID: wireTypeID
+ )
+ }
+
+ private func readCompatibleTypeInfo() throws -> TypeInfo {
+ let buffer = self.buffer
+ let compatibleTypeDefTypeInfos = self.compatibleTypeDefTypeInfos
+ let indexMarker = try buffer.readVarUInt32()
+ let isRef = (indexMarker & 1) == 1
+ let index = Int(indexMarker >> 1)
+ if isRef {
+ guard let typeInfo = compatibleTypeDefTypeInfos.get(index) else {
+ throw ForyError.invalidData("unknown compatible type definition ref index \(index)")
+ }
+ return typeInfo
+ }
+
+ let typeMetaStart = buffer.getCursor()
+ let header = try buffer.readUInt64()
+ var bodySize = Int(header & UInt64(typeMetaSizeMask))
+ if bodySize == typeMetaSizeMask {
+ bodySize += Int(try buffer.readVarUInt32())
+ }
+ if let cached = typeResolver.getTypeInfo(forHeader: header) {
+ try buffer.skip(bodySize)
+ compatibleTypeDefTypeInfos.push(cached)
+ return cached
+ }
+
+ buffer.setCursor(typeMetaStart)
+ let decoded = try TypeMeta.decode(buffer)
+ let cachedTypeInfo = try typeResolver.cacheTypeInfo(decoded, forHeader: header)
+ compatibleTypeDefTypeInfos.push(cachedTypeInfo)
+ return cachedTypeInfo
+ }
+
+ @inline(__always)
+ private func readCompatibleTypeInfo(
+ for localTypeInfo: TypeInfo,
+ wireTypeID: TypeId
+ ) throws -> TypeInfo {
+ let buffer = self.buffer
+ let compatibleTypeDefTypeInfos = self.compatibleTypeDefTypeInfos
+ let remoteTypeInfo: TypeInfo
+ if compatibleTypeDefTypeInfos.isEmpty,
+ let localTypeDefHeader = localTypeInfo.typeDefHeader {
+ let typeMetaStart = buffer.getCursor()
+ let indexMarker = try buffer.readVarUInt32()
+ if indexMarker != 0 {
+ buffer.setCursor(typeMetaStart)
+ remoteTypeInfo = try readCompatibleTypeInfo()
+ } else {
+ let header = try buffer.readUInt64()
+ var bodySize = Int(header & UInt64(typeMetaSizeMask))
+ if bodySize == typeMetaSizeMask {
+ bodySize += Int(try buffer.readVarUInt32())
+ }
+
+ if header == localTypeDefHeader {
+ compatibleTypeDefTypeInfos.push(localTypeInfo)
+ try buffer.skip(bodySize)
+ return localTypeInfo
+ }
+
+ buffer.setCursor(typeMetaStart)
+ remoteTypeInfo = try readCompatibleTypeInfo()
+ }
+ } else {
+ remoteTypeInfo = try readCompatibleTypeInfo()
+ }
+ guard let remoteTypeMeta = remoteTypeInfo.compatibleTypeMeta else {
+ throw ForyError.invalidData("compatible type metadata is required")
+ }
+ if let localTypeMeta = localTypeInfo.typeMeta,
+ remoteTypeMeta === localTypeMeta {
+ return localTypeInfo
+ }
+ if remoteTypeMeta.registerByName {
+ guard localTypeInfo.registerByName else {
+ throw ForyError.invalidData("received name-registered compatible metadata for id-registered local type")
+ }
+ if remoteTypeMeta.namespace.value != localTypeInfo.namespace.value {
+ throw ForyError.invalidData(
+ "namespace mismatch: expected \(localTypeInfo.namespace.value), got \(remoteTypeMeta.namespace.value)"
+ )
+ }
+ if remoteTypeMeta.typeName.value != localTypeInfo.typeName.value {
+ throw ForyError.invalidData(
+ "type name mismatch: expected \(localTypeInfo.typeName.value), got \(remoteTypeMeta.typeName.value)"
+ )
+ }
+ } else {
+ guard !localTypeInfo.registerByName else {
+ throw ForyError.invalidData("received id-registered compatible metadata for name-registered local type")
+ }
+ guard let remoteUserTypeID = remoteTypeMeta.userTypeID else {
+ throw ForyError.invalidData("missing user type id in compatible type metadata")
+ }
+ guard let localUserTypeID = localTypeInfo.userTypeID else {
+ throw ForyError.invalidData("missing local user type id metadata for id-registered type")
+ }
+ if remoteUserTypeID != localUserTypeID {
+ throw ForyError.typeMismatch(expected: localUserTypeID, actual: remoteUserTypeID)
+ }
+ }
+
+ if let remoteTypeID = remoteTypeMeta.typeID,
+ let remoteWireTypeID = TypeId(rawValue: remoteTypeID),
+ !isAllowedRegisteredWireTypeID(
+ remoteWireTypeID,
+ declaredTypeID: localTypeInfo.typeID,
+ registerByName: localTypeInfo.registerByName,
+ compatible: compatible
+ ) {
+ throw ForyError.typeMismatch(expected: wireTypeID.rawValue, actual: remoteTypeID)
+ }
+ return remoteTypeInfo
+ }
+
+ func readAnyValue(typeInfo: TypeInfo) throws -> Any {
+ try enterDynamicAnyDepth()
+ defer { leaveDynamicAnyDepth() }
+
+ let value: Any
+ switch typeInfo.typeID {
+ case .bool:
+ value = try Bool.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .int8:
+ value = try Int8.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .int16:
+ value = try Int16.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .int32:
+ value = try ForyInt32Fixed.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .varint32:
+ value = try Int32.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .int64:
+ value = try ForyInt64Fixed.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .varint64:
+ value = try Int64.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .taggedInt64:
+ value = try ForyInt64Tagged.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .uint8:
+ value = try UInt8.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .uint16:
+ value = try UInt16.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .uint32:
+ value = try ForyUInt32Fixed.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .varUInt32:
+ value = try UInt32.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .uint64:
+ value = try ForyUInt64Fixed.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .varUInt64:
+ value = try UInt64.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .taggedUInt64:
+ value = try ForyUInt64Tagged.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .float16:
+ value = try Float16.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .bfloat16:
+ value = try BFloat16.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .float32:
+ value = try Float.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .float64:
+ value = try Double.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .string:
+ value = try String.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .duration:
+ value = try Duration.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .timestamp:
+ value = try Date.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .date:
+ value = try ForyDate.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .binary, .uint8Array:
+ value = try Data.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .boolArray:
+ value = try [Bool].foryRead(self, refMode: .none, readTypeInfo: false)
+ case .int8Array:
+ value = try [Int8].foryRead(self, refMode: .none, readTypeInfo: false)
+ case .int16Array:
+ value = try [Int16].foryRead(self, refMode: .none, readTypeInfo: false)
+ case .int32Array:
+ value = try [Int32].foryRead(self, refMode: .none, readTypeInfo: false)
+ case .int64Array:
+ value = try [Int64].foryRead(self, refMode: .none, readTypeInfo: false)
+ case .uint16Array:
+ value = try [UInt16].foryRead(self, refMode: .none, readTypeInfo: false)
+ case .uint32Array:
+ value = try [UInt32].foryRead(self, refMode: .none, readTypeInfo: false)
+ case .uint64Array:
+ value = try [UInt64].foryRead(self, refMode: .none, readTypeInfo: false)
+ case .float16Array:
+ value = try [Float16].foryRead(self, refMode: .none, readTypeInfo: false)
+ case .bfloat16Array:
+ value = try [BFloat16].foryRead(self, refMode: .none, readTypeInfo: false)
+ case .float32Array:
+ value = try [Float].foryRead(self, refMode: .none, readTypeInfo: false)
+ case .float64Array:
+ value = try [Double].foryRead(self, refMode: .none, readTypeInfo: false)
+ case .array, .list:
+ value = try readAnyList(refMode: .none) ?? []
+ case .set:
+ value = try Set<AnyHashable>.foryRead(self, refMode: .none, readTypeInfo: false)
+ case .map:
+ value = try readDynamicAnyMapValue(context: self)
+ case .none:
+ value = ForyAnyNullValue()
+ default:
+ if typeInfo.typeID.isUserTypeKind {
+ value = try typeInfo.read(self)
+ } else {
+ throw ForyError.invalidData("unsupported dynamic type id \(typeInfo.typeID)")
+ }
+ }
+ return value
+ }
+
+ @inline(__always)
+ func getTypeInfo<T: Serializer>(for type: T.Type) -> TypeInfo? {
+ typeInfoStack.value(for: UInt64(UInt(bitPattern: ObjectIdentifier(type))))
+ }
+
+ func withTypeInfo<T: Serializer, R>(
+ _ typeInfo: TypeInfo?,
+ for type: T.Type,
+ _ body: () throws -> R
+ ) rethrows -> R {
+ guard let typeInfo else {
+ return try body()
+ }
+
+ let typeKey = UInt64(UInt(bitPattern: ObjectIdentifier(type)))
+ let previousTypeInfo = typeInfoStack.value(for: typeKey)
+ typeInfoScopeStack.append((typeKey: typeKey, previousTypeInfo: previousTypeInfo))
+ typeInfoStack.set(typeInfo, for: typeKey)
+ defer {
+ if let scope = typeInfoScopeStack.popLast() {
+ if let previousTypeInfo = scope.previousTypeInfo {
+ typeInfoStack.set(previousTypeInfo, for: scope.typeKey)
+ } else {
+ _ = typeInfoStack.removeValue(for: scope.typeKey)
+ }
+ } else {
+ assertionFailure("type info scope stack underflow")
+ }
+ }
+ return try body()
+ }
+
+ @inline(__always)
+ func getReadMetaString(at index: Int) -> MetaString? {
+ metaStrings.get(index)
+ }
+
+ @inline(__always)
+ func appendReadMetaString(_ value: MetaString) {
+ metaStrings.push(value)
+ }
+
+ func reset() {
+ if dynamicAnyDepth != 0 {
+ dynamicAnyDepth = 0
+ }
+ if trackRef {
+ refReader.reset()
+ }
+ if !typeInfoStack.isEmpty {
+ typeInfoStack.clear()
+ }
+ if !typeInfoScopeStack.isEmpty {
+ typeInfoScopeStack.removeAll(keepingCapacity: true)
+ }
+ compatibleTypeDefTypeInfos.reset()
+ metaStrings.reset()
+ }
+}
+
+public extension ReadContext {
+ func readAny(
+ refMode: RefMode,
+ readTypeInfo: Bool = true
+ ) throws -> Any? {
+ try DynamicAnyValue.foryRead(self, refMode: refMode, readTypeInfo: readTypeInfo).anyValue()
+ }
+
+ func readAnyList(
+ refMode: RefMode,
+ readTypeInfo: Bool = false
+ ) throws -> [Any]? {
+ let wrapped: [DynamicAnyValue]? = try [DynamicAnyValue]?.foryRead(
+ self,
+ refMode: refMode,
+ readTypeInfo: readTypeInfo
+ )
+ return wrapped?.map { $0.anyValueForCollection() }
+ }
+
+ func readStringAnyMap(
+ refMode: RefMode,
+ readTypeInfo: Bool = false
+ ) throws -> [String: Any]? {
+ let wrapped: [String: DynamicAnyValue]? = try [String: DynamicAnyValue]?.foryRead(
+ self,
+ refMode: refMode,
+ readTypeInfo: readTypeInfo
+ )
+ guard let wrapped else {
+ return nil
+ }
+ var map: [String: Any] = [:]
+ map.reserveCapacity(wrapped.count)
+ for pair in wrapped {
+ map[pair.key] = pair.value.anyValueForCollection()
+ }
+ return map
+ }
+
+ func readInt32AnyMap(
+ refMode: RefMode,
+ readTypeInfo: Bool = false
+ ) throws -> [Int32: Any]? {
+ let wrapped: [Int32: DynamicAnyValue]? = try [Int32: DynamicAnyValue]?.foryRead(
+ self,
+ refMode: refMode,
+ readTypeInfo: readTypeInfo
+ )
+ guard let wrapped else {
+ return nil
+ }
+ var map: [Int32: Any] = [:]
+ map.reserveCapacity(wrapped.count)
+ for pair in wrapped {
+ map[pair.key] = pair.value.anyValueForCollection()
+ }
+ return map
+ }
+
+ func readAnyHashableAnyMap(
+ refMode: RefMode,
+ readTypeInfo: Bool = false
+ ) throws -> [AnyHashable: Any]? {
+ let wrapped: [AnyHashable: DynamicAnyValue]? = try [AnyHashable: DynamicAnyValue]?.foryRead(
+ self,
+ refMode: refMode,
+ readTypeInfo: readTypeInfo
+ )
+ guard let wrapped else {
+ return nil
+ }
+ var map: [AnyHashable: Any] = [:]
+ map.reserveCapacity(wrapped.count)
+ for pair in wrapped {
+ map[pair.key] = pair.value.anyValueForCollection()
+ }
+ return map
+ }
+}
diff --git a/swift/Sources/Fory/RefResolver.swift b/swift/Sources/Fory/RefResolver.swift
index c3321f2f4..f7d0823af 100644
--- a/swift/Sources/Fory/RefResolver.swift
+++ b/swift/Sources/Fory/RefResolver.swift
@@ -23,7 +23,7 @@ public final class RefWriter {
public init() {}
- public func tryWriteReference(buffer: ByteBuffer, object: AnyObject) -> Bool {
+ public func tryWriteRef(buffer: ByteBuffer, object: AnyObject) -> Bool {
let objectID = ObjectIdentifier(object)
if let refID = refs[objectID] {
buffer.writeInt8(RefFlag.ref.rawValue)
@@ -36,12 +36,6 @@ public final class RefWriter {
return false
}
- public func reserveRefID() -> UInt32 {
- let id = nextRefID
- nextRefID &+= 1
- return id
- }
-
public func reset() {
if !refs.isEmpty {
refs.removeAll(keepingCapacity: true)
diff --git a/swift/Sources/Fory/Serializer.swift b/swift/Sources/Fory/Serializer.swift
index e2d281b8f..81621210f 100644
--- a/swift/Sources/Fory/Serializer.swift
+++ b/swift/Sources/Fory/Serializer.swift
@@ -17,23 +17,14 @@
import Foundation
-private struct CompatibleTypeMetaCacheKey: Hashable {
- let swiftType: ObjectIdentifier
- let wireTypeID: TypeId
- let trackRef: Bool
- let registerByName: Bool
- let userTypeID: UInt32?
- let namespace: MetaString?
- let typeName: MetaString
+@inline(__always)
+func writeBuiltinTypeInfo(_ context: WriteContext, _ typeID: TypeId) {
+ context.writeStaticTypeInfo(typeID)
}
-private struct CompatibleTypeMetaCacheEntry {
- let encodedTypeMeta: [UInt8]
-}
-
-private enum CompatibleTypeMetaCache {
- nonisolated(unsafe) static var values: [CompatibleTypeMetaCacheKey: CompatibleTypeMetaCacheEntry] = [:]
- static let lock = NSLock()
+@inline(__always)
+func readBuiltinTypeInfo(_ context: ReadContext, _ typeID: TypeId) throws -> TypeInfo? {
+ try context.readStaticTypeInfo(typeID)
}
public protocol Serializer {
@@ -41,7 +32,7 @@ public protocol Serializer {
static var staticTypeId: TypeId { get }
static var isNullableType: Bool { get }
- static var isReferenceTrackableType: Bool { get }
+ static var isRefType: Bool { get }
var foryIsNone: Bool { get }
@@ -61,9 +52,10 @@ public protocol Serializer {
readTypeInfo: Bool
) throws -> Self
- static func foryWriteTypeInfo(_ context: WriteContext) throws
- static func foryReadTypeInfo(_ context: ReadContext) throws
- static func foryCompatibleTypeMetaFields(trackRef: Bool) -> [TypeMetaFieldInfo]
+ static func foryWriteStaticTypeInfo(_ context: WriteContext) throws
+ static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo?
+ static func foryReadCompatibleData(_ context: ReadContext, remoteTypeInfo: TypeInfo) throws -> Self
+ static func foryFieldsInfo(trackRef: Bool) -> [TypeMeta.FieldInfo]
func foryWriteTypeInfo(_ context: WriteContext) throws
}
@@ -72,19 +64,56 @@ public extension Serializer {
static var isNullableType: Bool { false }
@inlinable
- static var isReferenceTrackableType: Bool { false }
+ static var isRefType: Bool { false }
@inlinable
var foryIsNone: Bool { false }
@inlinable
- static func foryCompatibleTypeMetaFields(trackRef _: Bool) -> [TypeMetaFieldInfo] {
+ func foryWriteTypeInfo(_ context: WriteContext) throws {
+ try Self.foryWriteStaticTypeInfo(context)
+ }
+
+ @inlinable
+ static func foryFieldsInfo(trackRef _: Bool) -> [TypeMeta.FieldInfo] {
[]
}
@inlinable
- func foryWriteTypeInfo(_ context: WriteContext) throws {
- try Self.foryWriteTypeInfo(context)
+ static func foryReadCompatibleData(_ context: ReadContext, remoteTypeInfo _: TypeInfo) throws -> Self {
+ try foryReadData(context)
+ }
+}
+
+public extension Serializer {
+ static func foryReadPayload(
+ _ context: ReadContext,
+ readTypeInfo: Bool
+ ) throws -> Self {
+ try Self.foryReadPayload(
+ context,
+ readTypeInfo: readTypeInfo,
+ readData: { try Self.foryReadData(context) },
+ readCompatibleData: { remoteTypeInfo in
+ try Self.foryReadCompatibleData(context, remoteTypeInfo: remoteTypeInfo)
+ }
+ )
+ }
+
+ static func foryReadPayload(
+ _ context: ReadContext,
+ readTypeInfo: Bool,
+ readData: () throws -> Self,
+ readCompatibleData: (TypeInfo) throws -> Self
+ ) throws -> Self {
+ if readTypeInfo {
+ if let remoteTypeInfo = try Self.foryReadTypeInfo(context) {
+ return try readCompatibleData(remoteTypeInfo)
+ }
+ } else if let remoteTypeInfo = context.getTypeInfo(for: Self.self) {
+ return try readCompatibleData(remoteTypeInfo)
+ }
+ return try readData()
}
@inlinable
@@ -95,8 +124,8 @@ public extension Serializer {
hasGenerics: Bool
) throws {
if refMode != .none {
- if refMode == .tracking, Self.isReferenceTrackableType, let object = self as AnyObject? {
- if context.refWriter.tryWriteReference(buffer: context.buffer, object: object) {
+ if refMode == .tracking, Self.isRefType, let object = self as AnyObject? {
+ if context.refWriter.tryWriteRef(buffer: context.buffer, object: object) {
return
}
} else {
@@ -105,7 +134,7 @@ public extension Serializer {
}
if writeTypeInfo {
- try Self.foryWriteTypeInfo(context)
+ try Self.foryWriteStaticTypeInfo(context)
}
try foryWriteData(context, hasGenerics: hasGenerics)
@@ -117,12 +146,37 @@ public extension Serializer {
refMode: RefMode,
readTypeInfo: Bool
) throws -> Self {
- if refMode != .none {
+ switch refMode {
+ case .none:
+ return try Self.foryReadPayload(context, readTypeInfo: readTypeInfo)
+ case .nullOnly:
+ let rawFlag = try context.buffer.readInt8()
+ switch rawFlag {
+ case RefFlag.null.rawValue:
+ return Self.foryDefault()
+ case RefFlag.notNullValue.rawValue:
+ return try Self.foryReadPayload(context, readTypeInfo: readTypeInfo)
+ case RefFlag.refValue.rawValue:
+ if context.trackRef {
+ let reservedRefID = context.refReader.reserveRefID()
+ let value = try Self.foryReadPayload(context, readTypeInfo: readTypeInfo)
+ if let object = value as AnyObject? {
+ context.refReader.storeRef(object, at: reservedRefID)
+ }
+ return value
+ }
+ return try Self.foryReadPayload(context, readTypeInfo: readTypeInfo)
+ case RefFlag.ref.rawValue:
+ let refID = try context.buffer.readVarUInt32()
+ return try context.refReader.readRef(refID, as: Self.self)
+ default:
+ throw ForyError.refError("invalid ref flag \(rawFlag)")
+ }
+ case .tracking:
let rawFlag = try context.buffer.readInt8()
guard let flag = RefFlag(rawValue: rawFlag) else {
throw ForyError.refError("invalid ref flag \(rawFlag)")
}
-
switch flag {
case .null:
return Self.foryDefault()
@@ -130,81 +184,56 @@ public extension Serializer {
let refID = try context.buffer.readVarUInt32()
return try context.refReader.readRef(refID, as: Self.self)
case .refValue:
- let reservedRefID = context.refReader.reserveRefID()
- context.pushPendingReference(reservedRefID)
- if readTypeInfo {
- try Self.foryReadTypeInfo(context)
+ let reservedRefID = context.trackRef ? context.refReader.reserveRefID() : nil
+ let value = try Self.foryReadPayload(context, readTypeInfo: readTypeInfo)
+ if let reservedRefID, let object = value as AnyObject? {
+ context.refReader.storeRef(object, at: reservedRefID)
}
- let value = try Self.foryReadData(context)
- context.finishPendingReferenceIfNeeded(value)
- context.popPendingReference()
return value
case .notNullValue:
- break
+ return try Self.foryReadPayload(context, readTypeInfo: readTypeInfo)
}
}
-
- if readTypeInfo {
- try Self.foryReadTypeInfo(context)
- }
- return try Self.foryReadData(context)
}
- static func foryWriteTypeInfo(_ context: WriteContext) throws {
+ static func foryWriteStaticTypeInfo(_ context: WriteContext) throws {
guard staticTypeId.isUserTypeKind else {
context.buffer.writeUInt8(UInt8(truncatingIfNeeded: staticTypeId.rawValue))
return
}
- let info = try context.typeResolver.requireRegisteredTypeInfo(for: Self.self)
- let wireTypeID = resolveWireTypeID(
- declaredKind: info.kind,
- registerByName: info.registerByName,
- compatible: context.compatible
- )
+ let typeInfo = try context.typeInfo(for: Self.self)
+ let wireTypeID = typeInfo.wireTypeID(compatible: context.compatible)
context.buffer.writeUInt8(UInt8(truncatingIfNeeded: wireTypeID.rawValue))
switch wireTypeID {
case .compatibleStruct, .namedCompatibleStruct:
- let cachedTypeMeta = try compatibleTypeMetaEntry(
- info: info,
- wireTypeID: wireTypeID,
- trackRef: context.trackRef
- )
- try context.writeCompatibleTypeMeta(
- for: Self.self,
- encodedTypeMeta: cachedTypeMeta.encodedTypeMeta
- )
+ guard typeInfo.typeDefBytes != nil else {
+ throw ForyError.invalidData("missing compatible type definition for \(typeInfo.typeID)")
+ }
+ context.writeTypeMeta(typeInfo)
case .namedEnum, .namedStruct, .namedExt, .namedUnion:
if context.compatible {
- let cachedTypeMeta = try compatibleTypeMetaEntry(
- info: info,
- wireTypeID: wireTypeID,
- trackRef: context.trackRef
- )
- try context.writeCompatibleTypeMeta(
- for: Self.self,
- encodedTypeMeta: cachedTypeMeta.encodedTypeMeta
- )
- } else {
- guard let namespace = info.namespace else {
- throw ForyError.invalidData("missing namespace metadata for name-registered type")
+ guard typeInfo.typeDefBytes != nil else {
+ throw ForyError.invalidData("missing compatible type definition for \(typeInfo.typeID)")
}
+ context.writeTypeMeta(typeInfo)
+ } else {
try writeMetaString(
context: context,
- value: namespace,
+ value: typeInfo.namespace,
encodings: namespaceMetaStringEncodings,
encoder: .namespace
)
try writeMetaString(
context: context,
- value: info.typeName,
+ value: typeInfo.typeName,
encodings: typeNameMetaStringEncodings,
encoder: .typeName
)
}
default:
- if !info.registerByName && wireTypeNeedsUserTypeID(wireTypeID) {
- guard let userTypeID = info.userTypeID else {
+ if !typeInfo.registerByName && registeredWireTypeNeedsUserTypeID(wireTypeID) {
+ guard let userTypeID = typeInfo.userTypeID else {
throw ForyError.invalidData("missing user type id for id-registered type")
}
context.buffer.writeVarUInt32(userTypeID)
@@ -212,393 +241,8 @@ public extension Serializer {
}
}
- static func foryReadTypeInfo(_ context: ReadContext) throws {
- let rawTypeID = try context.buffer.readVarUInt32()
- guard let typeID = TypeId(rawValue: rawTypeID) else {
- throw ForyError.invalidData("unknown type id \(rawTypeID)")
- }
-
- guard staticTypeId.isUserTypeKind else {
- if typeID != staticTypeId {
- throw ForyError.typeMismatch(expected: staticTypeId.rawValue, actual: rawTypeID)
- }
- return
- }
-
- let info = try context.typeResolver.requireRegisteredTypeInfo(for: Self.self)
- let allowed = allowedWireTypeIDs(
- declaredKind: info.kind,
- registerByName: info.registerByName,
- compatible: context.compatible
- )
- if !allowed.contains(typeID) {
- if let expected = allowed.first {
- throw ForyError.typeMismatch(expected: expected.rawValue, actual: rawTypeID)
- }
- throw ForyError.invalidData("no expected wire type ids for \(info.kind)")
- }
-
- switch typeID {
- case .compatibleStruct, .namedCompatibleStruct:
- let remoteTypeMeta = try context.readCompatibleTypeMeta()
- try validateCompatibleTypeMeta(
- remoteTypeMeta,
- localInfo: info,
- expectedWireTypes: allowed,
- actualWireTypeID: typeID
- )
- context.pushCompatibleTypeMeta(for: Self.self, remoteTypeMeta)
- case .namedEnum, .namedStruct, .namedExt, .namedUnion:
- if context.compatible {
- let remoteTypeMeta = try context.readCompatibleTypeMeta()
- try validateCompatibleTypeMeta(
- remoteTypeMeta,
- localInfo: info,
- expectedWireTypes: allowed,
- actualWireTypeID: typeID
- )
- if typeID == .namedStruct {
- context.pushCompatibleTypeMeta(for: Self.self, remoteTypeMeta)
- }
- } else {
- let namespace = try readMetaString(
- context: context,
- decoder: .namespace,
- encodings: namespaceMetaStringEncodings
- )
- let typeName = try readMetaString(
- context: context,
- decoder: .typeName,
- encodings: typeNameMetaStringEncodings
- )
- guard info.registerByName else {
- throw ForyError.invalidData("received name-registered type info for id-registered local type")
- }
- guard let localNamespace = info.namespace else {
- throw ForyError.invalidData("missing local namespace metadata for name-registered type")
- }
- if namespace.value != localNamespace.value || typeName.value != info.typeName.value {
- throw ForyError.invalidData(
- "type name mismatch: expected \(localNamespace.value)::\(info.typeName.value), got \(namespace.value)::\(typeName.value)"
- )
- }
- }
- default:
- if !info.registerByName && wireTypeNeedsUserTypeID(typeID) {
- guard let localUserTypeID = info.userTypeID else {
- throw ForyError.invalidData("missing user type id for id-registered type")
- }
- let remoteUserTypeID = try context.buffer.readVarUInt32()
- if remoteUserTypeID != localUserTypeID {
- throw ForyError.typeMismatch(expected: localUserTypeID, actual: remoteUserTypeID)
- }
- }
- }
- }
-
- private static func normalizeBaseKind(_ kind: TypeId) -> TypeId {
- switch kind {
- case .namedEnum:
- return .enumType
- case .compatibleStruct, .namedCompatibleStruct, .namedStruct:
- return .structType
- case .namedExt:
- return .ext
- case .namedUnion, .union:
- return .typedUnion
- default:
- return kind
- }
+ static func foryReadTypeInfo(_ context: ReadContext) throws -> TypeInfo? {
+ try context.readTypeInfo(for: Self.self)
}
- private static func namedKind(for baseKind: TypeId, compatible: Bool) -> TypeId {
- switch baseKind {
- case .structType:
- return compatible ? .namedCompatibleStruct : .namedStruct
- case .enumType:
- return .namedEnum
- case .ext:
- return .namedExt
- case .typedUnion:
- return .namedUnion
- default:
- return baseKind
- }
- }
-
- private static func idKind(for baseKind: TypeId, compatible: Bool) -> TypeId {
- switch baseKind {
- case .structType:
- return compatible ? .compatibleStruct : .structType
- default:
- return baseKind
- }
- }
-
- private static func resolveWireTypeID(
- declaredKind: TypeId,
- registerByName: Bool,
- compatible: Bool
- ) -> TypeId {
- let baseKind = normalizeBaseKind(declaredKind)
- if registerByName {
- return namedKind(for: baseKind, compatible: compatible)
- }
- return idKind(for: baseKind, compatible: compatible)
- }
-
- private static func allowedWireTypeIDs(
- declaredKind: TypeId,
- registerByName: Bool,
- compatible: Bool
- ) -> Set<TypeId> {
- let baseKind = normalizeBaseKind(declaredKind)
- let expected = resolveWireTypeID(
- declaredKind: declaredKind,
- registerByName: registerByName,
- compatible: compatible
- )
- var allowed: Set<TypeId> = [expected]
- if baseKind == .structType, compatible {
- // Be permissive across peers while struct compatibility converges.
- allowed.insert(.compatibleStruct)
- allowed.insert(.namedCompatibleStruct)
- allowed.insert(.structType)
- allowed.insert(.namedStruct)
- }
- if baseKind == .typedUnion {
- allowed.insert(.union)
- if registerByName {
- allowed.insert(.namedUnion)
- }
- }
- return allowed
- }
-
- private static func wireTypeNeedsUserTypeID(_ typeID: TypeId) -> Bool {
- switch typeID {
- case .enumType, .structType, .ext, .typedUnion, .union:
- return true
- default:
- return false
- }
- }
-
- private static func compatibleTypeMetaEntry(
- info: RegisteredTypeInfo,
- wireTypeID: TypeId,
- trackRef: Bool
- ) throws -> CompatibleTypeMetaCacheEntry {
- let cacheKey = CompatibleTypeMetaCacheKey(
- swiftType: ObjectIdentifier(Self.self),
- wireTypeID: wireTypeID,
- trackRef: trackRef,
- registerByName: info.registerByName,
- userTypeID: info.userTypeID,
- namespace: info.namespace,
- typeName: info.typeName
- )
-
- CompatibleTypeMetaCache.lock.lock()
- if let cached = CompatibleTypeMetaCache.values[cacheKey] {
- CompatibleTypeMetaCache.lock.unlock()
- return cached
- }
- CompatibleTypeMetaCache.lock.unlock()
-
- let typeMeta = try buildCompatibleTypeMeta(
- info: info,
- wireTypeID: wireTypeID,
- trackRef: trackRef
- )
- let cacheEntry = CompatibleTypeMetaCacheEntry(
- encodedTypeMeta: try typeMeta.encode()
- )
-
- CompatibleTypeMetaCache.lock.lock()
- if let cached = CompatibleTypeMetaCache.values[cacheKey] {
- CompatibleTypeMetaCache.lock.unlock()
- return cached
- }
- CompatibleTypeMetaCache.values[cacheKey] = cacheEntry
- CompatibleTypeMetaCache.lock.unlock()
- return cacheEntry
- }
-
- private static func buildCompatibleTypeMeta(
- info: RegisteredTypeInfo,
- wireTypeID: TypeId,
- trackRef: Bool
- ) throws -> TypeMeta {
- let fields = foryCompatibleTypeMetaFields(trackRef: trackRef)
- let hasFieldsMeta = !fields.isEmpty
- if info.registerByName {
- guard let namespace = info.namespace else {
- throw ForyError.invalidData("missing namespace metadata for name-registered type")
- }
- return try TypeMeta(
- typeID: wireTypeID.rawValue,
- userTypeID: nil,
- namespace: namespace,
- typeName: info.typeName,
- registerByName: true,
- fields: fields,
- hasFieldsMeta: hasFieldsMeta
- )
- }
-
- guard let userTypeID = info.userTypeID else {
- throw ForyError.invalidData("missing user type id metadata for id-registered type")
- }
- return try TypeMeta(
- typeID: wireTypeID.rawValue,
- userTypeID: userTypeID,
- namespace: MetaString.empty(specialChar1: ".", specialChar2: "_"),
- typeName: MetaString.empty(specialChar1: "$", specialChar2: "_"),
- registerByName: false,
- fields: fields,
- hasFieldsMeta: hasFieldsMeta
- )
- }
-
- private static func validateCompatibleTypeMeta(
- _ remoteTypeMeta: TypeMeta,
- localInfo: RegisteredTypeInfo,
- expectedWireTypes: Set<TypeId>,
- actualWireTypeID: TypeId
- ) throws {
- if remoteTypeMeta.registerByName {
- guard localInfo.registerByName else {
- throw ForyError.invalidData("received name-registered compatible metadata for id-registered local type")
- }
- guard let localNamespace = localInfo.namespace else {
- throw ForyError.invalidData("missing local namespace metadata for name-registered type")
- }
- if remoteTypeMeta.namespace.value != localNamespace.value {
- throw ForyError.invalidData(
- "namespace mismatch: expected \(localNamespace.value), got \(remoteTypeMeta.namespace.value)"
- )
- }
- if remoteTypeMeta.typeName.value != localInfo.typeName.value {
- throw ForyError.invalidData(
- "type name mismatch: expected \(localInfo.typeName.value), got \(remoteTypeMeta.typeName.value)"
- )
- }
- } else {
- guard !localInfo.registerByName else {
- throw ForyError.invalidData("received id-registered compatible metadata for name-registered local type")
- }
- guard let remoteUserTypeID = remoteTypeMeta.userTypeID else {
- throw ForyError.invalidData("missing user type id in compatible type metadata")
- }
- guard let localUserTypeID = localInfo.userTypeID else {
- throw ForyError.invalidData("missing local user type id metadata for id-registered type")
- }
- if remoteUserTypeID != localUserTypeID {
- throw ForyError.typeMismatch(expected: localUserTypeID, actual: remoteUserTypeID)
- }
- }
-
- if let remoteTypeID = remoteTypeMeta.typeID,
- let remoteWireTypeID = TypeId(rawValue: remoteTypeID),
- !expectedWireTypes.contains(remoteWireTypeID) {
- throw ForyError.typeMismatch(expected: actualWireTypeID.rawValue, actual: remoteTypeID)
- }
- }
-
- private static func writeMetaString(
- context: WriteContext,
- value: MetaString,
- encodings: [MetaStringEncoding],
- encoder: MetaStringEncoder
- ) throws {
- let normalized: MetaString
- if encodings.contains(value.encoding) {
- normalized = value
- } else {
- normalized = try encoder.encode(value.value, allowedEncodings: encodings)
- }
-
- guard encodings.contains(normalized.encoding) else {
- throw ForyError.encodingError("failed to normalize meta string encoding")
- }
-
- context.markMetaStringWriteStateUsed()
- let bytes = normalized.bytes
- let assignment = context.metaStringWriteState.assignIndexIfAbsent(for: normalized)
- if assignment.isNew {
- context.buffer.writeVarUInt32(UInt32(bytes.count) << 1)
- if bytes.count > 16 {
- context.buffer.writeInt64(Int64(bitPattern: javaMetaStringHash(metaString: normalized)))
- } else if !bytes.isEmpty {
- context.buffer.writeUInt8(normalized.encoding.rawValue)
- }
- context.buffer.writeBytes(bytes)
- } else {
- context.buffer.writeVarUInt32(((assignment.index + 1) << 1) | 1)
- }
- }
-
- private static func readMetaString(
- context: ReadContext,
- decoder: MetaStringDecoder,
- encodings: [MetaStringEncoding]
- ) throws -> MetaString {
- context.markMetaStringReadStateUsed()
- let header = try context.buffer.readVarUInt32()
- let length = Int(header >> 1)
- let isRef = (header & 1) == 1
- if isRef {
- let index = length - 1
- guard let cached = context.metaStringReadState.value(at: index) else {
- throw ForyError.invalidData("unknown meta string ref index \(index)")
- }
- return cached
- }
-
- let value: MetaString
- if length == 0 {
- value = MetaString.empty(
- specialChar1: decoder.specialChar1,
- specialChar2: decoder.specialChar2
- )
- } else {
- let encoding: MetaStringEncoding
- if length > 16 {
- let hash = try context.buffer.readInt64()
- let rawEncoding = UInt8(truncatingIfNeeded: hash & 0xFF)
- guard let resolved = MetaStringEncoding(rawValue: rawEncoding) else {
- throw ForyError.invalidData("invalid meta string encoding \(rawEncoding)")
- }
- encoding = resolved
- } else {
- let rawEncoding = try context.buffer.readUInt8()
- guard let resolved = MetaStringEncoding(rawValue: rawEncoding) else {
- throw ForyError.invalidData("invalid meta string encoding \(rawEncoding)")
- }
- encoding = resolved
- }
- guard encodings.contains(encoding) else {
- throw ForyError.invalidData("meta string encoding \(encoding) not allowed in this context")
- }
- let bytes = try context.buffer.readBytes(count: length)
- value = try decoder.decode(bytes: bytes, encoding: encoding)
- }
- context.metaStringReadState.append(value)
- return value
- }
-
- private static func javaMetaStringHash(metaString: MetaString) -> UInt64 {
- var hash = Int64(bitPattern: MurmurHash3.x64_128(metaString.bytes, seed: 47).0)
- if hash != Int64.min {
- hash = Swift.abs(hash)
- }
- var result = UInt64(bitPattern: hash)
- if result == 0 {
- result &+= 256
- }
- result &= 0xffffffffffffff00
- result |= UInt64(metaString.encoding.rawValue & 0xFF)
- return result
- }
}
diff --git a/swift/Sources/Fory/TypeMeta.swift b/swift/Sources/Fory/TypeMeta.swift
index 4e4585aa0..78cb17d0c 100644
--- a/swift/Sources/Fory/TypeMeta.swift
+++ b/swift/Sources/Fory/TypeMeta.swift
@@ -48,135 +48,157 @@ public let fieldNameMetaStringEncodings: [MetaStringEncoding] = [
.lowerUpperDigitSpecial
]
-public struct TypeMetaFieldType: Equatable, Sendable {
- public var typeID: UInt32
- public var nullable: Bool
- public var trackRef: Bool
- public var generics: [TypeMetaFieldType]
-
- public init(
- typeID: UInt32,
- nullable: Bool,
- trackRef: Bool = false,
- generics: [TypeMetaFieldType] = []
- ) {
- self.typeID = typeID
- self.nullable = nullable
- self.trackRef = trackRef
- self.generics = generics
- }
-
- fileprivate func write(
- _ buffer: ByteBuffer,
- writeFlags: Bool,
- nullableOverride: Bool? = nil
- ) {
- if writeFlags {
- var header = typeID << 2
- if nullableOverride ?? nullable {
- header |= 0b10
+public final class TypeMeta: Equatable, @unchecked Sendable {
+
+ public struct FieldType: Equatable, Sendable {
+ public var typeID: UInt32
+ public var nullable: Bool
+ public var trackRef: Bool
+ public var generics: [FieldType]
+
+ public init(
+ typeID: UInt32,
+ nullable: Bool,
+ trackRef: Bool = false,
+ generics: [FieldType] = []
+ ) {
+ self.typeID = typeID
+ self.nullable = nullable
+ self.trackRef = trackRef
+ self.generics = generics
+ }
+
+ fileprivate func write(
+ _ buffer: ByteBuffer,
+ writeFlags: Bool,
+ nullableOverride: Bool? = nil
+ ) {
+ if writeFlags {
+ var header = typeID << 2
+ if nullableOverride ?? nullable {
+ header |= 0b10
+ }
+ if trackRef {
+ header |= 0b1
+ }
+ buffer.writeVarUInt32(header)
+ } else {
+ buffer.writeUInt8(UInt8(truncatingIfNeeded: typeID))
}
- if trackRef {
- header |= 0b1
+
+ if typeID == TypeId.list.rawValue || typeID == TypeId.set.rawValue {
+ let element = generics.first ?? FieldType(typeID: TypeId.unknown.rawValue, nullable: true)
+ element.write(buffer, writeFlags: true, nullableOverride: element.nullable)
+ } else if typeID == TypeId.map.rawValue {
+ let key = generics.first ?? FieldType(typeID: TypeId.unknown.rawValue, nullable: true)
+ let value = generics.dropFirst().first ?? FieldType(typeID: TypeId.unknown.rawValue, nullable: true)
+ key.write(buffer, writeFlags: true, nullableOverride: key.nullable)
+ value.write(buffer, writeFlags: true, nullableOverride: value.nullable)
}
- buffer.writeVarUInt32(header)
- } else {
- buffer.writeUInt8(UInt8(truncatingIfNeeded: typeID))
}
- if typeID == TypeId.list.rawValue || typeID == TypeId.set.rawValue {
- let element = generics.first ?? TypeMetaFieldType(typeID: TypeId.unknown.rawValue, nullable: true)
- element.write(buffer, writeFlags: true, nullableOverride: element.nullable)
- } else if typeID == TypeId.map.rawValue {
- let key = generics.first ?? TypeMetaFieldType(typeID: TypeId.unknown.rawValue, nullable: true)
- let value = generics.dropFirst().first ?? TypeMetaFieldType(typeID: TypeId.unknown.rawValue, nullable: true)
- key.write(buffer, writeFlags: true, nullableOverride: key.nullable)
- value.write(buffer, writeFlags: true, nullableOverride: value.nullable)
- }
- }
+ fileprivate static func read(
+ _ buffer: ByteBuffer,
+ readFlags: Bool,
+ nullable: Bool? = nil,
+ trackRef: Bool? = nil
+ ) throws -> FieldType {
+ let header: UInt32
+ if readFlags {
+ header = try buffer.readVarUInt32()
+ } else {
+ header = UInt32(try buffer.readUInt8())
+ }
- fileprivate static func read(
- _ buffer: ByteBuffer,
- readFlags: Bool,
- nullable: Bool? = nil,
- trackRef: Bool? = nil
- ) throws -> TypeMetaFieldType {
- let header: UInt32
- if readFlags {
- header = try buffer.readVarUInt32()
- } else {
- header = UInt32(try buffer.readUInt8())
- }
+ let typeID: UInt32
+ let resolvedNullable: Bool
+ let resolvedTrackRef: Bool
- let typeID: UInt32
- let resolvedNullable: Bool
- let resolvedTrackRef: Bool
+ if readFlags {
+ typeID = header >> 2
+ resolvedNullable = (header & 0b10) != 0
+ resolvedTrackRef = (header & 0b1) != 0
+ } else {
+ typeID = header
+ resolvedNullable = nullable ?? false
+ resolvedTrackRef = trackRef ?? false
+ }
- if readFlags {
- typeID = header >> 2
- resolvedNullable = (header & 0b10) != 0
- resolvedTrackRef = (header & 0b1) != 0
- } else {
- typeID = header
- resolvedNullable = nullable ?? false
- resolvedTrackRef = trackRef ?? false
- }
+ if typeID == TypeId.list.rawValue || typeID == TypeId.set.rawValue {
+ let element = try read(buffer, readFlags: true)
+ return FieldType(
+ typeID: typeID,
+ nullable: resolvedNullable,
+ trackRef: resolvedTrackRef,
+ generics: [element]
+ )
+ }
+ if typeID == TypeId.map.rawValue {
+ let key = try read(buffer, readFlags: true)
+ let value = try read(buffer, readFlags: true)
+ return FieldType(
+ typeID: typeID,
+ nullable: resolvedNullable,
+ trackRef: resolvedTrackRef,
+ generics: [key, value]
+ )
+ }
- if typeID == TypeId.list.rawValue || typeID == TypeId.set.rawValue {
- let element = try read(buffer, readFlags: true)
- return TypeMetaFieldType(
+ return FieldType(
typeID: typeID,
nullable: resolvedNullable,
trackRef: resolvedTrackRef,
- generics: [element]
+ generics: []
)
}
- if typeID == TypeId.map.rawValue {
- let key = try read(buffer, readFlags: true)
- let value = try read(buffer, readFlags: true)
- return TypeMetaFieldType(
- typeID: typeID,
- nullable: resolvedNullable,
- trackRef: resolvedTrackRef,
- generics: [key, value]
- )
- }
-
- return TypeMetaFieldType(
- typeID: typeID,
- nullable: resolvedNullable,
- trackRef: resolvedTrackRef,
- generics: []
- )
}
-}
-
-public struct TypeMetaFieldInfo: Equatable, Sendable {
- public var fieldID: Int16?
- public var fieldName: String
- public var fieldType: TypeMetaFieldType
- public init(fieldID: Int16?, fieldName: String, fieldType: TypeMetaFieldType) {
- self.fieldID = fieldID
- self.fieldName = fieldName
- self.fieldType = fieldType
- }
+ public struct FieldInfo: Equatable, Sendable {
+ public var fieldID: Int16?
+ public var fieldName: String
+ public var fieldType: FieldType
- fileprivate func write(_ buffer: ByteBuffer) throws {
- var header: UInt8 = 0
- if fieldType.trackRef {
- header |= 0b1
- }
- if fieldType.nullable {
- header |= 0b10
+ public init(fieldID: Int16?, fieldName: String, fieldType: FieldType) {
+ self.fieldID = fieldID
+ self.fieldName = fieldName
+ self.fieldType = fieldType
}
- if let fieldID {
- if fieldID < 0 {
- throw ForyError.encodingError("negative field id is invalid")
+ fileprivate func write(_ buffer: ByteBuffer) throws {
+ var header: UInt8 = 0
+ if fieldType.trackRef {
+ header |= 0b1
+ }
+ if fieldType.nullable {
+ header |= 0b10
+ }
+
+ if let fieldID {
+ if fieldID < 0 {
+ throw ForyError.encodingError("negative field id is invalid")
+ }
+ let size = Int(fieldID)
+ header |= UInt8(0b11 << 6)
+ if size >= fieldNameSizeThreshold {
+ header |= 0b0011_1100
+ buffer.writeUInt8(header)
+ buffer.writeVarUInt32(UInt32(size - fieldNameSizeThreshold))
+ } else {
+ header |= UInt8(size << 2)
+ buffer.writeUInt8(header)
+ }
+ fieldType.write(buffer, writeFlags: false)
+ return
}
- let size = Int(fieldID)
- header |= UInt8(0b11 << 6)
+
+ let snakeName = lowerCamelToLowerUnderscore(fieldName)
+ let encoded = try MetaStringEncoder.fieldName.encode(snakeName, allowedEncodings: fieldNameMetaStringEncodings)
+ guard let encodingIndex = fieldNameMetaStringEncodings.firstIndex(of: encoded.encoding) else {
+ throw ForyError.encodingError("unsupported field name encoding")
+ }
+
+ let size = encoded.bytes.count - 1
+ header |= UInt8(encodingIndex << 6)
if size >= fieldNameSizeThreshold {
header |= 0b0011_1100
buffer.writeUInt8(header)
@@ -185,77 +207,56 @@ public struct TypeMetaFieldInfo: Equatable, Sendable {
header |= UInt8(size << 2)
buffer.writeUInt8(header)
}
- fieldType.write(buffer, writeFlags: false)
- return
- }
-
- let snakeName = lowerCamelToLowerUnderscore(fieldName)
- let encoded = try MetaStringEncoder.fieldName.encode(snakeName, allowedEncodings: fieldNameMetaStringEncodings)
- guard let encodingIndex = fieldNameMetaStringEncodings.firstIndex(of: encoded.encoding) else {
- throw ForyError.encodingError("unsupported field name encoding")
- }
- let size = encoded.bytes.count - 1
- header |= UInt8(encodingIndex << 6)
- if size >= fieldNameSizeThreshold {
- header |= 0b0011_1100
- buffer.writeUInt8(header)
- buffer.writeVarUInt32(UInt32(size - fieldNameSizeThreshold))
- } else {
- header |= UInt8(size << 2)
- buffer.writeUInt8(header)
+ fieldType.write(buffer, writeFlags: false)
+ buffer.writeBytes(encoded.bytes)
}
- fieldType.write(buffer, writeFlags: false)
- buffer.writeBytes(encoded.bytes)
- }
+ fileprivate static func read(_ buffer: ByteBuffer) throws -> FieldInfo {
+ let header = try buffer.readUInt8()
+ let encodingFlags = Int((header >> 6) & 0b11)
+ var size = Int((header >> 2) & 0b1111)
+ if size == fieldNameSizeThreshold {
+ size += Int(try buffer.readVarUInt32())
+ }
+ size += 1
+
+ let nullable = (header & 0b10) != 0
+ let trackRef = (header & 0b1) != 0
+ let fieldType = try FieldType.read(
+ buffer,
+ readFlags: false,
+ nullable: nullable,
+ trackRef: trackRef
+ )
- fileprivate static func read(_ buffer: ByteBuffer) throws -> TypeMetaFieldInfo {
- let header = try buffer.readUInt8()
- let encodingFlags = Int((header >> 6) & 0b11)
- var size = Int((header >> 2) & 0b1111)
- if size == fieldNameSizeThreshold {
- size += Int(try buffer.readVarUInt32())
- }
- size += 1
-
- let nullable = (header & 0b10) != 0
- let trackRef = (header & 0b1) != 0
- let fieldType = try TypeMetaFieldType.read(
- buffer,
- readFlags: false,
- nullable: nullable,
- trackRef: trackRef
- )
+ if encodingFlags == 3 {
+ let fieldID = Int16(size - 1)
+ return FieldInfo(
+ fieldID: fieldID,
+ fieldName: "$tag\(fieldID)",
+ fieldType: fieldType
+ )
+ }
- if encodingFlags == 3 {
- let fieldID = Int16(size - 1)
- return TypeMetaFieldInfo(
- fieldID: fieldID,
- fieldName: "$tag\(fieldID)",
- fieldType: fieldType
- )
- }
+ guard encodingFlags < fieldNameMetaStringEncodings.count else {
+ throw ForyError.invalidData("invalid field name encoding id")
+ }
+ let nameBytes = try buffer.readBytes(count: size)
+ let name = try MetaStringDecoder.fieldName
+ .decode(bytes: nameBytes, encoding: fieldNameMetaStringEncodings[encodingFlags])
+ .value
- guard encodingFlags < fieldNameMetaStringEncodings.count else {
- throw ForyError.invalidData("invalid field name encoding id")
+ return FieldInfo(fieldID: nil, fieldName: name, fieldType: fieldType)
}
- let nameBytes = try buffer.readBytes(count: size)
- let name = try MetaStringDecoder.fieldName
- .decode(bytes: nameBytes, encoding: fieldNameMetaStringEncodings[encodingFlags])
- .value
-
- return TypeMetaFieldInfo(fieldID: nil, fieldName: name, fieldType: fieldType)
}
-}
-public struct TypeMeta: Equatable, Sendable {
public let typeID: UInt32?
public let userTypeID: UInt32?
public let namespace: MetaString
public let typeName: MetaString
public let registerByName: Bool
- public let fields: [TypeMetaFieldInfo]
+ public let fields: [FieldInfo]
public let hasFieldsMeta: Bool
public let compressed: Bool
public let headerHash: UInt64
@@ -266,7 +267,7 @@ public struct TypeMeta: Equatable, Sendable {
namespace: MetaString,
typeName: MetaString,
registerByName: Bool,
- fields: [TypeMetaFieldInfo],
+ fields: [FieldInfo],
hasFieldsMeta: Bool = true,
compressed: Bool = false,
headerHash: UInt64 = 0
@@ -295,6 +296,18 @@ public struct TypeMeta: Equatable, Sendable {
self.headerHash = headerHash
}
+ public static func == (lhs: TypeMeta, rhs: TypeMeta) -> Bool {
+ lhs.typeID == rhs.typeID &&
+ lhs.userTypeID == rhs.userTypeID &&
+ lhs.namespace == rhs.namespace &&
+ lhs.typeName == rhs.typeName &&
+ lhs.registerByName == rhs.registerByName &&
+ lhs.fields == rhs.fields &&
+ lhs.hasFieldsMeta == rhs.hasFieldsMeta &&
+ lhs.compressed == rhs.compressed &&
+ lhs.headerHash == rhs.headerHash
+ }
+
public func encode() throws -> [UInt8] {
if compressed {
throw ForyError.encodingError("compressed TypeMeta is not supported yet")
@@ -321,7 +334,7 @@ public struct TypeMeta: Equatable, Sendable {
buffer.writeVarUInt32(UInt32(body.count - Int(typeMetaSizeMask)))
}
buffer.writeBytes(body)
- return buffer.storage
+ return Array(buffer.storage.prefix(buffer.count))
}
public static func decode(_ bytes: [UInt8]) throws -> TypeMeta {
@@ -371,7 +384,7 @@ public struct TypeMeta: Equatable, Sendable {
typeName = MetaString.empty(specialChar1: "$", specialChar2: "_")
}
- var fieldInfos: [TypeMetaFieldInfo] = []
+ var fieldInfos: [FieldInfo] = []
if numFields > bodyReader.remaining {
throw ForyError.invalidData(
"type meta field count \(numFields) exceeds remaining bytes \(bodyReader.remaining)"
@@ -379,7 +392,7 @@ public struct TypeMeta: Equatable, Sendable {
}
fieldInfos.reserveCapacity(numFields)
for _ in 0..<numFields {
- fieldInfos.append(try TypeMetaFieldInfo.read(bodyReader))
+ fieldInfos.append(try FieldInfo.read(bodyReader))
}
if bodyReader.remaining != 0 {
@@ -430,7 +443,7 @@ public struct TypeMeta: Equatable, Sendable {
try field.write(buffer)
}
- return buffer.storage
+ return Array(buffer.storage.prefix(buffer.count))
}
private static func writeName(
@@ -485,6 +498,134 @@ public struct TypeMeta: Equatable, Sendable {
let bytes = try buffer.readBytes(count: length)
return try decoder.decode(bytes: bytes, encoding: encodings[encodingIndex])
}
+
+ func assigningFieldIDs(from localTypeMeta: TypeMeta) throws -> TypeMeta {
+ guard !fields.isEmpty else {
+ return self
+ }
+
+ let localFields = localTypeMeta.fields
+ guard !localFields.isEmpty else {
+ return self
+ }
+
+ var fieldIndexByName: [String: (Int, FieldInfo)] = [:]
+ var fieldIndexByID: [Int16: (Int, FieldInfo)] = [:]
+ fieldIndexByName.reserveCapacity(localFields.count)
+ fieldIndexByID.reserveCapacity(localFields.count)
+
+ for (index, localField) in localFields.enumerated() {
+ fieldIndexByName[toSnakeCase(localField.fieldName)] = (index, localField)
+ if let fieldID = localField.fieldID, fieldID >= 0 {
+ fieldIndexByID[fieldID] = (index, localField)
+ }
+ }
+
+ var resolvedFields = fields
+ var changed = false
+ var usedLocalFields = Array(repeating: false, count: localFields.count)
+
+ for index in resolvedFields.indices {
+ let field = resolvedFields[index]
+
+ var localMatch: (Int, FieldInfo)?
+ if let fieldID = field.fieldID, fieldID >= 0 {
+ localMatch = fieldIndexByID[fieldID]
+ }
+
+ if localMatch == nil {
+ if let candidate = fieldIndexByName[toSnakeCase(field.fieldName)],
+ Self.isCompatibleFieldType(field.fieldType, candidate.1.fieldType) {
+ localMatch = candidate
+ }
+ }
+
+ if localMatch == nil {
+ for localIndex in localFields.indices where !usedLocalFields[localIndex] {
+ if Self.isCompatibleFieldType(field.fieldType, localFields[localIndex].fieldType) {
+ localMatch = (localIndex, localFields[localIndex])
+ break
+ }
+ }
+ }
+
+ guard let (sortedIndex, _) = localMatch,
+ sortedIndex <= Int(Int16.max) else {
+ if field.fieldID != -1 {
+ resolvedFields[index].fieldID = -1
+ changed = true
+ }
+ continue
+ }
+
+ let resolvedFieldID = Int16(sortedIndex)
+ if field.fieldID != resolvedFieldID {
+ resolvedFields[index].fieldID = resolvedFieldID
+ changed = true
+ }
+ usedLocalFields[sortedIndex] = true
+ }
+
+ guard changed else {
+ return self
+ }
+
+ return try TypeMeta(
+ typeID: typeID,
+ userTypeID: userTypeID,
+ namespace: namespace,
+ typeName: typeName,
+ registerByName: registerByName,
+ fields: resolvedFields,
+ hasFieldsMeta: hasFieldsMeta,
+ compressed: compressed,
+ headerHash: headerHash
+ )
+ }
+
+ private static func isCompatibleFieldType(
+ _ remoteType: FieldType,
+ _ localType: FieldType
+ ) -> Bool {
+ if normalizeCompatibleTypeIDForComparison(remoteType.typeID) != normalizeCompatibleTypeIDForComparison(localType.typeID) {
+ return false
+ }
+ if remoteType.generics.count != localType.generics.count {
+ return false
+ }
+ for (remoteGeneric, localGeneric) in zip(remoteType.generics, localType.generics)
+ where !isCompatibleFieldType(remoteGeneric, localGeneric) {
+ return false
+ }
+ return true
+ }
+
+ private static func normalizeCompatibleTypeIDForComparison(_ typeID: UInt32) -> UInt32 {
+ switch typeID {
+ case TypeId.structType.rawValue,
+ TypeId.compatibleStruct.rawValue,
+ TypeId.namedStruct.rawValue,
+ TypeId.namedCompatibleStruct.rawValue,
+ TypeId.unknown.rawValue:
+ return TypeId.structType.rawValue
+ case TypeId.enumType.rawValue,
+ TypeId.namedEnum.rawValue:
+ return TypeId.enumType.rawValue
+ case TypeId.ext.rawValue,
+ TypeId.namedExt.rawValue:
+ return TypeId.ext.rawValue
+ case TypeId.binary.rawValue,
+ TypeId.int8Array.rawValue,
+ TypeId.uint8Array.rawValue:
+ return TypeId.binary.rawValue
+ case TypeId.union.rawValue,
+ TypeId.typedUnion.rawValue,
+ TypeId.namedUnion.rawValue:
+ return TypeId.union.rawValue
+ default:
+ return typeID
+ }
+ }
}
private func lowerCamelToLowerUnderscore(_ name: String) -> String {
@@ -513,3 +654,30 @@ private func lowerCamelToLowerUnderscore(_ name: String) -> String {
return result
}
+
+private func toSnakeCase(_ name: String) -> String {
+ if name.isEmpty {
+ return name
+ }
+
+ let chars = Array(name)
+ var result = String()
+ result.reserveCapacity(name.count + 4)
+
+ for (index, char) in chars.enumerated() {
+ if char.isUppercase {
+ if index > 0 {
+ let prevUpper = chars[index - 1].isUppercase
+ let nextUpperOrEnd = (index + 1 >= chars.count) || chars[index + 1].isUppercase
+ if !prevUpper || !nextUpperOrEnd {
+ result.append("_")
+ }
+ }
+ result.append(char.lowercased())
+ } else {
+ result.append(char)
+ }
+ }
+
+ return result
+}
diff --git a/swift/Sources/Fory/TypeResolver.swift b/swift/Sources/Fory/TypeResolver.swift
index 1726ba2bf..dc734180c 100644
--- a/swift/Sources/Fory/TypeResolver.swift
+++ b/swift/Sources/Fory/TypeResolver.swift
@@ -17,25 +17,326 @@
import Foundation
-public struct RegisteredTypeInfo: Equatable {
- public let userTypeID: UInt32?
- public let kind: TypeId
- public let registerByName: Bool
- public let namespace: MetaString?
- public let typeName: MetaString
-
- public init(
+@inline(__always)
+func normalizeRegisteredTypeID(_ typeID: TypeId) -> TypeId {
+ switch typeID {
+ case .namedEnum:
+ return .enumType
+ case .compatibleStruct, .namedCompatibleStruct, .namedStruct:
+ return .structType
+ case .namedExt:
+ return .ext
+ case .namedUnion, .union:
+ return .typedUnion
+ default:
+ return typeID
+ }
+}
+
+@inline(__always)
+func namedRegisteredTypeID(for baseTypeID: TypeId, compatible: Bool) -> TypeId {
+ switch baseTypeID {
+ case .structType:
+ return compatible ? .namedCompatibleStruct : .namedStruct
+ case .enumType:
+ return .namedEnum
+ case .ext:
+ return .namedExt
+ case .typedUnion:
+ return .namedUnion
+ default:
+ return baseTypeID
+ }
+}
+
+@inline(__always)
+func idRegisteredTypeID(for baseTypeID: TypeId, compatible: Bool) -> TypeId {
+ switch baseTypeID {
+ case .structType:
+ return compatible ? .compatibleStruct : .structType
+ default:
+ return baseTypeID
+ }
+}
+
+@inline(__always)
+func resolveRegisteredWireTypeID(
+ declaredTypeID: TypeId,
+ registerByName: Bool,
+ compatible: Bool
+) -> TypeId {
+ let baseTypeID = normalizeRegisteredTypeID(declaredTypeID)
+ if registerByName {
+ return namedRegisteredTypeID(for: baseTypeID, compatible: compatible)
+ }
+ return idRegisteredTypeID(for: baseTypeID, compatible: compatible)
+}
+
+@inline(__always)
+func isAllowedRegisteredWireTypeID(
+ _ wireTypeID: TypeId,
+ declaredTypeID: TypeId,
+ registerByName: Bool,
+ compatible: Bool
+) -> Bool {
+ let baseTypeID = normalizeRegisteredTypeID(declaredTypeID)
+ let expected = resolveRegisteredWireTypeID(
+ declaredTypeID: declaredTypeID,
+ registerByName: registerByName,
+ compatible: compatible
+ )
+ if wireTypeID == expected {
+ return true
+ }
+ if baseTypeID == .structType, compatible {
+ return wireTypeID == .compatibleStruct ||
+ wireTypeID == .namedCompatibleStruct ||
+ wireTypeID == .structType ||
+ wireTypeID == .namedStruct
+ }
+ if baseTypeID == .typedUnion {
+ return wireTypeID == .union || (registerByName && wireTypeID == .namedUnion)
+ }
+ return false
+}
+
+@inline(__always)
+func registeredWireTypeNeedsUserTypeID(_ wireTypeID: TypeId) -> Bool {
+ switch wireTypeID {
+ case .enumType, .structType, .ext, .typedUnion, .union:
+ return true
+ default:
+ return false
+ }
+}
+
+@inline(__always)
+private func encodedTypeDefHeader(_ bytes: [UInt8]) throws -> UInt64 {
+ guard bytes.count >= 8 else {
+ throw ForyError.invalidData("encoded compatible type metadata must include an 8-byte header")
+ }
+ let buffer = ByteBuffer(bytes: bytes)
+ return try buffer.readUInt64()
+}
+
+@inline(__always)
+private func encodedTypeDefHeaderHash(_ bytes: [UInt8]) throws -> UInt64 {
+ guard bytes.count >= 8 else {
+ throw ForyError.invalidData("encoded compatible type metadata must include an 8-byte header")
+ }
+ let buffer = ByteBuffer(bytes: bytes)
+ let header = try buffer.readUInt64()
+ return header >> 14
+}
+
+private func fieldNeedsTypeInfo(_ fieldType: TypeMeta.FieldType) -> Bool {
+ if let typeID = TypeId(rawValue: fieldType.typeID),
+ TypeId.needsTypeInfoForField(typeID) {
+ return true
+ }
+ return fieldType.generics.contains { fieldNeedsTypeInfo($0) }
+}
+
+private func encodedTypeDefHasUserTypeFields(_ fields: [TypeMeta.FieldInfo]) -> Bool {
+ fields.contains { fieldNeedsTypeInfo($0.fieldType) }
+}
+
+public final class TypeInfo: @unchecked Sendable {
+ static let uncached = TypeInfo(typeID: .unknown)
+
+ let swiftTypeID: ObjectIdentifier
+ let typeID: TypeId
+ let userTypeID: UInt32?
+ let registerByName: Bool
+ let namespace: MetaString
+ let typeName: MetaString
+ let typeMeta: TypeMeta?
+ public let compatibleTypeMeta: TypeMeta?
+ let typeDefBytes: [UInt8]?
+ let firstTypeDefBytes: [UInt8]?
+ let typeDefHeader: UInt64?
+ public let typeDefHeaderHash: UInt64?
+ public let typeDefHasUserTypeFields: Bool
+
+ private let reader: (ReadContext) throws -> Any
+ private let compatibleReader: (ReadContext, TypeInfo) throws -> Any
+ private let nativeWireTypeID: TypeId
+ private let compatibleWireTypeID: TypeId
+
+ init(
+ swiftTypeID: ObjectIdentifier,
+ typeID: TypeId,
userTypeID: UInt32?,
- kind: TypeId,
registerByName: Bool,
- namespace: MetaString?,
- typeName: MetaString
+ namespace: MetaString,
+ typeName: MetaString,
+ typeMeta: TypeMeta? = nil,
+ compatibleTypeMeta: TypeMeta? = nil,
+ typeDefBytes: [UInt8]? = nil,
+ firstTypeDefBytes: [UInt8]? = nil,
+ typeDefHeader: UInt64? = nil,
+ typeDefHeaderHash: UInt64? = nil,
+ typeDefHasUserTypeFields: Bool = true,
+ reader: @escaping (ReadContext) throws -> Any,
+ compatibleReader: @escaping (ReadContext, TypeInfo) throws -> Any
) {
+ self.swiftTypeID = swiftTypeID
+ self.typeID = typeID
self.userTypeID = userTypeID
- self.kind = kind
self.registerByName = registerByName
self.namespace = namespace
self.typeName = typeName
+ self.typeMeta = typeMeta
+ self.compatibleTypeMeta = compatibleTypeMeta ?? typeMeta
+ self.typeDefBytes = typeDefBytes
+ self.firstTypeDefBytes = firstTypeDefBytes
+ self.typeDefHeader = typeDefHeader
+ self.typeDefHeaderHash = typeDefHeaderHash
+ self.typeDefHasUserTypeFields = typeDefHasUserTypeFields
+ self.reader = reader
+ self.compatibleReader = compatibleReader
+ nativeWireTypeID = resolveRegisteredWireTypeID(
+ declaredTypeID: typeID,
+ registerByName: registerByName,
+ compatible: false
+ )
+ compatibleWireTypeID = resolveRegisteredWireTypeID(
+ declaredTypeID: typeID,
+ registerByName: registerByName,
+ compatible: true
+ )
+ }
+
+ convenience init(
+ swiftTypeID: ObjectIdentifier,
+ typeID: TypeId,
+ userTypeID: UInt32?,
+ registerByName: Bool,
+ namespace: MetaString,
+ typeName: MetaString,
+ fields: [TypeMeta.FieldInfo],
+ reader: @escaping (ReadContext) throws -> Any,
+ compatibleReader: @escaping (ReadContext, TypeInfo) throws -> Any
+ ) throws {
+ let compatibleWireTypeID = resolveRegisteredWireTypeID(
+ declaredTypeID: typeID,
+ registerByName: registerByName,
+ compatible: true
+ )
+ let typeMeta = try TypeMeta(
+ typeID: registerByName ? nil : compatibleWireTypeID.rawValue,
+ userTypeID: registerByName ? nil : userTypeID,
+ namespace: namespace,
+ typeName: typeName,
+ registerByName: registerByName,
+ fields: fields,
+ hasFieldsMeta: !fields.isEmpty
+ )
+ let typeDefBytes = try typeMeta.encode()
+ var firstTypeDefBytes = [UInt8]()
+ firstTypeDefBytes.reserveCapacity(typeDefBytes.count + 1)
+ firstTypeDefBytes.append(0)
+ firstTypeDefBytes.append(contentsOf: typeDefBytes)
+ let typeDefHeader = try encodedTypeDefHeader(typeDefBytes)
+ let typeDefHeaderHash = try encodedTypeDefHeaderHash(typeDefBytes)
+ let canonicalTypeMeta = try TypeMeta(
+ typeID: registerByName ? nil : compatibleWireTypeID.rawValue,
+ userTypeID: registerByName ? nil : userTypeID,
+ namespace: namespace,
+ typeName: typeName,
+ registerByName: registerByName,
+ fields: fields,
+ hasFieldsMeta: !fields.isEmpty,
+ headerHash: typeDefHeaderHash
+ )
+ self.init(
+ swiftTypeID: swiftTypeID,
+ typeID: typeID,
+ userTypeID: userTypeID,
+ registerByName: registerByName,
+ namespace: namespace,
+ typeName: typeName,
+ typeMeta: canonicalTypeMeta,
+ compatibleTypeMeta: canonicalTypeMeta,
+ typeDefBytes: typeDefBytes,
+ firstTypeDefBytes: firstTypeDefBytes,
+ typeDefHeader: typeDefHeader,
+ typeDefHeaderHash: typeDefHeaderHash,
+ typeDefHasUserTypeFields: encodedTypeDefHasUserTypeFields(fields),
+ reader: reader,
+ compatibleReader: compatibleReader
+ )
+ }
+
+ convenience init(typeID: TypeId) {
+ self.init(
+ swiftTypeID: ObjectIdentifier(TypeInfo.self),
+ typeID: typeID,
+ userTypeID: nil,
+ registerByName: false,
+ namespace: MetaString.empty(specialChar1: ".", specialChar2: "_"),
+ typeName: MetaString.empty(specialChar1: "$", specialChar2: "_"),
+ reader: { _ in
+ throw ForyError.invalidData("dynamic type \(typeID) uses runtime-only decode path")
+ },
+ compatibleReader: { _, _ in
+ throw ForyError.invalidData("dynamic compatible type \(typeID) uses runtime-only decode path")
+ }
+ )
+ }
+
+ convenience init(dynamic typeInfo: TypeInfo, compatibleTypeMeta: TypeMeta) {
+ self.init(
+ swiftTypeID: typeInfo.swiftTypeID,
+ typeID: typeInfo.typeID,
+ userTypeID: typeInfo.userTypeID,
+ registerByName: typeInfo.registerByName,
+ namespace: typeInfo.namespace,
+ typeName: typeInfo.typeName,
+ typeMeta: typeInfo.typeMeta,
+ compatibleTypeMeta: compatibleTypeMeta,
+ typeDefBytes: typeInfo.typeDefBytes,
+ firstTypeDefBytes: typeInfo.firstTypeDefBytes,
+ typeDefHeader: typeInfo.typeDefHeader,
+ typeDefHeaderHash: typeInfo.typeDefHeaderHash,
+ typeDefHasUserTypeFields: typeInfo.typeDefHasUserTypeFields,
+ reader: typeInfo.reader,
+ compatibleReader: typeInfo.compatibleReader
+ )
+ }
+
+ @inline(__always)
+ func matches(
+ typeID: TypeId,
+ userTypeID: UInt32?,
+ registerByName: Bool,
+ namespace: String,
+ typeName: String
+ ) -> Bool {
+ self.typeID == typeID &&
+ self.userTypeID == userTypeID &&
+ self.registerByName == registerByName &&
+ self.namespace.value == namespace &&
+ self.typeName.value == typeName
+ }
+
+ @inline(__always)
+ func wireTypeID(compatible: Bool) -> TypeId {
+ compatible ? compatibleWireTypeID : nativeWireTypeID
+ }
+
+ @inline(__always)
+ func read(_ context: ReadContext, typeInfo: TypeInfo? = nil) throws -> Any {
+ if let typeInfo {
+ return try compatibleReader(context, typeInfo)
+ }
+ if context.compatible {
+ return try compatibleReader(context, self)
+ }
+ if compatibleTypeMeta !== typeMeta {
+ return try compatibleReader(context, self)
+ }
+ return try reader(context)
}
}
@@ -44,28 +345,20 @@ private struct TypeNameKey: Hashable {
let typeName: String
}
-private enum DynamicRegistrationMode {
- case idOnly
- case nameOnly
- case mixed
-}
+final class TypeResolver {
+ private let trackRef: Bool
-private struct TypeReader {
- let swiftType: ObjectIdentifier
- let kind: TypeId
- let reader: (ReadContext) throws -> Any
- let compatibleReader: (ReadContext, TypeMeta) throws -> Any
-}
+ private var bySwiftType = UInt64Map<TypeInfo>(initialCapacity: 64)
+ private var byUserTypeID = UInt64Map<TypeInfo>(initialCapacity: 64)
+ private var byTypeName: [TypeNameKey: TypeInfo] = [:]
+ private var builtinTypeInfoByID: [TypeInfo?] = []
+ private var typeInfoByHeader = UInt64Map<TypeInfo>(initialCapacity: 64)
-public final class TypeResolver {
- private var bySwiftType: [ObjectIdentifier: RegisteredTypeInfo] = [:]
- private var byUserTypeID: [UInt32: TypeReader] = [:]
- private var byTypeName: [TypeNameKey: TypeReader] = [:]
- private var registrationModeByKind: [TypeId: DynamicRegistrationMode] = [:]
-
- public init() {}
+ init(trackRef: Bool = false) {
+ self.trackRef = trackRef
+ }
- public func register<T: Serializer>(_ type: T.Type, id: UInt32) {
+ func register<T: Serializer>(_ type: T.Type, id: UInt32) {
do {
try registerByID(type, id: id)
} catch {
@@ -74,34 +367,40 @@ public final class TypeResolver {
}
private func registerByID<T: Serializer>(_ type: T.Type, id: UInt32) throws {
- let key = ObjectIdentifier(type)
- try validateIDRegistration(key: key, type: type, id: id)
- let info = RegisteredTypeInfo(
+ let swiftTypeID = ObjectIdentifier(type)
+ try validateIDRegistration(key: swiftTypeID, type: type, id: id)
+
+ let typeInfo = try TypeInfo(
+ swiftTypeID: swiftTypeID,
+ typeID: T.staticTypeId,
userTypeID: id,
- kind: T.staticTypeId,
registerByName: false,
- namespace: nil,
- typeName: MetaString.empty(specialChar1: "$", specialChar2: "_")
- )
- if bySwiftType[key] == info {
- return
- }
- bySwiftType[key] = info
- markRegistrationMode(kind: info.kind, registerByName: false)
- byUserTypeID[id] = TypeReader(
- swiftType: key,
- kind: T.staticTypeId,
+ namespace: MetaString.empty(specialChar1: ".", specialChar2: "_"),
+ typeName: MetaString.empty(specialChar1: "$", specialChar2: "_"),
+ fields: T.foryFieldsInfo(trackRef: trackRef),
reader: { context in
try T.foryRead(context, refMode: .none, readTypeInfo: false)
},
- compatibleReader: { context, typeMeta in
- context.pushCompatibleTypeMeta(for: T.self, typeMeta)
- return try T.foryRead(context, refMode: .none, readTypeInfo: false)
+ compatibleReader: { context, remoteTypeInfo in
+ try T.foryReadCompatibleData(context, remoteTypeInfo: remoteTypeInfo)
}
)
+
+ if let existing = bySwiftType.value(for: UInt64(UInt(bitPattern: swiftTypeID))),
+ existing.matches(
+ typeID: T.staticTypeId,
+ userTypeID: id,
+ registerByName: false,
+ namespace: "",
+ typeName: ""
+ ) {
+ return
+ }
+
+ try store(typeInfo, for: swiftTypeID, userTypeID: id)
}
- public func register<T: Serializer>(_ type: T.Type, namespace: String, typeName: String) throws {
+ func register<T: Serializer>(_ type: T.Type, namespace: String, typeName: String) throws {
let namespaceMeta = try MetaStringEncoder.namespace.encode(
namespace,
allowedEncodings: namespaceMetaStringEncodings
@@ -110,39 +409,45 @@ public final class TypeResolver {
typeName,
allowedEncodings: typeNameMetaStringEncodings
)
- let key = ObjectIdentifier(type)
+ let swiftTypeID = ObjectIdentifier(type)
try validateNameRegistration(
- key: key,
+ key: swiftTypeID,
type: type,
namespace: namespace,
typeName: typeName
)
- let info = RegisteredTypeInfo(
+
+ let typeInfo = try TypeInfo(
+ swiftTypeID: swiftTypeID,
+ typeID: T.staticTypeId,
userTypeID: nil,
- kind: T.staticTypeId,
registerByName: true,
namespace: namespaceMeta,
- typeName: typeNameMeta
- )
- if bySwiftType[key] == info {
- return
- }
- bySwiftType[key] = info
- markRegistrationMode(kind: info.kind, registerByName: true)
- byTypeName[TypeNameKey(namespace: namespace, typeName: typeName)] = TypeReader(
- swiftType: key,
- kind: T.staticTypeId,
+ typeName: typeNameMeta,
+ fields: T.foryFieldsInfo(trackRef: trackRef),
reader: { context in
try T.foryRead(context, refMode: .none, readTypeInfo: false)
},
- compatibleReader: { context, typeMeta in
- context.pushCompatibleTypeMeta(for: T.self, typeMeta)
- return try T.foryRead(context, refMode: .none, readTypeInfo: false)
+ compatibleReader: { context, remoteTypeInfo in
+ try T.foryReadCompatibleData(context, remoteTypeInfo: remoteTypeInfo)
}
)
+
+ if let existing = bySwiftType.value(for: UInt64(UInt(bitPattern: swiftTypeID))),
+ existing.matches(
+ typeID: T.staticTypeId,
+ userTypeID: nil,
+ registerByName: true,
+ namespace: namespace,
+ typeName: typeName
+ ) {
+ return
+ }
+
+ try store(typeInfo, for: swiftTypeID, typeNameKey: TypeNameKey(namespace: namespace, typeName: typeName))
}
- public func register<T: Serializer>(_ type: T.Type, name: String) throws {
+ func register<T: Serializer>(_ type: T.Type, name: String) throws {
let parts = name.components(separatedBy: ".")
if parts.count <= 1 {
try register(type, namespace: "", typeName: name)
@@ -154,301 +459,93 @@ public final class TypeResolver {
try register(type, namespace: resolvedNamespace, typeName: resolvedTypeName)
}
- public func registeredTypeInfo<T: Serializer>(for type: T.Type) -> RegisteredTypeInfo? {
- bySwiftType[ObjectIdentifier(type)]
- }
-
- public func registeredTypeInfo(for type: any Serializer.Type) -> RegisteredTypeInfo? {
- bySwiftType[ObjectIdentifier(type)]
- }
-
- public func requireRegisteredTypeInfo<T: Serializer>(for type: T.Type) throws -> RegisteredTypeInfo {
- if let info = bySwiftType[ObjectIdentifier(type)] {
+ func requireTypeInfo<T: Serializer>(for type: T.Type) throws -> TypeInfo {
+ if let info = bySwiftType.value(for: UInt64(UInt(bitPattern: ObjectIdentifier(type)))) {
return info
}
throw ForyError.typeNotRegistered("\(type) is not registered")
}
- public func readByUserTypeID(_ userTypeID: UInt32, context: ReadContext) throws -> Any {
- try readByUserTypeID(userTypeID, context: context, compatibleTypeMeta: nil)
+ @inline(__always)
+ func getTypeInfo(forHeader header: UInt64) -> TypeInfo? {
+ typeInfoByHeader.value(for: header)
}
- public func readByUserTypeID(
- _ userTypeID: UInt32,
- context: ReadContext,
- compatibleTypeMeta: TypeMeta?
- ) throws -> Any {
- guard let entry = byUserTypeID[userTypeID] else {
- throw ForyError.typeNotRegistered("user_type_id=\(userTypeID)")
- }
- if let compatibleTypeMeta {
- return try entry.compatibleReader(context, compatibleTypeMeta)
+ @inline(__always)
+ func cacheTypeInfo(_ typeMeta: TypeMeta, forHeader header: UInt64) throws -> TypeInfo {
+ if let cached = typeInfoByHeader.value(for: header) {
+ return cached
}
- return try entry.reader(context)
- }
-
- public func readByTypeName(namespace: String, typeName: String, context: ReadContext) throws -> Any {
- try readByTypeName(namespace: namespace, typeName: typeName, context: context, compatibleTypeMeta: nil)
- }
-
- public func readByTypeName(
- namespace: String,
- typeName: String,
- context: ReadContext,
- compatibleTypeMeta: TypeMeta?
- ) throws -> Any {
- guard let entry = byTypeName[TypeNameKey(namespace: namespace, typeName: typeName)] else {
- throw ForyError.typeNotRegistered("namespace=\(namespace), type=\(typeName)")
+ let localTypeInfo = try requireTypeInfo(for: typeMeta)
+ if header == localTypeInfo.typeDefHeader {
+ typeInfoByHeader.set(localTypeInfo, for: header)
+ return localTypeInfo
}
- if let compatibleTypeMeta {
- return try entry.compatibleReader(context, compatibleTypeMeta)
+ let canonicalTypeMeta: TypeMeta
+ if let localTypeMeta = localTypeInfo.typeMeta,
+ let remapped = try? typeMeta.assigningFieldIDs(from: localTypeMeta) {
+ canonicalTypeMeta = remapped
+ } else {
+ canonicalTypeMeta = typeMeta
}
- return try entry.reader(context)
+ let typeInfo = TypeInfo(dynamic: localTypeInfo, compatibleTypeMeta: canonicalTypeMeta)
+ typeInfoByHeader.set(typeInfo, for: header)
+ return typeInfo
}
- public func readDynamicTypeInfo(context: ReadContext) throws -> DynamicTypeInfo {
- let rawTypeID = try context.buffer.readVarUInt32()
- guard let wireTypeID = TypeId(rawValue: rawTypeID) else {
- throw ForyError.invalidData("unknown dynamic type id \(rawTypeID)")
+ private func store(
+ _ typeInfo: TypeInfo,
+ for swiftTypeID: ObjectIdentifier,
+ userTypeID: UInt32? = nil,
+ typeNameKey: TypeNameKey? = nil
+ ) throws {
+ bySwiftType.set(typeInfo, for: UInt64(UInt(bitPattern: swiftTypeID)))
+ if let userTypeID {
+ byUserTypeID.set(typeInfo, for: UInt64(userTypeID))
}
-
- switch wireTypeID {
- case .compatibleStruct, .namedCompatibleStruct:
- let typeMeta = try context.readCompatibleTypeMeta()
- if typeMeta.registerByName {
- return DynamicTypeInfo(
- wireTypeID: wireTypeID,
- userTypeID: nil,
- namespace: typeMeta.namespace,
- typeName: typeMeta.typeName,
+ if let typeNameKey {
+ byTypeName[typeNameKey] = typeInfo
+ }
+ if let typeMeta = typeInfo.typeMeta,
+ let typeDefHeader = typeInfo.typeDefHeader {
+ typeInfoByHeader.set(
+ TypeInfo(
+ dynamic: typeInfo,
compatibleTypeMeta: typeMeta
- )
- }
- return DynamicTypeInfo(
- wireTypeID: wireTypeID,
- userTypeID: typeMeta.userTypeID,
- namespace: nil,
- typeName: nil,
- compatibleTypeMeta: typeMeta
- )
- case .namedStruct, .namedEnum, .namedExt, .namedUnion:
- let namespace = try Self.readMetaString(
- buffer: context.buffer,
- decoder: .namespace,
- encodings: namespaceMetaStringEncodings
- )
- let typeName = try Self.readMetaString(
- buffer: context.buffer,
- decoder: .typeName,
- encodings: typeNameMetaStringEncodings
- )
- return DynamicTypeInfo(
- wireTypeID: wireTypeID,
- userTypeID: nil,
- namespace: namespace,
- typeName: typeName,
- compatibleTypeMeta: nil
- )
- case .structType, .enumType, .ext, .typedUnion, .union:
- let registrationKind: TypeId = wireTypeID == .union ? .typedUnion : wireTypeID
- switch try dynamicRegistrationMode(for: registrationKind) {
- case .idOnly:
- return DynamicTypeInfo(
- wireTypeID: wireTypeID,
- userTypeID: try context.buffer.readVarUInt32(),
- namespace: nil,
- typeName: nil,
- compatibleTypeMeta: nil
- )
- case .nameOnly:
- let namespace = try Self.readMetaString(
- buffer: context.buffer,
- decoder: .namespace,
- encodings: namespaceMetaStringEncodings
- )
- let typeName = try Self.readMetaString(
- buffer: context.buffer,
- decoder: .typeName,
- encodings: typeNameMetaStringEncodings
- )
- return DynamicTypeInfo(
- wireTypeID: wireTypeID,
- userTypeID: nil,
- namespace: namespace,
- typeName: typeName,
- compatibleTypeMeta: nil
- )
- case .mixed:
- // Wire ids for user kinds are explicit: plain ids always carry user_type_id.
- return DynamicTypeInfo(
- wireTypeID: wireTypeID,
- userTypeID: try context.buffer.readVarUInt32(),
- namespace: nil,
- typeName: nil,
- compatibleTypeMeta: nil
- )
- }
- default:
- return DynamicTypeInfo(
- wireTypeID: wireTypeID,
- userTypeID: nil,
- namespace: nil,
- typeName: nil,
- compatibleTypeMeta: nil
+ ),
+ for: typeDefHeader
)
}
}
- public func readDynamicValue(typeInfo: DynamicTypeInfo, context: ReadContext) throws -> Any {
- try context.enterDynamicAnyDepth()
- defer { context.leaveDynamicAnyDepth() }
-
- let value: Any
- switch typeInfo.wireTypeID {
- case .bool:
- value = try Bool.foryRead(context, refMode: .none, readTypeInfo: false)
- case .int8:
- value = try Int8.foryRead(context, refMode: .none, readTypeInfo: false)
- case .int16:
- value = try Int16.foryRead(context, refMode: .none, readTypeInfo: false)
- case .int32:
- value = try ForyInt32Fixed.foryRead(context, refMode: .none, readTypeInfo: false)
- case .varint32:
- value = try Int32.foryRead(context, refMode: .none, readTypeInfo: false)
- case .int64:
- value = try ForyInt64Fixed.foryRead(context, refMode: .none, readTypeInfo: false)
- case .varint64:
- value = try Int64.foryRead(context, refMode: .none, readTypeInfo: false)
- case .taggedInt64:
- value = try ForyInt64Tagged.foryRead(context, refMode: .none, readTypeInfo: false)
- case .uint8:
- value = try UInt8.foryRead(context, refMode: .none, readTypeInfo: false)
- case .uint16:
- value = try UInt16.foryRead(context, refMode: .none, readTypeInfo: false)
- case .uint32:
- value = try ForyUInt32Fixed.foryRead(context, refMode: .none, readTypeInfo: false)
- case .varUInt32:
- value = try UInt32.foryRead(context, refMode: .none, readTypeInfo: false)
- case .uint64:
- value = try ForyUInt64Fixed.foryRead(context, refMode: .none, readTypeInfo: false)
- case .varUInt64:
- value = try UInt64.foryRead(context, refMode: .none, readTypeInfo: false)
- case .taggedUInt64:
- value = try ForyUInt64Tagged.foryRead(context, refMode: .none, readTypeInfo: false)
- case .float16:
- value = try Float16.foryRead(context, refMode: .none, readTypeInfo: false)
- case .bfloat16:
- value = try BFloat16.foryRead(context, refMode: .none, readTypeInfo: false)
- case .float32:
- value = try Float.foryRead(context, refMode: .none, readTypeInfo: false)
- case .float64:
- value = try Double.foryRead(context, refMode: .none, readTypeInfo: false)
- case .string:
- value = try String.foryRead(context, refMode: .none, readTypeInfo: false)
- case .duration:
- value = try Duration.foryRead(context, refMode: .none, readTypeInfo: false)
- case .timestamp:
- value = try Date.foryRead(context, refMode: .none, readTypeInfo: false)
- case .date:
- value = try ForyDate.foryRead(context, refMode: .none, readTypeInfo: false)
- case .binary, .uint8Array:
- value = try Data.foryRead(context, refMode: .none, readTypeInfo: false)
- case .boolArray:
- value = try [Bool].foryRead(context, refMode: .none, readTypeInfo: false)
- case .int8Array:
- value = try [Int8].foryRead(context, refMode: .none, readTypeInfo: false)
- case .int16Array:
- value = try [Int16].foryRead(context, refMode: .none, readTypeInfo: false)
- case .int32Array:
- value = try [Int32].foryRead(context, refMode: .none, readTypeInfo: false)
- case .int64Array:
- value = try [Int64].foryRead(context, refMode: .none, readTypeInfo: false)
- case .uint16Array:
- value = try [UInt16].foryRead(context, refMode: .none, readTypeInfo: false)
- case .uint32Array:
- value = try [UInt32].foryRead(context, refMode: .none, readTypeInfo: false)
- case .uint64Array:
- value = try [UInt64].foryRead(context, refMode: .none, readTypeInfo: false)
- case .float16Array:
- value = try [Float16].foryRead(context, refMode: .none, readTypeInfo: false)
- case .bfloat16Array:
- value = try [BFloat16].foryRead(context, refMode: .none, readTypeInfo: false)
- case .float32Array:
- value = try [Float].foryRead(context, refMode: .none, readTypeInfo: false)
- case .float64Array:
- value = try [Double].foryRead(context, refMode: .none, readTypeInfo: false)
- case .array, .list:
- value = try context.readAnyList(refMode: .none) ?? []
- case .set:
- value = try Set<AnyHashable>.foryRead(context, refMode: .none, readTypeInfo: false)
- case .map:
- value = try readDynamicAnyMapValue(context: context)
- case .structType, .enumType, .ext, .typedUnion, .union:
- if let userTypeID = typeInfo.userTypeID {
- value = try readByUserTypeID(userTypeID, context: context)
- } else if let namespace = typeInfo.namespace, let typeName = typeInfo.typeName {
- value = try readByTypeName(
- namespace: namespace.value,
- typeName: typeName.value,
- context: context
- )
- } else {
- throw ForyError.invalidData("missing dynamic registration info for \(typeInfo.wireTypeID)")
- }
- case .namedStruct, .namedEnum, .namedExt, .namedUnion:
- guard let namespace = typeInfo.namespace, let typeName = typeInfo.typeName else {
- throw ForyError.invalidData("missing dynamic type name for \(typeInfo.wireTypeID)")
- }
- value = try readByTypeName(
- namespace: namespace.value,
- typeName: typeName.value,
- context: context
- )
- case .compatibleStruct, .namedCompatibleStruct:
- guard let compatibleTypeMeta = typeInfo.compatibleTypeMeta else {
- throw ForyError.invalidData("missing compatible type meta for \(typeInfo.wireTypeID)")
- }
- if compatibleTypeMeta.registerByName {
- value = try readByTypeName(
- namespace: compatibleTypeMeta.namespace.value,
- typeName: compatibleTypeMeta.typeName.value,
- context: context,
- compatibleTypeMeta: compatibleTypeMeta
- )
- } else {
- guard let userTypeID = compatibleTypeMeta.userTypeID else {
- throw ForyError.invalidData("missing user type id in compatible dynamic type meta")
- }
- value = try readByUserTypeID(
- userTypeID,
- context: context,
- compatibleTypeMeta: compatibleTypeMeta
- )
- }
- case .none:
- value = ForyAnyNullValue()
- default:
- throw ForyError.invalidData("unsupported dynamic type id \(typeInfo.wireTypeID)")
+ @inline(__always)
+ func builtinTypeInfo(for typeID: TypeId) -> TypeInfo {
+ let index = Int(typeID.rawValue)
+ if index < builtinTypeInfoByID.count, let cached = builtinTypeInfoByID[index] {
+ return cached
}
- return value
+ let info = TypeInfo(typeID: typeID)
+ if index >= builtinTypeInfoByID.count {
+ builtinTypeInfoByID.append(contentsOf: repeatElement(nil, count: index - builtinTypeInfoByID.count + 1))
+ }
+ builtinTypeInfoByID[index] = info
+ return info
}
- private func markRegistrationMode(kind: TypeId, registerByName: Bool) {
- let mode: DynamicRegistrationMode = registerByName ? .nameOnly : .idOnly
- guard let existing = registrationModeByKind[kind] else {
- registrationModeByKind[kind] = mode
- return
- }
- if existing != mode {
- registrationModeByKind[kind] = .mixed
+ @inline(__always)
+ func requireTypeInfo(userTypeID: UInt32) throws -> TypeInfo {
+ guard let typeInfo = byUserTypeID.value(for: UInt64(userTypeID)) else {
+ throw ForyError.typeNotRegistered("user_type_id=\(userTypeID)")
}
+ return typeInfo
}
- private func dynamicRegistrationMode(for kind: TypeId) throws -> DynamicRegistrationMode {
- guard let mode = registrationModeByKind[kind] else {
- throw ForyError.typeNotRegistered("no dynamic registration mode for kind \(kind)")
+ @inline(__always)
+ func requireTypeInfo(namespace: String, typeName: String) throws -> TypeInfo {
+ guard let typeInfo = byTypeName[TypeNameKey(namespace: namespace, typeName: typeName)] else {
+ throw ForyError.typeNotRegistered("namespace=\(namespace), type=\(typeName)")
}
- return mode
+ return typeInfo
}
private func validateIDRegistration<T: Serializer>(
@@ -456,13 +553,14 @@ public final class TypeResolver {
type: T.Type,
id: UInt32
) throws {
- if let existing = bySwiftType[key] {
+ let swiftKey = UInt64(UInt(bitPattern: key))
+ if let existing = bySwiftType.value(for: swiftKey) {
if existing.registerByName {
throw ForyError.invalidData(
"\(type) was already registered by name, cannot re-register by id"
)
}
- if existing.kind != T.staticTypeId || existing.userTypeID != id {
+ if existing.typeID != T.staticTypeId || existing.userTypeID != id {
let existingID = existing.userTypeID.map { String($0) } ?? "nil"
throw ForyError.invalidData(
"\(type) registration conflict: existing id=\(existingID), new id=\(id)"
@@ -470,7 +568,7 @@ public final class TypeResolver {
}
}
- if let existing = byUserTypeID[id], existing.swiftType != key {
+ if let existing = byUserTypeID.value(for: UInt64(id)), existing.swiftTypeID != key {
throw ForyError.invalidData("user type id \(id) is already registered by another type")
}
}
@@ -481,18 +579,18 @@ public final class TypeResolver {
namespace: String,
typeName: String
) throws {
- if let existing = bySwiftType[key] {
+ if let existing = bySwiftType.value(for: UInt64(UInt(bitPattern: key))) {
if !existing.registerByName {
throw ForyError.invalidData(
"\(type) was already registered by id, cannot re-register by name"
)
}
- if existing.kind != T.staticTypeId ||
- existing.namespace?.value != namespace ||
+ if existing.typeID != T.staticTypeId ||
+ existing.namespace.value != namespace ||
existing.typeName.value != typeName {
throw ForyError.invalidData(
"""
- \(type) registration conflict: existing name=\(existing.namespace?.value ?? "")::\(existing.typeName.value), \
+ \(type) registration conflict: existing name=\(existing.namespace.value)::\(existing.typeName.value), \
new name=\(namespace)::\(typeName)
"""
)
@@ -500,27 +598,28 @@ public final class TypeResolver {
}
let nameKey = TypeNameKey(namespace: namespace, typeName: typeName)
- if let existing = byTypeName[nameKey], existing.swiftType != key {
+ if let existing = byTypeName[nameKey], existing.swiftTypeID != key {
throw ForyError.invalidData("type name \(namespace)::\(typeName) is already registered by another type")
}
}
- private static func readMetaString(
- buffer: ByteBuffer,
- decoder: MetaStringDecoder,
- encodings: [MetaStringEncoding]
- ) throws -> MetaString {
- let header = try buffer.readUInt8()
- let encodingIndex = Int(header & 0b11)
- guard encodingIndex < encodings.count else {
- throw ForyError.invalidData("invalid meta string encoding index")
+ @inline(__always)
+ private func requireTypeInfo(for typeMeta: TypeMeta) throws -> TypeInfo {
+ if typeMeta.registerByName {
+ guard let typeInfo = byTypeName[TypeNameKey(namespace: typeMeta.namespace.value, typeName: typeMeta.typeName.value)] else {
+ throw ForyError.typeNotRegistered(
+ "namespace=\(typeMeta.namespace.value), type=\(typeMeta.typeName.value)"
+ )
+ }
+ return typeInfo
}
-
- var length = Int(header >> 2)
- if length >= 0b11_1111 {
- length = 0b11_1111 + Int(try buffer.readVarUInt32())
+ if let userTypeID = typeMeta.userTypeID {
+ guard let typeInfo = byUserTypeID.value(for: UInt64(userTypeID)) else {
+ throw ForyError.typeNotRegistered("user_type_id=\(userTypeID)")
+ }
+ return typeInfo
}
- let bytes = try buffer.readBytes(count: length)
- return try decoder.decode(bytes: bytes, encoding: encodings[encodingIndex])
+ throw ForyError.invalidData("missing user type id in compatible dynamic type meta")
}
+
}
diff --git a/swift/Sources/Fory/UnsafeUtil.swift b/swift/Sources/Fory/UnsafeUtil.swift
new file mode 100644
index 000000000..ce5d15054
--- /dev/null
+++ b/swift/Sources/Fory/UnsafeUtil.swift
@@ -0,0 +1,1383 @@
+// 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.
+
+import Foundation
+
+// swiftlint:disable type_body_length
+public enum UnsafeUtil {
+ @inlinable
+ @inline(__always)
+ public static func writeBool(
+ _ value: Bool,
+ to base: UnsafeMutablePointer<UInt8>,
+ index: Int
+ ) -> Int {
+ base[index] = value ? 1 : 0
+ return index + 1
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func writeInt8(
+ _ value: Int8,
+ to base: UnsafeMutablePointer<UInt8>,
+ index: Int
+ ) -> Int {
+ base[index] = UInt8(bitPattern: value)
+ return index + 1
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func writeUInt8(
+ _ value: UInt8,
+ to base: UnsafeMutablePointer<UInt8>,
+ index: Int
+ ) -> Int {
+ base[index] = value
+ return index + 1
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func writeInt16(
+ _ value: Int16,
+ to base: UnsafeMutablePointer<UInt8>,
+ index: Int
+ ) -> Int {
+ writeUInt16(UInt16(bitPattern: value), to: base, index: index)
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func writeUInt16(
+ _ value: UInt16,
+ to base: UnsafeMutablePointer<UInt8>,
+ index: Int
+ ) -> Int {
+ let little = value.littleEndian
+ base[index] = UInt8(truncatingIfNeeded: little)
+ base[index + 1] = UInt8(truncatingIfNeeded: little >> 8)
+ return index + 2
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func writeInt32(
+ _ value: Int32,
+ to base: UnsafeMutablePointer<UInt8>,
+ index: Int
+ ) -> Int {
+ let zigzag = UInt32(bitPattern: (value << 1) ^ (value >> 31))
+ return writeVarUInt32(zigzag, to: base, index: index)
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func writeInt64(
+ _ value: Int64,
+ to base: UnsafeMutablePointer<UInt8>,
+ index: Int
+ ) -> Int {
+ let zigzag = UInt64(bitPattern: (value << 1) ^ (value >> 63))
+ return writeVarUInt64(zigzag, to: base, index: index)
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func writeInt(
+ _ value: Int,
+ to base: UnsafeMutablePointer<UInt8>,
+ index: Int
+ ) -> Int {
+ writeInt64(Int64(value), to: base, index: index)
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func writeUInt32(
+ _ value: UInt32,
+ to base: UnsafeMutablePointer<UInt8>,
+ index: Int
+ ) -> Int {
+ writeVarUInt32(value, to: base, index: index)
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func writeUInt64(
+ _ value: UInt64,
+ to base: UnsafeMutablePointer<UInt8>,
+ index: Int
+ ) -> Int {
+ writeVarUInt64(value, to: base, index: index)
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func writeUInt(
+ _ value: UInt,
+ to base: UnsafeMutablePointer<UInt8>,
+ index: Int
+ ) -> Int {
+ writeVarUInt64(UInt64(value), to: base, index: index)
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func writeFloat32(
+ _ value: Float,
+ to base: UnsafeMutablePointer<UInt8>,
+ index: Int
+ ) -> Int {
+ writeFixedUInt32(value.bitPattern, to: base, index: index)
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func writeFloat64(
+ _ value: Double,
+ to base: UnsafeMutablePointer<UInt8>,
+ index: Int
+ ) -> Int {
+ writeFixedUInt64(value.bitPattern, to: base, index: index)
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func varUInt32Size(_ value: UInt32) -> Int {
+ if value < 0x80 {
+ return 1
+ }
+ if value < 0x4000 {
+ return 2
+ }
+ if value < 0x20_0000 {
+ return 3
+ }
+ if value < 0x1000_0000 {
+ return 4
+ }
+ return 5
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func varInt32Size(_ value: Int32) -> Int {
+ let zigzag = UInt32(bitPattern: (value << 1) ^ (value >> 31))
+ return varUInt32Size(zigzag)
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func varUInt64Size(_ value: UInt64) -> Int {
+ if value < 0x80 {
+ return 1
+ }
+ if value < 0x4000 {
+ return 2
+ }
+ if value < 0x20_0000 {
+ return 3
+ }
+ if value < 0x1000_0000 {
+ return 4
+ }
+ if value < 0x8_0000_0000 {
+ return 5
+ }
+ if value < 0x400_0000_0000 {
+ return 6
+ }
+ if value < 0x2_0000_0000_0000 {
+ return 7
+ }
+ if value < 0x100_0000_0000_0000 {
+ return 8
+ }
+ return 9
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func varInt64Size(_ value: Int64) -> Int {
+ let zigzag = UInt64(bitPattern: (value << 1) ^ (value >> 63))
+ return varUInt64Size(zigzag)
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func varIntSize(_ value: Int) -> Int {
+ varInt64Size(Int64(value))
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func varUIntSize(_ value: UInt) -> Int {
+ varUInt64Size(UInt64(value))
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func writeVarUInt32(
+ _ value: UInt32,
+ to base: UnsafeMutablePointer<UInt8>,
+ index: Int
+ ) -> Int {
+ if value < 0x80 {
+ base[index] = UInt8(value)
+ return index + 1
+ }
+ if value < 0x4000 {
+ base[index] = (UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80
+ base[index + 1] = UInt8(truncatingIfNeeded: value >> 7)
+ return index + 2
+ }
+ if value < 0x20_0000 {
+ base[index] = (UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80
+ base[index + 1] = (UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80
+ base[index + 2] = UInt8(truncatingIfNeeded: value >> 14)
+ return index + 3
+ }
+ if value < 0x1000_0000 {
+ base[index] = (UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80
+ base[index + 1] = (UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80
+ base[index + 2] = (UInt8(truncatingIfNeeded: value >> 14) & 0x7F) | 0x80
+ base[index + 3] = UInt8(truncatingIfNeeded: value >> 21)
+ return index + 4
+ }
+ base[index] = (UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80
+ base[index + 1] = (UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80
+ base[index + 2] = (UInt8(truncatingIfNeeded: value >> 14) & 0x7F) | 0x80
+ base[index + 3] = (UInt8(truncatingIfNeeded: value >> 21) & 0x7F) | 0x80
+ base[index + 4] = UInt8(truncatingIfNeeded: value >> 28)
+ return index + 5
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func writeVarUInt64(
+ _ value: UInt64,
+ to base: UnsafeMutablePointer<UInt8>,
+ index: Int
+ ) -> Int {
+ if value < 0x80 {
+ base[index] = UInt8(value)
+ return index + 1
+ }
+ if value < 0x4000 {
+ base[index] = (UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80
+ base[index + 1] = UInt8(truncatingIfNeeded: value >> 7)
+ return index + 2
+ }
+ if value < 0x20_0000 {
+ base[index] = (UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80
+ base[index + 1] = (UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80
+ base[index + 2] = UInt8(truncatingIfNeeded: value >> 14)
+ return index + 3
+ }
+ if value < 0x1000_0000 {
+ base[index] = (UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80
+ base[index + 1] = (UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80
+ base[index + 2] = (UInt8(truncatingIfNeeded: value >> 14) & 0x7F) | 0x80
+ base[index + 3] = UInt8(truncatingIfNeeded: value >> 21)
+ return index + 4
+ }
+ if value < 0x8_0000_0000 {
+ base[index] = (UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80
+ base[index + 1] = (UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80
+ base[index + 2] = (UInt8(truncatingIfNeeded: value >> 14) & 0x7F) | 0x80
+ base[index + 3] = (UInt8(truncatingIfNeeded: value >> 21) & 0x7F) | 0x80
+ base[index + 4] = UInt8(truncatingIfNeeded: value >> 28)
+ return index + 5
+ }
+ if value < 0x400_0000_0000 {
+ base[index] = (UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80
+ base[index + 1] = (UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80
+ base[index + 2] = (UInt8(truncatingIfNeeded: value >> 14) & 0x7F) | 0x80
+ base[index + 3] = (UInt8(truncatingIfNeeded: value >> 21) & 0x7F) | 0x80
+ base[index + 4] = (UInt8(truncatingIfNeeded: value >> 28) & 0x7F) | 0x80
+ base[index + 5] = UInt8(truncatingIfNeeded: value >> 35)
+ return index + 6
+ }
+ if value < 0x2_0000_0000_0000 {
+ base[index] = (UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80
+ base[index + 1] = (UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80
+ base[index + 2] = (UInt8(truncatingIfNeeded: value >> 14) & 0x7F) | 0x80
+ base[index + 3] = (UInt8(truncatingIfNeeded: value >> 21) & 0x7F) | 0x80
+ base[index + 4] = (UInt8(truncatingIfNeeded: value >> 28) & 0x7F) | 0x80
+ base[index + 5] = (UInt8(truncatingIfNeeded: value >> 35) & 0x7F) | 0x80
+ base[index + 6] = UInt8(truncatingIfNeeded: value >> 42)
+ return index + 7
+ }
+ if value < 0x100_0000_0000_0000 {
+ base[index] = (UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80
+ base[index + 1] = (UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80
+ base[index + 2] = (UInt8(truncatingIfNeeded: value >> 14) & 0x7F) | 0x80
+ base[index + 3] = (UInt8(truncatingIfNeeded: value >> 21) & 0x7F) | 0x80
+ base[index + 4] = (UInt8(truncatingIfNeeded: value >> 28) & 0x7F) | 0x80
+ base[index + 5] = (UInt8(truncatingIfNeeded: value >> 35) & 0x7F) | 0x80
+ base[index + 6] = (UInt8(truncatingIfNeeded: value >> 42) & 0x7F) | 0x80
+ base[index + 7] = UInt8(truncatingIfNeeded: value >> 49)
+ return index + 8
+ }
+ base[index] = (UInt8(truncatingIfNeeded: value) & 0x7F) | 0x80
+ base[index + 1] = (UInt8(truncatingIfNeeded: value >> 7) & 0x7F) | 0x80
+ base[index + 2] = (UInt8(truncatingIfNeeded: value >> 14) & 0x7F) | 0x80
+ base[index + 3] = (UInt8(truncatingIfNeeded: value >> 21) & 0x7F) | 0x80
+ base[index + 4] = (UInt8(truncatingIfNeeded: value >> 28) & 0x7F) | 0x80
+ base[index + 5] = (UInt8(truncatingIfNeeded: value >> 35) & 0x7F) | 0x80
+ base[index + 6] = (UInt8(truncatingIfNeeded: value >> 42) & 0x7F) | 0x80
+ base[index + 7] = (UInt8(truncatingIfNeeded: value >> 49) & 0x7F) | 0x80
+ base[index + 8] = UInt8(truncatingIfNeeded: value >> 56)
+ return index + 9
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readBoolUnchecked(
+ from base: UnsafePointer<UInt8>,
+ index: Int
+ ) -> Bool {
+ base[index] != 0
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readInt8Unchecked(
+ from base: UnsafePointer<UInt8>,
+ index: Int
+ ) -> Int8 {
+ Int8(bitPattern: base[index])
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readUInt8Unchecked(
+ from base: UnsafePointer<UInt8>,
+ index: Int
+ ) -> UInt8 {
+ base[index]
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readInt16Unchecked(
+ from base: UnsafePointer<UInt8>,
+ index: Int
+ ) -> Int16 {
+ Int16(bitPattern: readUInt16Unchecked(from: base, index: index))
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readUInt16Unchecked(
+ from base: UnsafePointer<UInt8>,
+ index: Int
+ ) -> UInt16 {
+ let b0 = UInt16(base[index])
+ let b1 = UInt16(base[index + 1]) << 8
+ return b0 | b1
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readFloat32Unchecked(
+ from base: UnsafePointer<UInt8>,
+ index: Int
+ ) -> Float {
+ Float(bitPattern: readFixedUInt32Unchecked(from: base, index: index))
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readFloat64Unchecked(
+ from base: UnsafePointer<UInt8>,
+ index: Int
+ ) -> Double {
+ Double(bitPattern: readFixedUInt64Unchecked(from: base, index: index))
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readBool(
+ from bytes: UnsafeBufferPointer<UInt8>,
+ index: inout Int
+ ) throws -> Bool {
+ try readUInt8(from: bytes, index: &index) != 0
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readInt8(
+ from bytes: UnsafeBufferPointer<UInt8>,
+ index: inout Int
+ ) throws -> Int8 {
+ Int8(bitPattern: try readUInt8(from: bytes, index: &index))
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readUInt8(
+ from bytes: UnsafeBufferPointer<UInt8>,
+ index: inout Int
+ ) throws -> UInt8 {
+ try checkReadable(bytes, index: index, need: 1)
+ guard let base = bytes.baseAddress else {
+ throw ForyError.outOfBounds(cursor: index, need: 1, length: bytes.count)
+ }
+ let value = base[index]
+ index += 1
+ return value
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readInt16(
+ from bytes: UnsafeBufferPointer<UInt8>,
+ index: inout Int
+ ) throws -> Int16 {
+ Int16(bitPattern: try readUInt16(from: bytes, index: &index))
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readUInt16(
+ from bytes: UnsafeBufferPointer<UInt8>,
+ index: inout Int
+ ) throws -> UInt16 {
+ try checkReadable(bytes, index: index, need: 2)
+ guard let base = bytes.baseAddress else {
+ throw ForyError.outOfBounds(cursor: index, need: 2, length: bytes.count)
+ }
+ let value = readUInt16Unchecked(from: UnsafePointer(base), index: index)
+ index += 2
+ return value
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readInt32(
+ from bytes: UnsafeBufferPointer<UInt8>,
+ index: inout Int
+ ) throws -> Int32 {
+ try readVarInt32(from: bytes, index: &index)
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readUInt32(
+ from bytes: UnsafeBufferPointer<UInt8>,
+ index: inout Int
+ ) throws -> UInt32 {
+ try readVarUInt32(from: bytes, index: &index)
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readInt64(
+ from bytes: UnsafeBufferPointer<UInt8>,
+ index: inout Int
+ ) throws -> Int64 {
+ try readVarInt64(from: bytes, index: &index)
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readUInt64(
+ from bytes: UnsafeBufferPointer<UInt8>,
+ index: inout Int
+ ) throws -> UInt64 {
+ try readVarUInt64(from: bytes, index: &index)
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readInt(
+ from bytes: UnsafeBufferPointer<UInt8>,
+ index: inout Int
+ ) throws -> Int {
+ Int(try readVarInt64(from: bytes, index: &index))
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readUInt(
+ from bytes: UnsafeBufferPointer<UInt8>,
+ index: inout Int
+ ) throws -> UInt {
+ UInt(try readVarUInt64(from: bytes, index: &index))
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readFloat32(
+ from bytes: UnsafeBufferPointer<UInt8>,
+ index: inout Int
+ ) throws -> Float {
+ Float(bitPattern: try readFixedUInt32(from: bytes, index: &index))
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readFloat64(
+ from bytes: UnsafeBufferPointer<UInt8>,
+ index: inout Int
+ ) throws -> Double {
+ Double(bitPattern: try readFixedUInt64(from: bytes, index: &index))
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readVarInt32(
+ from bytes: UnsafeBufferPointer<UInt8>,
+ index: inout Int
+ ) throws -> Int32 {
+ let encoded = try readVarUInt32(from: bytes, index: &index)
+ return Int32(bitPattern: (encoded >> 1) ^ (~(encoded & 1) &+ 1))
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readVarInt64(
+ from bytes: UnsafeBufferPointer<UInt8>,
+ index: inout Int
+ ) throws -> Int64 {
+ let encoded = try readVarUInt64(from: bytes, index: &index)
+ return Int64(bitPattern: (encoded >> 1) ^ (~(encoded & 1) &+ 1))
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readInt32(
+ from base: UnsafePointer<UInt8>,
+ length: Int,
+ index: inout Int
+ ) throws -> Int32 {
+ try readVarInt32(from: base, length: length, index: &index)
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readUInt32(
+ from base: UnsafePointer<UInt8>,
+ length: Int,
+ index: inout Int
+ ) throws -> UInt32 {
+ try readVarUInt32(from: base, length: length, index: &index)
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readInt64(
+ from base: UnsafePointer<UInt8>,
+ length: Int,
+ index: inout Int
+ ) throws -> Int64 {
+ try readVarInt64(from: base, length: length, index: &index)
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readUInt64(
+ from base: UnsafePointer<UInt8>,
+ length: Int,
+ index: inout Int
+ ) throws -> UInt64 {
+ try readVarUInt64(from: base, length: length, index: &index)
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readInt(
+ from base: UnsafePointer<UInt8>,
+ length: Int,
+ index: inout Int
+ ) throws -> Int {
+ Int(try readVarInt64(from: base, length: length, index: &index))
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readUInt(
+ from base: UnsafePointer<UInt8>,
+ length: Int,
+ index: inout Int
+ ) throws -> UInt {
+ UInt(try readVarUInt64(from: base, length: length, index: &index))
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readVarUInt32(
+ from bytes: UnsafeBufferPointer<UInt8>,
+ index: inout Int
+ ) throws -> UInt32 {
+ guard let base = bytes.baseAddress else {
+ throw ForyError.outOfBounds(cursor: index, need: 1, length: bytes.count)
+ }
+ let available = bytes.count - index
+ guard available > 0 else {
+ throw ForyError.outOfBounds(cursor: index, need: 1, length: bytes.count)
+ }
+
+ let b0 = base[index]
+ if b0 < 0x80 {
+ index += 1
+ return UInt32(b0)
+ }
+ guard available >= 2 else {
+ throw ForyError.outOfBounds(cursor: index, need: 2, length: bytes.count)
+ }
+ let b1 = base[index + 1]
+ if b1 < 0x80 {
+ index += 2
+ return UInt32(b0 & 0x7F) | (UInt32(b1) << 7)
+ }
+ guard available >= 3 else {
+ throw ForyError.outOfBounds(cursor: index, need: 3, length: bytes.count)
+ }
+ let b2 = base[index + 2]
+ if b2 < 0x80 {
+ index += 3
+ return UInt32(b0 & 0x7F) | (UInt32(b1 & 0x7F) << 7) | (UInt32(b2) << 14)
+ }
+ guard available >= 4 else {
+ throw ForyError.outOfBounds(cursor: index, need: 4, length: bytes.count)
+ }
+ let b3 = base[index + 3]
+ if b3 < 0x80 {
+ index += 4
+ return UInt32(b0 & 0x7F) |
+ (UInt32(b1 & 0x7F) << 7) |
+ (UInt32(b2 & 0x7F) << 14) |
+ (UInt32(b3) << 21)
+ }
+ guard available >= 5 else {
+ throw ForyError.outOfBounds(cursor: index, need: 5, length: bytes.count)
+ }
+ let b4 = base[index + 4]
+ if b4 >= 0x80 {
+ throw ForyError.encodingError("varuint32 overflow")
+ }
+ index += 5
+ return UInt32(b0 & 0x7F) |
+ (UInt32(b1 & 0x7F) << 7) |
+ (UInt32(b2 & 0x7F) << 14) |
+ (UInt32(b3 & 0x7F) << 21) |
+ (UInt32(b4) << 28)
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readVarUInt64(
+ from bytes: UnsafeBufferPointer<UInt8>,
+ index: inout Int
+ ) throws -> UInt64 {
+ let available = bytes.count - index
+ guard let base = bytes.baseAddress else {
+ throw ForyError.outOfBounds(cursor: index, need: 1, length: bytes.count)
+ }
+ if available >= 9 {
+ let offset = index
+ let b0 = base[offset]
+ if b0 < 0x80 {
+ index = offset + 1
+ return UInt64(b0)
+ }
+
+ let b1 = base[offset + 1]
+ if b1 < 0x80 {
+ index = offset + 2
+ return UInt64(b0 & 0x7F) | (UInt64(b1) << 7)
+ }
+
+ let b2 = base[offset + 2]
+ if b2 < 0x80 {
+ index = offset + 3
+ return UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2) << 14)
+ }
+
+ let b3 = base[offset + 3]
+ if b3 < 0x80 {
+ index = offset + 4
+ return UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2 & 0x7F) << 14) |
+ (UInt64(b3) << 21)
+ }
+
+ let b4 = base[offset + 4]
+ if b4 < 0x80 {
+ index = offset + 5
+ return UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2 & 0x7F) << 14) |
+ (UInt64(b3 & 0x7F) << 21) |
+ (UInt64(b4) << 28)
+ }
+
+ let b5 = base[offset + 5]
+ if b5 < 0x80 {
+ index = offset + 6
+ return UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2 & 0x7F) << 14) |
+ (UInt64(b3 & 0x7F) << 21) |
+ (UInt64(b4 & 0x7F) << 28) |
+ (UInt64(b5) << 35)
+ }
+
+ let b6 = base[offset + 6]
+ if b6 < 0x80 {
+ index = offset + 7
+ return UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2 & 0x7F) << 14) |
+ (UInt64(b3 & 0x7F) << 21) |
+ (UInt64(b4 & 0x7F) << 28) |
+ (UInt64(b5 & 0x7F) << 35) |
+ (UInt64(b6) << 42)
+ }
+
+ let b7 = base[offset + 7]
+ if b7 < 0x80 {
+ index = offset + 8
+ return UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2 & 0x7F) << 14) |
+ (UInt64(b3 & 0x7F) << 21) |
+ (UInt64(b4 & 0x7F) << 28) |
+ (UInt64(b5 & 0x7F) << 35) |
+ (UInt64(b6 & 0x7F) << 42) |
+ (UInt64(b7) << 49)
+ }
+
+ let b8 = base[offset + 8]
+ index = offset + 9
+ let low = UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2 & 0x7F) << 14) |
+ (UInt64(b3 & 0x7F) << 21)
+ let high = (UInt64(b4 & 0x7F) << 28) |
+ (UInt64(b5 & 0x7F) << 35) |
+ (UInt64(b6 & 0x7F) << 42) |
+ (UInt64(b7 & 0x7F) << 49) |
+ (UInt64(b8) << 56)
+ return low | high
+ }
+
+ try checkReadable(bytes, index: index, need: 1)
+ let b0 = base[index]
+ if b0 < 0x80 {
+ index += 1
+ return UInt64(b0)
+ }
+
+ try checkReadable(bytes, index: index, need: 2)
+ let b1 = base[index + 1]
+ if b1 < 0x80 {
+ index += 2
+ return UInt64(b0 & 0x7F) | (UInt64(b1) << 7)
+ }
+
+ try checkReadable(bytes, index: index, need: 3)
+ let b2 = base[index + 2]
+ if b2 < 0x80 {
+ index += 3
+ return UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2) << 14)
+ }
+
+ try checkReadable(bytes, index: index, need: 4)
+ let b3 = base[index + 3]
+ if b3 < 0x80 {
+ index += 4
+ return UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2 & 0x7F) << 14) |
+ (UInt64(b3) << 21)
+ }
+
+ try checkReadable(bytes, index: index, need: 5)
+ let b4 = base[index + 4]
+ if b4 < 0x80 {
+ index += 5
+ return UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2 & 0x7F) << 14) |
+ (UInt64(b3 & 0x7F) << 21) |
+ (UInt64(b4) << 28)
+ }
+
+ try checkReadable(bytes, index: index, need: 6)
+ let b5 = base[index + 5]
+ if b5 < 0x80 {
+ index += 6
+ return UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2 & 0x7F) << 14) |
+ (UInt64(b3 & 0x7F) << 21) |
+ (UInt64(b4 & 0x7F) << 28) |
+ (UInt64(b5) << 35)
+ }
+
+ try checkReadable(bytes, index: index, need: 7)
+ let b6 = base[index + 6]
+ if b6 < 0x80 {
+ index += 7
+ return UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2 & 0x7F) << 14) |
+ (UInt64(b3 & 0x7F) << 21) |
+ (UInt64(b4 & 0x7F) << 28) |
+ (UInt64(b5 & 0x7F) << 35) |
+ (UInt64(b6) << 42)
+ }
+
+ try checkReadable(bytes, index: index, need: 8)
+ let b7 = base[index + 7]
+ if b7 < 0x80 {
+ index += 8
+ return UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2 & 0x7F) << 14) |
+ (UInt64(b3 & 0x7F) << 21) |
+ (UInt64(b4 & 0x7F) << 28) |
+ (UInt64(b5 & 0x7F) << 35) |
+ (UInt64(b6 & 0x7F) << 42) |
+ (UInt64(b7) << 49)
+ }
+
+ try checkReadable(bytes, index: index, need: 9)
+ let b8 = base[index + 8]
+ index += 9
+ let low = UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2 & 0x7F) << 14) |
+ (UInt64(b3 & 0x7F) << 21)
+ let high = (UInt64(b4 & 0x7F) << 28) |
+ (UInt64(b5 & 0x7F) << 35) |
+ (UInt64(b6 & 0x7F) << 42) |
+ (UInt64(b7 & 0x7F) << 49) |
+ (UInt64(b8) << 56)
+ return low | high
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readVarInt32(
+ from base: UnsafePointer<UInt8>,
+ length: Int,
+ index: inout Int
+ ) throws -> Int32 {
+ let encoded = try readVarUInt32(from: base, length: length, index: &index)
+ return Int32(bitPattern: (encoded >> 1) ^ (~(encoded & 1) &+ 1))
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readVarInt64(
+ from base: UnsafePointer<UInt8>,
+ length: Int,
+ index: inout Int
+ ) throws -> Int64 {
+ let encoded = try readVarUInt64(from: base, length: length, index: &index)
+ return Int64(bitPattern: (encoded >> 1) ^ (~(encoded & 1) &+ 1))
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readVarUInt32(
+ from base: UnsafePointer<UInt8>,
+ length: Int,
+ index: inout Int
+ ) throws -> UInt32 {
+ let available = length - index
+ guard available > 0 else {
+ throw ForyError.outOfBounds(cursor: index, need: 1, length: length)
+ }
+
+ let b0 = base[index]
+ if b0 < 0x80 {
+ index += 1
+ return UInt32(b0)
+ }
+ guard available >= 2 else {
+ throw ForyError.outOfBounds(cursor: index, need: 2, length: length)
+ }
+ let b1 = base[index + 1]
+ if b1 < 0x80 {
+ index += 2
+ return UInt32(b0 & 0x7F) | (UInt32(b1) << 7)
+ }
+ guard available >= 3 else {
+ throw ForyError.outOfBounds(cursor: index, need: 3, length: length)
+ }
+ let b2 = base[index + 2]
+ if b2 < 0x80 {
+ index += 3
+ return UInt32(b0 & 0x7F) | (UInt32(b1 & 0x7F) << 7) | (UInt32(b2) << 14)
+ }
+ guard available >= 4 else {
+ throw ForyError.outOfBounds(cursor: index, need: 4, length: length)
+ }
+ let b3 = base[index + 3]
+ if b3 < 0x80 {
+ index += 4
+ return UInt32(b0 & 0x7F) |
+ (UInt32(b1 & 0x7F) << 7) |
+ (UInt32(b2 & 0x7F) << 14) |
+ (UInt32(b3) << 21)
+ }
+ guard available >= 5 else {
+ throw ForyError.outOfBounds(cursor: index, need: 5, length: length)
+ }
+ let b4 = base[index + 4]
+ if b4 >= 0x80 {
+ throw ForyError.encodingError("varuint32 overflow")
+ }
+ index += 5
+ return UInt32(b0 & 0x7F) |
+ (UInt32(b1 & 0x7F) << 7) |
+ (UInt32(b2 & 0x7F) << 14) |
+ (UInt32(b3 & 0x7F) << 21) |
+ (UInt32(b4) << 28)
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readVarUInt64(
+ from base: UnsafePointer<UInt8>,
+ length: Int,
+ index: inout Int
+ ) throws -> UInt64 {
+ let available = length - index
+ if available >= 9 {
+ let offset = index
+ let b0 = base[offset]
+ if b0 < 0x80 {
+ index = offset + 1
+ return UInt64(b0)
+ }
+
+ let b1 = base[offset + 1]
+ if b1 < 0x80 {
+ index = offset + 2
+ return UInt64(b0 & 0x7F) | (UInt64(b1) << 7)
+ }
+
+ let b2 = base[offset + 2]
+ if b2 < 0x80 {
+ index = offset + 3
+ return UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2) << 14)
+ }
+
+ let b3 = base[offset + 3]
+ if b3 < 0x80 {
+ index = offset + 4
+ return UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2 & 0x7F) << 14) |
+ (UInt64(b3) << 21)
+ }
+
+ let b4 = base[offset + 4]
+ if b4 < 0x80 {
+ index = offset + 5
+ return UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2 & 0x7F) << 14) |
+ (UInt64(b3 & 0x7F) << 21) |
+ (UInt64(b4) << 28)
+ }
+
+ let b5 = base[offset + 5]
+ if b5 < 0x80 {
+ index = offset + 6
+ return UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2 & 0x7F) << 14) |
+ (UInt64(b3 & 0x7F) << 21) |
+ (UInt64(b4 & 0x7F) << 28) |
+ (UInt64(b5) << 35)
+ }
+
+ let b6 = base[offset + 6]
+ if b6 < 0x80 {
+ index = offset + 7
+ return UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2 & 0x7F) << 14) |
+ (UInt64(b3 & 0x7F) << 21) |
+ (UInt64(b4 & 0x7F) << 28) |
+ (UInt64(b5 & 0x7F) << 35) |
+ (UInt64(b6) << 42)
+ }
+
+ let b7 = base[offset + 7]
+ if b7 < 0x80 {
+ index = offset + 8
+ return UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2 & 0x7F) << 14) |
+ (UInt64(b3 & 0x7F) << 21) |
+ (UInt64(b4 & 0x7F) << 28) |
+ (UInt64(b5 & 0x7F) << 35) |
+ (UInt64(b6 & 0x7F) << 42) |
+ (UInt64(b7) << 49)
+ }
+
+ let b8 = base[offset + 8]
+ index = offset + 9
+ let low = UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2 & 0x7F) << 14) |
+ (UInt64(b3 & 0x7F) << 21)
+ let high = (UInt64(b4 & 0x7F) << 28) |
+ (UInt64(b5 & 0x7F) << 35) |
+ (UInt64(b6 & 0x7F) << 42) |
+ (UInt64(b7 & 0x7F) << 49) |
+ (UInt64(b8) << 56)
+ return low | high
+ }
+
+ try checkReadable(length: length, index: index, need: 1)
+ let b0 = base[index]
+ if b0 < 0x80 {
+ index += 1
+ return UInt64(b0)
+ }
+
+ try checkReadable(length: length, index: index, need: 2)
+ let b1 = base[index + 1]
+ if b1 < 0x80 {
+ index += 2
+ return UInt64(b0 & 0x7F) | (UInt64(b1) << 7)
+ }
+
+ try checkReadable(length: length, index: index, need: 3)
+ let b2 = base[index + 2]
+ if b2 < 0x80 {
+ index += 3
+ return UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2) << 14)
+ }
+
+ try checkReadable(length: length, index: index, need: 4)
+ let b3 = base[index + 3]
+ if b3 < 0x80 {
+ index += 4
+ return UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2 & 0x7F) << 14) |
+ (UInt64(b3) << 21)
+ }
+
+ try checkReadable(length: length, index: index, need: 5)
+ let b4 = base[index + 4]
+ if b4 < 0x80 {
+ index += 5
+ return UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2 & 0x7F) << 14) |
+ (UInt64(b3 & 0x7F) << 21) |
+ (UInt64(b4) << 28)
+ }
+
+ try checkReadable(length: length, index: index, need: 6)
+ let b5 = base[index + 5]
+ if b5 < 0x80 {
+ index += 6
+ return UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2 & 0x7F) << 14) |
+ (UInt64(b3 & 0x7F) << 21) |
+ (UInt64(b4 & 0x7F) << 28) |
+ (UInt64(b5) << 35)
+ }
+
+ try checkReadable(length: length, index: index, need: 7)
+ let b6 = base[index + 6]
+ if b6 < 0x80 {
+ index += 7
+ return UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2 & 0x7F) << 14) |
+ (UInt64(b3 & 0x7F) << 21) |
+ (UInt64(b4 & 0x7F) << 28) |
+ (UInt64(b5 & 0x7F) << 35) |
+ (UInt64(b6) << 42)
+ }
+
+ try checkReadable(length: length, index: index, need: 8)
+ let b7 = base[index + 7]
+ if b7 < 0x80 {
+ index += 8
+ return UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2 & 0x7F) << 14) |
+ (UInt64(b3 & 0x7F) << 21) |
+ (UInt64(b4 & 0x7F) << 28) |
+ (UInt64(b5 & 0x7F) << 35) |
+ (UInt64(b6 & 0x7F) << 42) |
+ (UInt64(b7) << 49)
+ }
+
+ try checkReadable(length: length, index: index, need: 9)
+ let b8 = base[index + 8]
+ index += 9
+ let low = UInt64(b0 & 0x7F) |
+ (UInt64(b1 & 0x7F) << 7) |
+ (UInt64(b2 & 0x7F) << 14) |
+ (UInt64(b3 & 0x7F) << 21)
+ let high = (UInt64(b4 & 0x7F) << 28) |
+ (UInt64(b5 & 0x7F) << 35) |
+ (UInt64(b6 & 0x7F) << 42) |
+ (UInt64(b7 & 0x7F) << 49) |
+ (UInt64(b8) << 56)
+ return low | high
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readFixedUInt32(
+ from bytes: UnsafeBufferPointer<UInt8>,
+ index: inout Int
+ ) throws -> UInt32 {
+ try checkReadable(bytes, index: index, need: 4)
+ guard let base = bytes.baseAddress else {
+ throw ForyError.outOfBounds(cursor: index, need: 4, length: bytes.count)
+ }
+ let value = readFixedUInt32Unchecked(from: UnsafePointer(base), index: index)
+ index += 4
+ return value
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readFixedUInt64(
+ from bytes: UnsafeBufferPointer<UInt8>,
+ index: inout Int
+ ) throws -> UInt64 {
+ try checkReadable(bytes, index: index, need: 8)
+ guard let base = bytes.baseAddress else {
+ throw ForyError.outOfBounds(cursor: index, need: 8, length: bytes.count)
+ }
+ let value = readFixedUInt64Unchecked(from: UnsafePointer(base), index: index)
+ index += 8
+ return value
+ }
+
+ @inlinable
+ @inline(__always)
+ static func writeFixedUInt32(
+ _ value: UInt32,
+ to base: UnsafeMutablePointer<UInt8>,
+ index: Int
+ ) -> Int {
+ let little = value.littleEndian
+ base[index] = UInt8(truncatingIfNeeded: little)
+ base[index + 1] = UInt8(truncatingIfNeeded: little >> 8)
+ base[index + 2] = UInt8(truncatingIfNeeded: little >> 16)
+ base[index + 3] = UInt8(truncatingIfNeeded: little >> 24)
+ return index + 4
+ }
+
+ @inlinable
+ @inline(__always)
+ static func writeFixedUInt64(
+ _ value: UInt64,
+ to base: UnsafeMutablePointer<UInt8>,
+ index: Int
+ ) -> Int {
+ let little = value.littleEndian
+ base[index] = UInt8(truncatingIfNeeded: little)
+ base[index + 1] = UInt8(truncatingIfNeeded: little >> 8)
+ base[index + 2] = UInt8(truncatingIfNeeded: little >> 16)
+ base[index + 3] = UInt8(truncatingIfNeeded: little >> 24)
+ base[index + 4] = UInt8(truncatingIfNeeded: little >> 32)
+ base[index + 5] = UInt8(truncatingIfNeeded: little >> 40)
+ base[index + 6] = UInt8(truncatingIfNeeded: little >> 48)
+ base[index + 7] = UInt8(truncatingIfNeeded: little >> 56)
+ return index + 8
+ }
+
+ @inlinable
+ @inline(__always)
+ static func readFixedUInt32Unchecked(
+ from base: UnsafePointer<UInt8>,
+ index: Int
+ ) -> UInt32 {
+ let b0 = UInt32(base[index])
+ let b1 = UInt32(base[index + 1]) << 8
+ let b2 = UInt32(base[index + 2]) << 16
+ let b3 = UInt32(base[index + 3]) << 24
+ return b0 | b1 | b2 | b3
+ }
+
+ @inlinable
+ @inline(__always)
+ static func readFixedUInt64Unchecked(
+ from base: UnsafePointer<UInt8>,
+ index: Int
+ ) -> UInt64 {
+ let b0 = UInt64(base[index])
+ let b1 = UInt64(base[index + 1]) << 8
+ let b2 = UInt64(base[index + 2]) << 16
+ let b3 = UInt64(base[index + 3]) << 24
+ let b4 = UInt64(base[index + 4]) << 32
+ let b5 = UInt64(base[index + 5]) << 40
+ let b6 = UInt64(base[index + 6]) << 48
+ let b7 = UInt64(base[index + 7]) << 56
+ return b0 | b1 | b2 | b3 | b4 | b5 | b6 | b7
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func checkReadable(
+ _ bytes: UnsafeBufferPointer<UInt8>,
+ index: Int,
+ need: Int
+ ) throws {
+ if index < 0 || need < 0 || index + need > bytes.count {
+ throw ForyError.outOfBounds(cursor: index, need: need, length: bytes.count)
+ }
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func checkReadable(
+ length: Int,
+ index: Int,
+ need: Int
+ ) throws {
+ if index < 0 || need < 0 || index + need > length {
+ throw ForyError.outOfBounds(cursor: index, need: need, length: length)
+ }
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func writeRegion(
+ buffer: ByteBuffer,
+ exactCount: Int,
+ _ body: (UnsafeMutablePointer<UInt8>) -> Void
+ ) {
+ guard exactCount > 0 else {
+ return
+ }
+ let start = buffer.storage.count
+ buffer.storage.append(contentsOf: repeatElement(0, count: exactCount))
+ buffer.storage.withUnsafeMutableBufferPointer { bytes in
+ body(bytes.baseAddress!.advanced(by: start))
+ }
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func writeRegion(
+ buffer: ByteBuffer,
+ maxCount: Int,
+ _ body: (UnsafeMutablePointer<UInt8>) -> Int
+ ) {
+ // Keep the closure non-throwing for the hot path; callers return the
+ // actual byte count and we trim the unused suffix afterward.
+ guard maxCount > 0 else {
+ return
+ }
+ let start = buffer.storage.count
+ buffer.storage.append(contentsOf: repeatElement(0, count: maxCount))
+ let written = buffer.storage.withUnsafeMutableBufferPointer { bytes in
+ body(bytes.baseAddress!.advanced(by: start))
+ }
+ precondition(
+ written >= 0 && written <= maxCount,
+ "writeRegion wrote \(written) bytes into a maxCount \(maxCount) region"
+ )
+ if written < maxCount {
+ buffer.storage.removeLast(maxCount - written)
+ }
+ }
+
+ @inlinable
+ @inline(__always)
+ public static func readRegion(
+ buffer: ByteBuffer,
+ _ body: (UnsafeBufferPointer<UInt8>) throws -> Int
+ ) throws {
+ let startIndex = buffer.cursor
+ let readableCount = buffer.count
+ if _slowPath(startIndex < 0 || startIndex > readableCount) {
+ throw ForyError.outOfBounds(
+ cursor: startIndex,
+ need: 0,
+ length: readableCount
+ )
+ }
+ let available = readableCount - startIndex
+ let consumed = try buffer.storage.withUnsafeBufferPointer { bytes -> Int in
+ let start = bytes.baseAddress.map { $0.advanced(by: startIndex) }
+ let region = UnsafeBufferPointer(start: start, count: available)
+ return try body(region)
+ }
+ if consumed < 0 || consumed > available {
+ throw ForyError.outOfBounds(cursor: startIndex, need: consumed, length: readableCount)
+ }
+ buffer.cursor = startIndex + consumed
+ }
+
+ @inline(__always)
+ static func copyBytes(
+ _ bytes: [UInt8],
+ to base: UnsafeMutablePointer<UInt8>,
+ index: Int
+ ) -> Int {
+ guard !bytes.isEmpty else {
+ return index
+ }
+ bytes.withUnsafeBufferPointer { source in
+ guard let sourceBase = source.baseAddress else {
+ return
+ }
+ UnsafeMutableRawPointer(base.advanced(by: index))
+ .copyMemory(from: UnsafeRawPointer(sourceBase), byteCount: source.count)
+ }
+ return index + bytes.count
+ }
+
+ @inline(__always)
+ static func copyBytes(
+ _ bytes: UnsafeBufferPointer<UInt8>,
+ to base: UnsafeMutablePointer<UInt8>,
+ index: Int
+ ) -> Int {
+ guard let sourceBase = bytes.baseAddress, bytes.count > 0 else {
+ return index
+ }
+ UnsafeMutableRawPointer(base.advanced(by: index))
+ .copyMemory(from: UnsafeRawPointer(sourceBase), byteCount: bytes.count)
+ return index + bytes.count
+ }
+}
+// swiftlint:enable type_body_length
diff --git a/swift/Sources/Fory/WriteContext.swift b/swift/Sources/Fory/WriteContext.swift
new file mode 100644
index 000000000..9d10aa10e
--- /dev/null
+++ b/swift/Sources/Fory/WriteContext.swift
@@ -0,0 +1,288 @@
+// 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.
+
+import Foundation
+
+private struct MetaStringCacheKey: Hashable {
+ let encoding: MetaStringEncoding
+ let bytes: [UInt8]
+}
+
+final class MetaStringWriteState {
+ private var stringIndexByKey: [MetaStringCacheKey: UInt32] = [:]
+ private var nextIndex: UInt32 = 0
+
+ init() {}
+
+ func index(for value: MetaString) -> UInt32? {
+ stringIndexByKey[MetaStringCacheKey(encoding: value.encoding, bytes: value.bytes)]
+ }
+
+ func assignIndexIfAbsent(for value: MetaString) -> (index: UInt32, isNew: Bool) {
+ let key = MetaStringCacheKey(encoding: value.encoding, bytes: value.bytes)
+ if let existing = stringIndexByKey[key] {
+ return (existing, false)
+ }
+ let index = nextIndex
+ nextIndex &+= 1
+ stringIndexByKey[key] = index
+ return (index, true)
+ }
+
+ func reset() {
+ if !stringIndexByKey.isEmpty {
+ stringIndexByKey.removeAll(keepingCapacity: true)
+ }
+ if nextIndex != 0 {
+ nextIndex = 0
+ }
+ }
+}
+
+public final class WriteContext {
+ public let buffer: ByteBuffer
+ let typeResolver: TypeResolver
+ public let trackRef: Bool
+ public let compatible: Bool
+ public let checkClassVersion: Bool
+ public let maxDepth: Int
+ public let refWriter: RefWriter
+ let metaStringWriteState: MetaStringWriteState
+ private let typeIndexBySwiftType = UInt64Map<UInt32>(initialCapacity: 8)
+ private var typeDefStateUsed = false
+ private var metaStringWriteStateUsed = false
+ private var dynamicAnyDepth = 0
+ private var lastTypeInfo = TypeInfo.uncached
+
+ convenience init(
+ buffer: ByteBuffer,
+ typeResolver: TypeResolver,
+ trackRef: Bool,
+ compatible: Bool = false,
+ checkClassVersion: Bool = true,
+ maxDepth: Int = 5
+ ) {
+ self.init(
+ buffer: buffer,
+ typeResolver: typeResolver,
+ trackRef: trackRef,
+ compatible: compatible,
+ checkClassVersion: checkClassVersion,
+ maxDepth: maxDepth,
+ metaStringWriteState: MetaStringWriteState()
+ )
+ }
+
+ init(
+ buffer: ByteBuffer,
+ typeResolver: TypeResolver,
+ trackRef: Bool,
+ compatible: Bool,
+ checkClassVersion: Bool,
+ maxDepth: Int,
+ metaStringWriteState: MetaStringWriteState
+ ) {
+ self.buffer = buffer
+ self.typeResolver = typeResolver
+ self.trackRef = trackRef
+ self.compatible = compatible
+ self.checkClassVersion = checkClassVersion
+ self.maxDepth = maxDepth
+ self.refWriter = RefWriter()
+ self.metaStringWriteState = metaStringWriteState
+ }
+
+ @inline(__always)
+ func enterDynamicAnyDepth() throws {
+ if maxDepth < 0 {
+ throw ForyError.invalidData("configured maxDepth \(maxDepth) is negative")
+ }
+ let nextDepth = dynamicAnyDepth + 1
+ if nextDepth > maxDepth {
+ throw ForyError.invalidData(
+ "dynamic Any nesting depth \(nextDepth) exceeds configured maxDepth \(maxDepth)"
+ )
+ }
+ dynamicAnyDepth = nextDepth
+ }
+
+ @inline(__always)
+ func typeInfo<T: Serializer>(for type: T.Type) throws -> TypeInfo {
+ let typeID = ObjectIdentifier(type)
+ if lastTypeInfo.swiftTypeID == typeID {
+ return lastTypeInfo
+ }
+ let info = try typeResolver.requireTypeInfo(for: type)
+ lastTypeInfo = info
+ return info
+ }
+
+ @inline(__always)
+ func writeStaticTypeInfo(_ typeID: TypeId) {
+ buffer.writeUInt8(UInt8(truncatingIfNeeded: typeID.rawValue))
+ }
+
+ @inline(__always)
+ func leaveDynamicAnyDepth() {
+ if dynamicAnyDepth > 0 {
+ dynamicAnyDepth -= 1
+ }
+ }
+
+ func writeTypeMeta(_ typeInfo: TypeInfo) {
+ if !typeDefStateUsed {
+ typeDefStateUsed = true
+ }
+
+ let typeIndexBySwiftType = self.typeIndexBySwiftType
+ let typeKey = UInt64(UInt(bitPattern: typeInfo.swiftTypeID))
+ let assignment = typeIndexBySwiftType.putIfAbsent(
+ UInt32(typeIndexBySwiftType.count),
+ for: typeKey
+ )
+ let buffer = self.buffer
+ if assignment.inserted {
+ if assignment.value == 0, let firstTypeDefBytes = typeInfo.firstTypeDefBytes {
+ buffer.writeBytes(firstTypeDefBytes)
+ return
+ }
+
+ let marker = assignment.value << 1
+ if marker < 0x80 {
+ buffer.writeUInt8(UInt8(truncatingIfNeeded: marker))
+ } else {
+ buffer.writeVarUInt32(marker)
+ }
+ if let typeDefBytes = typeInfo.typeDefBytes {
+ buffer.writeBytes(typeDefBytes)
+ }
+ } else {
+ let marker = (assignment.value << 1) | 1
+ if marker < 0x80 {
+ buffer.writeUInt8(UInt8(truncatingIfNeeded: marker))
+ } else {
+ buffer.writeVarUInt32(marker)
+ }
+ }
+ }
+
+ @inline(__always)
+ func markMetaStringWriteStateUsed() {
+ metaStringWriteStateUsed = true
+ }
+
+ func reset() {
+ if dynamicAnyDepth != 0 {
+ dynamicAnyDepth = 0
+ }
+ if trackRef {
+ refWriter.reset()
+ }
+ if typeDefStateUsed {
+ if !typeIndexBySwiftType.isEmpty {
+ typeIndexBySwiftType.clear()
+ }
+ typeDefStateUsed = false
+ }
+ if metaStringWriteStateUsed {
+ metaStringWriteState.reset()
+ metaStringWriteStateUsed = false
+ }
+ }
+}
+
+public extension WriteContext {
+ func writeAny(
+ _ value: Any?,
+ refMode: RefMode,
+ writeTypeInfo: Bool = true,
+ hasGenerics: Bool = false
+ ) throws {
+ try DynamicAnyValue.wrapped(value).foryWrite(
+ self,
+ refMode: refMode,
+ writeTypeInfo: writeTypeInfo,
+ hasGenerics: hasGenerics
+ )
+ }
+
+ func writeAnyList(
+ _ value: [Any]?,
+ refMode: RefMode,
+ writeTypeInfo: Bool = false,
+ hasGenerics: Bool = true
+ ) throws {
+ let wrapped = value?.map { DynamicAnyValue.wrapped($0) }
+ try wrapped.foryWrite(
+ self,
+ refMode: refMode,
+ writeTypeInfo: writeTypeInfo,
+ hasGenerics: hasGenerics
+ )
+ }
+
+ func writeStringAnyMap(
+ _ value: [String: Any]?,
+ refMode: RefMode,
+ writeTypeInfo: Bool = false,
+ hasGenerics: Bool = true
+ ) throws {
+ let wrapped = value?.reduce(into: [String: DynamicAnyValue]()) { result, pair in
+ result[pair.key] = DynamicAnyValue.wrapped(pair.value)
+ }
+ try wrapped.foryWrite(
+ self,
+ refMode: refMode,
+ writeTypeInfo: writeTypeInfo,
+ hasGenerics: hasGenerics
+ )
+ }
+
+ func writeInt32AnyMap(
+ _ value: [Int32: Any]?,
+ refMode: RefMode,
+ writeTypeInfo: Bool = false,
+ hasGenerics: Bool = true
+ ) throws {
+ let wrapped = value?.reduce(into: [Int32: DynamicAnyValue]()) { result, pair in
+ result[pair.key] = DynamicAnyValue.wrapped(pair.value)
+ }
+ try wrapped.foryWrite(
+ self,
+ refMode: refMode,
+ writeTypeInfo: writeTypeInfo,
+ hasGenerics: hasGenerics
+ )
+ }
+
+ func writeAnyHashableAnyMap(
+ _ value: [AnyHashable: Any]?,
+ refMode: RefMode,
+ writeTypeInfo: Bool = false,
+ hasGenerics: Bool = true
+ ) throws {
+ let wrapped = value?.reduce(into: [AnyHashable: DynamicAnyValue]()) { result, pair in
+ result[pair.key] = DynamicAnyValue.wrapped(pair.value)
+ }
+ try wrapped.foryWrite(
+ self,
+ refMode: refMode,
+ writeTypeInfo: writeTypeInfo,
+ hasGenerics: hasGenerics
+ )
+ }
+}
diff --git a/swift/Sources/ForyMacro/ForyObjectMacro.swift b/swift/Sources/ForyMacro/ForyObjectMacro.swift
index 35774b2b3..8a26ad6d0 100644
--- a/swift/Sources/ForyMacro/ForyObjectMacro.swift
+++ b/swift/Sources/ForyMacro/ForyObjectMacro.swift
@@ -48,7 +48,7 @@ public struct ForyObjectMacro: MemberMacro, ExtensionMacro {
"""
let referenceTrackDecl: DeclSyntax? = parsed.isClass ? """
- \(raw: accessPrefix)static var isReferenceTrackableType: Bool { true }
+ \(raw: accessPrefix)static var isRefType: Bool { true }
""" : nil
let schemaHashDecl: DeclSyntax = DeclSyntax(stringLiteral: buildSchemaHashDecl(fields: parsed.fields))
@@ -59,7 +59,9 @@ public struct ForyObjectMacro: MemberMacro, ExtensionMacro {
stringLiteral: buildDefaultDecl(isClass: parsed.isClass, fields: parsed.fields, accessPrefix: accessPrefix)
)
let writeWrapperDecl: DeclSyntax = DeclSyntax(stringLiteral: buildWriteWrapperDecl(accessPrefix: accessPrefix))
- let readWrapperDecl: DeclSyntax = DeclSyntax(stringLiteral: buildReadWrapperDecl(accessPrefix: accessPrefix))
+ let readWrapperDecl: DeclSyntax? = parsed.isClass
+ ? DeclSyntax(stringLiteral: buildClassReadWrapperDecl(accessPrefix: accessPrefix))
+ : nil
let writeDecl: DeclSyntax = DeclSyntax(
stringLiteral: buildWriteDataDecl(sortedFields: sortedFields, accessPrefix: accessPrefix)
)
@@ -71,7 +73,14 @@ public struct ForyObjectMacro: MemberMacro, ExtensionMacro {
accessPrefix: accessPrefix
)
)
-
+ let readCompatibleDecl: DeclSyntax = DeclSyntax(
+ stringLiteral: buildReadCompatibleDataDecl(
+ isClass: parsed.isClass,
+ fields: parsed.fields,
+ sortedFields: sortedFields,
+ accessPrefix: accessPrefix
+ )
+ )
return [
staticTypeIDDecl,
referenceTrackDecl,
@@ -81,7 +90,8 @@ public struct ForyObjectMacro: MemberMacro, ExtensionMacro {
writeWrapperDecl,
readWrapperDecl,
writeDecl,
- readDecl
+ readDecl,
+ readCompatibleDecl
].compactMap { $0 }
}
@@ -131,7 +141,7 @@ private enum FieldEncoding: String {
case tagged
}
-private enum DynamicAnyCodecKind {
+enum DynamicAnyCodecKind {
case anyValue
case anyHashableValue
case anyList
@@ -140,7 +150,7 @@ private enum DynamicAnyCodecKind {
case anyHashableAnyMap
}
-private struct ParsedField {
+struct ParsedField {
let name: String
let typeText: String
let originalIndex: Int
@@ -318,7 +328,6 @@ private func buildOrdinalEnumDecls(_ cases: [ParsedEnumCase], accessPrefix: Stri
\(raw: accessPrefix)static var staticTypeId: TypeId { .enumType }
"""
let writeWrapperDecl: DeclSyntax = DeclSyntax(stringLiteral: buildWriteWrapperDecl(accessPrefix: accessPrefix))
- let readWrapperDecl: DeclSyntax = DeclSyntax(stringLiteral: buildReadWrapperDecl(accessPrefix: accessPrefix))
let writeDecl: DeclSyntax = DeclSyntax(
stringLiteral: """
@@ -346,7 +355,7 @@ private func buildOrdinalEnumDecls(_ cases: [ParsedEnumCase], accessPrefix: Stri
"""
)
- return [defaultDecl, staticTypeIDDecl, writeWrapperDecl, readWrapperDecl, writeDecl, readDecl]
+ return [defaultDecl, staticTypeIDDecl, writeWrapperDecl, writeDecl, readDecl]
}
private func buildTaggedUnionEnumDecls(_ cases: [ParsedEnumCase], accessPrefix: String) -> [DeclSyntax] {
@@ -403,7 +412,6 @@ private func buildTaggedUnionEnumDecls(_ cases: [ParsedEnumCase], accessPrefix:
\(raw: accessPrefix)static var staticTypeId: TypeId { .typedUnion }
"""
let writeWrapperDecl: DeclSyntax = DeclSyntax(stringLiteral: buildWriteWrapperDecl(accessPrefix: accessPrefix))
- let readWrapperDecl: DeclSyntax = DeclSyntax(stringLiteral: buildReadWrapperDecl(accessPrefix: accessPrefix))
let writeDecl: DeclSyntax = DeclSyntax(
stringLiteral: """
@@ -431,7 +439,7 @@ private func buildTaggedUnionEnumDecls(_ cases: [ParsedEnumCase], accessPrefix:
"""
)
- return [defaultDecl, staticTypeIDDecl, writeWrapperDecl, readWrapperDecl, writeDecl, readDecl]
+ return [defaultDecl, staticTypeIDDecl, writeWrapperDecl, writeDecl, readDecl]
}
private func enumCasePattern(_ enumCase: ParsedEnumCase) -> String {
@@ -904,33 +912,37 @@ private func buildSchemaHashDecl(fields: [ParsedField]) -> String {
}
private func buildCompatibleTypeMetaFieldsDecl(sortedFields: [ParsedField], accessPrefix: String) -> String {
- func fieldIDExpression(_ field: ParsedField) -> String {
- if let fieldID = field.fieldID {
- return "\(fieldID)"
- }
- return "nil"
+ let disabledExpr = compatibleTypeMetaFieldsExpr(sortedFields: sortedFields, trackRefExpression: "false")
+ let enabledExpr = compatibleTypeMetaFieldsExpr(sortedFields: sortedFields, trackRefExpression: "true")
+ return """
+ private static let __foryFieldsInfoTrackRefDisabled: [TypeMeta.FieldInfo] = \(disabledExpr)
+ private static let __foryFieldsInfoTrackRefEnabled: [TypeMeta.FieldInfo] = \(enabledExpr)
+
+ \(accessPrefix)static func foryFieldsInfo(trackRef: Bool) -> [TypeMeta.FieldInfo] {
+ trackRef ? Self.__foryFieldsInfoTrackRefEnabled : Self.__foryFieldsInfoTrackRefDisabled
}
+ """
+}
- let fieldInfosTrackRefDisabled = sortedFields.map { field in
- "TypeMetaFieldInfo(fieldID: \(fieldIDExpression(field)), fieldName: \"\(field.name)\", fieldType: \(compatibleTypeMetaFieldExpression(field, trackRefExpression: "false")))"
+private func compatibleTypeMetaFieldsExpr(
+ sortedFields: [ParsedField],
+ trackRefExpression: String
+) -> String {
+ let fieldInfos = sortedFields.map { field in
+ let fieldTypeExpr = compatibleTypeMetaFieldExpression(field, trackRefExpression: trackRefExpression)
+ return "TypeMeta.FieldInfo(fieldID: \(compatibleFieldIDExpr(field)), fieldName: \"\(field.name)\", fieldType: \(fieldTypeExpr))"
}
- let fieldInfosTrackRefEnabled = sortedFields.map { field in
- "TypeMetaFieldInfo(fieldID: \(fieldIDExpression(field)), fieldName: \"\(field.name)\", fieldType: \(compatibleTypeMetaFieldExpression(field, trackRefExpression: "true")))"
+ guard !fieldInfos.isEmpty else {
+ return "[]"
}
- let disabledExpr = fieldInfosTrackRefDisabled.isEmpty
- ? "[]"
- : "[\n \(fieldInfosTrackRefDisabled.joined(separator: ",\n "))\n ]"
- let enabledExpr = fieldInfosTrackRefEnabled.isEmpty
- ? "[]"
- : "[\n \(fieldInfosTrackRefEnabled.joined(separator: ",\n "))\n ]"
- return """
- private static let __foryCompatibleTypeMetaFieldsTrackRefDisabled: [TypeMetaFieldInfo] = \(disabledExpr)
- private static let __foryCompatibleTypeMetaFieldsTrackRefEnabled: [TypeMetaFieldInfo] = \(enabledExpr)
+ return "[\n \(fieldInfos.joined(separator: ",\n "))\n ]"
+}
- \(accessPrefix)static func foryCompatibleTypeMetaFields(trackRef: Bool) -> [TypeMetaFieldInfo] {
- trackRef ? Self.__foryCompatibleTypeMetaFieldsTrackRefEnabled : Self.__foryCompatibleTypeMetaFieldsTrackRefDisabled
+private func compatibleFieldIDExpr(_ field: ParsedField) -> String {
+ if let fieldID = field.fieldID {
+ return "\(fieldID)"
}
- """
+ return "nil"
}
private func buildSchemaFingerprint(fields: [ParsedField], trackRefExpression: String) -> String {
@@ -953,7 +965,7 @@ private func buildSchemaFingerprint(fields: [ParsedField], trackRefExpression: S
trackRefExpr = "0"
}
} else {
- trackRefExpr = "((\(trackRefExpression)) && \(field.typeText).isReferenceTrackableType) ? 1 : 0"
+ trackRefExpr = "((\(trackRefExpression)) && \(field.typeText).isRefType) ? 1 : 0"
}
return "\"\(field.schemaIdentifier),\(typeID),\\(\(trackRefExpr)),\(nullable);\""
}
@@ -992,10 +1004,7 @@ private func buildDefaultDecl(isClass: Bool, fields: [ParsedField], accessPrefix
let args = fields
.sorted(by: { $0.originalIndex < $1.originalIndex })
.map { field in
- if field.dynamicAnyCodec != nil {
- return "\(field.name): \(dynamicAnyDefaultExpr(typeText: field.typeText))"
- }
- return "\(field.name): \(field.typeText).foryDefault()"
+ "\(field.name): \(fieldDefaultExpr(field))"
}
.joined(separator: ",\n ")
@@ -1016,18 +1025,19 @@ private func buildWriteWrapperDecl(accessPrefix: String) -> String {
writeTypeInfo: Bool,
hasGenerics: Bool
) throws {
+ let __buffer = context.buffer
if refMode != .none {
- if refMode == .tracking, Self.isReferenceTrackableType, let object = self as AnyObject? {
- if context.refWriter.tryWriteReference(buffer: context.buffer, object: object) {
+ if refMode == .tracking, Self.isRefType, let object = self as AnyObject? {
+ if context.refWriter.tryWriteRef(buffer: __buffer, object: object) {
return
}
} else {
- context.buffer.writeInt8(RefFlag.notNullValue.rawValue)
+ __buffer.writeInt8(RefFlag.notNullValue.rawValue)
}
}
if writeTypeInfo {
- try Self.foryWriteTypeInfo(context)
+ try Self.foryWriteStaticTypeInfo(context)
}
try foryWriteData(context, hasGenerics: hasGenerics)
@@ -1035,90 +1045,49 @@ private func buildWriteWrapperDecl(accessPrefix: String) -> String {
"""
}
-private func buildReadWrapperDecl(accessPrefix: String) -> String {
- """
- \(accessPrefix)static func foryRead(
- _ context: ReadContext,
- refMode: RefMode,
- readTypeInfo: Bool
- ) throws -> Self {
- if refMode != .none {
- let rawFlag = try context.buffer.readInt8()
- guard let flag = RefFlag(rawValue: rawFlag) else {
- throw ForyError.refError("invalid ref flag \\(rawFlag)")
- }
-
- switch flag {
- case .null:
- return Self.foryDefault()
- case .ref:
- let refID = try context.buffer.readVarUInt32()
- return try context.refReader.readRef(refID, as: Self.self)
- case .refValue:
- let reservedRefID = context.refReader.reserveRefID()
- context.pushPendingReference(reservedRefID)
- if readTypeInfo {
- try Self.foryReadTypeInfo(context)
- }
- let value = try Self.foryReadData(context)
- context.finishPendingReferenceIfNeeded(value)
- context.popPendingReference()
- return value
- case .notNullValue:
- break
- }
- }
-
- if readTypeInfo {
- try Self.foryReadTypeInfo(context)
- }
- return try Self.foryReadData(context)
- }
- """
-}
-
private func buildWriteDataDecl(sortedFields: [ParsedField], accessPrefix: String) -> String {
- let schemaFieldLines = sortedFields.map { field in
- schemaWriteLine(for: field)
+ let allFieldLines = sortedFields.map { field in
+ writeLine(for: field)
}
- let compatibleLines = sortedFields.map { field in
- compatibleWriteLine(for: field)
+ let leadingPrimitiveFields = leadingPrimitiveFastPathFields(sortedFields)
+ let remainingFieldLines = sortedFields.dropFirst(leadingPrimitiveFields.count).map { field in
+ writeLine(for: field)
}
- var schemaBodyLines = [
+ var schemaHeaderLines = [
"if context.checkClassVersion {",
- " context.buffer.writeInt32(Int32(bitPattern: Self.__forySchemaHash(context.trackRef)))",
+ " __buffer.writeInt32(Int32(bitPattern: Self.__forySchemaHash(context.trackRef)))",
"}"
]
let primitiveReserveBytes = schemaPrimitiveReserveBytes(sortedFields)
if primitiveReserveBytes > 0 {
- schemaBodyLines.insert(
- "context.buffer.reserve(\(primitiveReserveBytes) + (context.checkClassVersion ? 4 : 0))",
+ schemaHeaderLines.insert(
+ "__buffer.reserve(\(primitiveReserveBytes) + (context.checkClassVersion ? 4 : 0))",
at: 0
)
}
- if schemaFieldLines.isEmpty {
- schemaBodyLines.append("_ = hasGenerics")
- } else {
- schemaBodyLines.append(contentsOf: schemaFieldLines)
+ let schemaHeader = schemaHeaderLines.joined(separator: "\n ")
+ let primitiveFastWriteBlock = buildPrimitiveFastWriteBlock(leadingPrimitiveFields)
+ var fastFieldLines: [String] = []
+ if let primitiveFastWriteBlock {
+ fastFieldLines.append(primitiveFastWriteBlock)
}
- let schemaBody = schemaBodyLines.joined(separator: "\n ")
+ fastFieldLines.append(contentsOf: remainingFieldLines)
- let compatibleWriteLines: String
- if compatibleLines.isEmpty {
- compatibleWriteLines = "\n _ = hasGenerics"
+ let fieldBody: String
+ if allFieldLines.isEmpty {
+ fieldBody = "_ = hasGenerics"
} else {
- let joinedCompatibleLines = compatibleLines.joined(separator: "\n ")
- compatibleWriteLines = "\n \(joinedCompatibleLines)"
+ fieldBody = fastFieldLines.joined(separator: "\n ")
}
return """
@inline(__always)
\(accessPrefix)func foryWriteData(_ context: WriteContext, hasGenerics: Bool) throws {
- if context.compatible {
- \(compatibleWriteLines)
- return
+ let __buffer = context.buffer
+ if !context.compatible {
+ \(schemaHeader)
}
- \(schemaBody)
+ \(fieldBody)
}
"""
}
@@ -1168,7 +1137,7 @@ private func schemaPrimitiveReserveBytes(for field: ParsedField) -> Int {
}
}
-private func schemaWriteLine(for field: ParsedField) -> String {
+private func writeLine(for field: ParsedField) -> String {
if let dynamicAnyCodec = field.dynamicAnyCodec {
let refMode = fieldRefModeExpression(field)
return dynamicAnyWriteLine(
@@ -1199,291 +1168,78 @@ private func schemaWriteLine(for field: ParsedField) -> String {
)
"""
}
- if !field.isOptional, field.typeID != 27 {
+ if !field.isOptional, !compatibleFieldNeedsTypeInfo(field) {
if let primitiveLine = primitiveSchemaWriteLine(field) {
return primitiveLine
}
return "try self.\(field.name).foryWriteData(context, hasGenerics: \(hasGenerics))"
}
let refMode = fieldRefModeExpression(field)
- return "try self.\(field.name).foryWrite(context, refMode: \(refMode), writeTypeInfo: false, hasGenerics: \(hasGenerics))"
-}
-
-private func compatibleWriteLine(for field: ParsedField) -> String {
- let refMode = fieldRefModeExpression(field)
- if let dynamicAnyCodec = field.dynamicAnyCodec {
- return dynamicAnyWriteLine(
- field: field,
- dynamicAnyCodec: dynamicAnyCodec,
- refModeExpr: refMode
- )
- }
- let hasGenerics = field.isCollection ? "true" : "false"
- if let codecType = field.customCodecType {
- if field.isOptional {
- return """
- try (self.\(field.name).map { \(codecType)(rawValue: $0) }).foryWrite(
- context,
- refMode: \(refMode),
- writeTypeInfo: false,
- hasGenerics: false
- )
- """
- }
- return """
- try \(codecType)(rawValue: self.\(field.name)).foryWrite(
- context,
- refMode: \(refMode),
- writeTypeInfo: false,
- hasGenerics: false
- )
- """
- }
+ let writeTypeInfoExpr = "context.compatible ? TypeId.needsTypeInfoForField(\(field.typeText).staticTypeId) : false"
return """
try self.\(field.name).foryWrite(
context,
refMode: \(refMode),
- writeTypeInfo: TypeId.needsTypeInfoForField(\(field.typeText).staticTypeId),
+ writeTypeInfo: \(writeTypeInfoExpr),
hasGenerics: \(hasGenerics)
)
"""
}
-private func buildReadDataDecl(
- isClass: Bool,
- fields: [ParsedField],
- sortedFields: [ParsedField],
- accessPrefix: String
-) -> String {
- if isClass {
- let assignLines = sortedFields.map { field -> String in
- let refMode = fieldRefModeExpression(field)
- let valueExpr = readFieldExpr(
- field,
- refModeExpr: refMode,
- readTypeInfoExpr: "false"
- )
- return "value.\(field.name) = \(valueExpr)"
- }.joined(separator: "\n ")
- let compatibleCases = sortedFields.map { field -> String in
- let valueExpr = readFieldExpr(
- field,
- refModeExpr: "RefMode.from(nullable: remoteField.fieldType.nullable, trackRef: remoteField.fieldType.trackRef)",
- readTypeInfoExpr: "TypeId.needsTypeInfoForField(TypeId(rawValue: remoteField.fieldType.typeID) ?? .unknown)"
- )
- return "case \"\(field.fieldIdentifier)\": value.\(field.name) = \(valueExpr)"
- }.joined(separator: "\n ")
-
- return """
- @inline(__always)
- \(accessPrefix)static func foryReadData(_ context: ReadContext) throws -> Self {
- if context.compatible, let typeMeta = context.consumeCompatibleTypeMetaIfPresent(for: Self.self) {
- let value = Self.init()
- context.bindPendingReference(value)
- for remoteField in typeMeta.fields {
- switch remoteField.fieldName {
- \(compatibleCases)
- default:
- try FieldSkipper.skipFieldValue(context: context, fieldType: remoteField.fieldType)
- }
- }
- return value
- }
- if context.checkClassVersion {
- let __schemaHash = UInt32(bitPattern: try context.buffer.readInt32())
- let __expectedHash = Self.__forySchemaHash(context.trackRef)
- if __schemaHash != __expectedHash {
- throw ForyError.invalidData("class version hash mismatch: expected \\(__expectedHash), got \\(__schemaHash)")
- }
- }
- let value = Self.init()
- context.bindPendingReference(value)
- \(assignLines)
- return value
- }
- """
- }
-
- if fields.isEmpty {
- return """
- @inline(__always)
- \(accessPrefix)static func foryReadData(_ context: ReadContext) throws -> Self {
- if context.compatible, let typeMeta = context.consumeCompatibleTypeMetaIfPresent(for: Self.self) {
- for remoteField in typeMeta.fields {
- try FieldSkipper.skipFieldValue(context: context, fieldType: remoteField.fieldType)
- }
- return Self()
- }
- if context.checkClassVersion {
- let __schemaHash = UInt32(bitPattern: try context.buffer.readInt32())
- let __expectedHash = Self.__forySchemaHash(context.trackRef)
- if __schemaHash != __expectedHash {
- throw ForyError.invalidData("class version hash mismatch: expected \\(__expectedHash), got \\(__schemaHash)")
- }
- }
- return Self()
- }
- """
- }
-
- let readLines = sortedFields.map { field -> String in
- let valueExpr = schemaReadFieldExpr(field)
- return "let __\(field.name) = \(valueExpr)"
- }.joined(separator: "\n ")
-
- let ctorArgs = fields
- .sorted(by: { $0.originalIndex < $1.originalIndex })
- .map { "\($0.name): __\($0.name)" }
- .joined(separator: ",\n ")
- let compatibleDefaults = fields
- .sorted(by: { $0.originalIndex < $1.originalIndex })
- .map { field in
- if field.dynamicAnyCodec != nil {
- return "var __\(field.name): \(field.typeText) = \(dynamicAnyDefaultExpr(typeText: field.typeText))"
- }
- return "var __\(field.name) = \(field.typeText).foryDefault()"
- }
- .joined(separator: "\n ")
- let compatibleCases = sortedFields.map { field -> String in
- let valueExpr = readFieldExpr(
- field,
- refModeExpr: "RefMode.from(nullable: remoteField.fieldType.nullable, trackRef: remoteField.fieldType.trackRef)",
- readTypeInfoExpr: "TypeId.needsTypeInfoForField(TypeId(rawValue: remoteField.fieldType.typeID) ?? .unknown)"
- )
- return "case \"\(field.fieldIdentifier)\": __\(field.name) = \(valueExpr)"
- }.joined(separator: "\n ")
-
- return """
- @inline(__always)
- \(accessPrefix)static func foryReadData(_ context: ReadContext) throws -> Self {
- if context.compatible, let typeMeta = context.consumeCompatibleTypeMetaIfPresent(for: Self.self) {
- \(compatibleDefaults)
- for remoteField in typeMeta.fields {
- switch remoteField.fieldName {
- \(compatibleCases)
- default:
- try FieldSkipper.skipFieldValue(context: context, fieldType: remoteField.fieldType)
- }
- }
- return Self(
- \(ctorArgs)
- )
- }
- if context.checkClassVersion {
- let __schemaHash = UInt32(bitPattern: try context.buffer.readInt32())
- let __expectedHash = Self.__forySchemaHash(context.trackRef)
- if __schemaHash != __expectedHash {
- throw ForyError.invalidData("class version hash mismatch: expected \\(__expectedHash), got \\(__schemaHash)")
- }
- }
- \(readLines)
- return Self(
- \(ctorArgs)
- )
- }
- """
-}
-
-private func readFieldExpr(
- _ field: ParsedField,
- refModeExpr: String,
- readTypeInfoExpr: String
-) -> String {
- if let dynamicAnyCodec = field.dynamicAnyCodec {
- return dynamicAnyReadExpr(
- field: field,
- dynamicAnyCodec: dynamicAnyCodec,
- refModeExpr: refModeExpr,
- readTypeInfoExpr: readTypeInfoExpr
- )
- }
- if let codecType = field.customCodecType {
- if field.isOptional {
- return "try \(codecType)?.foryRead(context, refMode: \(refModeExpr), readTypeInfo: false)?.rawValue"
- }
- return "try \(codecType).foryRead(context, refMode: \(refModeExpr), readTypeInfo: false).rawValue"
- }
- return "try \(field.typeText).foryRead(context, refMode: \(refModeExpr), readTypeInfo: \(readTypeInfoExpr))"
-}
-
-private func schemaReadFieldExpr(_ field: ParsedField) -> String {
- if field.dynamicAnyCodec != nil || field.customCodecType != nil || field.isOptional || field.typeID == 27 {
- let refMode = fieldRefModeExpression(field)
- return readFieldExpr(
- field,
- refModeExpr: refMode,
- readTypeInfoExpr: "false"
- )
- }
- if let primitiveExpr = primitiveSchemaReadExpr(field) {
- return primitiveExpr
- }
- return "try \(field.typeText).foryReadData(context)"
-}
-
-private func primitiveSchemaWriteLine(_ field: ParsedField) -> String? {
- let type = trimType(field.typeText)
- switch type {
- case "Bool":
- return "context.buffer.writeUInt8(self.\(field.name) ? 1 : 0)"
- case "Int8":
- return "context.buffer.writeInt8(self.\(field.name))"
- case "Int16":
- return "context.buffer.writeInt16(self.\(field.name))"
- case "Int32":
- return "context.buffer.writeVarInt32(self.\(field.name))"
- case "Int64":
- return "context.buffer.writeVarInt64(self.\(field.name))"
- case "Int":
- return "context.buffer.writeVarInt64(Int64(self.\(field.name)))"
- case "UInt8":
- return "context.buffer.writeUInt8(self.\(field.name))"
- case "UInt16":
- return "context.buffer.writeUInt16(self.\(field.name))"
- case "UInt32":
- return "context.buffer.writeVarUInt32(self.\(field.name))"
- case "UInt64":
- return "context.buffer.writeVarUInt64(self.\(field.name))"
- case "UInt":
- return "context.buffer.writeVarUInt64(UInt64(self.\(field.name)))"
- case "Float":
- return "context.buffer.writeFloat32(self.\(field.name))"
- case "Double":
- return "context.buffer.writeFloat64(self.\(field.name))"
+private enum MacroTypeId {
+ static let unknown: UInt32 = 0
+ static let compatibleStruct: UInt32 = 27
+ static let namedStruct: UInt32 = 28
+ static let namedCompatibleStruct: UInt32 = 29
+ static let enumType: UInt32 = 30
+ static let namedEnum: UInt32 = 31
+ static let ext: UInt32 = 32
+}
+
+func compatibleFieldNeedsTypeInfo(_ field: ParsedField) -> Bool {
+ switch field.typeID {
+ case MacroTypeId.unknown,
+ MacroTypeId.compatibleStruct,
... 1497 lines suppressed ...
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org