[Swift] Rebuild the way swift handles structs from scratch (#6326)

* Rebuild the way swift handles structs from scratch

* Updates docs, and sample binary

* Replaces InMemory to Mutable

* Migrates docs from inmemory

* use inline for some functions

* Renamed Mutable objects

* Updates documentation
This commit is contained in:
mustiikhalil
2020-12-18 01:55:32 +03:00
committed by GitHub
parent 05192553f4
commit 4e79d129cb
21 changed files with 1051 additions and 720 deletions

View File

@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'FlatBuffers'
s.version = '0.8.1'
s.version = '1.0.0'
s.summary = 'FlatBuffers: Memory Efficient Serialization Library'
s.description = "FlatBuffers is a cross platform serialization library architected for

View File

@@ -10,7 +10,7 @@ and Cocoapods
1- To report any error please use the main repository.
2- `0.6.0` deprecates `add(condition:bool)` for `add(element:bool)`. You can download the [binary here](https://github.com/google/flatbuffers/actions) and select the latest push to master
2- `1.0.0` deprecates `MyGame_Example_Vec3.createVec3(builder: &fbb, x: 10, test2: .blue)` for `MyGame_Example_Vec3(x: 10, test2: .blue)`. This uses Swift native structs instead of workarounds that which leads to a huge performance increase when serializing structs. You can download the [binary here](https://github.com/google/flatbuffers/actions) and select the latest push to master
### Contribute

View File

@@ -187,7 +187,7 @@ public struct ByteBuffer {
_writerSize = _writerSize &+ (MemoryLayout<UInt8>.size &* padding)
}
///Adds an array of type Scalar to the buffer memory
/// Adds an array of type Scalar to the buffer memory
/// - Parameter elements: An array of Scalars
@usableFromInline
mutating func push<T: Scalar>(elements: [T]) {
@@ -198,41 +198,16 @@ public struct ByteBuffer {
}
}
/// A custom type of structs that are padded according to the flatbuffer padding,
/// Adds an object of type NativeStruct into the buffer
/// - Parameters:
/// - value: Pointer to the object in memory
/// - size: Size of Value being written to the buffer
@available(
*,
deprecated,
message: "0.9.0 will be removing the following method. Regenerate the code")
@usableFromInline
mutating func push(struct value: UnsafeMutableRawPointer, size: Int) {
/// - value: Object that will be written to the buffer
/// - size: size to subtract from the WriterIndex
@inline(__always)
mutating func push<T: NativeStruct>(struct value: T, size: Int) {
ensureSpace(size: size)
memcpy(_storage.memory.advanced(by: writerIndex &- size), value, size)
defer { value.deallocate() }
_writerSize = _writerSize &+ size
}
/// Prepares the buffer to receive a struct of certian size.
/// The alignment of the memory is already handled since we already called preAlign
/// - Parameter size: size of the struct
@usableFromInline
mutating func prepareBufferToReceiveStruct(of size: Int) {
ensureSpace(size: size)
_writerSize = _writerSize &+ size
}
/// Reverse the input direction to the buffer, since `FlatBuffers` uses a back to front, following method will take current `writerIndex`
/// and writes front to back into the buffer, respecting the padding & the alignment
/// - Parameters:
/// - value: value of type Scalar
/// - position: position relative to the `writerIndex`
/// - len: length of the value in terms of bytes
@usableFromInline
mutating func reversePush<T: Scalar>(value: T, position: Int, len: Int) {
var v = value
memcpy(_storage.memory.advanced(by: writerIndex &+ position), &v, len)
memcpy(_storage.memory.advanced(by: writerIndex &- size), &v, size)
_writerSize = _writerSize &+ size
}
/// Adds an object of type Scalar into the buffer
@@ -266,7 +241,7 @@ public struct ByteBuffer {
/// - Parameters:
/// - bytes: Pointer to the view
/// - len: Size of string
@usableFromInline
@inline(__always)
mutating internal func push(
bytes: UnsafeBufferPointer<String.UTF8View.Element>,
len: Int) -> Bool
@@ -300,7 +275,7 @@ public struct ByteBuffer {
/// Makes sure that buffer has enouch space for each of the objects that will be written into it
/// - Parameter size: size of object
@discardableResult
@usableFromInline
@inline(__always)
mutating func ensureSpace(size: Int) -> Int {
if size &+ _writerSize > _storage.capacity {
_storage.reallocate(size, writerSize: _writerSize, alignment: alignment)
@@ -311,7 +286,7 @@ public struct ByteBuffer {
/// pops the written VTable if it's already written into the buffer
/// - Parameter size: size of the `VTable`
@usableFromInline
@inline(__always)
mutating internal func pop(_ size: Int) {
assert((_writerSize &- size) > 0, "New size should NOT be a negative number")
memset(_storage.memory.advanced(by: writerIndex), 0, _writerSize &- size)
@@ -319,11 +294,13 @@ public struct ByteBuffer {
}
/// Clears the current size of the buffer
@inline(__always)
mutating public func clearSize() {
_writerSize = 0
}
/// Clears the current instance of the buffer, replacing it with new memory
@inline(__always)
mutating public func clear() {
_writerSize = 0
alignment = 1
@@ -345,6 +322,7 @@ public struct ByteBuffer {
/// - Parameters:
/// - index: index of the object to be read from the buffer
/// - count: count of bytes in memory
@inline(__always)
public func readSlice<T>(
index: Int32,
count: Int32) -> [T]
@@ -362,6 +340,7 @@ public struct ByteBuffer {
/// - index: index of the string in the buffer
/// - count: length of the string
/// - type: Encoding of the string
@inline(__always)
public func readString(
at index: Int32,
count: Int32,

View File

@@ -20,11 +20,11 @@ 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] = []
/// 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
@@ -227,7 +227,7 @@ public struct FlatBufferBuilder {
/// Changes the minimuim alignment of the buffer
/// - Parameter size: size of the current alignment
@usableFromInline
@inline(__always)
mutating internal func minAlignment(size: Int) {
if size > _minAlignment {
_minAlignment = size
@@ -238,7 +238,7 @@ public struct FlatBufferBuilder {
/// - Parameters:
/// - bufSize: Current size of the buffer + the offset of the object to be written
/// - elementSize: Element size
@usableFromInline
@inline(__always)
mutating internal func padding(bufSize: UInt32, elementSize: UInt32) -> UInt32 {
((~bufSize) &+ 1) & (elementSize - 1)
}
@@ -282,7 +282,7 @@ public struct FlatBufferBuilder {
_vtableStorage.add(loc: FieldLoc(offset: offset, position: position))
}
// MARK: - Vectors
// MARK: - Inserting Vectors
/// Starts a vector of length and Element size
mutating public func startVector(_ len: Int, elementSize: Int) {
@@ -296,10 +296,10 @@ public struct FlatBufferBuilder {
///
/// The current function will fatalError if startVector is called before serializing the vector
/// - Parameter len: Length of the buffer
mutating public func endVector(len: Int) -> UOffset {
mutating public func endVector(len: Int) -> Offset<UOffset> {
assert(isNested, "Calling endVector without calling startVector")
isNested = false
return push(element: Int32(len))
return Offset(offset: push(element: Int32(len)))
}
/// Creates a vector of type Scalar in the buffer
@@ -317,7 +317,7 @@ public struct FlatBufferBuilder {
let size = size
startVector(size, elementSize: MemoryLayout<T>.size)
_bb.push(elements: elements)
return Offset(offset: endVector(len: size))
return endVector(len: size)
}
/// Creates a vector of type Enums in the buffer
@@ -337,7 +337,7 @@ public struct FlatBufferBuilder {
for e in elements.reversed() {
_bb.push(value: e.value, len: T.byteSize)
}
return Offset(offset: endVector(len: size))
return endVector(len: size)
}
/// Creates a vector of type Offsets in the buffer
@@ -356,7 +356,7 @@ public struct FlatBufferBuilder {
for o in offsets.reversed() {
push(element: o)
}
return Offset(offset: endVector(len: len))
return endVector(len: len)
}
/// Creates a vector of Strings
@@ -370,101 +370,47 @@ public struct FlatBufferBuilder {
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
@available(
*,
deprecated,
message: "0.9.0 will be removing the following method. Regenerate the code")
mutating public func createVector<T: Readable>(
structs: [UnsafeMutableRawPointer],
type: T.Type) -> Offset<UOffset>
{
startVector(structs.count &* T.size, elementSize: T.alignment)
/// Creates a vector of `Native swift structs` which were padded to flatbuffers standards
/// - Parameter structs: A vector of structs
/// - Returns: offset of the vector
mutating public func createVector<T: NativeStruct>(ofStructs structs: [T]) -> Offset<UOffset> {
startVector(structs.count * MemoryLayout<T>.size, elementSize: MemoryLayout<T>.alignment)
for i in structs.reversed() {
create(struct: i, type: T.self)
_ = create(struct: i)
}
return Offset(offset: endVector(len: structs.count))
}
/// Starts a vector of struct that considers the size and alignment of the struct
/// - Parameters:
/// - count: number of elements to be written
/// - size: size of struct
/// - alignment: alignment of the struct
mutating public func startVectorOfStructs(count: Int, size: Int, alignment: Int) {
startVector(count &* size, elementSize: alignment)
}
/// Ends the vector of structs and writtens the current offset
/// - Parameter count: number of written elements
/// - Returns: Offset of type UOffset
mutating public func endVectorOfStructs(count: Int) -> Offset<UOffset> {
Offset<UOffset>(offset: endVector(len: count))
return endVector(len: structs.count)
}
// MARK: - Inserting Structs
/// Writes a Flatbuffer struct into the buffer
/// Fills the buffer with a native struct that's build and padded according to flatbuffers standards
/// - Parameters:
/// - s: Flatbuffer struct
/// - type: Type of the element to be serialized
/// - returns: Offset of the Object
@available(
*,
deprecated,
message: "0.9.0 will be removing the following method. Regenerate the code")
/// - s: `Native swift` struct to insert
/// - position: The predefined position of the object
/// - Returns: offset of written struct
@discardableResult
mutating public func create<T: Readable>(
struct s: UnsafeMutableRawPointer,
type: T.Type) -> Offset<UOffset>
mutating public func create<T: NativeStruct>(
struct s: T, position: VOffset) -> Offset<UOffset>
{
let size = T.size
preAlign(len: size, alignment: T.alignment)
let offset = create(struct: s)
_vtableStorage.add(loc: FieldLoc(offset: _bb.size, position: VOffset(position)))
return offset
}
/// Fills the buffer with a native struct that's build and padded according to flatbuffers standards
/// - Parameters:
/// - s: `Native swift` struct to insert
/// - Returns: offset of written struct
@discardableResult
mutating public func create<T: NativeStruct>(
struct s: T) -> Offset<UOffset>
{
let size = MemoryLayout<T>.size
preAlign(len: size, alignment: MemoryLayout<T>.alignment)
_bb.push(struct: s, size: size)
return Offset(offset: _bb.size)
}
/// prepares the ByteBuffer to receive a struct of size and alignment
/// - Parameters:
/// - size: size of written struct
/// - alignment: alignment of written struct
mutating public func createStructOf(size: Int, alignment: Int) {
preAlign(len: size, alignment: alignment)
_bb.prepareBufferToReceiveStruct(of: size)
}
/// Adds scalars front to back instead of the default behavior of the normal add
/// - Parameters:
/// - v: element of type Scalar
/// - postion: position relative to the `writerIndex`
mutating public func reverseAdd<T: Scalar>(v: T, postion: Int) {
_bb.reversePush(
value: v,
position: postion,
len: MemoryLayout<T>.size)
}
/// Ends the struct and returns the current buffer size
/// - Returns: Offset of type UOffset
@discardableResult
public func endStruct() -> Offset<UOffset> {
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
mutating public func add(structOffset o: VOffset) {
_vtableStorage.add(loc: FieldLoc(offset: _bb.size, position: VOffset(o)))
}
// MARK: - Inserting Strings
/// Insets a string into the buffer using UTF8
@@ -596,6 +542,7 @@ extension FlatBufferBuilder: CustomDebugStringConvertible {
/// 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
@@ -621,6 +568,7 @@ extension FlatBufferBuilder: CustomDebugStringConvertible {
/// 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()
@@ -631,6 +579,7 @@ extension FlatBufferBuilder: CustomDebugStringConvertible {
/// 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)
}

View File

@@ -16,6 +16,10 @@
import Foundation
/// NativeStruct is a protocol that indicates if the struct is a native `swift` struct
/// since now we will be serializing native structs into the buffer.
public protocol NativeStruct {}
/// FlatbufferObject structures all the Flatbuffers objects
public protocol FlatBufferObject {
var __buffer: ByteBuffer! { get }
@@ -28,15 +32,6 @@ public protocol ObjectAPI {
mutating func unpack() -> T
}
/// 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 }

View File

@@ -56,9 +56,9 @@ public struct Table {
/// 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
/// - type: Type of Element 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 {
public func readBuffer<T>(of type: T.Type, at o: Int32) -> T {
directRead(of: T.self, offset: o + postion)
}
@@ -73,9 +73,9 @@ public struct Table {
/// offset: __t.vector(at: offset) + index * 1)
/// ```
/// - Parameters:
/// - type: Type of Scalar that needs to be read from the buffer
/// - type: Type of Element 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 {
public func directRead<T>(of type: T.Type, offset o: Int32) -> T {
let r = bb.read(def: T.self, position: Int(o))
return r
}

View File

@@ -16,9 +16,9 @@
import Foundation
public protocol NativeTable {}
public protocol UnionObject {}
extension NativeTable {
extension UnionObject {
/// Serialize is a helper function that serailizes the data from the Object API to a bytebuffer directly th
/// - Parameter type: Type of the Flatbuffer object