Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitcc62d17

Browse files
Merge pull request#383 from swiftwasm/yt/add-async-closure-executor-pref
Add JSClosure APIs to support specifying TaskExecutor and TaskPriority
2 parents83e2335 +a3a3868 commitcc62d17

File tree

4 files changed

+288
-90
lines changed

4 files changed

+288
-90
lines changed

‎Sources/JavaScriptKit/FundamentalObjects/JSClosure.swift‎

Lines changed: 90 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public protocol JSClosureProtocol: JSValueCompatible {
1818
publicclassJSOneshotClosure:JSObject,JSClosureProtocol{
1919
privatevarhostFuncRef:JavaScriptHostFuncRef=0
2020

21-
publicinit(_ body:@escaping(sending[JSValue])->JSValue,file:String= #fileID, line:UInt32= #line){
21+
publicinit(file:String= #fileID, line:UInt32= #line, _ body:@escaping(sending[JSValue])->JSValue){
2222
// 1. Fill `id` as zero at first to access `self` to get `ObjectIdentifier`.
2323
super.init(id:0)
2424

@@ -44,11 +44,40 @@ public class JSOneshotClosure: JSObject, JSClosureProtocol {
4444
}
4545

4646
#if compiler(>=5.5) && (!hasFeature(Embedded) || os(WASI))
47+
/// Creates a new `JSOneshotClosure` that calls the given Swift function asynchronously.
48+
///
49+
/// - Parameters:
50+
/// - priority: The priority of the new unstructured Task created under the hood.
51+
/// - body: The Swift function to call asynchronously.
4752
@available(macOS10.15, iOS13.0, watchOS6.0, tvOS13.0,*)
4853
publicstaticfuncasync(
54+
priority:TaskPriority?=nil,
55+
file:String= #fileID,
56+
line:UInt32= #line,
4957
_ body:sending@escaping(sending[JSValue])asyncthrows(JSException)->JSValue
5058
)->JSOneshotClosure{
51-
JSOneshotClosure(makeAsyncClosure(body))
59+
JSOneshotClosure(file: file, line: line,makeAsyncClosure(priority: priority, body))
60+
}
61+
62+
/// Creates a new `JSOneshotClosure` that calls the given Swift function asynchronously.
63+
///
64+
/// - Parameters:
65+
/// - taskExecutor: The executor preference of the new unstructured Task created under the hood.
66+
/// - priority: The priority of the new unstructured Task created under the hood.
67+
/// - body: The Swift function to call asynchronously.
68+
@available(macOS15.0, iOS18.0, watchOS11.0, tvOS18.0, visionOS2.0,*)
69+
publicstaticfuncasync(
70+
executorPreference taskExecutor:(anyTaskExecutor)?=nil,
71+
priority:TaskPriority?=nil,
72+
file:String= #fileID,
73+
line:UInt32= #line,
74+
_ body:@Sendable@escaping(sending[JSValue])asyncthrows(JSException)->JSValue
75+
)->JSOneshotClosure{
76+
JSOneshotClosure(
77+
file: file,
78+
line: line,
79+
makeAsyncClosure(executorPreference: taskExecutor, priority: priority, body)
80+
)
5281
}
5382
#endif
5483

@@ -117,7 +146,7 @@ public class JSClosure: JSFunction, JSClosureProtocol {
117146
})
118147
}
119148

120-
publicinit(_ body:@escaping(sending[JSValue])->JSValue,file:String= #fileID, line:UInt32= #line){
149+
publicinit(file:String= #fileID, line:UInt32= #line, _ body:@escaping(sending[JSValue])->JSValue){
121150
// 1. Fill `id` as zero at first to access `self` to get `ObjectIdentifier`.
122151
super.init(id:0)
123152

@@ -137,11 +166,36 @@ public class JSClosure: JSFunction, JSClosureProtocol {
137166
}
138167

139168
#if compiler(>=5.5) && (!hasFeature(Embedded) || os(WASI))
169+
/// Creates a new `JSClosure` that calls the given Swift function asynchronously.
170+
///
171+
/// - Parameters:
172+
/// - priority: The priority of the new unstructured Task created under the hood.
173+
/// - body: The Swift function to call asynchronously.
140174
@available(macOS10.15, iOS13.0, watchOS6.0, tvOS13.0,*)
141175
publicstaticfuncasync(
142-
_ body:@Sendable@escaping(sending[JSValue])asyncthrows(JSException)->JSValue
176+
priority:TaskPriority?=nil,
177+
file:String= #fileID,
178+
line:UInt32= #line,
179+
_ body:sending@escaping@isolated(any)(sending[JSValue])asyncthrows(JSException)->JSValue
180+
)->JSClosure{
181+
JSClosure(file: file, line: line,makeAsyncClosure(priority: priority, body))
182+
}
183+
184+
/// Creates a new `JSClosure` that calls the given Swift function asynchronously.
185+
///
186+
/// - Parameters:
187+
/// - taskExecutor: The executor preference of the new unstructured Task created under the hood.
188+
/// - priority: The priority of the new unstructured Task created under the hood.
189+
/// - body: The Swift function to call asynchronously.
190+
@available(macOS15.0, iOS18.0, watchOS11.0, tvOS18.0, visionOS2.0,*)
191+
publicstaticfuncasync(
192+
executorPreference taskExecutor:(anyTaskExecutor)?=nil,
193+
priority:TaskPriority?=nil,
194+
file:String= #fileID,
195+
line:UInt32= #line,
196+
_ body:sending@escaping(sending[JSValue])asyncthrows(JSException)->JSValue
143197
)->JSClosure{
144-
JSClosure(makeAsyncClosure(body))
198+
JSClosure(file: file, line: line,makeAsyncClosure(executorPreference: taskExecutor, priority: priority,body))
145199
}
146200
#endif
147201

@@ -157,6 +211,36 @@ public class JSClosure: JSFunction, JSClosureProtocol {
157211
#if compiler(>=5.5) && (!hasFeature(Embedded) || os(WASI))
158212
@available(macOS10.15, iOS13.0, watchOS6.0, tvOS13.0,*)
159213
privatefunc makeAsyncClosure(
214+
priority:TaskPriority?,
215+
_ body:sending@escaping@isolated(any)(sending[JSValue])asyncthrows(JSException)->JSValue
216+
)->((sending[JSValue])->JSValue){
217+
{ argumentsin
218+
JSPromise{ resolverin
219+
// NOTE: The context is fully transferred to the unstructured task
220+
// isolation but the compiler can't prove it yet, so we need to
221+
// use `@unchecked Sendable` to make it compile with the Swift 6 mode.
222+
structContext:@uncheckedSendable{
223+
letresolver:(JSPromise.Result)->Void
224+
letarguments:[JSValue]
225+
letbody:(sending[JSValue])asyncthrows(JSException)->JSValue
226+
}
227+
letcontext=Context(resolver: resolver, arguments: arguments, body: body)
228+
Task(priority: priority){
229+
dothrows(JSException){
230+
letresult=tryawait context.body(context.arguments)
231+
context.resolver(.success(result))
232+
} catch{
233+
context.resolver(.failure(error.thrownValue))
234+
}
235+
}
236+
}.jsValue()
237+
}
238+
}
239+
240+
@available(macOS15.0, iOS18.0, watchOS11.0, tvOS18.0, visionOS2.0,*)
241+
privatefunc makeAsyncClosure(
242+
executorPreference taskExecutor:(anyTaskExecutor)?,
243+
priority:TaskPriority?,
160244
_ body:sending@escaping(sending[JSValue])asyncthrows(JSException)->JSValue
161245
)->((sending[JSValue])->JSValue){
162246
{ argumentsin
@@ -170,7 +254,7 @@ private func makeAsyncClosure(
170254
letbody:(sending[JSValue])asyncthrows(JSException)->JSValue
171255
}
172256
letcontext=Context(resolver: resolver, arguments: arguments, body: body)
173-
Task{
257+
Task(executorPreference: taskExecutor, priority: priority){
174258
dothrows(JSException){
175259
letresult=tryawait context.body(context.arguments)
176260
context.resolver(.success(result))
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import JavaScriptKit
2+
import XCTest
3+
4+
classJSClosureAsyncTests:XCTestCase{
5+
@available(macOS15.0, iOS18.0, watchOS11.0, tvOS18.0, visionOS2.0,*)
6+
finalclassAnyTaskExecutor:TaskExecutor{
7+
func enqueue(_ job:UnownedJob){
8+
job.runSynchronously(on:asUnownedTaskExecutor())
9+
}
10+
}
11+
12+
finalclassUnsafeSendableBox<T>:@uncheckedSendable{
13+
varvalue:T
14+
init(_ value:T){
15+
self.value= value
16+
}
17+
}
18+
19+
func testAsyncClosure()asyncthrows{
20+
letclosure=JSClosure.async{ _in
21+
return(42.0).jsValue
22+
}.jsValue
23+
letresult=tryawaitJSPromise(from: closure.function!())!.value()
24+
XCTAssertEqual(result,42.0)
25+
}
26+
27+
func testAsyncClosureWithPriority()asyncthrows{
28+
letpriority=UnsafeSendableBox<TaskPriority?>(nil)
29+
letclosure=JSClosure.async(priority:.high){ _in
30+
priority.value=Task.currentPriority
31+
return(42.0).jsValue
32+
}.jsValue
33+
letresult=tryawaitJSPromise(from: closure.function!())!.value()
34+
XCTAssertEqual(result,42.0)
35+
XCTAssertEqual(priority.value,.high)
36+
}
37+
38+
@available(macOS15.0, iOS18.0, watchOS11.0, tvOS18.0, visionOS2.0,*)
39+
func testAsyncClosureWithTaskExecutor()asyncthrows{
40+
letexecutor=AnyTaskExecutor()
41+
letclosure=JSClosure.async(executorPreference: executor){ _in
42+
return(42.0).jsValue
43+
}.jsValue
44+
letresult=tryawaitJSPromise(from: closure.function!())!.value()
45+
XCTAssertEqual(result,42.0)
46+
}
47+
48+
@available(macOS15.0, iOS18.0, watchOS11.0, tvOS18.0, visionOS2.0,*)
49+
func testAsyncClosureWithTaskExecutorPreference()asyncthrows{
50+
letexecutor=AnyTaskExecutor()
51+
letpriority=UnsafeSendableBox<TaskPriority?>(nil)
52+
letclosure=JSClosure.async(executorPreference: executor, priority:.high){ _in
53+
priority.value=Task.currentPriority
54+
return(42.0).jsValue
55+
}.jsValue
56+
letresult=tryawaitJSPromise(from: closure.function!())!.value()
57+
XCTAssertEqual(result,42.0)
58+
XCTAssertEqual(priority.value,.high)
59+
}
60+
61+
// TODO: Enable the following tests once:
62+
// - Make JSObject a final-class
63+
// - Unify JSFunction and JSObject into JSValue
64+
// - Make JS(Oneshot)Closure as a wrapper of JSObject, not a subclass
65+
/*
66+
func testAsyncOneshotClosure() async throws {
67+
let closure = JSOneshotClosure.async { _ in
68+
return (42.0).jsValue
69+
}.jsValue
70+
let result = try await JSPromise(
71+
from: closure.function!()
72+
)!.value()
73+
XCTAssertEqual(result, 42.0)
74+
}
75+
76+
func testAsyncOneshotClosureWithPriority() async throws {
77+
let priority = UnsafeSendableBox<TaskPriority?>(nil)
78+
let closure = JSOneshotClosure.async(priority: .high) { _ in
79+
priority.value = Task.currentPriority
80+
return (42.0).jsValue
81+
}.jsValue
82+
let result = try await JSPromise(from: closure.function!())!.value()
83+
XCTAssertEqual(result, 42.0)
84+
XCTAssertEqual(priority.value, .high)
85+
}
86+
87+
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
88+
func testAsyncOneshotClosureWithTaskExecutor() async throws {
89+
let executor = AnyTaskExecutor()
90+
let closure = JSOneshotClosure.async(executorPreference: executor) { _ in
91+
return (42.0).jsValue
92+
}.jsValue
93+
let result = try await JSPromise(from: closure.function!())!.value()
94+
XCTAssertEqual(result, 42.0)
95+
}
96+
97+
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
98+
func testAsyncOneshotClosureWithTaskExecutorPreference() async throws {
99+
let executor = AnyTaskExecutor()
100+
let priority = UnsafeSendableBox<TaskPriority?>(nil)
101+
let closure = JSOneshotClosure.async(executorPreference: executor, priority: .high) { _ in
102+
priority.value = Task.currentPriority
103+
return (42.0).jsValue
104+
}.jsValue
105+
let result = try await JSPromise(from: closure.function!())!.value()
106+
XCTAssertEqual(result, 42.0)
107+
XCTAssertEqual(priority.value, .high)
108+
}
109+
*/
110+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import JavaScriptKit
2+
import XCTest
3+
4+
classJSClosureTests:XCTestCase{
5+
func testClosureLifetime(){
6+
letevalClosure=JSObject.global.globalObject1.eval_closure.function!
7+
8+
do{
9+
letc1=JSClosure{ argumentsin
10+
returnarguments[0]
11+
}
12+
XCTAssertEqual(evalClosure(c1,JSValue.number(1.0)),.number(1.0))
13+
#if JAVASCRIPTKIT_WITHOUT_WEAKREFS
14+
c1.release()
15+
#endif
16+
}
17+
18+
do{
19+
letarray=JSObject.global.Array.function!.new()
20+
letc1=JSClosure{ _in.number(3)}
21+
_= array.push!(c1)
22+
XCTAssertEqual(array[0].function!().number,3.0)
23+
#if JAVASCRIPTKIT_WITHOUT_WEAKREFS
24+
c1.release()
25+
#endif
26+
}
27+
28+
do{
29+
letc1=JSClosure{ _in.undefined}
30+
XCTAssertEqual(c1(),.undefined)
31+
}
32+
33+
do{
34+
letc1=JSClosure{ _in.number(4)}
35+
XCTAssertEqual(c1(),.number(4))
36+
}
37+
}
38+
39+
func testHostFunctionRegistration(){
40+
// ```js
41+
// global.globalObject1 = {
42+
// ...
43+
// "prop_6": {
44+
// "call_host_1": function() {
45+
// return global.globalObject1.prop_6.host_func_1()
46+
// }
47+
// }
48+
// }
49+
// ```
50+
letglobalObject1=getJSValue(this:.global, name:"globalObject1")
51+
letglobalObject1Ref=try!XCTUnwrap(globalObject1.object)
52+
letprop_6=getJSValue(this: globalObject1Ref, name:"prop_6")
53+
letprop_6Ref=try!XCTUnwrap(prop_6.object)
54+
55+
varisHostFunc1Called=false
56+
lethostFunc1=JSClosure{(_)->JSValuein
57+
isHostFunc1Called=true
58+
return.number(1)
59+
}
60+
61+
setJSValue(this: prop_6Ref, name:"host_func_1", value:.object(hostFunc1))
62+
63+
letcall_host_1=getJSValue(this: prop_6Ref, name:"call_host_1")
64+
letcall_host_1Func=try!XCTUnwrap(call_host_1.function)
65+
XCTAssertEqual(call_host_1Func(),.number(1))
66+
XCTAssertEqual(isHostFunc1Called,true)
67+
68+
#if JAVASCRIPTKIT_WITHOUT_WEAKREFS
69+
hostFunc1.release()
70+
#endif
71+
72+
letevalClosure=JSObject.global.globalObject1.eval_closure.function!
73+
lethostFunc2=JSClosure{(arguments)->JSValuein
74+
iflet input=arguments[0].number{
75+
return.number(input*2)
76+
}else{
77+
return.string(String(describing:arguments[0]))
78+
}
79+
}
80+
81+
XCTAssertEqual(evalClosure(hostFunc2,3),.number(6))
82+
XCTAssertTrue(evalClosure(hostFunc2,true).string!=nil)
83+
84+
#if JAVASCRIPTKIT_WITHOUT_WEAKREFS
85+
hostFunc2.release()
86+
#endif
87+
}
88+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp