Skip to content

Commit

Permalink
Optimized unkeyed array decode.
Browse files Browse the repository at this point in the history
  • Loading branch information
DimaRU committed Feb 1, 2024
1 parent e276c02 commit 4c5ad06
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 97 deletions.
112 changes: 62 additions & 50 deletions Sources/CDRCodable/Decoder/CDRDecoder.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import Foundation

/**
An object that decodes instances of a data type from CDRCodable objects.
*/
/// An object that decodes instances of a data type from Common Data Representation binary.
final public class CDRDecoder {
public init() {}

Expand All @@ -12,53 +10,67 @@ final public class CDRDecoder {
*/
public var userInfo: [CodingUserInfoKey : Any] = [:]

/**
Returns a value of the type you specify,
decoded from a CDRCodable object.

- Parameters:
- type: The type of the value to decode
from the supplied CDRCodable object.
- data: The CDRCodable object to decode.
- Throws: `DecodingError.dataCorrupted(_:)`
if the data is not valid CDRCodable.
*/
/// Returns a value of the type you specify, decoded from a Common Data Representation binary data
/// - Parameters:
/// - type: The type of the value to decode
/// - data: Common Data Representation binary data, little endian
/// - Returns: decoded object
/// - Throws: `DecodingError.dataCorrupted(_:)` if the data is not valid
public func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable {
let decoder = _CDRDecoder(data: _CDRDecoder.DataStore(data: data))
decoder.userInfo = self.userInfo

return try T(from: decoder)
let dataStore = DataStore(data: data)
switch T.self {
case is [Double].Type: return try dataStore.readArray(Double.self) as! T
case is [Float].Type: return try dataStore.readArray(Float.self) as! T
case is [Int].Type: return try dataStore.readArray(Int.self) as! T
case is [Int8].Type: return try dataStore.readArray(Int8.self) as! T
case is [Int16].Type: return try dataStore.readArray(Int16.self) as! T
case is [Int32].Type: return try dataStore.readArray(Int32.self) as! T
case is [Int64].Type: return try dataStore.readArray(Int64.self) as! T
case is [UInt].Type: return try dataStore.readArray(UInt.self) as! T
case is [UInt8].Type: return try dataStore.readArray(UInt8.self) as! T
case is [UInt16].Type: return try dataStore.readArray(UInt16.self) as! T
case is [UInt32].Type: return try dataStore.readArray(UInt32.self) as! T
case is [UInt64].Type: return try dataStore.readArray(UInt64.self) as! T
case is Data.Type:
return try dataStore.readData() as! T
default:
let decoder = _CDRDecoder(dataStore: dataStore, userInfo: userInfo)
return try T(from: decoder)
}
}
}

// MARK: -


final class _CDRDecoder {

final class DataStore {
let data: Data
var index: Data.Index
init(data: Data) {
self.data = data
self.index = self.data.startIndex
}
final class DataStore {
let data: Data
var index: Data.Index
var getCodingPath: () -> [CodingKey]
init(data: Data) {
self.data = data
self.index = self.data.startIndex
self.getCodingPath = { [] }
}
}

final class _CDRDecoder {
var codingPath: [CodingKey] = []
var userInfo: [CodingUserInfoKey : Any] = [:]
let userInfo: [CodingUserInfoKey : Any]
var container: _CDRDecodingContainer?
var dataStore: DataStore

init(data: DataStore) {
self.dataStore = data
init(dataStore: DataStore, userInfo: [CodingUserInfoKey : Any]) {
self.dataStore = dataStore
self.userInfo = userInfo
}
}

extension _CDRDecoder: Decoder {
func container<Key>(keyedBy type: Key.Type) -> KeyedDecodingContainer<Key> where Key : CodingKey {
precondition(self.container == nil)

let container = KeyedContainer<Key>(data: self.dataStore, codingPath: self.codingPath, userInfo: self.userInfo)
let container = KeyedContainer<Key>(dataStore: self.dataStore, codingPath: self.codingPath, userInfo: self.userInfo)
self.container = container

return KeyedDecodingContainer(container)
Expand All @@ -67,7 +79,7 @@ extension _CDRDecoder: Decoder {
func unkeyedContainer() throws -> UnkeyedDecodingContainer {
precondition(self.container == nil)

let container = UnkeyedContainer(data: self.dataStore, codingPath: self.codingPath, userInfo: self.userInfo)
let container = try UnkeyedContainer(data: self.dataStore, codingPath: self.codingPath, userInfo: self.userInfo)
self.container = container

return container
Expand All @@ -86,23 +98,23 @@ extension _CDRDecoder: Decoder {
protocol _CDRDecodingContainer {
var codingPath: [CodingKey] { get set }
var userInfo: [CodingUserInfoKey : Any] { get }
var dataStore: _CDRDecoder.DataStore { get }
var dataStore: DataStore { get }
}

extension _CDRDecodingContainer {
extension DataStore {
@inline(__always)
func align(to aligment: Int) {
let offset = self.dataStore.index % aligment
let offset = index % aligment
if offset != 0 {
self.dataStore.index = self.dataStore.index.advanced(by: aligment - offset)
index = index.advanced(by: aligment - offset)
}
}

@inline(__always)
func checkDataEnd(_ length: Int) throws {
let nextIndex = self.dataStore.index.advanced(by: length)
guard nextIndex <= self.dataStore.data.endIndex else {
let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "Unexpected end of data")
let nextIndex = index.advanced(by: length)
guard nextIndex <= data.endIndex else {
let context = DecodingError.Context(codingPath: getCodingPath(), debugDescription: "Unexpected end of data")
throw DecodingError.dataCorrupted(context)
}
}
Expand All @@ -117,27 +129,27 @@ extension _CDRDecodingContainer {
@inline(__always)
func read<T>(_ type: T.Type) throws -> T where T : Numeric {
let aligment = MemoryLayout<T>.alignment
let offset = self.dataStore.index % aligment
let offset = index % aligment
if offset != 0 {
self.dataStore.index = self.dataStore.index.advanced(by: aligment - offset)
index = index.advanced(by: aligment - offset)
}
let stride = MemoryLayout<T>.stride
try checkDataEnd(stride)
defer {
dataStore.index = dataStore.index.advanced(by: stride)
index = index.advanced(by: stride)
}
return dataStore.data.withUnsafeBytes{ $0.load(fromByteOffset: dataStore.index, as: T.self) }
return data.withUnsafeBytes{ $0.load(fromByteOffset: index, as: T.self) }
}

@inline(__always)
func readArray<T>(_ type: T.Type) throws -> [T] where T: Numeric {
let size = MemoryLayout<T>.size
let count = try readCheckBlockCount(of: size)
defer {
dataStore.index = dataStore.index.advanced(by: count * size)
index = index.advanced(by: count * size)
}
return Array<T>.init(unsafeUninitializedCapacity: count) {
dataStore.data.copyBytes(to: $0, from: dataStore.index...)
data.copyBytes(to: $0, from: index...)
$1 = count
}
}
Expand All @@ -147,10 +159,10 @@ extension _CDRDecodingContainer {
let length = try readCheckBlockCount(of: 1)

defer {
dataStore.index = dataStore.index.advanced(by: length)
index = index.advanced(by: length)
}
guard let string = String(data: dataStore.data[dataStore.index..<dataStore.index.advanced(by: length - 1)], encoding: .utf8) else {
let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "Couldn't decode string with UTF-8 encoding")
guard let string = String(data: data[index..<index.advanced(by: length - 1)], encoding: .utf8) else {
let context = DecodingError.Context(codingPath: getCodingPath(), debugDescription: "Couldn't decode string with UTF-8 encoding")
throw DecodingError.dataCorrupted(context)
}
return string
Expand All @@ -160,8 +172,8 @@ extension _CDRDecodingContainer {
func readData() throws -> Data {
let length = try readCheckBlockCount(of: 1)
defer {
dataStore.index = dataStore.index.advanced(by: length)
index = index.advanced(by: length)
}
return dataStore.data.subdata(in: dataStore.index..<dataStore.index.advanced(by: length))
return data.subdata(in: index..<index.advanced(by: length))
}
}
49 changes: 26 additions & 23 deletions Sources/CDRCodable/Decoder/KeyedDecodingContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ extension _CDRDecoder {
return self.codingPath + [key]
}

init(data: DataStore, codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) {
init(dataStore: DataStore, codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) {
self.codingPath = codingPath
self.userInfo = userInfo
self.dataStore = data
self.dataStore = dataStore
self.dataStore.getCodingPath = {
self.codingPath
}
}
}
}
Expand All @@ -29,55 +32,55 @@ extension _CDRDecoder.KeyedContainer: KeyedDecodingContainerProtocol {
}

func decode(_ type: Bool.Type, forKey key: Key) throws -> Bool {
try read(UInt8.self) != 0
try dataStore.read(UInt8.self) != 0
}

func decode(_ type: String.Type, forKey key: Key) throws -> String {
try readString()
try dataStore.readString()
}

func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T : Numeric & Decodable {
try read(T.self)
try dataStore.read(T.self)
}

func decode<T>(_ type: T.Type, forKey key: Key) throws -> T where T : Decodable {
switch T.self {
case is [Double].Type: return try readArray(Double.self) as! T
case is [Float].Type: return try readArray(Float.self) as! T
case is [Int].Type: return try readArray(Int.self) as! T
case is [Int8].Type: return try readArray(Int8.self) as! T
case is [Int16].Type: return try readArray(Int16.self) as! T
case is [Int32].Type: return try readArray(Int32.self) as! T
case is [Int64].Type: return try readArray(Int64.self) as! T
case is [UInt].Type: return try readArray(UInt.self) as! T
case is [UInt8].Type: return try readArray(UInt8.self) as! T
case is [UInt16].Type: return try readArray(UInt16.self) as! T
case is [UInt32].Type: return try readArray(UInt32.self) as! T
case is [UInt64].Type: return try readArray(UInt64.self) as! T
case is [Double].Type: return try dataStore.readArray(Double.self) as! T
case is [Float].Type: return try dataStore.readArray(Float.self) as! T
case is [Int].Type: return try dataStore.readArray(Int.self) as! T
case is [Int8].Type: return try dataStore.readArray(Int8.self) as! T
case is [Int16].Type: return try dataStore.readArray(Int16.self) as! T
case is [Int32].Type: return try dataStore.readArray(Int32.self) as! T
case is [Int64].Type: return try dataStore.readArray(Int64.self) as! T
case is [UInt].Type: return try dataStore.readArray(UInt.self) as! T
case is [UInt8].Type: return try dataStore.readArray(UInt8.self) as! T
case is [UInt16].Type: return try dataStore.readArray(UInt16.self) as! T
case is [UInt32].Type: return try dataStore.readArray(UInt32.self) as! T
case is [UInt64].Type: return try dataStore.readArray(UInt64.self) as! T
case is Data.Type:
return try readData() as! T
return try dataStore.readData() as! T
default:
let decoder = _CDRDecoder(data: self.dataStore)
let decoder = _CDRDecoder(dataStore: dataStore, userInfo: userInfo)
return try T(from: decoder)
}
}

func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer {
let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "Nested unsupported")
let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Nested unsupported")
throw DecodingError.dataCorrupted(context)
}

func nestedContainer<NestedKey>(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> where NestedKey : CodingKey {
let context = DecodingError.Context(codingPath: self.codingPath, debugDescription: "Nested unsupported")
let context = DecodingError.Context(codingPath: codingPath, debugDescription: "Nested unsupported")
throw DecodingError.dataCorrupted(context)
}

func superDecoder() throws -> Decoder {
return _CDRDecoder(data: self.dataStore)
return _CDRDecoder(dataStore: dataStore, userInfo: userInfo)
}

func superDecoder(forKey key: Key) throws -> Decoder {
let decoder = _CDRDecoder(data: self.dataStore)
let decoder = _CDRDecoder(dataStore: dataStore, userInfo: userInfo)
decoder.codingPath = [key]
return decoder
}
Expand Down
13 changes: 8 additions & 5 deletions Sources/CDRCodable/Decoder/SingleValueDecodingContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ extension _CDRDecoder {
self.codingPath = codingPath
self.userInfo = userInfo
self.dataStore = data
self.dataStore.getCodingPath = {
self.codingPath
}
}
}
}
Expand All @@ -20,23 +23,23 @@ extension _CDRDecoder.SingleValueContainer: SingleValueDecodingContainer {
}

func decode(_ type: Bool.Type) throws -> Bool {
try read(UInt8.self) != 0
try dataStore.read(UInt8.self) != 0
}

func decode(_ type: String.Type) throws -> String {
try readString()
try dataStore.readString()
}

func decode<T>(_ type: T.Type) throws -> T where T : Numeric & Decodable {
try read(T.self)
try dataStore.read(T.self)
}

func decode<T>(_ type: T.Type) throws -> T where T : Decodable {
switch type {
case is Data.Type:
return try readData() as! T
return try dataStore.readData() as! T
default:
let decoder = _CDRDecoder(data: self.dataStore)
let decoder = _CDRDecoder(dataStore: dataStore, userInfo: userInfo)
return try T(from: decoder)
}
}
Expand Down
30 changes: 11 additions & 19 deletions Sources/CDRCodable/Decoder/UnkeyedDecodingContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,20 @@ extension _CDRDecoder {
var userInfo: [CodingUserInfoKey: Any]
var dataStore: DataStore

lazy var count: Int? = {
do {
return Int(try read(UInt32.self))
} catch {
return nil
}
} ()

var currentIndex: Int = 0


init(data: DataStore, codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) {
init(data: DataStore, codingPath: [CodingKey], userInfo: [CodingUserInfoKey : Any]) throws {
self.codingPath = codingPath
self.userInfo = userInfo
self.dataStore = data
self.dataStore.getCodingPath = {
self.codingPath
}
count = Int(try dataStore.read(UInt32.self))
}


var count: Int?
var currentIndex: Int = 0
var isAtEnd: Bool {
guard let count = self.count else {
return true
}
return currentIndex >= count
currentIndex >= count!
}
}
}
Expand All @@ -39,7 +31,7 @@ extension _CDRDecoder.UnkeyedContainer: UnkeyedDecodingContainer {

func decode<T>(_ type: T.Type) throws -> T where T : Decodable {
defer { self.currentIndex += 1 }
let decoder = _CDRDecoder(data: self.dataStore)
let decoder = _CDRDecoder(dataStore: self.dataStore, userInfo: userInfo)
let value = try T(from: decoder)
return value
}
Expand All @@ -56,7 +48,7 @@ extension _CDRDecoder.UnkeyedContainer: UnkeyedDecodingContainer {
}

func superDecoder() throws -> Decoder {
return _CDRDecoder(data: self.dataStore)
return _CDRDecoder(dataStore: dataStore, userInfo: userInfo)
}
}

Expand Down

0 comments on commit 4c5ad06

Please sign in to comment.