[Swift] Swift implementation 🎉🎉 (#5603)

* Implemented the swift version of Flatbuffers

Implemented serailzing, reading, and mutating data from object monster

Fixes mis-aligned pointer issue

Fixes issue when shared strings are removed from table

Adds swift enum, structs code gen

Fixed namespace issues + started implementing the table gen

Added Mutate function to the code generator

Generated linux test cases

Fixed an issue with bools, and structs readers in table writer

Swift docker image added

Updated the test cases, and removed a method parameters in swift

Fixed createVector api when called with scalars

Fixed issues with scalar arrays, and fixed the code gen namespaces, added sample_binary.swift

Cleaned up project

Added enum vectors, and their readers

Refactored code

Added swift into the support document

Added documentation in docs, and fixed a small issue with Data() not being returned correctly

Fixes Lowercase issue, and prevents generating lookups for deprecated keys

* Made all the required funcs to have const + removed unneeded code + fix lowercase func

* Removed transform from lowercased and moved it to function

* Fixes an issue with iOS allocation from read

* Refactored cpp code to be more readable

* casts position into int for position

* Fix enums issue, moves scalar writer code to use memcpy

* Removed c_str from struct function

* Fixed script to generate new objects when ran on travis ci: fix

* Handles deallocating space allocated for structs

* Updated the test cases to adhere to the fileprivate lookup, no mutation for unions, and updated the names of the vector functions
This commit is contained in:
mustiikhalil
2020-01-09 23:12:10 +03:00
committed by Wouter van Oortmerssen
parent 55686100aa
commit 04d80f255d
41 changed files with 4015 additions and 40 deletions

View File

@@ -0,0 +1,243 @@
import Foundation
public final class ByteBuffer {
/// pointer to the start of the buffer object in memory
private var _memory: UnsafeMutableRawPointer
/// The size of the elements written to the buffer + their paddings
private var _writerSize: Int = 0
/// Capacity of UInt8 the buffer can hold
private var _capacity: Int
/// Aliginment of the current memory being written to the buffer
internal var alignment = 1
/// Current Index which is being used to write to the buffer, it is written from the end to the start of the buffer
internal var writerIndex: Int { return _capacity - _writerSize }
/// Reader is the position of the current Writer Index (capacity - size)
public var reader: Int { return writerIndex }
/// Current size of the buffer
public var size: UOffset { return UOffset(_writerSize) }
/// Public Pointer to the buffer object in memory. This should NOT be modified for any reason
public var memory: UnsafeMutableRawPointer { return _memory }
/// Current capacity for the buffer
public var capacity: Int { return _capacity }
/// Constructor that creates a Flatbuffer object from a UInt8
/// - Parameter bytes: Array of UInt8
public init(bytes: [UInt8]) {
let ptr = UnsafePointer(bytes)
_memory = UnsafeMutableRawPointer.allocate(byteCount: bytes.count, alignment: alignment)
_memory.copyMemory(from: ptr, byteCount: bytes.count)
_capacity = bytes.count
_writerSize = _capacity
}
/// Constructor that creates a Flatbuffer from the Swift Data type object
/// - Parameter data: Swift data Object
public init(data: Data) {
let pointer = UnsafeMutablePointer<UInt8>.allocate(capacity: data.count)
data.copyBytes(to: pointer, count: data.count)
_memory = UnsafeMutableRawPointer(pointer)
_capacity = data.count
_writerSize = _capacity
}
/// Constructor that creates a Flatbuffer instance with a size
/// - Parameter size: Length of the buffer
init(initialSize size: Int) {
let size = size.convertToPowerofTwo
_memory = UnsafeMutableRawPointer.allocate(byteCount: size, alignment: alignment)
_memory.initializeMemory(as: UInt8.self, repeating: 0, count: size)
_capacity = size
}
/// Creates a copy of the existing flatbuffer, by copying it to a different memory.
/// - Parameters:
/// - memory: Current memory of the buffer
/// - count: count of bytes
/// - removeBytes: Removes a number of bytes from the current size
internal init(memory: UnsafeMutableRawPointer, count: Int, removing removeBytes: Int) {
_memory = UnsafeMutableRawPointer.allocate(byteCount: count, alignment: alignment)
_memory.copyMemory(from: memory, byteCount: count)
_capacity = count
_writerSize = removeBytes
}
deinit { _memory.deallocate() }
/// Fills the buffer with padding by adding to the writersize
/// - Parameter padding: Amount of padding between two to be serialized objects
func fill(padding: UInt32) {
ensureSpace(size: UInt8(padding))
_writerSize += (MemoryLayout<UInt8>.size * Int(padding))
}
///Adds an array of type Scalar to the buffer memory
/// - Parameter elements: An array of Scalars
func push<T: Scalar>(elements: [T]) {
let size = elements.count * MemoryLayout<T>.size
ensureSpace(size: UInt8(size))
elements.lazy.reversed().forEach { (s) in
push(value: s, len: MemoryLayout.size(ofValue: s))
}
}
/// A custom type of structs that are padded according to the flatbuffer padding,
/// - Parameters:
/// - value: Pointer to the object in memory
/// - size: Size of Value being written to the buffer
func push(struct value: UnsafeMutableRawPointer, size: Int) {
ensureSpace(size: UInt8(size))
_memory.advanced(by: writerIndex - size).copyMemory(from: value, byteCount: size)
defer { value.deallocate() }
_writerSize += size
}
/// Adds an object of type Scalar into the buffer
/// - Parameters:
/// - value: Object that will be written to the buffer
/// - len: Offset to subtract from the WriterIndex
func push<T: Scalar>(value: T, len: Int) {
ensureSpace(size: UInt8(len))
var v = value.convertedEndian
memcpy(_memory.advanced(by: writerIndex - len), &v, len)
_writerSize += len
}
/// 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
func push(string str: String, len: Int) {
ensureSpace(size: UInt8(len))
if str.utf8.withContiguousStorageIfAvailable({ self.push(bytes: $0, len: len) }) != nil {
} else {
let utf8View = str.utf8
for c in utf8View.lazy.reversed() {
push(value: c, len: 1)
}
}
}
/// Writes a string to Bytebuffer using UTF8View
/// - Parameters:
/// - bytes: Pointer to the view
/// - len: Size of string
private func push(bytes: UnsafeBufferPointer<String.UTF8View.Element>, len: Int) -> Bool {
_memory.advanced(by: writerIndex - len).copyMemory(from:
UnsafeRawPointer(bytes.baseAddress!), byteCount: len)
_writerSize += len
return true
}
/// Write stores an object into the buffer directly or indirectly.
///
/// Direct: ignores the capacity of buffer which would mean we are referring to the direct point in memory
/// indirect: takes into respect the current capacity of the buffer (capacity - index), writing to the buffer from the end
/// - Parameters:
/// - value: Value that needs to be written to the buffer
/// - index: index to write to
/// - direct: Should take into consideration the capacity of the buffer
func write<T>(value: T, index: Int, direct: Bool = false) {
var index = index
if !direct {
index = _capacity - index
}
_memory.storeBytes(of: value, toByteOffset: index, as: T.self)
}
/// Makes sure that buffer has enouch space for each of the objects that will be written into it
/// - Parameter size: size of object
@discardableResult
func ensureSpace(size: UInt8) -> UInt8 {
if Int(size) + _writerSize > _capacity { reallocate(size) }
assert(size < FlatBufferMaxSize, "Buffer can't grow beyond 2 Gigabytes")
return size
}
/// Reallocates the buffer incase the object to be written doesnt fit in the current buffer
/// - Parameter size: Size of the current object
fileprivate func reallocate(_ size: UInt8) {
let currentWritingIndex = writerIndex
while _capacity <= _writerSize + Int(size) {
_capacity = _capacity << 1
}
/// solution take from Apple-NIO
_capacity = _capacity.convertToPowerofTwo
let newData = UnsafeMutableRawPointer.allocate(byteCount: _capacity, alignment: alignment)
newData.initializeMemory(as: UInt8.self, repeating: 0, count: _capacity)
newData
.advanced(by: writerIndex)
.copyMemory(from: _memory.advanced(by: currentWritingIndex), byteCount: _writerSize)
_memory.deallocate()
_memory = newData
}
/// Clears the current size of the buffer
public func clearSize() {
_writerSize = 0
}
/// Clears the current instance of the buffer, replacing it with new memory
public func clear() {
_writerSize = 0
alignment = 1
_memory.deallocate()
_memory = UnsafeMutableRawPointer.allocate(byteCount: _capacity, alignment: alignment)
}
/// Resizes the buffer size
/// - Parameter size: new size for the buffer
internal func resize(_ size: Int) {
_writerSize = size
}
/// Reads an object from the buffer
/// - Parameters:
/// - def: Type of the object
/// - position: the index of the object in the buffer
public func read<T>(def: T.Type, position: Int) -> T {
return _memory.advanced(by: position).load(as: T.self)
}
/// Reads a slice from the memory assuming a type of T
/// - Parameters:
/// - index: index of the object to be read from the buffer
/// - count: count of bytes in memory
public func readSlice<T>(index: Int32,
count: Int32) -> [T] {
let start = _memory.advanced(by: Int(index)).assumingMemoryBound(to: T.self)
let array = UnsafeBufferPointer(start: start, count: Int(count))
return Array(array)
}
/// Reads a string from the buffer and encodes it to a swift string
/// - Parameters:
/// - index: index of the string in the buffer
/// - count: length of the string
/// - type: Encoding of the string
public func readString(at index: Int32,
count: Int32,
type: String.Encoding = .utf8) -> String? {
let start = _memory.advanced(by: Int(index)).assumingMemoryBound(to: UInt8.self)
let bufprt = UnsafeBufferPointer(start: start, count: Int(count))
return String(bytes: Array(bufprt), encoding: type)
}
/// Creates a new Flatbuffer object that's duplicated from the current one
/// - Parameter removeBytes: the amount of bytes to remove from the current Size
public func duplicate(removing removeBytes: Int = 0) -> ByteBuffer {
return ByteBuffer(memory: _memory, count: _capacity, removing: _writerSize - removeBytes)
}
#if DEBUG
func debugMemory(str: String) {
let bufprt = UnsafeBufferPointer(start: _memory.assumingMemoryBound(to: UInt8.self),
count: _capacity)
let a = Array(bufprt)
print(str, a, " \nwith buffer size: \(a.count) and writer size: \(_writerSize)")
}
#endif
}

View File

@@ -0,0 +1,89 @@
#if os(Linux)
import CoreFoundation
#else
import Foundation
#endif
/// A boolean to see if the system is littleEndian
let isLitteEndian = CFByteOrderGetCurrent() == Int(CFByteOrderLittleEndian.rawValue)
/// Constant for the file id length
let FileIdLength = 4
/// Type aliases
public typealias Byte = UInt8
public typealias UOffset = UInt32
public typealias SOffset = Int32
public typealias VOffset = UInt16
/// Maximum size for a buffer
public let FlatBufferMaxSize = UInt32.max << ((MemoryLayout<SOffset>.size * 8 - 1) - 1)
/// Protocol that confirms all the numbers
///
/// Scalar is used to confirm all the numbers that can be represented in a FlatBuffer. It's used to write/read from the buffer.
public protocol Scalar: Equatable {
associatedtype NumericValue
var convertedEndian: NumericValue { get }
}
extension Scalar where Self: FixedWidthInteger {
/// Converts the value from BigEndian to LittleEndian
///
/// Converts values to little endian on machines that work with BigEndian, however this is NOT TESTED yet.
public var convertedEndian: NumericValue {
if isLitteEndian { return self as! Self.NumericValue }
fatalError("This is not tested! please report an issue on the offical flatbuffers repo")
}
}
extension Double: Scalar {
public typealias NumericValue = UInt64
public var convertedEndian: UInt64 {
if isLitteEndian { return self.bitPattern }
return self.bitPattern.littleEndian
}
}
extension Float32: Scalar {
public typealias NumericValue = UInt32
public var convertedEndian: UInt32 {
if isLitteEndian { return self.bitPattern }
return self.bitPattern.littleEndian
}
}
extension Int: Scalar {
public typealias NumericValue = Int
}
extension Int8: Scalar {
public typealias NumericValue = Int8
}
extension Int16: Scalar {
public typealias NumericValue = Int16
}
extension Int32: Scalar {
public typealias NumericValue = Int32
}
extension Int64: Scalar {
public typealias NumericValue = Int64
}
extension UInt8: Scalar {
public typealias NumericValue = UInt8
}
extension UInt16: Scalar {
public typealias NumericValue = UInt16
}
extension UInt32: Scalar {
public typealias NumericValue = UInt32
}
extension UInt64: Scalar {
public typealias NumericValue = UInt64
}

View File

@@ -0,0 +1,486 @@
import Foundation
public final class FlatBufferBuilder {
/// Vtables used in the buffer are stored in here, so they would be written later in EndTable
private var _vtable: [UInt32] = []
/// Reference Vtables that were already written to the buffer
private var _vtables: [UOffset] = []
/// Flatbuffer data will be written into
private var _bb: ByteBuffer
/// 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<String>] = [:]
/// 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 { return _bb.size }
/// Data representation of the buffer
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)
}
/// Get's the fully sized buffer stored in memory
public var fullSizedByteArray: [UInt8] {
let ptr = UnsafeBufferPointer(start: _bb.memory.assumingMemoryBound(to: UInt8.self),
count: _bb.capacity)
return Array(ptr)
}
/// Returns the written size of the buffer
public var sizedByteArray: [UInt8] {
let cp = _bb.capacity - _bb.writerIndex
let start = _bb.memory.advanced(by: _bb.writerIndex)
.bindMemory(to: UInt8.self, capacity: cp)
let ptr = UnsafeBufferPointer(start: start, count: cp)
return Array(ptr)
}
/// Returns the buffer
public var buffer: ByteBuffer { return _bb }
// MARK: - Init
/// initialize the buffer with a size
/// - Parameters:
/// - initialSize: Initial size for the buffer
/// - force: Allows default to be serialized into the buffer
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 buffer and the builder from it's data
public func clear() {
_minAlignment = 0
isNested = false
_bb.clear()
stringOffsetMap = [:]
}
/// Removes all the offsets from the VTable
public func clearOffsets() {
_vtable = []
}
}
// MARK: - Create Tables
extension FlatBufferBuilder {
/// 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
public func require(table: Offset<UOffset>, 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
public func finish<T>(offset: Offset<T>, 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
public func finish<T>(offset: Offset<T>, 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) }
clearOffsets()
finished = true
}
/// starttable 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
/// - Parameter numOfFields: Number of elements to be written to the buffer
public func startTable(with numOfFields: Int) -> UOffset {
notNested()
isNested = true
_vtable = [UInt32](repeating: 0, count: numOfFields)
return _bb.size
}
/// Endtable will let the builder 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 vtable into the buffer.
/// it will fatalError in case the object is called without starttable, or the object has exceeded the limit of
/// 2GB,
/// - Parameter startOffset:Start point of the object written
/// - returns: The root of the table
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")
var writeIndex = 0
for (index,j) in _vtable.lazy.reversed().enumerated() {
if j != 0 {
writeIndex = _vtable.count - index
break
}
}
for i in stride(from: writeIndex - 1, to: -1, by: -1) {
let off = _vtable[i] == 0 ? 0 : vTableOffset - _vtable[i]
_bb.push(value: VOffset(off), len: sizeofVoffset)
}
_bb.push(value: VOffset(tableObjectSize), len: sizeofVoffset)
_bb.push(value: (UInt16(writeIndex + 2) * UInt16(sizeofVoffset)), len: sizeofVoffset)
clearOffsets()
let vt_use = _bb.size
var isAlreadyAdded: Int?
mainLoop: for table in _vtables {
let vt1 = _bb.capacity - Int(table)
let vt2 = _bb.writerIndex
let len = _bb.read(def: Int16.self, position: vt1)
guard len == _bb.read(def: Int16.self, position: vt2) else { break }
for i in stride(from: sizeofVoffset, to: Int(len), by: sizeofVoffset) {
let vt1ReadValue = _bb.read(def: Int16.self, position: vt1 + i)
let vt2ReadValue = _bb.read(def: Int16.self, position: vt2 + i)
if vt1ReadValue != vt2ReadValue {
break mainLoop
}
}
isAlreadyAdded = Int(table)
}
if let offset = isAlreadyAdded {
let vTableOff = Int(vTableOffset)
let space = _bb.capacity - vTableOff
_bb.write(value: Int32(offset - vTableOff), index: space, direct: true)
_bb.resize(_bb.capacity - space)
} else {
_bb.write(value: Int32(vt_use) - Int32(vTableOffset), index: Int(vTableOffset))
_vtables.append(_bb.size)
}
isNested = false
return vTableOffset
}
// MARK: - Builds Buffer
/// asserts to see if the object is not nested
fileprivate func notNested() {
assert(!isNested, "Object serialization must not be nested")
}
/// Changes the minimuim alignment of the buffer
/// - Parameter size: size of the current alignment
fileprivate 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
fileprivate 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
fileprivate func preAlign(len: Int, alignment: Int) {
minAlignment(size: alignment)
_bb.fill(padding: 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
fileprivate 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
fileprivate 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
fileprivate func track(offset: UOffset, at position: VOffset) {
_vtable[Int(position)] = offset
}
// MARK: - Vectors
/// Starts a vector of length and Element size
public func startVector(_ len: Int, elementSize: Int) {
notNested()
isNested = true
preAlign(len: len * elementSize, type: UOffset.self)
preAlign(len: len * elementSize, alignment: elementSize)
}
/// Ends the vector of at length
///
/// The current function will fatalError if startVector is called before serializing the vector
/// - Parameter len: Length of the buffer
public func endVector(len: Int) -> UOffset {
assert(isNested, "Calling endVector without calling startVector")
isNested = false
return push(element: Int32(len))
}
/// Creates a vector of type Scalar in the buffer
/// - Parameter elements: elements to be written into the buffer
/// - returns: Offset of the vector
public func createVector<T: Scalar>(_ elements: [T]) -> Offset<UOffset> {
return createVector(elements, size: elements.count)
}
/// Creates a vector of type Scalar in the buffer
/// - Parameter elements: Elements to be written into the buffer
/// - Parameter size: Count of elements
/// - returns: Offset of the vector
public func createVector<T: Scalar>(_ elements: [T], size: Int) -> Offset<UOffset> {
let size = size
startVector(size, elementSize: MemoryLayout<T>.size)
_bb.push(elements: elements)
return Offset(offset: endVector(len: size))
}
/// Creates a vector of type Enums in the buffer
/// - Parameter elements: elements to be written into the buffer
/// - returns: Offset of the vector
public func createVector<T: Enum>(_ elements: [T]) -> Offset<UOffset> {
return createVector(elements, size: elements.count)
}
/// Creates a vector of type Enums in the buffer
/// - Parameter elements: Elements to be written into the buffer
/// - Parameter size: Count of elements
/// - returns: Offset of the vector
public func createVector<T: Enum>(_ elements: [T], size: Int) -> Offset<UOffset> {
let size = size
startVector(size, elementSize: T.byteSize)
for e in elements.lazy.reversed() {
_bb.push(value: e.value, len: T.byteSize)
}
return Offset(offset: endVector(len: size))
}
/// Creates a vector of type Offsets in the buffer
/// - Parameter offsets:Array of offsets of type T
/// - returns: Offset of the vector
public func createVector<T>(ofOffsets offsets: [Offset<T>]) -> Offset<UOffset> {
createVector(ofOffsets: offsets, len: offsets.count)
}
/// Creates a vector of type Offsets in the buffer
/// - Parameter elements: Array of offsets of type T
/// - Parameter size: Count of elements
/// - returns: Offset of the vector
public func createVector<T>(ofOffsets offsets: [Offset<T>], len: Int) -> Offset<UOffset> {
startVector(len, elementSize: MemoryLayout<Offset<T>>.size)
for o in offsets.lazy.reversed() {
push(element: o)
}
return Offset(offset: endVector(len: len))
}
/// Creates a vector of Strings
/// - Parameter str: a vector of strings that will be written into the buffer
/// - returns: Offset of the vector
public func createVector(ofStrings str: [String]) -> Offset<UOffset> {
var offsets: [Offset<String>] = []
for s in str {
offsets.append(create(string: s))
}
return createVector(ofOffsets: offsets)
}
/// Creates a vector of Flatbuffer structs.
///
/// The function takes a Type to know what size it is, and alignment
/// - Parameters:
/// - structs: An array of UnsafeMutableRawPointer
/// - type: Type of the struct being written
/// - returns: Offset of the vector
public func createVector<T: Readable>(structs: [UnsafeMutableRawPointer],
type: T.Type) -> Offset<UOffset> {
startVector(structs.count * T.size, elementSize: T.alignment)
for i in structs.lazy.reversed() {
create(struct: i, type: T.self)
}
return Offset(offset: endVector(len: structs.count))
}
// MARK: - Inserting Structs
/// Writes a Flatbuffer struct into the buffer
/// - Parameters:
/// - s: Flatbuffer struct
/// - type: Type of the element to be serialized
/// - returns: Offset of the Object
@discardableResult
public func create<T: Readable>(struct s: UnsafeMutableRawPointer,
type: T.Type) -> Offset<UOffset> {
let size = T.size
preAlign(len: size, alignment: T.alignment)
_bb.push(struct: s, size: size)
return Offset(offset: _bb.size)
}
/// Adds the offset of a struct into the vTable
///
/// The function fatalErrors if we pass an offset that is out of range
/// - Parameter o: offset
public func add(structOffset o: UOffset) {
guard Int(o) < _vtable.count else { fatalError("Out of the table range") }
_vtable[Int(o)] = _bb.size
}
// MARK: - Inserting Strings
/// Insets a string into the buffer using UTF8
/// - Parameter str: String to be serialized
/// - returns: The strings offset in the buffer
public func create(string str: String) -> Offset<String> {
let len = str.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)
}
/// Inserts a shared string to the buffer
///
/// The function checks the stringOffsetmap if it's seen a similar string before
/// - Parameter str: String to be serialized
/// - returns: The strings offset in the buffer
public func createShared(string str: String) -> Offset<String> {
if let offset = stringOffsetMap[str] {
return offset
}
let offset = create(string: str)
stringOffsetMap[str] = offset
return offset
}
// MARK: - Inseting offsets
/// Adds the offset of an object into the buffer
/// - Parameters:
/// - offset: Offset of another object to be written
/// - position: The predefined position of the object
public func add<T>(offset: Offset<T>, at position: VOffset) {
if offset.isEmpty {
track(offset: 0, at: position)
return
}
add(element: refer(to: offset.o), def: 0, at: position)
}
/// Pushes a value of type offset into the buffer
/// - Parameter o: Offset
/// - returns: Position of the offset
@discardableResult
public func push<T>(element o: Offset<T>) -> UOffset {
push(element: refer(to: o.o))
}
// MARK: - Inserting Scalars to Buffer
/// Adds a value into the buffer of type Scalar
///
/// - Parameters:
/// - element: Element to insert
/// - def: Default value for that element
/// - position: The predefined position of the element
public func add<T: Scalar>(element: T, def: T, at position: VOffset) {
if (element == def && !serializeDefaults) {
track(offset: 0, at: position)
return
}
let off = push(element: element)
track(offset: off, at: position)
}
/// Adds Boolean values into the buffer
/// - Parameters:
/// - condition: Condition to insert
/// - def: Default condition
/// - position: The predefined position of the element
public func add(condition: Bool, def: Bool, at position: VOffset) {
if (condition == def && !serializeDefaults) {
track(offset: 0, at: position)
return
}
let off = push(element: Byte(condition ? 1 : 0))
track(offset: off, at: position)
}
/// Pushes the values into the buffer
/// - Parameter element: Element to insert
/// - returns: Postion of the Element
@discardableResult
public func push<T: Scalar>(element: T) -> UOffset {
preAlign(len: MemoryLayout<T>.size,
alignment: MemoryLayout<T>.size)
_bb.push(value: element, len: MemoryLayout<T>.size)
return _bb.size
}
#if DEBUG
/// Used to debug the buffer and the implementation
public func debug(str: String = "normal memory: ") {
_bb.debugMemory(str: str)
}
#endif
}

