From f35184aef9395d61f07a947fb2749fe1d734053b Mon Sep 17 00:00:00 2001 From: Liu Liu Date: Mon, 22 Jun 2020 17:05:36 -0700 Subject: [PATCH] [Swift] Add parsing from unowned UnsafeMutableRawPointer for ByteBuffer (#5981) * RFC: Add ExternalStorage for ByteBuffer in Swift implementation This PR proposed one more API for ByteBuffer such that no copy is required to parse FlatBuffers content. This API has limited use, but for cases that you need to read part of the flatbuffers' data to decide whether you want to parse / copy the full buffer out, it is useful. * Use a variable rather than protocol. * Revert grouping changes from the other PR. * Add unit test to read from unowned UnsafePointer. * Manifest changed. --- swift/Sources/FlatBuffers/ByteBuffer.swift | 24 +++++++++++++++++-- .../FlatBuffersMonsterWriterTests.swift | 14 ++++++++++- .../XCTestManifests.swift | 3 ++- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/swift/Sources/FlatBuffers/ByteBuffer.swift b/swift/Sources/FlatBuffers/ByteBuffer.swift index 28d19e326..7a4f89198 100644 --- a/swift/Sources/FlatBuffers/ByteBuffer.swift +++ b/swift/Sources/FlatBuffers/ByteBuffer.swift @@ -5,7 +5,8 @@ public struct ByteBuffer { /// 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 { - + // This storage doesn't own the memory, therefore, we won't deallocate on deinit. + private let unowned: Bool /// pointer to the start of the buffer object in memory var memory: UnsafeMutableRawPointer /// Capacity of UInt8 the buffer can hold @@ -14,17 +15,28 @@ public struct ByteBuffer { init(count: Int, alignment: Int) { memory = UnsafeMutableRawPointer.allocate(byteCount: count, alignment: alignment) capacity = count + unowned = false + } + + init(memory: UnsafeMutableRawPointer, capacity: Int, unowned: Bool) { + self.memory = memory + self.capacity = capacity + self.unowned = unowned } deinit { - memory.deallocate() + if !unowned { + memory.deallocate() + } } func copy(from ptr: UnsafeRawPointer, count: Int) { + precondition(!unowned) memory.copyMemory(from: ptr, byteCount: count) } func initalize(for size: Int) { + precondition(!unowned) memset(memory, 0, size) } @@ -94,6 +106,14 @@ public struct ByteBuffer { _storage = Storage(count: size, alignment: alignment) _storage.initalize(for: size) } + + /// Constructor that creates a Flatbuffer from unsafe memory region without copying + /// - Parameter assumingMemoryBound: The unsafe memory region + /// - Parameter capacity: The size of the given memory region + public init(assumingMemoryBound memory: UnsafeMutableRawPointer, capacity: Int) { + _storage = Storage(memory: memory, capacity: capacity, unowned: true) + _writerSize = capacity + } #if swift(>=5.0) /// Constructor that creates a Flatbuffer object from a ContiguousBytes diff --git a/tests/FlatBuffers.Test.Swift/Tests/FlatBuffers.Test.SwiftTests/FlatBuffersMonsterWriterTests.swift b/tests/FlatBuffers.Test.Swift/Tests/FlatBuffers.Test.SwiftTests/FlatBuffersMonsterWriterTests.swift index 8d0a63769..0d1f9fd82 100644 --- a/tests/FlatBuffers.Test.Swift/Tests/FlatBuffers.Test.SwiftTests/FlatBuffersMonsterWriterTests.swift +++ b/tests/FlatBuffers.Test.Swift/Tests/FlatBuffers.Test.SwiftTests/FlatBuffersMonsterWriterTests.swift @@ -15,7 +15,7 @@ class FlatBuffersMonsterWriterTests: XCTestCase { readMonster(fb: _data) } - func testReadFromOtherLangagues() { + func testReadFromOtherLanguages() { let path = FileManager.default.currentDirectoryPath let url = URL(fileURLWithPath: path, isDirectory: true).appendingPathComponent("monsterdata_test").appendingPathExtension("mon") guard let data = try? Data(contentsOf: url) else { return } @@ -44,6 +44,18 @@ class FlatBuffersMonsterWriterTests: XCTestCase { let newBuf = FlatBuffersUtils.removeSizePrefix(bb: bytes.buffer) readMonster(fb: newBuf) } + + func testReadMonsterFromUnsafePointerWithoutCopying() { + var array: [UInt8] = [48, 0, 0, 0, 77, 79, 78, 83, 0, 0, 0, 0, 36, 0, 72, 0, 40, 0, 0, 0, 38, 0, 32, 0, 0, 0, 28, 0, 0, 0, 27, 0, 20, 0, 16, 0, 12, 0, 4, 0, 0, 0, 0, 0, 0, 0, 11, 0, 36, 0, 0, 0, 164, 0, 0, 0, 0, 0, 0, 1, 60, 0, 0, 0, 68, 0, 0, 0, 76, 0, 0, 0, 0, 0, 0, 1, 88, 0, 0, 0, 120, 0, 0, 0, 0, 0, 80, 0, 0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 64, 2, 0, 5, 0, 6, 0, 0, 0, 2, 0, 0, 0, 64, 0, 0, 0, 48, 0, 0, 0, 2, 0, 0, 0, 30, 0, 40, 0, 10, 0, 20, 0, 152, 255, 255, 255, 4, 0, 0, 0, 4, 0, 0, 0, 70, 114, 101, 100, 0, 0, 0, 0, 5, 0, 0, 0, 0, 1, 2, 3, 4, 0, 0, 0, 5, 0, 0, 0, 116, 101, 115, 116, 50, 0, 0, 0, 5, 0, 0, 0, 116, 101, 115, 116, 49, 0, 0, 0, 9, 0, 0, 0, 77, 121, 77, 111, 110, 115, 116, 101, 114, 0, 0, 0, 3, 0, 0, 0, 20, 0, 0, 0, 36, 0, 0, 0, 4, 0, 0, 0, 240, 255, 255, 255, 32, 0, 0, 0, 248, 255, 255, 255, 36, 0, 0, 0, 12, 0, 8, 0, 0, 0, 0, 0, 0, 0, 4, 0, 12, 0, 0, 0, 28, 0, 0, 0, 5, 0, 0, 0, 87, 105, 108, 109, 97, 0, 0, 0, 6, 0, 0, 0, 66, 97, 114, 110, 101, 121, 0, 0, 5, 0, 0, 0, 70, 114, 111, 100, 111, 0, 0, 0] + let unpacked = array.withUnsafeMutableBytes { (memory) -> MyGame.Example.MonsterT in + let bytes = ByteBuffer(assumingMemoryBound: memory.baseAddress!, capacity: memory.count) + var monster = Monster.getRootAsMonster(bb: bytes) + readFlatbufferMonster(monster: &monster) + let unpacked = monster.unpack() + return unpacked + } + readObjectApi(monster: unpacked) + } func readMonster(fb: ByteBuffer) { var monster = Monster.getRootAsMonster(bb: fb) diff --git a/tests/FlatBuffers.Test.Swift/Tests/FlatBuffers.Test.SwiftTests/XCTestManifests.swift b/tests/FlatBuffers.Test.Swift/Tests/FlatBuffers.Test.SwiftTests/XCTestManifests.swift index d9e118d51..f2adc4f52 100644 --- a/tests/FlatBuffers.Test.Swift/Tests/FlatBuffers.Test.SwiftTests/XCTestManifests.swift +++ b/tests/FlatBuffers.Test.Swift/Tests/FlatBuffers.Test.SwiftTests/XCTestManifests.swift @@ -21,7 +21,8 @@ extension FlatBuffersMonsterWriterTests { ("testCreateMonsterPrefixed", testCreateMonsterPrefixed), ("testCreateMonsterResizedBuffer", testCreateMonsterResizedBuffer), ("testData", testData), - ("testReadFromOtherLangagues", testReadFromOtherLangagues), + ("testReadFromOtherLanguages", testReadFromOtherLanguages), + ("testReadMonsterFromUnsafePointerWithoutCopying", testReadMonsterFromUnsafePointerWithoutCopying), ] }