1 import Foundation 2 3 public final class FlatBufferBuilder { 4 5 /// Vtables used in the buffer are stored in here, so they would be written later in EndTable 6 private var _vtable: [UInt32] = [] 7 /// Reference Vtables that were already written to the buffer 8 private var _vtables: [UOffset] = [] 9 /// Flatbuffer data will be written into 10 private var _bb: ByteBuffer 11 /// A check if the buffer is being written into by a different table 12 private var isNested = false 13 /// Dictonary that stores a map of all the strings that were written to the buffer 14 private var stringOffsetMap: [String: Offset<String>] = [:] 15 /// A check to see if finish(::) was ever called to retreive data object 16 private var finished = false 17 /// A check to see if the buffer should serialize Default values 18 private var serializeDefaults: Bool 19 20 /// Current alignment for the buffer 21 var _minAlignment: Int = 0 { 22 didSet { 23 _bb.alignment = _minAlignment 24 } 25 } 26 27 /// Gives a read access to the buffer's size 28 public var size: UOffset { return _bb.size } 29 /// Data representation of the buffer 30 public var data: Data { 31 assert(finished, "Data shouldn't be called before finish()") 32 return Data(bytes: _bb.memory.advanced(by: _bb.writerIndex), 33 count: _bb.capacity - _bb.writerIndex) 34 } 35 /// Get's the fully sized buffer stored in memory 36 public var fullSizedByteArray: [UInt8] { 37 let ptr = UnsafeBufferPointer(start: _bb.memory.assumingMemoryBound(to: UInt8.self), 38 count: _bb.capacity) 39 return Array(ptr) 40 } 41 /// Returns the written size of the buffer 42 public var sizedByteArray: [UInt8] { 43 let cp = _bb.capacity - _bb.writerIndex 44 let start = _bb.memory.advanced(by: _bb.writerIndex) 45 .bindMemory(to: UInt8.self, capacity: cp) 46 47 let ptr = UnsafeBufferPointer(start: start, count: cp) 48 return Array(ptr) 49 } 50 /// Returns the buffer 51 public var buffer: ByteBuffer { return _bb } 52 53 /// Returns A sized Buffer from the readable bytes 54 public var sizedBuffer: ByteBuffer { 55 assert(finished, "Data shouldn't be called before finish()") 56 return ByteBuffer(memory: _bb.memory.advanced(by: _bb.reader), count: _bb.reader) 57 } 58 59 // MARK: - Init 60 61 /// initialize the buffer with a size 62 /// - Parameters: 63 /// - initialSize: Initial size for the buffer 64 /// - force: Allows default to be serialized into the buffer 65 public init(initialSize: Int32 = 1024, serializeDefaults force: Bool = false) { 66 assert(initialSize > 0, "Size should be greater than zero!") 67 guard isLitteEndian else { 68 fatalError("Reading/Writing a buffer in big endian machine is not supported on swift") 69 } 70 serializeDefaults = force 71 _bb = ByteBuffer(initialSize: Int(initialSize)) 72 } 73 74 /// Clears the buffer and the builder from it's data clearnull75 public func clear() { 76 _minAlignment = 0 77 isNested = false 78 _bb.clear() 79 stringOffsetMap = [:] 80 } 81 82 /// Removes all the offsets from the VTable clearOffsetsnull83 public func clearOffsets() { 84 _vtable = [] 85 } 86 87 // MARK: - Create Tables 88 89 /// Checks if the required fields were serialized into the buffer 90 /// - Parameters: 91 /// - table: offset for the table 92 /// - fields: Array of all the important fields to be serialized requirenull93 public func require(table: Offset<UOffset>, fields: [Int32]) { 94 for field in fields { 95 let start = _bb.capacity - Int(table.o) 96 let startTable = start - Int(_bb.read(def: Int32.self, position: start)) 97 let isOkay = _bb.read(def: VOffset.self, position: startTable + Int(field)) != 0 98 assert(isOkay, "Flatbuffers requires the following field") 99 } 100 } 101 102 /// Finished the buffer by adding the file id and then calling finish 103 /// - Parameters: 104 /// - offset: Offset of the table 105 /// - fileId: Takes the fileId 106 /// - prefix: if false it wont add the size of the buffer finish<T>null107 public func finish<T>(offset: Offset<T>, fileId: String, addPrefix prefix: Bool = false) { 108 let size = MemoryLayout<UOffset>.size 109 preAlign(len: size + (prefix ? size : 0) + FileIdLength, alignment: _minAlignment) 110 assert(fileId.count == FileIdLength, "Flatbuffers requires file id to be 4") 111 _bb.push(string: fileId, len: 4) 112 finish(offset: offset, addPrefix: prefix) 113 } 114 115 /// Finished the buffer by adding the file id, offset, and prefix to it. 116 /// - Parameters: 117 /// - offset: Offset of the table 118 /// - prefix: if false it wont add the size of the buffer finish<T>null119 public func finish<T>(offset: Offset<T>, addPrefix prefix: Bool = false) { 120 notNested() 121 let size = MemoryLayout<UOffset>.size 122 preAlign(len: size + (prefix ? size : 0), alignment: _minAlignment) 123 push(element: refer(to: offset.o)) 124 if prefix { push(element: _bb.size) } 125 clearOffsets() 126 finished = true 127 } 128 129 /// starttable will let the builder know, that a new object is being serialized. 130 /// 131 /// The function will fatalerror if called while there is another object being serialized 132 /// - Parameter numOfFields: Number of elements to be written to the buffer startTablenull133 public func startTable(with numOfFields: Int) -> UOffset { 134 notNested() 135 isNested = true 136 _vtable = [UInt32](repeating: 0, count: numOfFields) 137 return _bb.size 138 } 139 140 141 /// Endtable will let the builder know that the object that's written to it is completed 142 /// 143 /// This would be called after all the elements are serialized, it will add the vtable into the buffer. 144 /// it will fatalError in case the object is called without starttable, or the object has exceeded the limit of 145 /// 2GB, 146 /// - Parameter startOffset:Start point of the object written 147 /// - returns: The root of the table endTablenull148 public func endTable(at startOffset: UOffset) -> UOffset { 149 assert(isNested, "Calling endtable without calling starttable") 150 let sizeofVoffset = MemoryLayout<VOffset>.size 151 let vTableOffset = push(element: SOffset(0)) 152 153 let tableObjectSize = vTableOffset - startOffset 154 assert(tableObjectSize < 0x10000, "Buffer can't grow beyond 2 Gigabytes") 155 156 var writeIndex = 0 157 for (index,j) in _vtable.lazy.reversed().enumerated() { 158 if j != 0 { 159 writeIndex = _vtable.count - index 160 break 161 } 162 } 163 164 for i in stride(from: writeIndex - 1, to: -1, by: -1) { 165 let off = _vtable[i] == 0 ? 0 : vTableOffset - _vtable[i] 166 _bb.push(value: VOffset(off), len: sizeofVoffset) 167 } 168 169 _bb.push(value: VOffset(tableObjectSize), len: sizeofVoffset) 170 _bb.push(value: (UInt16(writeIndex + 2) * UInt16(sizeofVoffset)), len: sizeofVoffset) 171 172 clearOffsets() 173 let vt_use = _bb.size 174 175 var isAlreadyAdded: Int? 176 177 let vt2 = _bb.memory.advanced(by: _bb.writerIndex) 178 let len2 = vt2.load(fromByteOffset: 0, as: Int16.self) 179 180 for table in _vtables { 181 let position = _bb.capacity - Int(table) 182 let vt1 = _bb.memory.advanced(by: position) 183 let len1 = _bb.read(def: Int16.self, position: position) 184 if (len2 != len1 || 0 != memcmp(vt1, vt2, Int(len2))) { continue } 185 186 isAlreadyAdded = Int(table) 187 break 188 } 189 190 if let offset = isAlreadyAdded { 191 let vTableOff = Int(vTableOffset) 192 let space = _bb.capacity - vTableOff 193 _bb.write(value: Int32(offset - vTableOff), index: space, direct: true) 194 _bb.resize(_bb.capacity - space) 195 } else { 196 _bb.write(value: Int32(vt_use) - Int32(vTableOffset), index: Int(vTableOffset)) 197 _vtables.append(_bb.size) 198 } 199 isNested = false 200 return vTableOffset 201 } 202 203 // MARK: - Builds Buffer 204 205 /// asserts to see if the object is not nested notNestednull206 fileprivate func notNested() { 207 assert(!isNested, "Object serialization must not be nested") 208 } 209 210 /// Changes the minimuim alignment of the buffer 211 /// - Parameter size: size of the current alignment minAlignmentnull212 fileprivate func minAlignment(size: Int) { 213 if size > _minAlignment { 214 _minAlignment = size 215 } 216 } 217 218 /// Gets the padding for the current element 219 /// - Parameters: 220 /// - bufSize: Current size of the buffer + the offset of the object to be written 221 /// - elementSize: Element size paddingnull222 fileprivate func padding(bufSize: UInt32, elementSize: UInt32) -> UInt32 { 223 ((~bufSize) &+ 1) & (elementSize - 1) 224 } 225 226 /// Prealigns the buffer before writting a new object into the buffer 227 /// - Parameters: 228 /// - len:Length of the object 229 /// - alignment: Alignment type preAlignnull230 fileprivate func preAlign(len: Int, alignment: Int) { 231 minAlignment(size: alignment) 232 _bb.fill(padding: padding(bufSize: _bb.size + UOffset(len), elementSize: UOffset(alignment))) 233 } 234 235 /// Prealigns the buffer before writting a new object into the buffer 236 /// - Parameters: 237 /// - len: Length of the object 238 /// - type: Type of the object to be written preAlign<T: Scalar>null239 fileprivate func preAlign<T: Scalar>(len: Int, type: T.Type) { 240 preAlign(len: len, alignment: MemoryLayout<T>.size) 241 } 242 243 /// Refers to an object that's written in the buffer 244 /// - Parameter off: the objects index value refernull245 fileprivate func refer(to off: UOffset) -> UOffset { 246 let size = MemoryLayout<UOffset>.size 247 preAlign(len: size, alignment: size) 248 return _bb.size - off + UInt32(size) 249 } 250 251 /// Tracks the elements written into the buffer 252 /// - Parameters: 253 /// - offset: The offset of the element witten 254 /// - position: The position of the element tracknull255 fileprivate func track(offset: UOffset, at position: VOffset) { 256 _vtable[Int(position)] = offset 257 } 258 259 // MARK: - Vectors 260 261 /// Starts a vector of length and Element size startVectornull262 public func startVector(_ len: Int, elementSize: Int) { 263 notNested() 264 isNested = true 265 preAlign(len: len * elementSize, type: UOffset.self) 266 preAlign(len: len * elementSize, alignment: elementSize) 267 } 268 269 /// Ends the vector of at length 270 /// 271 /// The current function will fatalError if startVector is called before serializing the vector 272 /// - Parameter len: Length of the buffer endVectornull273 public func endVector(len: Int) -> UOffset { 274 assert(isNested, "Calling endVector without calling startVector") 275 isNested = false 276 return push(element: Int32(len)) 277 } 278 279 /// Creates a vector of type Scalar in the buffer 280 /// - Parameter elements: elements to be written into the buffer 281 /// - returns: Offset of the vector createVector<T: Scalar>null282 public func createVector<T: Scalar>(_ elements: [T]) -> Offset<UOffset> { 283 return createVector(elements, size: elements.count) 284 } 285 286 /// Creates a vector of type Scalar in the buffer 287 /// - Parameter elements: Elements to be written into the buffer 288 /// - Parameter size: Count of elements 289 /// - returns: Offset of the vector createVector<T: Scalar>null290 public func createVector<T: Scalar>(_ elements: [T], size: Int) -> Offset<UOffset> { 291 let size = size 292 startVector(size, elementSize: MemoryLayout<T>.size) 293 _bb.push(elements: elements) 294 return Offset(offset: endVector(len: size)) 295 } 296 297 /// Creates a vector of type Enums in the buffer 298 /// - Parameter elements: elements to be written into the buffer 299 /// - returns: Offset of the vector createVector<T: Enum>null300 public func createVector<T: Enum>(_ elements: [T]) -> Offset<UOffset> { 301 return createVector(elements, size: elements.count) 302 } 303 304 /// Creates a vector of type Enums in the buffer 305 /// - Parameter elements: Elements to be written into the buffer 306 /// - Parameter size: Count of elements 307 /// - returns: Offset of the vector createVector<T: Enum>null308 public func createVector<T: Enum>(_ elements: [T], size: Int) -> Offset<UOffset> { 309 let size = size 310 startVector(size, elementSize: T.byteSize) 311 for e in elements.lazy.reversed() { 312 _bb.push(value: e.value, len: T.byteSize) 313 } 314 return Offset(offset: endVector(len: size)) 315 } 316 317 /// Creates a vector of type Offsets in the buffer 318 /// - Parameter offsets:Array of offsets of type T 319 /// - returns: Offset of the vector createVector<T>null320 public func createVector<T>(ofOffsets offsets: [Offset<T>]) -> Offset<UOffset> { 321 createVector(ofOffsets: offsets, len: offsets.count) 322 } 323 324 /// Creates a vector of type Offsets in the buffer 325 /// - Parameter elements: Array of offsets of type T 326 /// - Parameter size: Count of elements 327 /// - returns: Offset of the vector createVector<T>null328 public func createVector<T>(ofOffsets offsets: [Offset<T>], len: Int) -> Offset<UOffset> { 329 startVector(len, elementSize: MemoryLayout<Offset<T>>.size) 330 for o in offsets.lazy.reversed() { 331 push(element: o) 332 } 333 return Offset(offset: endVector(len: len)) 334 } 335 336 /// Creates a vector of Strings 337 /// - Parameter str: a vector of strings that will be written into the buffer 338 /// - returns: Offset of the vector createVectornull339 public func createVector(ofStrings str: [String]) -> Offset<UOffset> { 340 var offsets: [Offset<String>] = [] 341 for s in str { 342 offsets.append(create(string: s)) 343 } 344 return createVector(ofOffsets: offsets) 345 } 346 347 /// Creates a vector of Flatbuffer structs. 348 /// 349 /// The function takes a Type to know what size it is, and alignment 350 /// - Parameters: 351 /// - structs: An array of UnsafeMutableRawPointer 352 /// - type: Type of the struct being written 353 /// - returns: Offset of the vector 354 public func createVector<T: Readable>(structs: [UnsafeMutableRawPointer], 355 type: T.Type) -> Offset<UOffset> { 356 startVector(structs.count * T.size, elementSize: T.alignment) 357 for i in structs.lazy.reversed() { 358 create(struct: i, type: T.self) 359 } 360 return Offset(offset: endVector(len: structs.count)) 361 } 362 363 // MARK: - Inserting Structs 364 365 /// Writes a Flatbuffer struct into the buffer 366 /// - Parameters: 367 /// - s: Flatbuffer struct 368 /// - type: Type of the element to be serialized 369 /// - returns: Offset of the Object 370 @discardableResult 371 public func create<T: Readable>(struct s: UnsafeMutableRawPointer, 372 type: T.Type) -> Offset<UOffset> { 373 let size = T.size 374 preAlign(len: size, alignment: T.alignment) 375 _bb.push(struct: s, size: size) 376 return Offset(offset: _bb.size) 377 } 378 379 /// Adds the offset of a struct into the vTable 380 /// 381 /// The function fatalErrors if we pass an offset that is out of range 382 /// - Parameter o: offset addnull383 public func add(structOffset o: UOffset) { 384 guard Int(o) < _vtable.count else { fatalError("Out of the table range") } 385 _vtable[Int(o)] = _bb.size 386 } 387 388 // MARK: - Inserting Strings 389 390 /// Insets a string into the buffer using UTF8 391 /// - Parameter str: String to be serialized 392 /// - returns: The strings offset in the buffer createnull393 public func create(string str: String) -> Offset<String> { 394 let len = str.count 395 notNested() 396 preAlign(len: len + 1, type: UOffset.self) 397 _bb.fill(padding: 1) 398 _bb.push(string: str, len: len) 399 push(element: UOffset(len)) 400 return Offset(offset: _bb.size) 401 } 402 403 /// Inserts a shared string to the buffer 404 /// 405 /// The function checks the stringOffsetmap if it's seen a similar string before 406 /// - Parameter str: String to be serialized 407 /// - returns: The strings offset in the buffer createSharednull408 public func createShared(string str: String) -> Offset<String> { 409 if let offset = stringOffsetMap[str] { 410 return offset 411 } 412 let offset = create(string: str) 413 stringOffsetMap[str] = offset 414 return offset 415 } 416 417 // MARK: - Inseting offsets 418 419 /// Adds the offset of an object into the buffer 420 /// - Parameters: 421 /// - offset: Offset of another object to be written 422 /// - position: The predefined position of the object add<T>null423 public func add<T>(offset: Offset<T>, at position: VOffset) { 424 if offset.isEmpty { 425 track(offset: 0, at: position) 426 return 427 } 428 add(element: refer(to: offset.o), def: 0, at: position) 429 } 430 431 /// Pushes a value of type offset into the buffer 432 /// - Parameter o: Offset 433 /// - returns: Position of the offset 434 @discardableResult push<T>null435 public func push<T>(element o: Offset<T>) -> UOffset { 436 push(element: refer(to: o.o)) 437 } 438 439 // MARK: - Inserting Scalars to Buffer 440 441 /// Adds a value into the buffer of type Scalar 442 /// 443 /// - Parameters: 444 /// - element: Element to insert 445 /// - def: Default value for that element 446 /// - position: The predefined position of the element add<T: Scalar>null447 public func add<T: Scalar>(element: T, def: T, at position: VOffset) { 448 if (element == def && !serializeDefaults) { 449 track(offset: 0, at: position) 450 return 451 } 452 let off = push(element: element) 453 track(offset: off, at: position) 454 } 455 456 /// Adds Boolean values into the buffer 457 /// - Parameters: 458 /// - condition: Condition to insert 459 /// - def: Default condition 460 /// - position: The predefined position of the element addnull461 public func add(condition: Bool, def: Bool, at position: VOffset) { 462 if (condition == def && !serializeDefaults) { 463 track(offset: 0, at: position) 464 return 465 } 466 let off = push(element: Byte(condition ? 1 : 0)) 467 track(offset: off, at: position) 468 } 469 470 /// Pushes the values into the buffer 471 /// - Parameter element: Element to insert 472 /// - returns: Postion of the Element 473 @discardableResult push<T: Scalar>null474 public func push<T: Scalar>(element: T) -> UOffset { 475 preAlign(len: MemoryLayout<T>.size, 476 alignment: MemoryLayout<T>.size) 477 _bb.push(value: element, len: MemoryLayout<T>.size) 478 return _bb.size 479 } 480 } 481 482 extension FlatBufferBuilder: CustomDebugStringConvertible { 483 484 public var debugDescription: String { 485 """ 486 buffer debug: 487 \(_bb) 488 builder debug: 489 { finished: \(finished), serializeDefaults: \(serializeDefaults), isNested: \(isNested) } 490 """ 491 } 492 } 493