| /* |
| * Copyright 2024 Google Inc. All rights reserved. |
| * |
| * Licensed 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 |
| |
| #if canImport(Common) |
| import Common |
| #endif |
| |
| private let twentyFourBytes: Int = 24 |
| public typealias FlexBuffersWriterBuilder = (inout FlexBuffersWriter) -> Void |
| |
| public struct FlexBuffersWriter { |
| |
| var capacity: Int { |
| _bb.capacity |
| } |
| |
| var writerIndex: Int { |
| _bb.writerIndex |
| } |
| |
| private var finished = false |
| private var hasDuplicatedKeys = false |
| private var minBitWidth: BitWidth = .w8 |
| private var _bb: _InternalByteBuffer |
| private var stack: [Value] = [] |
| private var keyPool: [Int: UInt] = [:] |
| private var stringPool: [Int: UInt] = [:] |
| private var flags: BuilderFlag |
| |
| public init(initialSize: Int = 1024, flags: BuilderFlag = .shareKeys) { |
| _bb = _InternalByteBuffer(initialSize: initialSize) |
| self.flags = flags |
| } |
| |
| /// Returns the written bytes into the ``ByteBuffer`` |
| /// |
| /// Should only be used after ``finish(offset:addPrefix:)`` is called |
| public var sizedByteArray: [UInt8] { |
| assert( |
| finished == true, |
| "function finish() should be called before accessing data") |
| return _bb.underlyingBytes |
| } |
| |
| public var sizedByteBuffer: ByteBuffer { |
| assert( |
| finished == true, |
| "function finish() should be called before accessing data") |
| return _bb.withUnsafeSlicedBytes { |
| ByteBuffer(copyingMemoryBound: $0.baseAddress!, capacity: $0.count) |
| } |
| } |
| |
| public var byteBuffer: ByteBuffer { |
| assert( |
| finished == true, |
| "function finish() should be called before accessing data") |
| return ByteBuffer(byteBuffer: _bb) |
| } |
| |
| #if !os(WASI) |
| /// Data representation of the buffer |
| /// |
| /// Should only be used after ``finish(offset:addPrefix:)`` is called |
| public var data: Data { |
| assert(finished, "Data shouldn't be called before finish()") |
| return _bb.withUnsafeSlicedBytes { ptr in |
| var data = Data() |
| data.append( |
| ptr.baseAddress!.bindMemory( |
| to: UInt8.self, |
| capacity: ptr.count), |
| count: ptr.count) |
| return data |
| } |
| } |
| #endif |
| |
| /// Resets the internal state. Automatically called before building a new flexbuffer. |
| public mutating func reset(keepingCapacity: Bool = false) { |
| _bb.clear(keepingCapacity: keepingCapacity) |
| stack.removeAll(keepingCapacity: keepingCapacity) |
| finished = false |
| minBitWidth = .w8 |
| keyPool.removeAll(keepingCapacity: keepingCapacity) |
| stringPool.removeAll(keepingCapacity: keepingCapacity) |
| } |
| |
| // MARK: - Storing root |
| public mutating func finish() { |
| assert(stack.count == 1) |
| |
| // Write root value. |
| var byteWidth = align( |
| width: stack[0].elementWidth( |
| size: writerIndex, |
| index: 0)) |
| |
| write(value: stack[0], byteWidth: byteWidth) |
| var storedType = stack[0].storedPackedType() |
| // Write root type. |
| _bb.writeBytes(&storedType, len: 1) |
| // Write root size. Normally determined by parent, but root has no parent :) |
| _bb.writeBytes(&byteWidth, len: 1) |
| |
| finished = true |
| } |
| |
| // MARK: - Vector |
| @discardableResult |
| @inline(__always) |
| public func startVector() -> Int { |
| stack.count |
| } |
| |
| @discardableResult |
| @inline(__always) |
| public mutating func startVector(key k: String) -> Int { |
| add(key: k) |
| return stack.count |
| } |
| |
| @discardableResult |
| public mutating func endVector( |
| start: Int, |
| typed: Bool = false, |
| fixed: Bool = false |
| ) -> UInt64 { |
| let vec = createVector( |
| start: start, |
| count: stack.count &- start, |
| step: 1, |
| typed: typed, |
| fixed: fixed, |
| keys: nil) |
| stack = Array(stack[..<start]) |
| stack.append(vec) |
| return vec.u |
| } |
| |
| @discardableResult |
| @inline(__always) |
| public mutating func create<T>(vector: [T]) -> Int where T: Scalar { |
| create(vector: vector, fixed: false) |
| } |
| |
| @discardableResult |
| @inline(__always) |
| public mutating func create<T>(vector: [T], key: borrowing String) -> Int |
| where T: Scalar { |
| add(key: key) |
| return create(vector: vector, fixed: false) |
| } |
| |
| @discardableResult |
| @inline(__always) |
| public mutating func createFixed<T>(vector: [T]) -> Int where T: Scalar { |
| assert(vector.count >= 2 && vector.count <= 4) |
| return create(vector: vector, fixed: true) |
| } |
| |
| @discardableResult |
| @inline(__always) |
| public mutating func createFixed<T>( |
| vector: [T], |
| key: borrowing String |
| ) -> Int where T: Scalar { |
| assert(vector.count >= 2 && vector.count <= 4) |
| add(key: key) |
| return create(vector: vector, fixed: true) |
| } |
| |
| // MARK: - Map |
| @discardableResult |
| @inline(__always) |
| public func startMap() -> Int { |
| stack.count |
| } |
| |
| @discardableResult |
| @inline(__always) |
| public mutating func startMap(key k: String) -> Int { |
| add(key: k) |
| return stack.count |
| } |
| |
| @discardableResult |
| public mutating func endMap(start: Int) -> UInt64 { |
| let len = sortMapByKeys(start: start) |
| |
| let keys = createVector( |
| start: start, |
| count: len, |
| step: 2, |
| typed: true, |
| fixed: false) |
| let vec = createVector( |
| start: start &+ 1, |
| count: len, |
| step: 2, |
| typed: false, |
| fixed: false, |
| keys: keys) |
| stack = Array(stack[..<start]) |
| stack.append(vec) |
| return numericCast(vec.u) |
| } |
| |
| // MARK: - Write Null |
| |
| @inline(__always) |
| public mutating func addNil() { |
| stack.append(Value.nil) |
| } |
| |
| @inline(__always) |
| public mutating func addNil(key: borrowing String) { |
| add(key: key) |
| addNil() |
| } |
| |
| // MARK: - Write Bool |
| |
| @inline(__always) |
| public mutating func add(bool: borrowing Bool) { |
| stack.append(Value(bool: bool)) |
| } |
| |
| @inline(__always) |
| public mutating func add(bool: borrowing Bool, key: borrowing String) { |
| add(key: key) |
| add(bool: bool) |
| } |
| |
| // MARK: - Write UInt |
| |
| @inline(__always) |
| public mutating func add(uint8 value: UInt8) { |
| add(uint64: numericCast(value)) |
| } |
| |
| @inline(__always) |
| public mutating func add(uint8 value: UInt8, key: borrowing String) { |
| add(key: key) |
| add(uint64: numericCast(value)) |
| } |
| |
| @inline(__always) |
| public mutating func add(uint16 value: UInt16) { |
| add(uint64: numericCast(value)) |
| } |
| |
| @inline(__always) |
| public mutating func add(uint16 value: UInt16, key: borrowing String) { |
| add(key: key) |
| add(uint64: numericCast(value)) |
| } |
| |
| @inline(__always) |
| public mutating func add(uint32 value: UInt32) { |
| add(uint64: numericCast(value)) |
| } |
| |
| @inline(__always) |
| public mutating func add(uint32 value: UInt32, key: borrowing String) { |
| add(key: key) |
| add(uint64: numericCast(value)) |
| } |
| |
| @inline(__always) |
| public mutating func add(uint value: UInt) { |
| add(uint64: numericCast(value)) |
| } |
| |
| @inline(__always) |
| public mutating func add(uint value: UInt, key: borrowing String) { |
| add(key: key) |
| add(uint64: numericCast(value)) |
| } |
| |
| @inline(__always) |
| public mutating func add(uint64 value: borrowing UInt64) { |
| stack.append(Value(v: value, type: .uint, bitWidth: widthU(value))) |
| } |
| |
| @inline(__always) |
| public mutating func add( |
| uint64 value: borrowing UInt64, |
| key: borrowing String |
| ) { |
| add(key: key) |
| add(uint64: value) |
| } |
| |
| @inline(__always) |
| public mutating func indirect(uint64 val: borrowing UInt64) { |
| pushIndirect(value: val, type: .indirectUInt, bitWidth: widthU(val)) |
| } |
| |
| @inline(__always) |
| public mutating func indirect( |
| uint64 val: borrowing UInt64, |
| key: borrowing String |
| ) { |
| add(key: key) |
| indirect(uint64: val) |
| } |
| |
| @inline(__always) |
| public mutating func indirect( |
| uint val: borrowing UInt, |
| key: borrowing String |
| ) { |
| add(key: key) |
| indirect(uint64: numericCast(val)) |
| } |
| |
| // MARK: - Write Int |
| |
| @inline(__always) |
| public mutating func add(int8 value: Int8) { |
| add(int64: numericCast(value)) |
| } |
| |
| @inline(__always) |
| public mutating func add(int8 value: Int8, key: borrowing String) { |
| add(key: key) |
| add(int64: numericCast(value)) |
| } |
| |
| @inline(__always) |
| public mutating func add(int16 value: Int16) { |
| add(int64: numericCast(value)) |
| } |
| |
| @inline(__always) |
| public mutating func add(int16 value: Int16, key: borrowing String) { |
| add(key: key) |
| add(int64: numericCast(value)) |
| } |
| |
| @inline(__always) |
| public mutating func add(int32 value: Int32) { |
| add(int64: numericCast(value)) |
| } |
| |
| @inline(__always) |
| public mutating func add(int32 value: Int32, key: borrowing String) { |
| add(key: key) |
| add(int64: numericCast(value)) |
| } |
| |
| @inline(__always) |
| public mutating func add(int value: Int) { |
| add(int64: numericCast(value)) |
| } |
| |
| @inline(__always) |
| public mutating func add(int value: Int, key: borrowing String) { |
| add(key: key) |
| add(int64: numericCast(value)) |
| } |
| |
| @inline(__always) |
| public mutating func add(int64 value: borrowing Int64) { |
| stack.append(Value(v: value, type: .int, bitWidth: widthI(value))) |
| } |
| |
| @inline(__always) |
| public mutating func add( |
| int64 value: borrowing Int64, |
| key: borrowing String |
| ) { |
| add(key: key) |
| add(int64: value) |
| } |
| |
| @inline(__always) |
| public mutating func indirect(int64 val: borrowing Int64) { |
| pushIndirect(value: val, type: .indirectInt, bitWidth: widthI(val)) |
| } |
| |
| @inline(__always) |
| public mutating func indirect( |
| int64 val: borrowing Int64, |
| key: borrowing String |
| ) { |
| add(key: key) |
| indirect(int64: val) |
| } |
| |
| @inline(__always) |
| public mutating func indirect( |
| int val: borrowing Int, |
| key: borrowing String |
| ) { |
| add(key: key) |
| indirect(int64: numericCast(val)) |
| } |
| |
| // MARK: - Write Floats |
| |
| @inline(__always) |
| public mutating func add(float32 value: borrowing Float32) { |
| stack.append(Value(v: Double(value), type: .float, bitWidth: .w32)) |
| } |
| |
| @inline(__always) |
| public mutating func add( |
| float32 value: borrowing Float32, |
| key: borrowing String |
| ) { |
| add(key: key) |
| add(float32: value) |
| } |
| |
| @inline(__always) |
| public mutating func indirect(float32 val: borrowing Float32) { |
| pushIndirect(value: val, type: .indirectFloat, bitWidth: .w32) |
| } |
| |
| @inline(__always) |
| public mutating func indirect( |
| float32 val: borrowing Float32, |
| key: borrowing String |
| ) { |
| add(key: key) |
| indirect(float32: val) |
| } |
| |
| @inline(__always) |
| public mutating func add(double value: borrowing Double) { |
| stack.append(Value(v: value, type: .float, bitWidth: widthF(value))) |
| } |
| |
| @inline(__always) |
| public mutating func add( |
| double value: borrowing Double, |
| key: borrowing String |
| ) { |
| add(key: key) |
| add(double: value) |
| } |
| |
| @inline(__always) |
| public mutating func indirect(double val: borrowing Double) { |
| pushIndirect(value: val, type: .indirectFloat, bitWidth: widthF(val)) |
| } |
| |
| @inline(__always) |
| public mutating func indirect( |
| double val: borrowing Double, |
| key: borrowing String |
| ) { |
| add(key: key) |
| indirect(double: val) |
| } |
| |
| // MARK: - Writing strings |
| @inline(__always) |
| public mutating func add(string: borrowing String, key: borrowing String) { |
| add(key: key) |
| write(str: string, len: string.count) |
| } |
| |
| @inline(__always) |
| public mutating func add(string: borrowing String) { |
| write(str: string, len: string.count) |
| } |
| |
| // MARK: - Writing Blobs |
| @discardableResult |
| @inline(__always) |
| public mutating func add<T>( |
| blob: borrowing T, |
| length l: Int |
| ) -> UInt where T: ContiguousBytes { |
| storeBlob(blob, len: l, type: .blob) |
| } |
| |
| @discardableResult |
| @inline(__always) |
| public mutating func add<T>( |
| blob: borrowing T, |
| key: borrowing String, |
| length l: Int |
| ) -> UInt where T: ContiguousBytes { |
| add(key: key) |
| return storeBlob(blob, len: l, type: .blob) |
| } |
| |
| // MARK: - Reuse Values |
| @inline(__always) |
| public func lastValue() -> Value? { |
| return stack.last |
| } |
| |
| @inline(__always) |
| public mutating func reuse(value: Value) { |
| stack.append(value) |
| } |
| |
| @inline(__always) |
| public mutating func reuse(value: Value, key: borrowing String) { |
| add(key: key) |
| reuse(value: value) |
| } |
| |
| // MARK: - Private - |
| |
| // MARK: Writing to buffer |
| |
| @usableFromInline |
| mutating func write(value: Value, byteWidth: Int) { |
| switch value.type { |
| case .null, .int: write(value: value.i, byteWidth: byteWidth) |
| case .bool, .uint: write(value: value.u, byteWidth: byteWidth) |
| case .float: write(double: value.f, byteWidth: byteWidth) |
| default: |
| write(offset: value.u, byteWidth: byteWidth) |
| } |
| } |
| |
| @usableFromInline |
| mutating func pushIndirect<T>( |
| value: T, |
| type: FlexBufferType, |
| bitWidth: BitWidth |
| ) { |
| let byteWidth = align(width: bitWidth) |
| let iloc = writerIndex |
| _bb.ensureSpace(size: byteWidth) |
| |
| _bb.write(value, len: byteWidth) |
| stack.append( |
| Value( |
| sloc: .u(numericCast(iloc)), |
| type: type, |
| bitWidth: bitWidth)) |
| } |
| |
| // MARK: Internal Writing Strings |
| |
| /// Adds a string to the buffer using swift.utf8 object |
| /// - Parameter str: String that will be added to the buffer |
| /// - Parameter len: length of the string |
| @discardableResult |
| @usableFromInline |
| mutating func write(str: borrowing String, len: Int) -> UInt { |
| let resetTo = writerIndex |
| var sloc = str.withCString { |
| storeBlob(pointer: $0, len: len, trailing: 1, type: .string) |
| } |
| |
| if flags >= .shareKeysAndStrings { |
| let loc = stringPool[str.hashValue] |
| if let loc { |
| _bb.resetWriter(to: resetTo) |
| sloc = loc |
| assert( |
| stack.count > 0, |
| "Attempting to override the location, but stack is empty") |
| stack[stack.count - 1].sloc = .u(numericCast(sloc)) |
| } else { |
| stringPool[str.hashValue] = sloc |
| } |
| } |
| return sloc |
| } |
| |
| // MARK: Write Keys |
| @discardableResult |
| @inline(__always) |
| private mutating func add(key: borrowing String) -> UInt { |
| add(key: key, len: key.count) |
| } |
| |
| @discardableResult |
| @usableFromInline |
| mutating func add(key: borrowing String, len: Int) -> UInt { |
| _bb.ensureSpace(size: len) |
| |
| var sloc: UInt = numericCast(writerIndex) |
| key.withCString { |
| _bb.writeBytes($0, len: len &+ 1) |
| } |
| |
| if flags > .shareKeys { |
| let loc = keyPool[key.hashValue] |
| if let loc { |
| _bb.resetWriter(to: Int(sloc)) |
| sloc = loc |
| } else { |
| keyPool[key.hashValue] = sloc |
| } |
| } |
| stack.append(Value(sloc: .u(numericCast(sloc)), type: .key, bitWidth: .w8)) |
| return sloc |
| } |
| |
| // MARK: - Storing Blobs |
| @usableFromInline |
| mutating func storeBlob<T>( |
| _ bytes: T, |
| len: Int, |
| type: FlexBufferType |
| ) -> UInt where T: ContiguousBytes { |
| return bytes.withUnsafeBytes { |
| storeBlob(pointer: $0.baseAddress!, len: len, type: type) |
| } |
| } |
| |
| @discardableResult |
| @usableFromInline |
| mutating func storeBlob( |
| pointer: borrowing UnsafeRawPointer, |
| len: Int, |
| trailing: Int = 0, |
| type: FlexBufferType |
| ) -> UInt { |
| _bb.ensureSpace(size: len &+ trailing) |
| let bitWidth = widthU(numericCast(len)) |
| |
| let bytes = align(width: bitWidth) |
| |
| var len = len |
| _bb.writeBytes(&len, len: bytes) |
| let sloc = writerIndex |
| |
| _bb.writeBytes(pointer, len: len &+ trailing) |
| stack.append( |
| Value( |
| sloc: .u(numericCast(sloc)), |
| type: type, |
| bitWidth: bitWidth)) |
| return numericCast(sloc) |
| } |
| |
| // MARK: Write Vectors |
| @discardableResult |
| @usableFromInline |
| mutating func create<T>(vector: [T], fixed: Bool) -> Int |
| where T: Scalar { |
| let length: UInt64 = numericCast(vector.count) |
| let vectorType = getScalarType(type: T.self) |
| let byteWidth = MemoryLayout<T>.size |
| let bitWidth = BitWidth.widthB(byteWidth) |
| |
| _bb.ensureSpace(size: vector.count &* Int(bitWidth.rawValue)) |
| |
| assert(widthU(length) <= bitWidth) |
| |
| align(width: bitWidth) |
| |
| if !fixed { |
| write(value: length, byteWidth: byteWidth) |
| } |
| let vloc = _bb.writerIndex |
| |
| for i in stride(from: 0, to: vector.count, by: 1) { |
| write(value: vector[i], byteWidth: byteWidth) |
| } |
| |
| stack.append( |
| Value( |
| sloc: .u(numericCast(vloc)), |
| type: toTypedVector(type: vectorType, length: fixed ? length : 0), |
| bitWidth: bitWidth)) |
| return vloc |
| } |
| |
| @usableFromInline |
| mutating func createVector( |
| start: Int, |
| count: Int, |
| step: Int, |
| typed: Bool, |
| fixed: Bool, |
| keys: Value? = nil |
| ) -> Value { |
| assert( |
| !fixed || typed, |
| "Typed false and fixed true is a combination not supported currently") |
| |
| var bitWidth = BitWidth.max(minBitWidth, rhs: widthU(numericCast(count))) |
| var prefixElements = 1 |
| if keys != nil { |
| /// If this vector is part of a map, we will pre-fix an offset to the keys |
| /// to this vector. |
| bitWidth = max(bitWidth, keys!.elementWidth(size: writerIndex, index: 0)) |
| prefixElements = prefixElements &+ 2 |
| } |
| var vectorType: FlexBufferType = .key |
| |
| for i in stride(from: start, to: stack.count, by: step) { |
| let elemWidth = stack[i].elementWidth( |
| size: _bb.writerIndex, |
| index: numericCast(i &- start &+ prefixElements)) |
| bitWidth = BitWidth.max(bitWidth, rhs: elemWidth) |
| guard typed else { continue } |
| if i == start { |
| vectorType = stack[i].type |
| } else { |
| assert( |
| vectorType == stack[i].type, |
| """ |
| If you get this assert you are writing a typed vector |
| with elements that are not all the same type |
| """) |
| } |
| } |
| assert( |
| !typed || isTypedVectorElementType(type: vectorType), |
| """ |
| If you get this assert, your typed types are not one of: |
| Int / UInt / Float / Key. |
| """) |
| |
| let byteWidth = align(width: bitWidth) |
| |
| let currentSize: Int = count &* step &* byteWidth |
| let requiredSize: Int = |
| if !typed { |
| // We ensure that we have enough space |
| // for loop two write operations & |
| // 24 bytes for when its not fixed, |
| // and keys isn't null. As an extra safe |
| // guard |
| (currentSize &* 2) &+ twentyFourBytes |
| } else { |
| currentSize |
| } |
| |
| _bb.ensureSpace( |
| size: requiredSize) |
| |
| if keys != nil { |
| write(offset: keys!.u, byteWidth: byteWidth) |
| write(value: UInt64.one << keys!.bitWidth.rawValue, byteWidth: byteWidth) |
| } |
| |
| if !fixed { |
| write(value: count, byteWidth: byteWidth) |
| } |
| |
| let vloc = _bb.writerIndex |
| |
| for i in stride(from: start, to: stack.count, by: step) { |
| write(value: stack[i], byteWidth: byteWidth) |
| } |
| |
| if !typed { |
| for i in stride(from: start, to: stack.count, by: step) { |
| _bb.write(stack[i].storedPackedType(width: bitWidth), len: 1) |
| } |
| } |
| |
| let type: FlexBufferType = |
| if keys != nil { |
| .map |
| } else if typed { |
| toTypedVector(type: vectorType, length: numericCast(fixed ? count : 0)) |
| } else { |
| .vector |
| } |
| |
| return Value(sloc: .u(numericCast(vloc)), type: type, bitWidth: bitWidth) |
| } |
| |
| // MARK: Write Scalar functions |
| @inline(__always) |
| private mutating func write(offset: UInt64, byteWidth: Int) { |
| let offset: UInt64 = numericCast(writerIndex) &- offset |
| assert(byteWidth == 8 || offset < UInt64.one << (byteWidth * 8)) |
| withUnsafePointer(to: offset) { |
| _bb.writeBytes($0, len: byteWidth) |
| } |
| } |
| |
| @inline(__always) |
| private mutating func write<T>(value: T, byteWidth: Int) where T: Scalar { |
| withUnsafePointer(to: value) { |
| _bb.writeBytes($0, len: byteWidth) |
| } |
| } |
| |
| @inline(__always) |
| private mutating func write(double value: Double, byteWidth: Int) { |
| switch byteWidth { |
| case 8: write(value: value, byteWidth: byteWidth) |
| case 4: write(value: Float(value), byteWidth: byteWidth) |
| default: assert(false, "Should never reach here") |
| } |
| } |
| |
| // MARK: Misc functions |
| @discardableResult |
| @inline(__always) |
| private mutating func align(width: BitWidth) -> Int { |
| let bytes: Int = numericCast(UInt32.one << width.rawValue) |
| _bb.addPadding(bytes: bytes) |
| return bytes |
| } |
| |
| @usableFromInline |
| mutating func sortMapByKeys(start: Int) -> Int { |
| let len = mapElementCount(start: start) |
| for index in stride(from: start, to: stack.count, by: 2) { |
| assert(stack[index].type == .key) |
| } |
| |
| struct TwoValue: Equatable { |
| let key, value: Value |
| } |
| |
| stack[start...].withUnsafeMutableBytes { buffer in |
| var ptr = buffer.assumingMemoryBound(to: TwoValue.self) |
| ptr.sort { a, b in |
| let aMem = _bb.memory.advanced(by: numericCast(a.key.u)) |
| .assumingMemoryBound(to: CChar.self) |
| let bMem = _bb.memory.advanced(by: numericCast(b.key.u)) |
| .assumingMemoryBound(to: CChar.self) |
| let comp = strcmp(aMem, bMem) |
| if (comp == 0) && a != b { hasDuplicatedKeys = true } |
| return comp < 0 |
| } |
| } |
| return len |
| } |
| |
| @inline(__always) |
| private func mapElementCount(start: Int) -> Int { |
| var len = stack.count - start |
| assert((len & 1) == 0) |
| len /= 2 |
| return len |
| } |
| } |
| |
| // MARK: - Vectors helper functions |
| extension FlexBuffersWriter { |
| @discardableResult |
| public mutating func vector( |
| key: String, |
| _ closure: FlexBuffersWriterBuilder |
| ) -> UInt64 { |
| let start = startVector(key: key) |
| closure(&self) |
| return endVector(start: start) |
| } |
| |
| @discardableResult |
| public mutating func vector(_ closure: FlexBuffersWriterBuilder) |
| -> UInt64 |
| { |
| let start = startVector() |
| closure(&self) |
| return endVector(start: start) |
| } |
| } |
| |
| // MARK: - Maps helper functions |
| extension FlexBuffersWriter { |
| @discardableResult |
| public mutating func map( |
| key: String, |
| _ closure: FlexBuffersWriterBuilder |
| ) -> UInt64 { |
| let start = startMap(key: key) |
| closure(&self) |
| return endMap(start: start) |
| } |
| |
| @discardableResult |
| public mutating func map(_ closure: FlexBuffersWriterBuilder) |
| -> UInt64 |
| { |
| let start = startMap() |
| closure(&self) |
| return endMap(start: start) |
| } |
| } |