View File

@@ -0,0 +1,87 @@
import Foundation
/// FlatbufferObject structures all the Flatbuffers objects
public protocol FlatBufferObject {
init(_ bb: ByteBuffer, o: Int32)
}
/// Readable is structures all the Flatbuffers structs
///
/// Readable is a procotol that each Flatbuffer struct should confirm to since
/// FlatBufferBuilder would require a Type to both create(struct:) and createVector(structs:) functions
public protocol Readable: FlatBufferObject {
static var size: Int { get }
static var alignment: Int { get }
}
public protocol Enum {
associatedtype T: Scalar
static var byteSize: Int { get }
var value: T { get }
}
/// Mutable is a protocol that allows us to mutate Scalar values within the buffer
public protocol Mutable {
/// makes Flatbuffer accessed within the Protocol
var bb: ByteBuffer { get }
/// makes position of the table/struct accessed within the Protocol
var postion: Int32 { get }
}
extension Mutable {
/// Mutates the memory in the buffer, this is only called from the access function of table and structs
/// - Parameters:
/// - value: New value to be inserted to the buffer
/// - index: index of the Element
func mutate<T: Scalar>(value: T, o: Int32) -> Bool {
guard o != 0 else { return false }
bb.write(value: value, index: Int(o), direct: true)
return true
}
}
extension Mutable where Self == Table {
/// Mutates a value by calling mutate with respect to the position in the table
/// - Parameters:
/// - value: New value to be inserted to the buffer
/// - index: index of the Element
public func mutate<T: Scalar>(_ value: T, index: Int32) -> Bool {
guard index != 0 else { return false }
return mutate(value: value, o: index + postion)
}
/// Directly mutates the element by calling mutate
///
/// Mutates the Element at index ignoring the current position by calling mutate
/// - Parameters:
/// - value: New value to be inserted to the buffer
/// - index: index of the Element
public func directMutate<T: Scalar>(_ value: T, index: Int32) -> Bool {
return mutate(value: value, o: index)
}
}
extension Mutable where Self == Struct {
/// Mutates a value by calling mutate with respect to the position in the struct
/// - Parameters:
/// - value: New value to be inserted to the buffer
/// - index: index of the Element
public func mutate<T: Scalar>(_ value: T, index: Int32) -> Bool {
return mutate(value: value, o: index + postion)
}
/// Directly mutates the element by calling mutate
///
/// Mutates the Element at index ignoring the current position by calling mutate
/// - Parameters:
/// - value: New value to be inserted to the buffer
/// - index: index of the Element
public func directMutate<T: Scalar>(_ value: T, index: Int32) -> Bool {
return mutate(value: value, o: index)
}
}
extension Struct: Mutable {}
extension Table: Mutable {}

