diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index 153797949..3f7758e16 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -424,8 +424,9 @@ try test("Closure Identifiers") { } #endif -func checkArray(_ array: [T]) throws where T: TypedArrayElement { +func checkArray(_ array: [T]) throws where T: TypedArrayElement & Equatable { try expectEqual(toString(JSTypedArray(array).jsValue().object!), jsStringify(array)) + try checkArrayUnsafeBytes(array) } func toString(_ object: T) -> String { @@ -436,14 +437,23 @@ func jsStringify(_ array: [Any]) -> String { array.map({ String(describing: $0) }).joined(separator: ",") } +func checkArrayUnsafeBytes(_ array: [T]) throws where T: TypedArrayElement & Equatable { + let copyOfArray: [T] = JSTypedArray(array).withUnsafeBytes { buffer in + Array(buffer) + } + try expectEqual(copyOfArray, array) +} + try test("TypedArray") { let numbers = [UInt8](0 ... 255) let typedArray = JSTypedArray(numbers) try expectEqual(typedArray[12], 12) + try expectEqual(numbers.count, typedArray.lengthInBytes) let numbersSet = Set(0 ... 255) let typedArrayFromSet = JSTypedArray(numbersSet) try expectEqual(typedArrayFromSet.jsObject.length, 256) + try expectEqual(typedArrayFromSet.lengthInBytes, 256 * MemoryLayout.size) try checkArray([0, .max, 127, 1] as [UInt8]) try checkArray([0, 1, .max, .min, -1] as [Int8]) diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index 2fc71c6c8..fae61b7ff 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -141,7 +141,7 @@ export class SwiftRuntime { private instance: WebAssembly.Instance | null; private heap: SwiftRuntimeHeap; private _closureHeap: SwiftClosureHeap | null; - private version: number = 703; + private version: number = 704; constructor() { this.instance = null; @@ -224,7 +224,7 @@ export class SwiftRuntime { return this.heap.referenceHeap(ref); }; - const writeString = (ptr: pointer, bytes: Uint8Array) => { + const writeBytes = (ptr: pointer, bytes: Uint8Array) => { const uint8Memory = new Uint8Array(memory().buffer); uint8Memory.set(bytes, ptr); }; @@ -445,7 +445,7 @@ export class SwiftRuntime { swjs_load_string: (ref: ref, buffer: pointer) => { const bytes = this.heap.referenceHeap(ref); - writeString(buffer, bytes); + writeBytes(buffer, bytes); }, swjs_call_function: ( @@ -582,6 +582,12 @@ export class SwiftRuntime { return this.heap.retain(array.slice()); }, + swjs_load_typed_array: (ref: ref, buffer: pointer) => { + const typedArray = this.heap.referenceHeap(ref); + const bytes = new Uint8Array(typedArray.buffer); + writeBytes(buffer, bytes); + }, + swjs_release: (ref: ref) => { this.heap.release(ref); }, diff --git a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift index 5c8294a89..3518c4b99 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift @@ -54,6 +54,38 @@ public class JSTypedArray: JSBridgedClass, ExpressibleByArrayLiteral wh public convenience init(_ sequence: S) where S.Element == Element { self.init(Array(sequence)) } + + /// Length (in bytes) of the typed array. + /// The value is established when a TypedArray is constructed and cannot be changed. + /// If the TypedArray is not specifying a `byteOffset` or a `length`, the `length` of the referenced `ArrayBuffer` will be returned. + public var lengthInBytes: Int { + Int(jsObject["byteLength"].number!) + } + + /// Calls the given closure with a pointer to a copy of the underlying bytes of the + /// array's storage. + /// + /// - Note: The pointer passed as an argument to `body` is valid only for the + /// lifetime of the closure. Do not escape it from the closure for later + /// use. + /// + /// - Parameter body: A closure with an `UnsafeBufferPointer` parameter + /// that points to the contiguous storage for the array. + /// If `body` has a return value, that value is also + /// used as the return value for the `withUnsafeBytes(_:)` method. The + /// argument is valid only for the duration of the closure's execution. + /// - Returns: The return value, if any, of the `body` closure parameter. + public func withUnsafeBytes(_ body: (UnsafeBufferPointer) throws -> R) rethrows -> R { + let bytesLength = lengthInBytes + let rawBuffer = malloc(bytesLength)! + defer { free(rawBuffer) } + _load_typed_array(jsObject.id, rawBuffer.assumingMemoryBound(to: UInt8.self)) + let length = lengthInBytes / MemoryLayout.size + let boundPtr = rawBuffer.bindMemory(to: Element.self, capacity: length) + let bufferPtr = UnsafeBufferPointer(start: boundPtr, count: length) + let result = try body(bufferPtr) + return result + } } // MARK: - Int and UInt support diff --git a/Sources/JavaScriptKit/XcodeSupport.swift b/Sources/JavaScriptKit/XcodeSupport.swift index 54b7c7671..5bb02e3a3 100644 --- a/Sources/JavaScriptKit/XcodeSupport.swift +++ b/Sources/JavaScriptKit/XcodeSupport.swift @@ -82,6 +82,10 @@ import _CJavaScriptKit _: UnsafePointer, _: Int32 ) -> JavaScriptObjectRef { fatalError() } + func _load_typed_array( + _: JavaScriptObjectRef, + _: UnsafeMutablePointer! + ) { fatalError() } func _release(_: JavaScriptObjectRef) { fatalError() } #endif diff --git a/Sources/_CJavaScriptKit/_CJavaScriptKit.c b/Sources/_CJavaScriptKit/_CJavaScriptKit.c index 383349a8e..6222bfe90 100644 --- a/Sources/_CJavaScriptKit/_CJavaScriptKit.c +++ b/Sources/_CJavaScriptKit/_CJavaScriptKit.c @@ -36,7 +36,7 @@ void swjs_cleanup_host_function_call(void *argv_buffer) { /// this and `SwiftRuntime.version` in `./Runtime/src/index.ts`. __attribute__((export_name("swjs_library_version"))) int swjs_library_version(void) { - return 703; + return 704; } int _library_features(void); diff --git a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h index acdd7d3fa..8979cee56 100644 --- a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h +++ b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h @@ -247,6 +247,14 @@ __attribute__((__import_module__("javascript_kit"), extern JavaScriptObjectRef _create_typed_array(const JavaScriptObjectRef constructor, const void *elements_ptr, const int length); +/// Copies the byte contents of a typed array into a Swift side memory buffer. +/// +/// @param ref A JavaScript typed array object. +/// @param buffer A Swift side buffer into which to copy the bytes. +__attribute__((__import_module__("javascript_kit"), + __import_name__("swjs_load_typed_array"))) +extern void _load_typed_array(const JavaScriptObjectRef ref, unsigned char *buffer); + /// Decrements reference count of `ref` retained by `SwiftRuntimeHeap` in JavaScript side. /// /// @param ref The target JavaScript object.