| /* |
| * Copyright 2021 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 |
| |
| /// ``FlatBufferBuilder`` builds a `FlatBuffer` through manipulating its internal state. |
| /// |
| /// This is done by creating a ``ByteBuffer`` that hosts the incoming data and |
| /// has a hardcoded growth limit of `2GiB` which is set by the Flatbuffers standards. |
| /// |
| /// ```swift |
| /// var builder = FlatBufferBuilder() |
| /// ``` |
| /// The builder should be always created as a variable, since it would be passed into the writers |
| /// |
| @frozen |
| public struct FlatBufferBuilder { |
| |
| /// Storage for the Vtables used in the buffer are stored in here, so they would be written later in EndTable |
| @usableFromInline internal var _vtableStorage = VTableStorage() |
| /// Flatbuffer data will be written into |
| @usableFromInline internal var _bb: ByteBuffer |
| |
| /// Reference Vtables that were already written to the buffer |
| private var _vtables: [UOffset] = [] |
| /// A check if the buffer is being written into by a different table |
| private var isNested = false |
| /// Dictonary that stores a map of all the strings that were written to the buffer |
| private var stringOffsetMap: [String: Offset] = [:] |
| /// A check to see if finish(::) was ever called to retreive data object |
| private var finished = false |
| /// A check to see if the buffer should serialize Default values |
| private var serializeDefaults: Bool |
| |
| /// Current alignment for the buffer |
| var _minAlignment: Int = 0 { |
| didSet { |
| _bb.alignment = _minAlignment |
| } |
| } |
| |
| /// Gives a read access to the buffer's size |
| public var size: UOffset { _bb.size } |
| |
| /// 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 Data( |
| bytes: _bb.memory.advanced(by: _bb.writerIndex), |
| count: _bb.capacity &- _bb.writerIndex) |
| } |
| |
| /// Returns the underlying bytes in the ``ByteBuffer`` |
| /// |
| /// Note: This should be used with caution. |
| public var fullSizedByteArray: [UInt8] { |
| let ptr = UnsafeBufferPointer( |
| start: _bb.memory.assumingMemoryBound(to: UInt8.self), |
| count: _bb.capacity) |
| return Array(ptr) |
| } |
| |
| /// Returns the written bytes into the ``ByteBuffer`` |
| /// |
| /// Should only be used after ``finish(offset:addPrefix:)`` is called |
| public var sizedByteArray: [UInt8] { |
| assert(finished, "Data shouldn't be called before finish()") |
| return _bb.underlyingBytes |
| } |
| |
| /// Returns the original ``ByteBuffer`` |
| /// |
| /// Returns the current buffer that was just created |
| /// with the offsets, and data written to it. |
| public var buffer: ByteBuffer { _bb } |
| |
| /// Returns a newly created sized ``ByteBuffer`` |
| /// |
| /// returns a new buffer that is sized to the data written |
| /// to the main buffer |
| public var sizedBuffer: ByteBuffer { |
| assert(finished, "Data shouldn't be called before finish()") |
| return ByteBuffer( |
| memory: _bb.memory.advanced(by: _bb.reader), |
| count: Int(_bb.size)) |
| } |
| |
| // MARK: - Init |
| |
| /// Initialize the buffer with a size |
| /// - Parameters: |
| /// - initialSize: Initial size for the buffer |
| /// - force: Allows default to be serialized into the buffer |
| /// |
| /// This initializes a new builder with an initialSize that would initialize |
| /// a new ``ByteBuffer``. ``FlatBufferBuilder`` by default doesnt serialize defaults |
| /// however the builder can be force by passing true for `serializeDefaults` |
| public init( |
| initialSize: Int32 = 1024, |
| serializeDefaults force: Bool = false) |
| { |
| assert(initialSize > 0, "Size should be greater than zero!") |
| guard isLitteEndian else { |
| fatalError( |
| "Reading/Writing a buffer in big endian machine is not supported on swift") |
| } |
| serializeDefaults = force |
| _bb = ByteBuffer(initialSize: Int(initialSize)) |
| } |
| |
| /// Clears the builder and the buffer from the written data. |
| mutating public func clear() { |
| _minAlignment = 0 |
| isNested = false |
| stringOffsetMap = [:] |
| _vtables = [] |
| _vtableStorage.clear() |
| _bb.clear() |
| } |
| |
| // MARK: - Create Tables |
| |
| /// Checks if the required fields were serialized into the buffer |
| /// - Parameters: |
| /// - table: offset for the table |
| /// - fields: Array of all the important fields to be serialized |
| /// |
| /// *NOTE: Never call this function, this is only supposed to be called |
| /// by the generated code* |
| mutating public func require(table: Offset, fields: [Int32]) { |
| for field in fields { |
| let start = _bb.capacity &- Int(table.o) |
| let startTable = start &- Int(_bb.read(def: Int32.self, position: start)) |
| let isOkay = _bb.read( |
| def: VOffset.self, |
| position: startTable &+ Int(field)) != 0 |
| assert(isOkay, "Flatbuffers requires the following field") |
| } |
| } |
| |
| /// Finished the buffer by adding the file id and then calling finish |
| /// - Parameters: |
| /// - offset: Offset of the table |
| /// - fileId: Takes the fileId |
| /// - prefix: if false it wont add the size of the buffer |
| /// |
| /// ``finish(offset:fileId:addPrefix:)`` should be called at the end of creating |
| /// a table |
| /// ```swift |
| /// var root = SomeObject |
| /// .createObject(&builder, |
| /// name: nameOffset) |
| /// builder.finish( |
| /// offset: root, |
| /// fileId: "ax1a", |
| /// addPrefix: true) |
| /// ``` |
| /// File id would append a file id name at the end of the written bytes before, |
| /// finishing the buffer. |
| /// |
| /// Whereas, if `addPrefix` is true, the written bytes would |
| /// include the size of the current buffer. |
| mutating public func finish( |
| offset: Offset, |
| fileId: String, |
| addPrefix prefix: Bool = false) |
| { |
| let size = MemoryLayout<UOffset>.size |
| preAlign( |
| len: size &+ (prefix ? size : 0) &+ FileIdLength, |
| alignment: _minAlignment) |
| assert(fileId.count == FileIdLength, "Flatbuffers requires file id to be 4") |
| _bb.push(string: fileId, len: 4) |
| finish(offset: offset, addPrefix: prefix) |
| } |
| |
| /// Finished the buffer by adding the file id, offset, and prefix to it. |
| /// - Parameters: |
| /// - offset: Offset of the table |
| /// - prefix: if false it wont add the size of the buffer |
| /// |
| /// ``finish(offset:addPrefix:)`` should be called at the end of creating |
| /// a table |
| /// ```swift |
| /// var root = SomeObject |
| /// .createObject(&builder, |
| /// name: nameOffset) |
| /// builder.finish( |
| /// offset: root, |
| /// addPrefix: true) |
| /// ``` |
| /// If `addPrefix` is true, the written bytes would |
| /// include the size of the current buffer. |
| mutating public func finish( |
| offset: Offset, |
| addPrefix prefix: Bool = false) |
| { |
| notNested() |
| let size = MemoryLayout<UOffset>.size |
| preAlign(len: size &+ (prefix ? size : 0), alignment: _minAlignment) |
| push(element: refer(to: offset.o)) |
| if prefix { push(element: _bb.size) } |
| _vtableStorage.clear() |
| finished = true |
| } |
| |
| /// ``startTable(with:)`` will let the builder know, that a new object is being serialized. |
| /// |
| /// The function will fatalerror if called while there is another object being serialized. |
| /// ```swift |
| /// let start = Monster |
| /// .startMonster(&fbb) |
| /// ``` |
| /// - Parameter numOfFields: Number of elements to be written to the buffer |
| /// - Returns: Offset of the newly started table |
| mutating public func startTable(with numOfFields: Int) -> UOffset { |
| notNested() |
| isNested = true |
| _vtableStorage.start(count: numOfFields) |
| return _bb.size |
| } |
| |
| /// ``endTable(at:)`` will let the ``FlatBufferBuilder`` know that the |
| /// object that's written to it is completed |
| /// |
| /// This would be called after all the elements are serialized, |
| /// it will add the current vtable into the ``ByteBuffer``. |
| /// The functions will `fatalError` in case the object is called |
| /// without ``startTable(with:)``, or the object has exceeded the limit of 2GB. |
| /// |
| /// - Parameter startOffset:Start point of the object written |
| /// - returns: The root of the table |
| mutating public func endTable(at startOffset: UOffset) -> UOffset { |
| assert(isNested, "Calling endtable without calling starttable") |
| let sizeofVoffset = MemoryLayout<VOffset>.size |
| let vTableOffset = push(element: SOffset(0)) |
| |
| let tableObjectSize = vTableOffset &- startOffset |
| assert(tableObjectSize < 0x10000, "Buffer can't grow beyond 2 Gigabytes") |
| let _max = Int(_vtableStorage.maxOffset) &+ sizeofVoffset |
| |
| _bb.fill(padding: _max) |
| _bb.write( |
| value: VOffset(tableObjectSize), |
| index: _bb.writerIndex &+ sizeofVoffset, |
| direct: true) |
| _bb.write(value: VOffset(_max), index: _bb.writerIndex, direct: true) |
| |
| var itr = 0 |
| while itr < _vtableStorage.writtenIndex { |
| let loaded = _vtableStorage.load(at: itr) |
| itr = itr &+ _vtableStorage.size |
| guard loaded.offset != 0 else { continue } |
| let _index = (_bb.writerIndex &+ Int(loaded.position)) |
| _bb.write( |
| value: VOffset(vTableOffset &- loaded.offset), |
| index: _index, |
| direct: true) |
| } |
| |
| _vtableStorage.clear() |
| let vt_use = _bb.size |
| |
| var isAlreadyAdded: Int? |
| |
| let vt2 = _bb.memory.advanced(by: _bb.writerIndex) |
| let len2 = vt2.load(fromByteOffset: 0, as: Int16.self) |
| |
| for table in _vtables { |
| let position = _bb.capacity &- Int(table) |
| let vt1 = _bb.memory.advanced(by: position) |
| let len1 = _bb.read(def: Int16.self, position: position) |
| if len2 != len1 || 0 != memcmp(vt1, vt2, Int(len2)) { continue } |
| |
| isAlreadyAdded = Int(table) |
| break |
| } |
| |
| if let offset = isAlreadyAdded { |
| let vTableOff = Int(vTableOffset) |
| let space = _bb.capacity &- vTableOff |
| _bb.write(value: Int32(offset &- vTableOff), index: space, direct: true) |
| _bb.pop(_bb.capacity &- space) |
| } else { |
| _bb.write(value: Int32(vt_use &- vTableOffset), index: Int(vTableOffset)) |
| _vtables.append(_bb.size) |
| } |
| isNested = false |
| return vTableOffset |
| } |
| |
| // MARK: - Builds Buffer |
| |
| /// Asserts to see if the object is not nested |
| @usableFromInline |
| mutating internal func notNested() { |
| assert(!isNested, "Object serialization must not be nested") |
| } |
| |
| /// Changes the minimuim alignment of the buffer |
| /// - Parameter size: size of the current alignment |
| @inline(__always) |
| mutating internal func minAlignment(size: Int) { |
| if size > _minAlignment { |
| _minAlignment = size |
| } |
| } |
| |
| /// Gets the padding for the current element |
| /// - Parameters: |
| /// - bufSize: Current size of the buffer + the offset of the object to be written |
| /// - elementSize: Element size |
| @inline(__always) |
| mutating internal func padding( |
| bufSize: UInt32, |
| elementSize: UInt32) -> UInt32 |
| { |
| ((~bufSize) &+ 1) & (elementSize - 1) |
| } |
| |
| /// Prealigns the buffer before writting a new object into the buffer |
| /// - Parameters: |
| /// - len:Length of the object |
| /// - alignment: Alignment type |
| @usableFromInline |
| mutating internal func preAlign(len: Int, alignment: Int) { |
| minAlignment(size: alignment) |
| _bb.fill(padding: Int(padding( |
| bufSize: _bb.size &+ UOffset(len), |
| elementSize: UOffset(alignment)))) |
| } |
| |
| /// Prealigns the buffer before writting a new object into the buffer |
| /// - Parameters: |
| /// - len: Length of the object |
| /// - type: Type of the object to be written |
| @usableFromInline |
| mutating internal func preAlign<T: Scalar>(len: Int, type: T.Type) { |
| preAlign(len: len, alignment: MemoryLayout<T>.size) |
| } |
| |
| /// Refers to an object that's written in the buffer |
| /// - Parameter off: the objects index value |
| @usableFromInline |
| mutating internal func refer(to off: UOffset) -> UOffset { |
| let size = MemoryLayout<UOffset>.size |
| preAlign(len: size, alignment: size) |
| return _bb.size &- off &+ UInt32(size) |
| } |
| |
| /// Tracks the elements written into the buffer |
| /// - Parameters: |
| /// - offset: The offset of the element witten |
| /// - position: The position of the element |
| @usableFromInline |
| mutating internal func track(offset: UOffset, at position: VOffset) { |
| _vtableStorage.add(loc: FieldLoc(offset: offset, position: position)) |
| } |
| |
| // MARK: - Inserting Vectors |
| |
| /// ``startVector(_:elementSize:)`` creates a new vector within buffer |
| /// |
| /// The function checks if there is a current object being written, if |
| /// the check passes it creates a buffer alignment of `length * elementSize` |
| /// ```swift |
| /// builder.startVector( |
| /// int32Values.count, elementSize: 4) |
| /// ``` |
| /// |
| /// - Parameters: |
| /// - len: Length of vector to be created |
| /// - elementSize: Size of object type to be written |
| mutating public func startVector(_ len: Int, elementSize: Int) { |
| notNested() |
| isNested = true |
| preAlign(len: len &* elementSize, type: UOffset.self) |
| preAlign(len: len &* elementSize, alignment: elementSize) |
| } |
| |
| /// ``endVector(len:)`` ends the currently created vector |
| /// |
| /// Calling ``endVector(len:)`` requires the length, of the current |
| /// vector. The length would be pushed to indicate the count of numbers |
| /// within the vector. If ``endVector(len:)`` is called without |
| /// ``startVector(_:elementSize:)`` it asserts. |
| /// |
| /// ```swift |
| /// let vectorOffset = builder. |
| /// endVector(len: int32Values.count) |
| /// ``` |
| /// |
| /// - Parameter len: Length of the buffer |
| /// - Returns: Returns the current ``Offset`` in the ``ByteBuffer`` |
| mutating public func endVector(len: Int) -> Offset { |
| assert(isNested, "Calling endVector without calling startVector") |
| isNested = false |
| return Offset(offset: push(element: Int32(len))) |
| } |
| |
| /// Creates a vector of type ``Scalar`` into the ``ByteBuffer`` |
| /// |
| /// ``createVector(_:)-4swl0`` writes a vector of type Scalars into |
| /// ``ByteBuffer``. This is a convenient method instead of calling, |
| /// ``startVector(_:elementSize:)`` and then ``endVector(len:)`` |
| /// ```swift |
| /// let vectorOffset = builder. |
| /// createVector([1, 2, 3, 4]) |
| /// ``` |
| /// |
| /// The underlying implementation simply calls ``createVector(_:size:)-4lhrv`` |
| /// |
| /// - Parameter elements: elements to be written into the buffer |
| /// - returns: ``Offset`` of the vector |
| mutating public func createVector<T: Scalar>(_ elements: [T]) -> Offset { |
| createVector(elements, size: elements.count) |
| } |
| |
| /// Creates a vector of type Scalar in the buffer |
| /// |
| /// ``createVector(_:)-4swl0`` writes a vector of type Scalars into |
| /// ``ByteBuffer``. This is a convenient method instead of calling, |
| /// ``startVector(_:elementSize:)`` and then ``endVector(len:)`` |
| /// ```swift |
| /// let vectorOffset = builder. |
| /// createVector([1, 2, 3, 4], size: 4) |
| /// ``` |
| /// |
| /// - Parameter elements: Elements to be written into the buffer |
| /// - Parameter size: Count of elements |
| /// - returns: ``Offset`` of the vector |
| mutating public func createVector<T: Scalar>( |
| _ elements: [T], |
| size: Int) -> Offset |
| { |
| let size = size |
| startVector(size, elementSize: MemoryLayout<T>.size) |
| _bb.push(elements: elements) |
| return endVector(len: size) |
| } |
| |
| /// Creates a vector of type ``Enum`` into the ``ByteBuffer`` |
| /// |
| /// ``createVector(_:)-9h189`` writes a vector of type ``Enum`` into |
| /// ``ByteBuffer``. This is a convenient method instead of calling, |
| /// ``startVector(_:elementSize:)`` and then ``endVector(len:)`` |
| /// ```swift |
| /// let vectorOffset = builder. |
| /// createVector([.swift, .cpp]) |
| /// ``` |
| /// |
| /// The underlying implementation simply calls ``createVector(_:size:)-7cx6z`` |
| /// |
| /// - Parameter elements: elements to be written into the buffer |
| /// - returns: ``Offset`` of the vector |
| mutating public func createVector<T: Enum>(_ elements: [T]) -> Offset { |
| createVector(elements, size: elements.count) |
| } |
| |
| /// Creates a vector of type ``Enum`` into the ``ByteBuffer`` |
| /// |
| /// ``createVector(_:)-9h189`` writes a vector of type ``Enum`` into |
| /// ``ByteBuffer``. This is a convenient method instead of calling, |
| /// ``startVector(_:elementSize:)`` and then ``endVector(len:)`` |
| /// ```swift |
| /// let vectorOffset = builder. |
| /// createVector([.swift, .cpp]) |
| /// ``` |
| /// |
| /// - Parameter elements: Elements to be written into the buffer |
| /// - Parameter size: Count of elements |
| /// - returns: ``Offset`` of the vector |
| mutating public func createVector<T: Enum>( |
| _ elements: [T], |
| size: Int) -> Offset |
| { |
| let size = size |
| startVector(size, elementSize: T.byteSize) |
| for e in elements.reversed() { |
| _bb.push(value: e.value, len: T.byteSize) |
| } |
| return endVector(len: size) |
| } |
| |
| /// Creates a vector of already written offsets |
| /// |
| /// ``createVector(ofOffsets:)`` creates a vector of ``Offset`` into |
| /// ``ByteBuffer``. This is a convenient method instead of calling, |
| /// ``startVector(_:elementSize:)`` and then ``endVector(len:)``. |
| /// |
| /// The underlying implementation simply calls ``createVector(ofOffsets:len:)`` |
| /// |
| /// ```swift |
| /// let namesOffsets = builder. |
| /// createVector(ofOffsets: [name1, name2]) |
| /// ``` |
| /// - Parameter offsets: Array of offsets of type ``Offset`` |
| /// - returns: ``Offset`` of the vector |
| mutating public func createVector(ofOffsets offsets: [Offset]) -> Offset { |
| createVector(ofOffsets: offsets, len: offsets.count) |
| } |
| |
| /// Creates a vector of already written offsets |
| /// |
| /// ``createVector(ofOffsets:)`` creates a vector of ``Offset`` into |
| /// ``ByteBuffer``. This is a convenient method instead of calling, |
| /// ``startVector(_:elementSize:)`` and then ``endVector(len:)`` |
| /// |
| /// ```swift |
| /// let namesOffsets = builder. |
| /// createVector(ofOffsets: [name1, name2]) |
| /// ``` |
| /// |
| /// - Parameter offsets: Array of offsets of type ``Offset`` |
| /// - Parameter size: Count of elements |
| /// - returns: ``Offset`` of the vector |
| mutating public func createVector( |
| ofOffsets offsets: [Offset], |
| len: Int) -> Offset |
| { |
| startVector(len, elementSize: MemoryLayout<Offset>.size) |
| for o in offsets.reversed() { |
| push(element: o) |
| } |
| return endVector(len: len) |
| } |
| |
| /// Creates a vector of strings |
| /// |
| /// ``createVector(ofStrings:)`` creates a vector of `String` into |
| /// ``ByteBuffer``. This is a convenient method instead of manually |
| /// creating the string offsets, you simply pass it to this function |
| /// and it would write the strings into the ``ByteBuffer``. |
| /// After that it calls ``createVector(ofOffsets:)`` |
| /// |
| /// ```swift |
| /// let namesOffsets = builder. |
| /// createVector(ofStrings: ["Name", "surname"]) |
| /// ``` |
| /// |
| /// - Parameter str: Array of string |
| /// - returns: ``Offset`` of the vector |
| mutating public func createVector(ofStrings str: [String]) -> Offset { |
| var offsets: [Offset] = [] |
| for s in str { |
| offsets.append(create(string: s)) |
| } |
| return createVector(ofOffsets: offsets) |
| } |
| |
| /// Creates a vector of type ``NativeStruct``. |
| /// |
| /// Any swift struct in the generated code, should confirm to |
| /// ``NativeStruct``. Since the generated swift structs are padded |
| /// to the `FlatBuffers` standards. |
| /// |
| /// ```swift |
| /// let offsets = builder. |
| /// createVector(ofStructs: [NativeStr(num: 1), NativeStr(num: 2)]) |
| /// ``` |
| /// |
| /// - Parameter structs: A vector of ``NativeStruct`` |
| /// - Returns: ``Offset`` of the vector |
| mutating public func createVector<T: NativeStruct>(ofStructs structs: [T]) |
| -> Offset |
| { |
| startVector( |
| structs.count * MemoryLayout<T>.size, |
| elementSize: MemoryLayout<T>.alignment) |
| for i in structs.reversed() { |
| _ = create(struct: i) |
| } |
| return endVector(len: structs.count) |
| } |
| |
| // MARK: - Inserting Structs |
| |
| /// Writes a ``NativeStruct`` into the ``ByteBuffer`` |
| /// |
| /// Adds a native struct that's build and padded according |
| /// to `FlatBuffers` standards. with a predefined position. |
| /// |
| /// ```swift |
| /// let offset = builder.create( |
| /// struct: NativeStr(num: 1), |
| /// position: 10) |
| /// ``` |
| /// |
| /// - Parameters: |
| /// - s: ``NativeStruct`` to be inserted into the ``ByteBuffer`` |
| /// - position: The predefined position of the object |
| /// - Returns: ``Offset`` of written struct |
| @discardableResult |
| mutating public func create<T: NativeStruct>( |
| struct s: T, position: VOffset) -> Offset |
| { |
| let offset = create(struct: s) |
| _vtableStorage.add(loc: FieldLoc( |
| offset: _bb.size, |
| position: VOffset(position))) |
| return offset |
| } |
| |
| /// Writes a ``NativeStruct`` into the ``ByteBuffer`` |
| /// |
| /// Adds a native struct that's build and padded according |
| /// to `FlatBuffers` standards, directly into the buffer without |
| /// a predefined position. |
| /// |
| /// ```swift |
| /// let offset = builder.create( |
| /// struct: NativeStr(num: 1)) |
| /// ``` |
| /// |
| /// - Parameters: |
| /// - s: ``NativeStruct`` to be inserted into the ``ByteBuffer`` |
| /// - Returns: ``Offset`` of written struct |
| @discardableResult |
| mutating public func create<T: NativeStruct>( |
| struct s: T) -> Offset |
| { |
| let size = MemoryLayout<T>.size |
| preAlign(len: size, alignment: MemoryLayout<T>.alignment) |
| _bb.push(struct: s, size: size) |
| return Offset(offset: _bb.size) |
| } |
| |
| // MARK: - Inserting Strings |
| |
| /// Insets a string into the buffer of type `UTF8` |
| /// |
| /// Adds a swift string into ``ByteBuffer`` by encoding it |
| /// using `UTF8` |
| /// |
| /// ```swift |
| /// let nameOffset = builder |
| /// .create(string: "welcome") |
| /// ``` |
| /// |
| /// - Parameter str: String to be serialized |
| /// - returns: ``Offset`` of inserted string |
| mutating public func create(string str: String?) -> Offset { |
| guard let str = str else { return Offset() } |
| let len = str.utf8.count |
| notNested() |
| preAlign(len: len &+ 1, type: UOffset.self) |
| _bb.fill(padding: 1) |
| _bb.push(string: str, len: len) |
| push(element: UOffset(len)) |
| return Offset(offset: _bb.size) |
| } |
| |
| /// Insets a shared string into the buffer of type `UTF8` |
| /// |
| /// Adds a swift string into ``ByteBuffer`` by encoding it |
| /// using `UTF8`. The function will check if the string, |
| /// is already written to the ``ByteBuffer`` |
| /// |
| /// ```swift |
| /// let nameOffset = builder |
| /// .createShared(string: "welcome") |
| /// |
| /// |
| /// let secondOffset = builder |
| /// .createShared(string: "welcome") |
| /// |
| /// assert(nameOffset.o == secondOffset.o) |
| /// ``` |
| /// |
| /// - Parameter str: String to be serialized |
| /// - returns: ``Offset`` of inserted string |
| mutating public func createShared(string str: String?) -> Offset { |
| guard let str = str else { return Offset() } |
| if let offset = stringOffsetMap[str] { |
| return offset |
| } |
| let offset = create(string: str) |
| stringOffsetMap[str] = offset |
| return offset |
| } |
| |
| // MARK: - Inseting offsets |
| |
| /// Writes the ``Offset`` of an already written table |
| /// |
| /// Writes the ``Offset`` of a table if not empty into the |
| /// ``ByteBuffer`` |
| /// |
| /// - Parameters: |
| /// - offset: ``Offset`` of another object to be written |
| /// - position: The predefined position of the object |
| mutating public func add(offset: Offset, at position: VOffset) { |
| if offset.isEmpty { return } |
| add(element: refer(to: offset.o), def: 0, at: position) |
| } |
| |
| /// Pushes a value of type ``Offset`` into the ``ByteBuffer`` |
| /// - Parameter o: ``Offset`` |
| /// - returns: Current position of the ``Offset`` |
| @discardableResult |
| mutating public func push(element o: Offset) -> UOffset { |
| push(element: refer(to: o.o)) |
| } |
| |
| // MARK: - Inserting Scalars to Buffer |
| |
| /// Writes a ``Scalar`` value into ``ByteBuffer`` |
| /// |
| /// ``add(element:def:at:)`` takes in a default value, and current value |
| /// and the position within the `VTable`. The default value would not |
| /// be serialized if the value is the same as the current value or |
| /// `serializeDefaults` is equal to false. |
| /// |
| /// If serializing defaults is important ``init(initialSize:serializeDefaults:)``, |
| /// passing true for `serializeDefaults` would do the job. |
| /// |
| /// ```swift |
| /// // Adds 10 to the buffer |
| /// builder.add(element: Int(10), def: 1, position 12) |
| /// ``` |
| /// |
| /// *NOTE: Never call this manually* |
| /// |
| /// - Parameters: |
| /// - element: Element to insert |
| /// - def: Default value for that element |
| /// - position: The predefined position of the element |
| mutating public func add<T: Scalar>( |
| element: T, |
| def: T, |
| at position: VOffset) |
| { |
| if element == def && !serializeDefaults { return } |
| track(offset: push(element: element), at: position) |
| } |
| |
| /// Writes a optional ``Scalar`` value into ``ByteBuffer`` |
| /// |
| /// Takes an optional value to be written into the ``ByteBuffer`` |
| /// |
| /// *NOTE: Never call this manually* |
| /// |
| /// - Parameters: |
| /// - element: Optional element of type scalar |
| /// - position: The predefined position of the element |
| mutating public func add<T: Scalar>(element: T?, at position: VOffset) { |
| guard let element = element else { return } |
| track(offset: push(element: element), at: position) |
| } |
| |
| /// Pushes a values of type ``Scalar`` into the ``ByteBuffer`` |
| /// |
| /// *NOTE: Never call this manually* |
| /// |
| /// - Parameter element: Element to insert |
| /// - returns: Postion of the Element |
| @discardableResult |
| mutating public func push<T: Scalar>(element: T) -> UOffset { |
| let size = MemoryLayout<T>.size |
| preAlign( |
| len: size, |
| alignment: size) |
| _bb.push(value: element, len: size) |
| return _bb.size |
| } |
| |
| } |
| |
| extension FlatBufferBuilder: CustomDebugStringConvertible { |
| |
| public var debugDescription: String { |
| """ |
| buffer debug: |
| \(_bb) |
| builder debug: |
| { finished: \(finished), serializeDefaults: \(serializeDefaults), isNested: \(isNested) } |
| """ |
| } |
| |
| /// VTableStorage is a class to contain the VTable buffer that would be serialized into buffer |
| @usableFromInline |
| internal class VTableStorage { |
| /// Memory check since deallocating each time we want to clear would be expensive |
| /// and memory leaks would happen if we dont deallocate the first allocated memory. |
| /// memory is promised to be available before adding `FieldLoc` |
| private var memoryInUse = false |
| /// Size of FieldLoc in memory |
| let size = MemoryLayout<FieldLoc>.stride |
| /// Memeory buffer |
| var memory: UnsafeMutableRawBufferPointer! |
| /// Capacity of the current buffer |
| var capacity: Int = 0 |
| /// Maximuim offset written to the class |
| var maxOffset: VOffset = 0 |
| /// number of fields written into the buffer |
| var numOfFields: Int = 0 |
| /// Last written Index |
| var writtenIndex: Int = 0 |
| |
| /// Creates the memory to store the buffer in |
| @usableFromInline |
| init() { |
| memory = UnsafeMutableRawBufferPointer.allocate( |
| byteCount: 0, |
| alignment: 0) |
| } |
| |
| deinit { |
| memory.deallocate() |
| } |
| |
| /// Builds a buffer with byte count of fieldloc.size * count of field numbers |
| /// - Parameter count: number of fields to be written |
| @inline(__always) |
| func start(count: Int) { |
| assert(count >= 0, "number of fields should NOT be negative") |
| let capacity = count &* size |
| ensure(space: capacity) |
| } |
| |
| /// Adds a FieldLoc into the buffer, which would track how many have been written, |
| /// and max offset |
| /// - Parameter loc: Location of encoded element |
| func add(loc: FieldLoc) { |
| memory.baseAddress?.advanced(by: writtenIndex).storeBytes( |
| of: loc, |
| as: FieldLoc.self) |
| writtenIndex = writtenIndex &+ size |
| numOfFields = numOfFields &+ 1 |
| maxOffset = max(loc.position, maxOffset) |
| } |
| |
| /// Clears the data stored related to the encoded buffer |
| func clear() { |
| maxOffset = 0 |
| numOfFields = 0 |
| writtenIndex = 0 |
| } |
| |
| /// Ensure that the buffer has enough space instead of recreating the buffer each time. |
| /// - Parameter space: space required for the new vtable |
| @inline(__always) |
| func ensure(space: Int) { |
| guard space &+ writtenIndex > capacity else { return } |
| memory.deallocate() |
| memory = UnsafeMutableRawBufferPointer.allocate( |
| byteCount: space, |
| alignment: size) |
| capacity = space |
| } |
| |
| /// Loads an object of type `FieldLoc` from buffer memory |
| /// - Parameter index: index of element |
| /// - Returns: a FieldLoc at index |
| @inline(__always) |
| func load(at index: Int) -> FieldLoc { |
| memory.load(fromByteOffset: index, as: FieldLoc.self) |
| } |
| |
| } |
| |
| internal struct FieldLoc { |
| var offset: UOffset |
| var position: VOffset |
| } |
| |
| } |