Austin Schuh | 58b9b47 | 2020-11-25 19:12:44 -0800 | [diff] [blame^] | 1 | /* |
| 2 | * Copyright 2020 Google Inc. All rights reserved. |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 17 | import Foundation |
| 18 | |
| 19 | public struct FlatBufferBuilder { |
Austin Schuh | 58b9b47 | 2020-11-25 19:12:44 -0800 | [diff] [blame^] | 20 | |
| 21 | /// Storage for the Vtables used in the buffer are stored in here, so they would be written later in EndTable |
| 22 | @usableFromInline internal var _vtableStorage = VTableStorage() |
| 23 | |
| 24 | /// Reference Vtables that were already written to the buffer |
| 25 | private var _vtables: [UOffset] = [] |
| 26 | /// Flatbuffer data will be written into |
| 27 | private var _bb: ByteBuffer |
| 28 | /// A check if the buffer is being written into by a different table |
| 29 | private var isNested = false |
| 30 | /// Dictonary that stores a map of all the strings that were written to the buffer |
| 31 | private var stringOffsetMap: [String: Offset<String>] = [:] |
| 32 | /// A check to see if finish(::) was ever called to retreive data object |
| 33 | private var finished = false |
| 34 | /// A check to see if the buffer should serialize Default values |
| 35 | private var serializeDefaults: Bool |
| 36 | |
| 37 | /// Current alignment for the buffer |
| 38 | var _minAlignment: Int = 0 { |
| 39 | didSet { |
| 40 | _bb.alignment = _minAlignment |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 41 | } |
Austin Schuh | 58b9b47 | 2020-11-25 19:12:44 -0800 | [diff] [blame^] | 42 | } |
| 43 | |
| 44 | /// Gives a read access to the buffer's size |
| 45 | public var size: UOffset { _bb.size } |
| 46 | /// Data representation of the buffer |
| 47 | public var data: Data { |
| 48 | assert(finished, "Data shouldn't be called before finish()") |
| 49 | return Data( |
| 50 | bytes: _bb.memory.advanced(by: _bb.writerIndex), |
| 51 | count: _bb.capacity &- _bb.writerIndex) |
| 52 | } |
| 53 | /// Get's the fully sized buffer stored in memory |
| 54 | public var fullSizedByteArray: [UInt8] { |
| 55 | let ptr = UnsafeBufferPointer( |
| 56 | start: _bb.memory.assumingMemoryBound(to: UInt8.self), |
| 57 | count: _bb.capacity) |
| 58 | return Array(ptr) |
| 59 | } |
| 60 | /// Returns the written size of the buffer |
| 61 | public var sizedByteArray: [UInt8] { |
| 62 | assert(finished, "Data shouldn't be called before finish()") |
| 63 | let cp = _bb.capacity &- _bb.writerIndex |
| 64 | let start = _bb.memory.advanced(by: _bb.writerIndex) |
| 65 | .bindMemory(to: UInt8.self, capacity: cp) |
| 66 | |
| 67 | let ptr = UnsafeBufferPointer(start: start, count: cp) |
| 68 | return Array(ptr) |
| 69 | } |
| 70 | /// Returns the buffer |
| 71 | public var buffer: ByteBuffer { _bb } |
| 72 | |
| 73 | /// Returns A sized Buffer from the readable bytes |
| 74 | public var sizedBuffer: ByteBuffer { |
| 75 | assert(finished, "Data shouldn't be called before finish()") |
| 76 | return ByteBuffer(memory: _bb.memory.advanced(by: _bb.reader), count: Int(_bb.size)) |
| 77 | } |
| 78 | |
| 79 | // MARK: - Init |
| 80 | |
| 81 | /// initialize the buffer with a size |
| 82 | /// - Parameters: |
| 83 | /// - initialSize: Initial size for the buffer |
| 84 | /// - force: Allows default to be serialized into the buffer |
| 85 | public init(initialSize: Int32 = 1024, serializeDefaults force: Bool = false) { |
| 86 | assert(initialSize > 0, "Size should be greater than zero!") |
| 87 | guard isLitteEndian else { |
| 88 | fatalError("Reading/Writing a buffer in big endian machine is not supported on swift") |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 89 | } |
Austin Schuh | 58b9b47 | 2020-11-25 19:12:44 -0800 | [diff] [blame^] | 90 | serializeDefaults = force |
| 91 | _bb = ByteBuffer(initialSize: Int(initialSize)) |
| 92 | } |
| 93 | |
| 94 | /// Clears the buffer and the builder from it's data |
| 95 | mutating public func clear() { |
| 96 | _minAlignment = 0 |
| 97 | isNested = false |
| 98 | stringOffsetMap = [:] |
| 99 | _vtables = [] |
| 100 | _vtableStorage.clear() |
| 101 | _bb.clear() |
| 102 | } |
| 103 | |
| 104 | // MARK: - Create Tables |
| 105 | |
| 106 | /// Checks if the required fields were serialized into the buffer |
| 107 | /// - Parameters: |
| 108 | /// - table: offset for the table |
| 109 | /// - fields: Array of all the important fields to be serialized |
| 110 | mutating public func require(table: Offset<UOffset>, fields: [Int32]) { |
| 111 | for field in fields { |
| 112 | let start = _bb.capacity &- Int(table.o) |
| 113 | let startTable = start &- Int(_bb.read(def: Int32.self, position: start)) |
| 114 | let isOkay = _bb.read(def: VOffset.self, position: startTable &+ Int(field)) != 0 |
| 115 | assert(isOkay, "Flatbuffers requires the following field") |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 116 | } |
Austin Schuh | 58b9b47 | 2020-11-25 19:12:44 -0800 | [diff] [blame^] | 117 | } |
| 118 | |
| 119 | /// Finished the buffer by adding the file id and then calling finish |
| 120 | /// - Parameters: |
| 121 | /// - offset: Offset of the table |
| 122 | /// - fileId: Takes the fileId |
| 123 | /// - prefix: if false it wont add the size of the buffer |
| 124 | mutating public func finish<T>(offset: Offset<T>, fileId: String, addPrefix prefix: Bool = false) { |
| 125 | let size = MemoryLayout<UOffset>.size |
| 126 | preAlign(len: size &+ (prefix ? size : 0) &+ FileIdLength, alignment: _minAlignment) |
| 127 | assert(fileId.count == FileIdLength, "Flatbuffers requires file id to be 4") |
| 128 | _bb.push(string: fileId, len: 4) |
| 129 | finish(offset: offset, addPrefix: prefix) |
| 130 | } |
| 131 | |
| 132 | /// Finished the buffer by adding the file id, offset, and prefix to it. |
| 133 | /// - Parameters: |
| 134 | /// - offset: Offset of the table |
| 135 | /// - prefix: if false it wont add the size of the buffer |
| 136 | mutating public func finish<T>(offset: Offset<T>, addPrefix prefix: Bool = false) { |
| 137 | notNested() |
| 138 | let size = MemoryLayout<UOffset>.size |
| 139 | preAlign(len: size &+ (prefix ? size : 0), alignment: _minAlignment) |
| 140 | push(element: refer(to: offset.o)) |
| 141 | if prefix { push(element: _bb.size) } |
| 142 | _vtableStorage.clear() |
| 143 | finished = true |
| 144 | } |
| 145 | |
| 146 | /// starttable will let the builder know, that a new object is being serialized. |
| 147 | /// |
| 148 | /// The function will fatalerror if called while there is another object being serialized |
| 149 | /// - Parameter numOfFields: Number of elements to be written to the buffer |
| 150 | mutating public func startTable(with numOfFields: Int) -> UOffset { |
| 151 | notNested() |
| 152 | isNested = true |
| 153 | _vtableStorage.start(count: numOfFields) |
| 154 | return _bb.size |
| 155 | } |
| 156 | |
| 157 | /// Endtable will let the builder know that the object that's written to it is completed |
| 158 | /// |
| 159 | /// This would be called after all the elements are serialized, it will add the vtable into the buffer. |
| 160 | /// it will fatalError in case the object is called without starttable, or the object has exceeded the limit of |
| 161 | /// 2GB, |
| 162 | /// - Parameter startOffset:Start point of the object written |
| 163 | /// - returns: The root of the table |
| 164 | mutating public func endTable(at startOffset: UOffset) -> UOffset { |
| 165 | assert(isNested, "Calling endtable without calling starttable") |
| 166 | let sizeofVoffset = MemoryLayout<VOffset>.size |
| 167 | let vTableOffset = push(element: SOffset(0)) |
| 168 | |
| 169 | let tableObjectSize = vTableOffset &- startOffset |
| 170 | assert(tableObjectSize < 0x10000, "Buffer can't grow beyond 2 Gigabytes") |
| 171 | let _max = Int(_vtableStorage.maxOffset) &+ sizeofVoffset |
| 172 | |
| 173 | _bb.fill(padding: _max) |
| 174 | _bb.write( |
| 175 | value: VOffset(tableObjectSize), |
| 176 | index: _bb.writerIndex &+ sizeofVoffset, |
| 177 | direct: true) |
| 178 | _bb.write(value: VOffset(_max), index: _bb.writerIndex, direct: true) |
| 179 | |
| 180 | var itr = 0 |
| 181 | while itr < _vtableStorage.writtenIndex { |
| 182 | let loaded = _vtableStorage.load(at: itr) |
| 183 | itr = itr &+ _vtableStorage.size |
| 184 | guard loaded.offset != 0 else { continue } |
| 185 | let _index = (_bb.writerIndex &+ Int(loaded.position)) |
| 186 | _bb.write(value: VOffset(vTableOffset &- loaded.offset), index: _index, direct: true) |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 187 | } |
| 188 | |
Austin Schuh | 58b9b47 | 2020-11-25 19:12:44 -0800 | [diff] [blame^] | 189 | _vtableStorage.clear() |
| 190 | let vt_use = _bb.size |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 191 | |
Austin Schuh | 58b9b47 | 2020-11-25 19:12:44 -0800 | [diff] [blame^] | 192 | var isAlreadyAdded: Int? |
| 193 | |
| 194 | let vt2 = _bb.memory.advanced(by: _bb.writerIndex) |
| 195 | let len2 = vt2.load(fromByteOffset: 0, as: Int16.self) |
| 196 | |
| 197 | for table in _vtables { |
| 198 | let position = _bb.capacity &- Int(table) |
| 199 | let vt1 = _bb.memory.advanced(by: position) |
| 200 | let len1 = _bb.read(def: Int16.self, position: position) |
| 201 | if len2 != len1 || 0 != memcmp(vt1, vt2, Int(len2)) { continue } |
| 202 | |
| 203 | isAlreadyAdded = Int(table) |
| 204 | break |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 205 | } |
| 206 | |
Austin Schuh | 58b9b47 | 2020-11-25 19:12:44 -0800 | [diff] [blame^] | 207 | if let offset = isAlreadyAdded { |
| 208 | let vTableOff = Int(vTableOffset) |
| 209 | let space = _bb.capacity &- vTableOff |
| 210 | _bb.write(value: Int32(offset &- vTableOff), index: space, direct: true) |
| 211 | _bb.pop(_bb.capacity &- space) |
| 212 | } else { |
| 213 | _bb.write(value: Int32(vt_use &- vTableOffset), index: Int(vTableOffset)) |
| 214 | _vtables.append(_bb.size) |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 215 | } |
Austin Schuh | 58b9b47 | 2020-11-25 19:12:44 -0800 | [diff] [blame^] | 216 | isNested = false |
| 217 | return vTableOffset |
| 218 | } |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 219 | |
Austin Schuh | 58b9b47 | 2020-11-25 19:12:44 -0800 | [diff] [blame^] | 220 | // MARK: - Builds Buffer |
| 221 | |
| 222 | /// asserts to see if the object is not nested |
| 223 | @usableFromInline |
| 224 | mutating internal func notNested() { |
| 225 | assert(!isNested, "Object serialization must not be nested") |
| 226 | } |
| 227 | |
| 228 | /// Changes the minimuim alignment of the buffer |
| 229 | /// - Parameter size: size of the current alignment |
| 230 | @usableFromInline |
| 231 | mutating internal func minAlignment(size: Int) { |
| 232 | if size > _minAlignment { |
| 233 | _minAlignment = size |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 234 | } |
Austin Schuh | 58b9b47 | 2020-11-25 19:12:44 -0800 | [diff] [blame^] | 235 | } |
| 236 | |
| 237 | /// Gets the padding for the current element |
| 238 | /// - Parameters: |
| 239 | /// - bufSize: Current size of the buffer + the offset of the object to be written |
| 240 | /// - elementSize: Element size |
| 241 | @usableFromInline |
| 242 | mutating internal func padding(bufSize: UInt32, elementSize: UInt32) -> UInt32 { |
| 243 | ((~bufSize) &+ 1) & (elementSize - 1) |
| 244 | } |
| 245 | |
| 246 | /// Prealigns the buffer before writting a new object into the buffer |
| 247 | /// - Parameters: |
| 248 | /// - len:Length of the object |
| 249 | /// - alignment: Alignment type |
| 250 | @usableFromInline |
| 251 | mutating internal func preAlign(len: Int, alignment: Int) { |
| 252 | minAlignment(size: alignment) |
| 253 | _bb.fill(padding: Int(padding( |
| 254 | bufSize: _bb.size &+ UOffset(len), |
| 255 | elementSize: UOffset(alignment)))) |
| 256 | } |
| 257 | |
| 258 | /// Prealigns the buffer before writting a new object into the buffer |
| 259 | /// - Parameters: |
| 260 | /// - len: Length of the object |
| 261 | /// - type: Type of the object to be written |
| 262 | @usableFromInline |
| 263 | mutating internal func preAlign<T: Scalar>(len: Int, type: T.Type) { |
| 264 | preAlign(len: len, alignment: MemoryLayout<T>.size) |
| 265 | } |
| 266 | |
| 267 | /// Refers to an object that's written in the buffer |
| 268 | /// - Parameter off: the objects index value |
| 269 | @usableFromInline |
| 270 | mutating internal func refer(to off: UOffset) -> UOffset { |
| 271 | let size = MemoryLayout<UOffset>.size |
| 272 | preAlign(len: size, alignment: size) |
| 273 | return _bb.size &- off &+ UInt32(size) |
| 274 | } |
| 275 | |
| 276 | /// Tracks the elements written into the buffer |
| 277 | /// - Parameters: |
| 278 | /// - offset: The offset of the element witten |
| 279 | /// - position: The position of the element |
| 280 | @usableFromInline |
| 281 | mutating internal func track(offset: UOffset, at position: VOffset) { |
| 282 | _vtableStorage.add(loc: FieldLoc(offset: offset, position: position)) |
| 283 | } |
| 284 | |
| 285 | // MARK: - Vectors |
| 286 | |
| 287 | /// Starts a vector of length and Element size |
| 288 | mutating public func startVector(_ len: Int, elementSize: Int) { |
| 289 | notNested() |
| 290 | isNested = true |
| 291 | preAlign(len: len &* elementSize, type: UOffset.self) |
| 292 | preAlign(len: len &* elementSize, alignment: elementSize) |
| 293 | } |
| 294 | |
| 295 | /// Ends the vector of at length |
| 296 | /// |
| 297 | /// The current function will fatalError if startVector is called before serializing the vector |
| 298 | /// - Parameter len: Length of the buffer |
| 299 | mutating public func endVector(len: Int) -> UOffset { |
| 300 | assert(isNested, "Calling endVector without calling startVector") |
| 301 | isNested = false |
| 302 | return push(element: Int32(len)) |
| 303 | } |
| 304 | |
| 305 | /// Creates a vector of type Scalar in the buffer |
| 306 | /// - Parameter elements: elements to be written into the buffer |
| 307 | /// - returns: Offset of the vector |
| 308 | mutating public func createVector<T: Scalar>(_ elements: [T]) -> Offset<UOffset> { |
| 309 | createVector(elements, size: elements.count) |
| 310 | } |
| 311 | |
| 312 | /// Creates a vector of type Scalar in the buffer |
| 313 | /// - Parameter elements: Elements to be written into the buffer |
| 314 | /// - Parameter size: Count of elements |
| 315 | /// - returns: Offset of the vector |
| 316 | mutating public func createVector<T: Scalar>(_ elements: [T], size: Int) -> Offset<UOffset> { |
| 317 | let size = size |
| 318 | startVector(size, elementSize: MemoryLayout<T>.size) |
| 319 | _bb.push(elements: elements) |
| 320 | return Offset(offset: endVector(len: size)) |
| 321 | } |
| 322 | |
| 323 | /// Creates a vector of type Enums in the buffer |
| 324 | /// - Parameter elements: elements to be written into the buffer |
| 325 | /// - returns: Offset of the vector |
| 326 | mutating public func createVector<T: Enum>(_ elements: [T]) -> Offset<UOffset> { |
| 327 | createVector(elements, size: elements.count) |
| 328 | } |
| 329 | |
| 330 | /// Creates a vector of type Enums in the buffer |
| 331 | /// - Parameter elements: Elements to be written into the buffer |
| 332 | /// - Parameter size: Count of elements |
| 333 | /// - returns: Offset of the vector |
| 334 | mutating public func createVector<T: Enum>(_ elements: [T], size: Int) -> Offset<UOffset> { |
| 335 | let size = size |
| 336 | startVector(size, elementSize: T.byteSize) |
| 337 | for e in elements.reversed() { |
| 338 | _bb.push(value: e.value, len: T.byteSize) |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 339 | } |
Austin Schuh | 58b9b47 | 2020-11-25 19:12:44 -0800 | [diff] [blame^] | 340 | return Offset(offset: endVector(len: size)) |
| 341 | } |
| 342 | |
| 343 | /// Creates a vector of type Offsets in the buffer |
| 344 | /// - Parameter offsets:Array of offsets of type T |
| 345 | /// - returns: Offset of the vector |
| 346 | mutating public func createVector<T>(ofOffsets offsets: [Offset<T>]) -> Offset<UOffset> { |
| 347 | createVector(ofOffsets: offsets, len: offsets.count) |
| 348 | } |
| 349 | |
| 350 | /// Creates a vector of type Offsets in the buffer |
| 351 | /// - Parameter elements: Array of offsets of type T |
| 352 | /// - Parameter size: Count of elements |
| 353 | /// - returns: Offset of the vector |
| 354 | mutating public func createVector<T>(ofOffsets offsets: [Offset<T>], len: Int) -> Offset<UOffset> { |
| 355 | startVector(len, elementSize: MemoryLayout<Offset<T>>.size) |
| 356 | for o in offsets.reversed() { |
| 357 | push(element: o) |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 358 | } |
Austin Schuh | 58b9b47 | 2020-11-25 19:12:44 -0800 | [diff] [blame^] | 359 | return Offset(offset: endVector(len: len)) |
| 360 | } |
| 361 | |
| 362 | /// Creates a vector of Strings |
| 363 | /// - Parameter str: a vector of strings that will be written into the buffer |
| 364 | /// - returns: Offset of the vector |
| 365 | mutating public func createVector(ofStrings str: [String]) -> Offset<UOffset> { |
| 366 | var offsets: [Offset<String>] = [] |
| 367 | for s in str { |
| 368 | offsets.append(create(string: s)) |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 369 | } |
Austin Schuh | 58b9b47 | 2020-11-25 19:12:44 -0800 | [diff] [blame^] | 370 | return createVector(ofOffsets: offsets) |
| 371 | } |
| 372 | |
| 373 | /// Creates a vector of Flatbuffer structs. |
| 374 | /// |
| 375 | /// The function takes a Type to know what size it is, and alignment |
| 376 | /// - Parameters: |
| 377 | /// - structs: An array of UnsafeMutableRawPointer |
| 378 | /// - type: Type of the struct being written |
| 379 | /// - returns: Offset of the vector |
| 380 | @available( |
| 381 | *, |
| 382 | deprecated, |
| 383 | message: "0.9.0 will be removing the following method. Regenerate the code") |
| 384 | mutating public func createVector<T: Readable>( |
| 385 | structs: [UnsafeMutableRawPointer], |
| 386 | type: T.Type) -> Offset<UOffset> |
| 387 | { |
| 388 | startVector(structs.count &* T.size, elementSize: T.alignment) |
| 389 | for i in structs.reversed() { |
| 390 | create(struct: i, type: T.self) |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 391 | } |
Austin Schuh | 58b9b47 | 2020-11-25 19:12:44 -0800 | [diff] [blame^] | 392 | return Offset(offset: endVector(len: structs.count)) |
| 393 | } |
| 394 | |
| 395 | /// Starts a vector of struct that considers the size and alignment of the struct |
| 396 | /// - Parameters: |
| 397 | /// - count: number of elements to be written |
| 398 | /// - size: size of struct |
| 399 | /// - alignment: alignment of the struct |
| 400 | mutating public func startVectorOfStructs(count: Int, size: Int, alignment: Int) { |
| 401 | startVector(count &* size, elementSize: alignment) |
| 402 | } |
| 403 | |
| 404 | /// Ends the vector of structs and writtens the current offset |
| 405 | /// - Parameter count: number of written elements |
| 406 | /// - Returns: Offset of type UOffset |
| 407 | mutating public func endVectorOfStructs(count: Int) -> Offset<UOffset> { |
| 408 | Offset<UOffset>(offset: endVector(len: count)) |
| 409 | } |
| 410 | |
| 411 | // MARK: - Inserting Structs |
| 412 | |
| 413 | /// Writes a Flatbuffer struct into the buffer |
| 414 | /// - Parameters: |
| 415 | /// - s: Flatbuffer struct |
| 416 | /// - type: Type of the element to be serialized |
| 417 | /// - returns: Offset of the Object |
| 418 | @available( |
| 419 | *, |
| 420 | deprecated, |
| 421 | message: "0.9.0 will be removing the following method. Regenerate the code") |
| 422 | @discardableResult |
| 423 | mutating public func create<T: Readable>( |
| 424 | struct s: UnsafeMutableRawPointer, |
| 425 | type: T.Type) -> Offset<UOffset> |
| 426 | { |
| 427 | let size = T.size |
| 428 | preAlign(len: size, alignment: T.alignment) |
| 429 | _bb.push(struct: s, size: size) |
| 430 | return Offset(offset: _bb.size) |
| 431 | } |
| 432 | |
| 433 | /// prepares the ByteBuffer to receive a struct of size and alignment |
| 434 | /// - Parameters: |
| 435 | /// - size: size of written struct |
| 436 | /// - alignment: alignment of written struct |
| 437 | mutating public func createStructOf(size: Int, alignment: Int) { |
| 438 | preAlign(len: size, alignment: alignment) |
| 439 | _bb.prepareBufferToReceiveStruct(of: size) |
| 440 | } |
| 441 | |
| 442 | /// Adds scalars front to back instead of the default behavior of the normal add |
| 443 | /// - Parameters: |
| 444 | /// - v: element of type Scalar |
| 445 | /// - postion: position relative to the `writerIndex` |
| 446 | mutating public func reverseAdd<T: Scalar>(v: T, postion: Int) { |
| 447 | _bb.reversePush( |
| 448 | value: v, |
| 449 | position: postion, |
| 450 | len: MemoryLayout<T>.size) |
| 451 | } |
| 452 | |
| 453 | /// Ends the struct and returns the current buffer size |
| 454 | /// - Returns: Offset of type UOffset |
| 455 | @discardableResult |
| 456 | public func endStruct() -> Offset<UOffset> { |
| 457 | Offset(offset: _bb.size) |
| 458 | } |
| 459 | |
| 460 | /// Adds the offset of a struct into the vTable |
| 461 | /// |
| 462 | /// The function fatalErrors if we pass an offset that is out of range |
| 463 | /// - Parameter o: offset |
| 464 | mutating public func add(structOffset o: VOffset) { |
| 465 | _vtableStorage.add(loc: FieldLoc(offset: _bb.size, position: VOffset(o))) |
| 466 | } |
| 467 | |
| 468 | // MARK: - Inserting Strings |
| 469 | |
| 470 | /// Insets a string into the buffer using UTF8 |
| 471 | /// - Parameter str: String to be serialized |
| 472 | /// - returns: The strings offset in the buffer |
| 473 | mutating public func create(string str: String?) -> Offset<String> { |
| 474 | guard let str = str else { return Offset() } |
| 475 | let len = str.utf8.count |
| 476 | notNested() |
| 477 | preAlign(len: len &+ 1, type: UOffset.self) |
| 478 | _bb.fill(padding: 1) |
| 479 | _bb.push(string: str, len: len) |
| 480 | push(element: UOffset(len)) |
| 481 | return Offset(offset: _bb.size) |
| 482 | } |
| 483 | |
| 484 | /// Inserts a shared string to the buffer |
| 485 | /// |
| 486 | /// The function checks the stringOffsetmap if it's seen a similar string before |
| 487 | /// - Parameter str: String to be serialized |
| 488 | /// - returns: The strings offset in the buffer |
| 489 | mutating public func createShared(string str: String?) -> Offset<String> { |
| 490 | guard let str = str else { return Offset() } |
| 491 | if let offset = stringOffsetMap[str] { |
| 492 | return offset |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 493 | } |
Austin Schuh | 58b9b47 | 2020-11-25 19:12:44 -0800 | [diff] [blame^] | 494 | let offset = create(string: str) |
| 495 | stringOffsetMap[str] = offset |
| 496 | return offset |
| 497 | } |
| 498 | |
| 499 | // MARK: - Inseting offsets |
| 500 | |
| 501 | /// Adds the offset of an object into the buffer |
| 502 | /// - Parameters: |
| 503 | /// - offset: Offset of another object to be written |
| 504 | /// - position: The predefined position of the object |
| 505 | mutating public func add<T>(offset: Offset<T>, at position: VOffset) { |
| 506 | if offset.isEmpty { return } |
| 507 | add(element: refer(to: offset.o), def: 0, at: position) |
| 508 | } |
| 509 | |
| 510 | /// Pushes a value of type offset into the buffer |
| 511 | /// - Parameter o: Offset |
| 512 | /// - returns: Position of the offset |
| 513 | @discardableResult |
| 514 | mutating public func push<T>(element o: Offset<T>) -> UOffset { |
| 515 | push(element: refer(to: o.o)) |
| 516 | } |
| 517 | |
| 518 | // MARK: - Inserting Scalars to Buffer |
| 519 | |
| 520 | /// Adds a value into the buffer of type Scalar |
| 521 | /// |
| 522 | /// - Parameters: |
| 523 | /// - element: Element to insert |
| 524 | /// - def: Default value for that element |
| 525 | /// - position: The predefined position of the element |
| 526 | mutating public func add<T: Scalar>(element: T, def: T, at position: VOffset) { |
| 527 | if element == def && !serializeDefaults { return } |
| 528 | track(offset: push(element: element), at: position) |
| 529 | } |
| 530 | |
| 531 | /// Adds a value into the buffer of type optional Scalar |
| 532 | /// - Parameters: |
| 533 | /// - element: Optional element of type scalar |
| 534 | /// - position: The predefined position of the element |
| 535 | mutating public func add<T: Scalar>(element: T?, at position: VOffset) { |
| 536 | guard let element = element else { return } |
| 537 | track(offset: push(element: element), at: position) |
| 538 | } |
| 539 | |
| 540 | /// Pushes the values into the buffer |
| 541 | /// - Parameter element: Element to insert |
| 542 | /// - returns: Postion of the Element |
| 543 | @discardableResult |
| 544 | mutating public func push<T: Scalar>(element: T) -> UOffset { |
| 545 | let size = MemoryLayout<T>.size |
| 546 | preAlign( |
| 547 | len: size, |
| 548 | alignment: size) |
| 549 | _bb.push(value: element, len: size) |
| 550 | return _bb.size |
| 551 | } |
| 552 | |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 553 | } |
| 554 | |
| 555 | extension FlatBufferBuilder: CustomDebugStringConvertible { |
Austin Schuh | 58b9b47 | 2020-11-25 19:12:44 -0800 | [diff] [blame^] | 556 | |
| 557 | public var debugDescription: String { |
| 558 | """ |
| 559 | buffer debug: |
| 560 | \(_bb) |
| 561 | builder debug: |
| 562 | { finished: \(finished), serializeDefaults: \(serializeDefaults), isNested: \(isNested) } |
| 563 | """ |
| 564 | } |
| 565 | |
| 566 | /// VTableStorage is a class to contain the VTable buffer that would be serialized into buffer |
| 567 | @usableFromInline |
| 568 | internal class VTableStorage { |
| 569 | /// Memory check since deallocating each time we want to clear would be expensive |
| 570 | /// and memory leaks would happen if we dont deallocate the first allocated memory. |
| 571 | /// memory is promised to be available before adding `FieldLoc` |
| 572 | private var memoryInUse = false |
| 573 | /// Size of FieldLoc in memory |
| 574 | let size = MemoryLayout<FieldLoc>.stride |
| 575 | /// Memeory buffer |
| 576 | var memory: UnsafeMutableRawBufferPointer! |
| 577 | /// Capacity of the current buffer |
| 578 | var capacity: Int = 0 |
| 579 | /// Maximuim offset written to the class |
| 580 | var maxOffset: VOffset = 0 |
| 581 | /// number of fields written into the buffer |
| 582 | var numOfFields: Int = 0 |
| 583 | /// Last written Index |
| 584 | var writtenIndex: Int = 0 |
| 585 | /// the amount of added elements into the buffer |
| 586 | var addedElements: Int { capacity - (numOfFields &* size) } |
| 587 | |
| 588 | /// Creates the memory to store the buffer in |
| 589 | init() { |
| 590 | memory = UnsafeMutableRawBufferPointer.allocate(byteCount: 0, alignment: 0) |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 591 | } |
| 592 | |
Austin Schuh | 58b9b47 | 2020-11-25 19:12:44 -0800 | [diff] [blame^] | 593 | deinit { |
| 594 | memory.deallocate() |
| 595 | } |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 596 | |
Austin Schuh | 58b9b47 | 2020-11-25 19:12:44 -0800 | [diff] [blame^] | 597 | /// Builds a buffer with byte count of fieldloc.size * count of field numbers |
| 598 | /// - Parameter count: number of fields to be written |
| 599 | func start(count: Int) { |
| 600 | assert(count >= 0, "number of fields should NOT be negative") |
| 601 | let capacity = count &* size |
| 602 | ensure(space: capacity) |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 603 | } |
Austin Schuh | 58b9b47 | 2020-11-25 19:12:44 -0800 | [diff] [blame^] | 604 | |
| 605 | /// Adds a FieldLoc into the buffer, which would track how many have been written, |
| 606 | /// and max offset |
| 607 | /// - Parameter loc: Location of encoded element |
| 608 | func add(loc: FieldLoc) { |
| 609 | memory.baseAddress?.advanced(by: writtenIndex).storeBytes(of: loc, as: FieldLoc.self) |
| 610 | writtenIndex = writtenIndex &+ size |
| 611 | numOfFields = numOfFields &+ 1 |
| 612 | maxOffset = max(loc.position, maxOffset) |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 613 | } |
| 614 | |
Austin Schuh | 58b9b47 | 2020-11-25 19:12:44 -0800 | [diff] [blame^] | 615 | /// Clears the data stored related to the encoded buffer |
| 616 | func clear() { |
| 617 | maxOffset = 0 |
| 618 | numOfFields = 0 |
| 619 | writtenIndex = 0 |
| 620 | } |
| 621 | |
| 622 | /// Ensure that the buffer has enough space instead of recreating the buffer each time. |
| 623 | /// - Parameter space: space required for the new vtable |
| 624 | func ensure(space: Int) { |
| 625 | guard space &+ writtenIndex > capacity else { return } |
| 626 | memory.deallocate() |
| 627 | memory = UnsafeMutableRawBufferPointer.allocate(byteCount: space, alignment: size) |
| 628 | capacity = space |
| 629 | } |
| 630 | |
| 631 | /// Loads an object of type `FieldLoc` from buffer memory |
| 632 | /// - Parameter index: index of element |
| 633 | /// - Returns: a FieldLoc at index |
| 634 | func load(at index: Int) -> FieldLoc { |
| 635 | memory.load(fromByteOffset: index, as: FieldLoc.self) |
| 636 | } |
| 637 | |
| 638 | } |
| 639 | |
| 640 | internal struct FieldLoc { |
| 641 | var offset: UOffset |
| 642 | var position: VOffset |
| 643 | } |
| 644 | |
Austin Schuh | 272c613 | 2020-11-14 16:37:52 -0800 | [diff] [blame] | 645 | } |