274 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Swift
		
	
	
	
			
		
		
	
	
			274 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Swift
		
	
	
	
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
 | 
						|
    }
 | 
						|
 | 
						|
#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<Bytes: ContiguousBytes>(
 | 
						|
        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<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: 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<T: Scalar>(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<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: 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<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)
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
extension ByteBuffer: CustomDebugStringConvertible {
 | 
						|
    
 | 
						|
    public var debugDescription: String {
 | 
						|
        """
 | 
						|
        buffer located at: \(_memory), with capacity of \(_capacity)
 | 
						|
        { writerSize: \(_writerSize), readerSize: \(reader), writerIndex: \(writerIndex) }
 | 
						|
        """
 | 
						|
    }
 | 
						|
}
 |