Skip to content

Commit 9b48693

Browse files
Merge pull request #317 from swiftwasm/katei/embedded-typed-array
Unlock `JSTypedArray` for Embedded Swift
2 parents fe6d2e5 + e99b990 commit 9b48693

File tree

3 files changed

+73
-47
lines changed

3 files changed

+73
-47
lines changed

Examples/Embedded/Sources/EmbeddedApp/main.swift

+35-4
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,49 @@ var divElement = document.createElement("div")
1111
divElement.innerText = .string("Count \(count)")
1212
_ = document.body.appendChild(divElement)
1313

14-
var buttonElement = document.createElement("button")
15-
buttonElement.innerText = "Click me"
16-
buttonElement.onclick = JSValue.object(
14+
var clickMeElement = document.createElement("button")
15+
clickMeElement.innerText = "Click me"
16+
clickMeElement.onclick = JSValue.object(
1717
JSClosure { _ in
1818
count += 1
1919
divElement.innerText = .string("Count \(count)")
2020
return .undefined
2121
}
2222
)
23+
_ = document.body.appendChild(clickMeElement)
2324

24-
_ = document.body.appendChild(buttonElement)
25+
var encodeResultElement = document.createElement("pre")
26+
var textInputElement = document.createElement("input")
27+
textInputElement.type = "text"
28+
textInputElement.placeholder = "Enter text to encode to UTF-8"
29+
textInputElement.oninput = JSValue.object(
30+
JSClosure { _ in
31+
let textEncoder = JSObject.global.TextEncoder.function!.new()
32+
let encode = textEncoder.encode.function!
33+
let encodedData = JSTypedArray<UInt8>(
34+
unsafelyWrapping: encode(this: textEncoder, textInputElement.value).object!
35+
)
36+
encodeResultElement.innerText = .string(
37+
encodedData.withUnsafeBytes { bytes in
38+
bytes.map { hex($0) }.joined(separator: " ")
39+
}
40+
)
41+
return .undefined
42+
}
43+
)
44+
let encoderContainer = document.createElement("div")
45+
_ = encoderContainer.appendChild(textInputElement)
46+
_ = encoderContainer.appendChild(encodeResultElement)
47+
_ = document.body.appendChild(encoderContainer)
2548

2649
func print(_ message: String) {
2750
_ = JSObject.global.console.log(message)
2851
}
52+
53+
func hex(_ value: UInt8) -> String {
54+
var result = "0x"
55+
let hexChars: [Character] = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"]
56+
result.append(hexChars[Int(value / 16)])
57+
result.append(hexChars[Int(value % 16)])
58+
return result
59+
}

Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift

+30-35
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
//
22
// Created by Manuel Burghard. Licensed unter MIT.
33
//
4-
#if !hasFeature(Embedded)
54
import _CJavaScriptKit
65

76
/// A protocol that allows a Swift numeric type to be mapped to the JavaScript TypedArray that holds integers of its type
8-
public protocol TypedArrayElement: ConvertibleToJSValue, ConstructibleFromJSValue {
7+
public protocol TypedArrayElement {
8+
associatedtype Element: ConvertibleToJSValue, ConstructibleFromJSValue = Self
99
/// The constructor function for the TypedArray class for this particular kind of number
1010
static var typedArrayClass: JSFunction { get }
1111
}
1212

1313
/// A wrapper around all [JavaScript `TypedArray`
1414
/// classes](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/TypedArray)
1515
/// that exposes their properties in a type-safe way.
16-
public class JSTypedArray<Element>: JSBridgedClass, ExpressibleByArrayLiteral where Element: TypedArrayElement {
17-
public class var constructor: JSFunction? { Element.typedArrayClass }
16+
public final class JSTypedArray<Traits>: JSBridgedClass, ExpressibleByArrayLiteral where Traits: TypedArrayElement {
17+
public typealias Element = Traits.Element
18+
public class var constructor: JSFunction? { Traits.typedArrayClass }
1819
public var jsObject: JSObject
1920

2021
public subscript(_ index: Int) -> Element {
@@ -139,33 +140,28 @@ public class JSTypedArray<Element>: JSBridgedClass, ExpressibleByArrayLiteral wh
139140
}
140141
}
141142

142-
// MARK: - Int and UInt support
143-
144-
// FIXME: Should be updated to support wasm64 when that becomes available.
145-
func valueForBitWidth<T>(typeName: String, bitWidth: Int, when32: T) -> T {
146-
if bitWidth == 32 {
147-
return when32
148-
} else if bitWidth == 64 {
149-
fatalError("64-bit \(typeName)s are not yet supported in JSTypedArray")
150-
} else {
151-
fatalError(
152-
"Unsupported bit width for type \(typeName): \(bitWidth) (hint: stick to fixed-size \(typeName)s to avoid this issue)"
153-
)
154-
}
155-
}
156-
157143
extension Int: TypedArrayElement {
158-
public static var typedArrayClass: JSFunction { _typedArrayClass.wrappedValue }
159-
private static let _typedArrayClass = LazyThreadLocal(initialize: {
160-
valueForBitWidth(typeName: "Int", bitWidth: Int.bitWidth, when32: JSObject.global.Int32Array).function!
161-
})
144+
public static var typedArrayClass: JSFunction {
145+
#if _pointerBitWidth(_32)
146+
return JSObject.global.Int32Array.function!
147+
#elseif _pointerBitWidth(_64)
148+
return JSObject.global.Int64Array.function!
149+
#else
150+
#error("Unsupported pointer width")
151+
#endif
152+
}
162153
}
163154

164155
extension UInt: TypedArrayElement {
165-
public static var typedArrayClass: JSFunction { _typedArrayClass.wrappedValue }
166-
private static let _typedArrayClass = LazyThreadLocal(initialize: {
167-
valueForBitWidth(typeName: "UInt", bitWidth: Int.bitWidth, when32: JSObject.global.Uint32Array).function!
168-
})
156+
public static var typedArrayClass: JSFunction {
157+
#if _pointerBitWidth(_32)
158+
return JSObject.global.Uint32Array.function!
159+
#elseif _pointerBitWidth(_64)
160+
return JSObject.global.Uint64Array.function!
161+
#else
162+
#error("Unsupported pointer width")
163+
#endif
164+
}
169165
}
170166

171167
extension Int8: TypedArrayElement {
@@ -176,13 +172,6 @@ extension UInt8: TypedArrayElement {
176172
public static var typedArrayClass: JSFunction { JSObject.global.Uint8Array.function! }
177173
}
178174

179-
/// A wrapper around [the JavaScript `Uint8ClampedArray`
180-
/// class](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)
181-
/// that exposes its properties in a type-safe and Swifty way.
182-
public class JSUInt8ClampedArray: JSTypedArray<UInt8> {
183-
override public class var constructor: JSFunction? { JSObject.global.Uint8ClampedArray.function! }
184-
}
185-
186175
extension Int16: TypedArrayElement {
187176
public static var typedArrayClass: JSFunction { JSObject.global.Int16Array.function! }
188177
}
@@ -206,4 +195,10 @@ extension Float32: TypedArrayElement {
206195
extension Float64: TypedArrayElement {
207196
public static var typedArrayClass: JSFunction { JSObject.global.Float64Array.function! }
208197
}
209-
#endif
198+
199+
public enum JSUInt8Clamped: TypedArrayElement {
200+
public typealias Element = UInt8
201+
public static var typedArrayClass: JSFunction { JSObject.global.Uint8ClampedArray.function! }
202+
}
203+
204+
public typealias JSUInt8ClampedArray = JSTypedArray<JSUInt8Clamped>

Tests/JavaScriptKitTests/JSTypedArrayTests.swift

+8-8
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ final class JSTypedArrayTests: XCTestCase {
1717
}
1818

1919
func testTypedArray() {
20-
func checkArray<T>(_ array: [T]) where T: TypedArrayElement & Equatable {
21-
XCTAssertEqual(toString(JSTypedArray(array).jsValue.object!), jsStringify(array))
20+
func checkArray<T>(_ array: [T]) where T: TypedArrayElement & Equatable, T.Element == T {
21+
XCTAssertEqual(toString(JSTypedArray<T>(array).jsValue.object!), jsStringify(array))
2222
checkArrayUnsafeBytes(array)
2323
}
2424

@@ -30,20 +30,20 @@ final class JSTypedArrayTests: XCTestCase {
3030
array.map({ String(describing: $0) }).joined(separator: ",")
3131
}
3232

33-
func checkArrayUnsafeBytes<T>(_ array: [T]) where T: TypedArrayElement & Equatable {
34-
let copyOfArray: [T] = JSTypedArray(array).withUnsafeBytes { buffer in
33+
func checkArrayUnsafeBytes<T>(_ array: [T]) where T: TypedArrayElement & Equatable, T.Element == T {
34+
let copyOfArray: [T] = JSTypedArray<T>(array).withUnsafeBytes { buffer in
3535
Array(buffer)
3636
}
3737
XCTAssertEqual(copyOfArray, array)
3838
}
3939

4040
let numbers = [UInt8](0...255)
41-
let typedArray = JSTypedArray(numbers)
41+
let typedArray = JSTypedArray<UInt8>(numbers)
4242
XCTAssertEqual(typedArray[12], 12)
4343
XCTAssertEqual(numbers.count, typedArray.lengthInBytes)
4444

4545
let numbersSet = Set(0...255)
46-
let typedArrayFromSet = JSTypedArray(numbersSet)
46+
let typedArrayFromSet = JSTypedArray<Int>(numbersSet)
4747
XCTAssertEqual(typedArrayFromSet.jsObject.length, 256)
4848
XCTAssertEqual(typedArrayFromSet.lengthInBytes, 256 * MemoryLayout<Int>.size)
4949

@@ -63,7 +63,7 @@ final class JSTypedArrayTests: XCTestCase {
6363
0, 1, .pi, .greatestFiniteMagnitude, .infinity, .leastNonzeroMagnitude,
6464
.leastNormalMagnitude, 42,
6565
]
66-
let jsFloat32Array = JSTypedArray(float32Array)
66+
let jsFloat32Array = JSTypedArray<Float32>(float32Array)
6767
for (i, num) in float32Array.enumerated() {
6868
XCTAssertEqual(num, jsFloat32Array[i])
6969
}
@@ -72,7 +72,7 @@ final class JSTypedArrayTests: XCTestCase {
7272
0, 1, .pi, .greatestFiniteMagnitude, .infinity, .leastNonzeroMagnitude,
7373
.leastNormalMagnitude, 42,
7474
]
75-
let jsFloat64Array = JSTypedArray(float64Array)
75+
let jsFloat64Array = JSTypedArray<Float64>(float64Array)
7676
for (i, num) in float64Array.enumerated() {
7777
XCTAssertEqual(num, jsFloat64Array[i])
7878
}

0 commit comments

Comments
 (0)