/* * 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 write and read data directly from memory thus the use of its /// functions should be used @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 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, alignment: Int) { let newData = UnsafeMutableRawPointer.allocate( byteCount: capacity, alignment: alignment) memset(newData, 0, capacity) memcpy( newData, memory, writerSize) memory.deallocate() memory = newData } } @usableFromInline var _storage: Storage // Initial size of the internal storage private let initialSize: Int /// The size of the elements written to the buffer + their paddings var writerIndex: Int = 0 /// Alignment of the current memory being written to the buffer private var alignment = 1 /// Public Pointer to the buffer object in memory. This should NOT be modified for any reason public var memory: UnsafeMutableRawPointer { _storage.memory } /// Current capacity for the buffer public private(set) var capacity: Int /// Returns the written bytes into the ``ByteBuffer`` public var underlyingBytes: [UInt8] { let start = memory.bindMemory(to: UInt8.self, capacity: writerIndex) let ptr = UnsafeBufferPointer(start: start, count: writerIndex) return Array(ptr) } /// 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) } /// Clears the current instance of the buffer, replacing it with new memory @inline(__always) mutating public func clear(keepingCapacity: Bool = false) { writerIndex = 0 alignment = 1 if keepingCapacity { _storage.initialize(for: capacity) } else { capacity = initialSize _storage = Storage(count: initialSize, alignment: alignment) } } @inline(__always) mutating public func resetWriter(to writer: Int) { writerIndex = writer } /// Makes sure that buffer has enouch space for each of the objects that will be written into it /// - Parameter size: size of object @usableFromInline mutating func ensureSpace(size: Int) { guard size &+ writerIndex > capacity else { return } while capacity <= writerIndex &+ size { capacity = capacity << 1 } /// solution take from Apple-NIO capacity = capacity.convertToPowerofTwo _storage.reallocate( capacity: capacity, writerSize: writerIndex, alignment: alignment) } @inline(__always) mutating func addPadding(bytes: Int) { writerIndex = writerIndex &+ numericCast( padding( bufSize: numericCast(writerIndex), elementSize: numericCast(bytes))) ensureSpace(size: writerIndex) } @inline(__always) mutating func writeBytes(_ ptr: UnsafeRawPointer, len: Int) { memcpy( _storage.memory.advanced(by: writerIndex), ptr, len) writerIndex = writerIndex &+ len } @inline(__always) mutating func write(_ v: T, len: Int) { withUnsafePointer(to: v) { memcpy( _storage.memory.advanced(by: writerIndex), $0, len) writerIndex = writerIndex &+ len } } @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, count: 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) { writerIndex: \(writerIndex) } """ } }