/* * Copyright 2024 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import Foundation #if canImport(Common) import Common #endif /// `ByteBuffer` is the interface that stores the data for a `Flatbuffers` object /// it allows users to Construct their buffers internally without much cost to performance @usableFromInline struct _InternalByteBuffer { /// Storage is a container that would hold the memory pointer to solve the issue of /// deallocating the memory that was held by (memory: UnsafeMutableRawPointer) @usableFromInline final class Storage { /// pointer to the start of the buffer object in memory private(set) var memory: UnsafeMutableRawPointer @usableFromInline init(count: Int, alignment: Int) { memory = UnsafeMutableRawPointer.allocate( byteCount: count, alignment: alignment) } deinit { memory.deallocate() } @usableFromInline func initialize(for size: Int) { memset(memory, 0, size) } /// Reallocates the buffer incase the object to be written doesnt fit in the current buffer /// - Parameter size: Size of the current object @usableFromInline func reallocate( capacity: Int, writerSize: Int, currentWritingIndex: Int, alignment: Int) { let newData = UnsafeMutableRawPointer.allocate( byteCount: capacity, alignment: alignment) memset(newData, 0, capacity &- writerSize) memcpy( newData.advanced(by: capacity &- writerSize), memory.advanced(by: currentWritingIndex), writerSize) memory.deallocate() memory = newData } } @usableFromInline var _storage: Storage private let initialSize: Int /// The size of the elements written to the buffer + their paddings private var _writerSize: Int = 0 /// Alignment of the current memory being written to the buffer 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 var writerIndex: Int { capacity &- _writerSize } /// Reader is the position of the current Writer Index (capacity - size) public var reader: Int { writerIndex } /// Current size of the buffer public var size: UOffset { UOffset(_writerSize) } /// Public Pointer to the buffer object in memory. This should NOT be modified for any reason @usableFromInline var memory: UnsafeMutableRawPointer { _storage.memory } /// Current capacity for the buffer public private(set) var capacity: Int /// Constructor that creates a Flatbuffer instance with a size /// - Parameter: /// - size: Length of the buffer /// - allowReadingUnalignedBuffers: allow reading from unaligned buffer init(initialSize size: Int) { initialSize = size.convertToPowerofTwo capacity = initialSize _storage = Storage(count: initialSize, alignment: alignment) _storage.initialize(for: initialSize) } /// Fills the buffer with padding by adding to the writersize /// - Parameter padding: Amount of padding between two to be serialized objects @inline(__always) @usableFromInline mutating func fill(padding: Int) { assert(padding >= 0, "Fill should be larger than or equal to zero") ensureSpace(size: padding) _writerSize = _writerSize &+ (MemoryLayout.size &* padding) } /// Adds an array of type Scalar to the buffer memory /// - Parameter elements: An array of Scalars @inline(__always) @usableFromInline mutating func push(elements: [T]) { elements.withUnsafeBytes { ptr in ensureSpace(size: ptr.count) memcpy( _storage.memory.advanced(by: writerIndex &- ptr.count), ptr.baseAddress!, ptr.count) _writerSize = _writerSize &+ ptr.count } } /// Adds an array of type Scalar to the buffer memory /// - Parameter elements: An array of Scalars @inline(__always) @usableFromInline mutating func push(elements: [T]) { elements.withUnsafeBytes { ptr in ensureSpace(size: ptr.count) memcpy( _storage.memory.advanced(by: writerIndex &- ptr.count), ptr.baseAddress!, ptr.count) _writerSize = _writerSize &+ ptr.count } } /// Adds a `ContiguousBytes` to buffer memory /// - Parameter value: bytes to copy #if !os(WASI) @inline(__always) @usableFromInline mutating func push(bytes: ContiguousBytes) { bytes.withUnsafeBytes { ptr in ensureSpace(size: ptr.count) memcpy( _storage.memory.advanced(by: writerIndex &- ptr.count), ptr.baseAddress!, ptr.count) _writerSize = _writerSize &+ ptr.count } } #endif /// Adds an object of type NativeStruct into the buffer /// - Parameters: /// - value: Object that will be written to the buffer /// - size: size to subtract from the WriterIndex @usableFromInline @inline(__always) mutating func push(struct value: T, size: Int) { ensureSpace(size: size) withUnsafePointer(to: value) { memcpy( _storage.memory.advanced(by: writerIndex &- size), $0, size) _writerSize = _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 @inline(__always) @usableFromInline mutating func push(value: T, len: Int) { ensureSpace(size: len) withUnsafePointer(to: value) { memcpy( _storage.memory.advanced(by: writerIndex &- len), $0, len) _writerSize = _writerSize &+ len } } /// Adds a RawPointer into the buffer /// - Parameter pointer: pointer to be copied into the buffer /// - Parameter len: length of the data @inline(__always) mutating func writeBytes(_ ptr: UnsafeRawPointer, len: Int) { ensureSpace(size: len) memcpy( _storage.memory.advanced(by: writerIndex &- len), ptr, len) _writerSize = _writerSize &+ len } /// 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 @inline(__always) func write(value: T, index: Int, direct: Bool = false) { var index = index if !direct { index = capacity &- index } assert(index < capacity, "Write index is out of writing bound") assert(index >= 0, "Writer index should be above zero") _ = withUnsafePointer(to: value) { memcpy( _storage.memory.advanced(by: index), $0, MemoryLayout.size) } } /// 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 mutating func ensureSpace(size: Int) -> Int { let expectedWriterIndex = size &+ _writerSize if expectedWriterIndex > capacity { let currentWritingIndex = capacity &- _writerSize while capacity <= expectedWriterIndex { capacity = capacity << 1 } /// solution take from Apple-NIO capacity = capacity.convertToPowerofTwo _storage.reallocate( capacity: capacity, writerSize: _writerSize, currentWritingIndex: currentWritingIndex, alignment: alignment) } assert(size < FlatBufferMaxSize, "Buffer can't grow beyond 2 Gigabytes") return size } /// pops the written VTable if it's already written into the buffer /// - Parameter size: size of the `VTable` @usableFromInline @inline(__always) mutating 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) _writerSize = size } /// 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(keepingCapacity: Bool = false) { _writerSize = 0 alignment = 1 if keepingCapacity { _storage.initialize(for: capacity) } else { capacity = initialSize _storage = Storage(count: initialSize, alignment: alignment) } } /// Reads an object from the buffer /// - Parameters: /// - def: Type of the object /// - position: the index of the object in the buffer @inline(__always) public func read(def: T.Type, position: Int) -> T { _storage.memory .advanced(by: position) .bindMemory(to: T.self, capacity: 1) .pointee } @discardableResult @inline(__always) func withUnsafeBytes( _ body: (UnsafeRawBufferPointer) throws -> T) rethrows -> T { try body( UnsafeRawBufferPointer( start: _storage.memory, count: capacity)) } @discardableResult @inline(__always) func withUnsafeSlicedBytes( _ body: (UnsafeRawBufferPointer) throws -> T) rethrows -> T { try body( UnsafeRawBufferPointer( start: _storage.memory.advanced(by: writerIndex), count: capacity &- writerIndex)) } @discardableResult @inline(__always) func withUnsafeRawPointer( _ body: (UnsafeMutableRawPointer) throws -> T) rethrows -> T { try body(_storage.memory) } @discardableResult @inline(__always) func readWithUnsafeRawPointer( position: Int, _ body: (UnsafeRawPointer) throws -> T) rethrows -> T { try body(_storage.memory.advanced(by: position)) } } extension _InternalByteBuffer: CustomDebugStringConvertible { public var debugDescription: String { """ buffer located at: \(_storage.memory), with capacity of \(capacity) { writerSize: \(_writerSize), readerSize: \(reader), writerIndex: \( writerIndex) } """ } }