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.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 } #if swift(>=5.0) /// Constructor that creates a Flatbuffer object from a ContiguousBytes /// - Parameters: /// - contiguousBytes: Binary stripe to use as the buffer /// - count: amount of readable bytes public init( contiguousBytes: Bytes, count: Int ) { _memory = UnsafeMutableRawPointer.allocate(byteCount: count, alignment: alignment) _capacity = count _writerSize = _capacity contiguousBytes.withUnsafeBytes { buf in _memory.copyMemory(from: buf.baseAddress!, byteCount: buf.count) } } #endif /// Creates a copy of the buffer that's being built by calling sizedBuffer /// - Parameters: /// - memory: Current memory of the buffer /// - count: count of bytes internal init(memory: UnsafeMutableRawPointer, count: Int) { _memory = UnsafeMutableRawPointer.allocate(byteCount: count, alignment: alignment) _memory.copyMemory(from: memory, byteCount: count) _capacity = count _writerSize = _capacity } /// 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: padding) _writerSize += (MemoryLayout.size * Int(padding)) } ///Adds an array of type Scalar to the buffer memory /// - Parameter elements: An array of Scalars func push(elements: [T]) { let size = elements.count * MemoryLayout.size ensureSpace(size: UInt32(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: UInt32(size)) memcpy(_memory.advanced(by: writerIndex - size), value, 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(value: T, len: Int) { ensureSpace(size: UInt32(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: UInt32(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, 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(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: UInt32) -> UInt32 { 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: UInt32) { 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(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(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) } } extension ByteBuffer: CustomDebugStringConvertible { public var debugDescription: String { """ buffer located at: \(_memory), with capacity of \(_capacity) { writerSize: \(_writerSize), readerSize: \(reader), writerIndex: \(writerIndex) } """ } }