View File

@@ -0,0 +1,16 @@
import Foundation
public final class FlatBuffersUtils {
/// Gets the size of the prefix
/// - Parameter bb: Flatbuffer object
public static func getSizePrefix(bb: ByteBuffer) -> Int32 {
return bb.read(def: Int32.self, position: bb.reader)
}
/// Removes the prefix by duplicating the Flatbuffer
/// - Parameter bb: Flatbuffer object
public static func removeSizePrefix(bb: ByteBuffer) -> ByteBuffer {
return bb.duplicate(removing: MemoryLayout<Int32>.size)
}
}

View File

@@ -0,0 +1,31 @@
import Foundation
extension Int {
/// Moves the current int into the nearest power of two
///
/// This is used since the UnsafeMutableRawPointer will face issues when writing/reading
/// if the buffer alignment exceeds that actual size of the buffer
var convertToPowerofTwo: Int {
guard self > 0 else { return 1 }
var n = UOffset(self)
#if arch(arm) || arch(i386)
let max = UInt32(Int.max)
#else
let max = UInt32.max
#endif
n -= 1
n |= n >> 1
n |= n >> 2
n |= n >> 4
n |= n >> 8
n |= n >> 16
if n != max {
n += 1
}
return Int(n)
}
}

View File

@@ -0,0 +1,12 @@
import Foundation
/// Offset object for all the Objects that are written into the buffer
public struct Offset<T> {
/// Offset of the object in the buffer
public var o: UOffset
/// Returns false if the offset is equal to zero
public var isEmpty: Bool { return o == 0 }
public init(offset: UOffset) { o = offset }
public init() { o = 0 }
}

