-
-
Notifications
You must be signed in to change notification settings - Fork 69
Expand file tree
/
Copy pathJSTypedArray.swift
More file actions
204 lines (177 loc) · 8.09 KB
/
JSTypedArray.swift
File metadata and controls
204 lines (177 loc) · 8.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
//
// Created by Manuel Burghard. Licensed unter MIT.
//
import _CJavaScriptKit
/// A protocol that allows a Swift numeric type to be mapped to the JavaScript TypedArray that holds integers of its type
public protocol TypedArrayElement {
associatedtype Element: ConvertibleToJSValue, ConstructibleFromJSValue = Self
/// The constructor function for the TypedArray class for this particular kind of number
static var typedArrayClass: JSObject { get }
}
/// A wrapper around all [JavaScript `TypedArray`
/// classes](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/TypedArray)
/// that exposes their properties in a type-safe way.
public final class JSTypedArray<Traits>: JSBridgedClass, ExpressibleByArrayLiteral where Traits: TypedArrayElement {
public typealias Element = Traits.Element
public class var constructor: JSObject? { Traits.typedArrayClass }
public var jsObject: JSObject
public subscript(_ index: Int) -> Element {
get {
return Element.construct(from: jsObject[index])!
}
set {
jsObject[index] = newValue.jsValue
}
}
/// Initialize a new instance of TypedArray in JavaScript environment with given length.
/// All the elements will be initialized to zero.
///
/// - Parameter length: The number of elements that will be allocated.
public init(length: Int) {
jsObject = Self.constructor!.new(length)
}
public required init(unsafelyWrapping jsObject: JSObject) {
self.jsObject = jsObject
}
public required convenience init(arrayLiteral elements: Element...) {
self.init(elements)
}
/// Initialize a new instance of TypedArray in JavaScript environment with given elements.
///
/// - Parameter array: The array that will be copied to create a new instance of TypedArray
public convenience init(_ array: [Element]) {
let object = array.withUnsafeBufferPointer { buffer in
Self.createTypedArray(from: buffer)
}
self.init(unsafelyWrapping: object)
}
/// Convenience initializer for `Sequence`.
public convenience init<S: Sequence>(_ sequence: S) where S.Element == Element {
self.init(Array(sequence))
}
/// Initialize a new instance of TypedArray in JavaScript environment with given buffer contents.
///
/// - Parameter buffer: The buffer that will be copied to create a new instance of TypedArray
public convenience init(buffer: UnsafeBufferPointer<Element>) {
self.init(unsafelyWrapping: Self.createTypedArray(from: buffer))
}
private static func createTypedArray(from buffer: UnsafeBufferPointer<Element>) -> JSObject {
// Retain the constructor function to avoid it being released before calling `swjs_create_typed_array`
let jsArrayRef = withExtendedLifetime(Self.constructor!) { ctor in
swjs_create_typed_array(ctor.id, buffer.baseAddress, Int32(buffer.count))
}
return JSObject(id: jsArrayRef)
}
/// 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!)
}
/// Length (in elements) of the typed array.
public var length: Int {
Int(jsObject["length"].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<R>(_ body: (UnsafeBufferPointer<Element>) throws -> R) rethrows -> R {
let buffer = UnsafeMutableBufferPointer<Element>.allocate(capacity: length)
defer { buffer.deallocate() }
copyMemory(to: buffer)
let result = try body(UnsafeBufferPointer(buffer))
return result
}
#if compiler(>=5.5)
/// Calls the given async 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 async 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`async closure parameter.
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
public func withUnsafeBytesAsync<R>(_ body: (UnsafeBufferPointer<Element>) async throws -> R) async rethrows -> R {
let buffer = UnsafeMutableBufferPointer<Element>.allocate(capacity: length)
defer { buffer.deallocate() }
copyMemory(to: buffer)
let result = try await body(UnsafeBufferPointer(buffer))
return result
}
#endif
/// Copies the contents of the array to the given buffer.
///
/// - Parameter buffer: The buffer to copy the contents of the array to.
/// The buffer must have enough space to accommodate the contents of the array.
public func copyMemory(to buffer: UnsafeMutableBufferPointer<Element>) {
precondition(buffer.count >= length, "Buffer is too small to hold the contents of the array")
swjs_load_typed_array(jsObject.id, buffer.baseAddress!)
}
}
extension Int: TypedArrayElement {
public static var typedArrayClass: JSObject {
#if _pointerBitWidth(_32)
return JSObject.global.Int32Array.object!
#elseif _pointerBitWidth(_64)
return JSObject.global.Int64Array.object!
#else
#error("Unsupported pointer width")
#endif
}
}
extension UInt: TypedArrayElement {
public static var typedArrayClass: JSObject {
#if _pointerBitWidth(_32)
return JSObject.global.Uint32Array.object!
#elseif _pointerBitWidth(_64)
return JSObject.global.Uint64Array.object!
#else
#error("Unsupported pointer width")
#endif
}
}
extension Int8: TypedArrayElement {
public static var typedArrayClass: JSObject { JSObject.global.Int8Array.object! }
}
extension UInt8: TypedArrayElement {
public static var typedArrayClass: JSObject { JSObject.global.Uint8Array.object! }
}
extension Int16: TypedArrayElement {
public static var typedArrayClass: JSObject { JSObject.global.Int16Array.object! }
}
extension UInt16: TypedArrayElement {
public static var typedArrayClass: JSObject { JSObject.global.Uint16Array.object! }
}
extension Int32: TypedArrayElement {
public static var typedArrayClass: JSObject { JSObject.global.Int32Array.object! }
}
extension UInt32: TypedArrayElement {
public static var typedArrayClass: JSObject { JSObject.global.Uint32Array.object! }
}
extension Float32: TypedArrayElement {
public static var typedArrayClass: JSObject { JSObject.global.Float32Array.object! }
}
extension Float64: TypedArrayElement {
public static var typedArrayClass: JSObject { JSObject.global.Float64Array.object! }
}
public enum JSUInt8Clamped: TypedArrayElement {
public typealias Element = UInt8
public static var typedArrayClass: JSObject { JSObject.global.Uint8ClampedArray.object! }
}
public typealias JSUInt8ClampedArray = JSTypedArray<JSUInt8Clamped>