View File

@@ -0,0 +1,16 @@
import Foundation
public struct Struct {
public private(set) var bb: ByteBuffer
public private(set) var postion: Int32
public init(bb: ByteBuffer, position: Int32 = 0) {
self.bb = bb
self.postion = position
}
public func readBuffer<T: Scalar>(of type: T.Type, at o: Int32) -> T {
let r = bb.read(def: T.self, position: Int(o + postion))
return r
}
}

View File

@@ -0,0 +1,144 @@
import Foundation
public struct Table {
public private(set) var bb: ByteBuffer
public private(set) var postion: Int32
public init(bb: ByteBuffer, position: Int32 = 0) {
guard isLitteEndian else {
fatalError("Reading/Writing a buffer in big endian machine is not supported on swift")
}
self.bb = bb
self.postion = position
}
public func offset(_ o: Int32) -> Int32 {
let vtable = postion - bb.read(def: Int32.self, position: Int(postion))
return o < bb.read(def: VOffset.self, position: Int(vtable)) ? Int32(bb.read(def: Int16.self, position: Int(vtable + o))) : 0
}
public func indirect(_ o: Int32) -> Int32 { return o + bb.read(def: Int32.self, position: Int(o)) }
/// String reads from the buffer with respect to position of the current table.
/// - Parameter offset: Offset of the string
public func string(at offset: Int32) -> String? {
return directString(at: offset + postion)
}
/// Direct string reads from the buffer disregarding the position of the table.
/// It would be preferable to use string unless the current position of the table is not needed
/// - Parameter offset: Offset of the string
public func directString(at offset: Int32) -> String? {
var offset = offset
offset += bb.read(def: Int32.self, position: Int(offset))
let count = bb.read(def: Int32.self, position: Int(offset))
let position = offset + Int32(MemoryLayout<Int32>.size)
return bb.readString(at: position, count: count)
}
/// Reads from the buffer with respect to the position in the table.
/// - Parameters:
/// - type: Type of Scalar that needs to be read from the buffer
/// - o: Offset of the Element
public func readBuffer<T: Scalar>(of type: T.Type, at o: Int32) -> T {
return directRead(of: T.self, offset: o + postion)
}
/// Reads from the buffer disregarding the position of the table.
/// It would be used when reading from an
/// ```
/// let offset = __t.offset(10)
/// //Only used when the we already know what is the
/// // position in the table since __t.vector(at:)
/// // returns the index with respect to the position
/// __t.directRead(of: Byte.self,
/// offset: __t.vector(at: offset) + index * 1)
/// ```
/// - Parameters:
/// - type: Type of Scalar that needs to be read from the buffer
/// - o: Offset of the Element
public func directRead<T: Scalar>(of type: T.Type, offset o: Int32) -> T {
let r = bb.read(def: T.self, position: Int(o))
return r
}
public func union<T: FlatBufferObject>(_ o: Int32) -> T {
let o = o + postion
return directUnion(o)
}
public func directUnion<T: FlatBufferObject>(_ o: Int32) -> T {
return T.init(bb, o: o + bb.read(def: Int32.self, position: Int(o)))
}
public func getVector<T>(at off: Int32) -> [T]? {
let o = offset(off)
guard o != 0 else { return nil }
return bb.readSlice(index: vector(at: o), count: vector(count: o))
}
/// Vector count gets the count of Elements within the array
/// - Parameter o: start offset of the vector
/// - returns: Count of elements
public func vector(count o: Int32) -> Int32 {
var o = o
o += postion
o += bb.read(def: Int32.self, position: Int(o))
return bb.read(def: Int32.self, position: Int(o))
}
/// Vector start index in the buffer
/// - Parameter o:start offset of the vector
/// - returns: the start index of the vector
public func vector(at o: Int32) -> Int32 {
var o = o
o += postion
return o + bb.read(def: Int32.self, position: Int(o)) + 4
}
}
extension Table {
static public func indirect(_ o: Int32, _ fbb: ByteBuffer) -> Int32 { return o + fbb.read(def: Int32.self, position: Int(o)) }
static public func offset(_ o: Int32, vOffset: Int32, fbb: ByteBuffer) -> Int32 {
let vTable = Int32(fbb.capacity) - o
return vTable + Int32(fbb.read(def: Int16.self, position: Int(vTable + vOffset - fbb.read(def: Int32.self, position: Int(vTable)))))
}
static public func compare(_ off1: Int32, _ off2: Int32, fbb: ByteBuffer) -> Int32 {
let memorySize = Int32(MemoryLayout<Int32>.size)
let _off1 = off1 + fbb.read(def: Int32.self, position: Int(off1))
let _off2 = off2 + fbb.read(def: Int32.self, position: Int(off2))
let len1 = fbb.read(def: Int32.self, position: Int(_off1))
let len2 = fbb.read(def: Int32.self, position: Int(_off2))
let startPos1 = _off1 + memorySize
let startPos2 = _off2 + memorySize
let minValue = min(len1, len2)
for i in 0...minValue {
let b1 = fbb.read(def: Int8.self, position: Int(i + startPos1))
let b2 = fbb.read(def: Int8.self, position: Int(i + startPos2))
if b1 != b2 {
return Int32(b2 - b1)
}
}
return len1 - len2
}
static public func compare(_ off1: Int32, _ key: [Byte], fbb: ByteBuffer) -> Int32 {
let memorySize = Int32(MemoryLayout<Int32>.size)
let _off1 = off1 + fbb.read(def: Int32.self, position: Int(off1))
let len1 = fbb.read(def: Int32.self, position: Int(_off1))
let len2 = Int32(key.count)
let startPos1 = _off1 + memorySize
let minValue = min(len1, len2)
for i in 0..<minValue {
let b = fbb.read(def: Int8.self, position: Int(i + startPos1))
let byte = key[Int(i)]
if b != byte {
return Int32(b - Int8(byte))
}
}
return len1 - len2
}
}