Skip to content

Commit 03704ec

Browse files
Add JSRemote<JSObject> with typed-throws owner-thread access
1 parent 7e242b0 commit 03704ec

File tree

8 files changed

+407
-35
lines changed

8 files changed

+407
-35
lines changed

Plugins/PackageToJS/Templates/runtime.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ declare class ITCInterface {
9797
sendingContext: pointer;
9898
transfer: Transferable[];
9999
};
100+
invokeRemoteJSObjectBody(invocationContext: pointer): {
101+
object: undefined;
102+
transfer: Transferable[];
103+
};
100104
release(objectRef: ref): {
101105
object: undefined;
102106
transfer: Transferable[];
@@ -140,6 +144,8 @@ type ResponseMessage = {
140144
sourceTid: number;
141145
/** The context pointer of the request */
142146
context: pointer;
147+
/** The request method this response corresponds to */
148+
requestMethod: keyof ITCInterface;
143149
/** The response content */
144150
response: {
145151
ok: true;

Plugins/PackageToJS/Templates/runtime.mjs

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ class ITCInterface {
135135
const transfer = transferringObjects.map((ref) => this.memory.getObject(ref));
136136
return { object: objects, sendingContext, transfer };
137137
}
138+
invokeRemoteJSObjectBody(invocationContext) {
139+
return { object: undefined, transfer: [] };
140+
}
138141
release(objectRef) {
139142
this.memory.release(objectRef);
140143
return { object: undefined, transfer: [] };
@@ -455,13 +458,51 @@ class SwiftRuntime {
455458
if (broker)
456459
return broker;
457460
const itcInterface = new ITCInterface(this.memory);
461+
const defaultRequestHandler = (message) => {
462+
const request = message.data.request;
463+
// @ts-ignore dynamic dispatch by method name
464+
const result = itcInterface[request.method].apply(itcInterface, request.parameters);
465+
return { ok: true, value: result };
466+
};
467+
const requestHandlers = {
468+
invokeRemoteJSObjectBody: (message) => {
469+
const invocationContext = message.data.request
470+
.parameters[0];
471+
const hasError = this.exports.swjs_invoke_remote_jsobject_body(invocationContext);
472+
return {
473+
ok: true,
474+
value: {
475+
object: hasError,
476+
sendingContext: message.data.context,
477+
transfer: [],
478+
},
479+
};
480+
},
481+
};
482+
const defaultResponseHandler = (message) => {
483+
if (message.data.response.ok) {
484+
const object = this.memory.retain(message.data.response.value.object);
485+
this.exports.swjs_receive_response(object, message.data.context);
486+
}
487+
else {
488+
const error = deserializeError(message.data.response.error);
489+
const errorObject = this.memory.retain(error);
490+
this.exports.swjs_receive_error(errorObject, message.data.context);
491+
}
492+
};
493+
const responseHandlers = {
494+
invokeRemoteJSObjectBody: (_message) => {
495+
// Swift continuation is resumed on the owner thread.
496+
},
497+
};
458498
const newBroker = new MessageBroker((_a = this.tid) !== null && _a !== void 0 ? _a : -1, threadChannel, {
459499
onRequest: (message) => {
500+
var _a;
460501
let returnValue;
461502
try {
462-
// @ts-ignore
463-
const result = itcInterface[message.data.request.method](...message.data.request.parameters);
464-
returnValue = { ok: true, value: result };
503+
const method = message.data.request.method;
504+
const handler = (_a = requestHandlers[method]) !== null && _a !== void 0 ? _a : defaultRequestHandler;
505+
returnValue = handler(message);
465506
}
466507
catch (error) {
467508
returnValue = {
@@ -474,6 +515,7 @@ class SwiftRuntime {
474515
data: {
475516
sourceTid: message.data.sourceTid,
476517
context: message.data.context,
518+
requestMethod: message.data.request.method,
477519
response: returnValue,
478520
},
479521
};
@@ -489,15 +531,10 @@ class SwiftRuntime {
489531
}
490532
},
491533
onResponse: (message) => {
492-
if (message.data.response.ok) {
493-
const object = this.memory.retain(message.data.response.value.object);
494-
this.exports.swjs_receive_response(object, message.data.context);
495-
}
496-
else {
497-
const error = deserializeError(message.data.response.error);
498-
const errorObject = this.memory.retain(error);
499-
this.exports.swjs_receive_error(errorObject, message.data.context);
500-
}
534+
var _a;
535+
const method = message.data.requestMethod;
536+
const handler = (_a = responseHandlers[method]) !== null && _a !== void 0 ? _a : defaultResponseHandler;
537+
handler(message);
501538
},
502539
});
503540
broker = newBroker;
@@ -842,6 +879,25 @@ class SwiftRuntime {
842879
},
843880
});
844881
},
882+
swjs_request_remote_jsobject_body: (object_source_tid, invocation_context) => {
883+
var _a;
884+
if (!this.options.threadChannel) {
885+
throw new Error("threadChannel is not set in options given to SwiftRuntime. Please set it to request remote JSObject access.");
886+
}
887+
const broker = getMessageBroker(this.options.threadChannel);
888+
broker.request({
889+
type: "request",
890+
data: {
891+
sourceTid: (_a = this.tid) !== null && _a !== void 0 ? _a : MAIN_THREAD_TID,
892+
targetTid: object_source_tid,
893+
context: invocation_context,
894+
request: {
895+
method: "invokeRemoteJSObjectBody",
896+
parameters: [invocation_context],
897+
},
898+
},
899+
});
900+
},
845901
};
846902
}
847903
postMessageToMainThread(message, transfer = []) {

Runtime/src/index.ts

Lines changed: 97 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
deserializeError,
1212
MainToWorkerMessage,
1313
MessageBroker,
14+
RequestMessage,
1415
ResponseMessage,
1516
ITCInterface,
1617
serializeError,
@@ -265,29 +266,94 @@ export class SwiftRuntime {
265266
const getMessageBroker = (threadChannel: SwiftRuntimeThreadChannel) => {
266267
if (broker) return broker;
267268
const itcInterface = new ITCInterface(this.memory);
269+
type ITCMethodName = keyof ITCInterface;
270+
271+
const defaultRequestHandler = (
272+
message: RequestMessage,
273+
): ResponseMessage["data"]["response"] => {
274+
const request = message.data.request;
275+
// @ts-ignore dynamic dispatch by method name
276+
const result = itcInterface[request.method].apply(
277+
itcInterface,
278+
request.parameters as any[],
279+
);
280+
return { ok: true, value: result };
281+
};
282+
283+
const requestHandlers: Partial<
284+
Record<
285+
ITCMethodName,
286+
(message: RequestMessage) => ResponseMessage["data"]["response"]
287+
>
288+
> = {
289+
invokeRemoteJSObjectBody: (message) => {
290+
const invocationContext = message.data.request
291+
.parameters[0] as pointer;
292+
const hasError = this.exports.swjs_invoke_remote_jsobject_body(
293+
invocationContext,
294+
);
295+
return {
296+
ok: true,
297+
value: {
298+
object: hasError,
299+
sendingContext: message.data.context,
300+
transfer: [],
301+
},
302+
};
303+
},
304+
};
305+
306+
const defaultResponseHandler = (message: ResponseMessage) => {
307+
if (message.data.response.ok) {
308+
const object = this.memory.retain(
309+
message.data.response.value.object,
310+
);
311+
this.exports.swjs_receive_response(
312+
object,
313+
message.data.context,
314+
);
315+
} else {
316+
const error = deserializeError(message.data.response.error);
317+
const errorObject = this.memory.retain(error);
318+
this.exports.swjs_receive_error(
319+
errorObject,
320+
message.data.context,
321+
);
322+
}
323+
};
324+
325+
const responseHandlers: Partial<
326+
Record<ITCMethodName, (message: ResponseMessage) => void>
327+
> = {
328+
invokeRemoteJSObjectBody: (_message) => {
329+
// Swift continuation is resumed on the owner thread.
330+
},
331+
};
332+
268333
const newBroker = new MessageBroker(this.tid ?? -1, threadChannel, {
269334
onRequest: (message) => {
270335
let returnValue: ResponseMessage["data"]["response"];
271336
try {
272-
// @ts-ignore
273-
const result = itcInterface[
274-
message.data.request.method
275-
](...message.data.request.parameters);
276-
returnValue = { ok: true, value: result };
337+
const method = message.data.request.method;
338+
const handler = requestHandlers[method] ?? defaultRequestHandler;
339+
returnValue = handler(message);
277340
} catch (error) {
278341
returnValue = {
279342
ok: false,
280343
error: serializeError(error),
281344
};
282345
}
346+
283347
const responseMessage: ResponseMessage = {
284348
type: "response",
285349
data: {
286350
sourceTid: message.data.sourceTid,
287351
context: message.data.context,
352+
requestMethod: message.data.request.method,
288353
response: returnValue,
289354
},
290355
};
356+
291357
try {
292358
newBroker.reply(responseMessage);
293359
} catch (error) {
@@ -303,24 +369,9 @@ export class SwiftRuntime {
303369
}
304370
},
305371
onResponse: (message) => {
306-
if (message.data.response.ok) {
307-
const object = this.memory.retain(
308-
message.data.response.value.object,
309-
);
310-
this.exports.swjs_receive_response(
311-
object,
312-
message.data.context,
313-
);
314-
} else {
315-
const error = deserializeError(
316-
message.data.response.error,
317-
);
318-
const errorObject = this.memory.retain(error);
319-
this.exports.swjs_receive_error(
320-
errorObject,
321-
message.data.context,
322-
);
323-
}
372+
const method = message.data.requestMethod;
373+
const handler = responseHandlers[method] ?? defaultResponseHandler;
374+
handler(message);
324375
},
325376
});
326377
broker = newBroker;
@@ -934,6 +985,29 @@ export class SwiftRuntime {
934985
},
935986
});
936987
},
988+
swjs_request_remote_jsobject_body: (
989+
object_source_tid: number,
990+
invocation_context: pointer,
991+
) => {
992+
if (!this.options.threadChannel) {
993+
throw new Error(
994+
"threadChannel is not set in options given to SwiftRuntime. Please set it to request remote JSObject access.",
995+
);
996+
}
997+
const broker = getMessageBroker(this.options.threadChannel);
998+
broker.request({
999+
type: "request",
1000+
data: {
1001+
sourceTid: this.tid ?? MAIN_THREAD_TID,
1002+
targetTid: object_source_tid,
1003+
context: invocation_context,
1004+
request: {
1005+
method: "invokeRemoteJSObjectBody",
1006+
parameters: [invocation_context],
1007+
},
1008+
},
1009+
});
1010+
},
9371011
};
9381012
}
9391013

Runtime/src/itc.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ export class ITCInterface {
117117
return { object: objects, sendingContext, transfer };
118118
}
119119

120+
invokeRemoteJSObjectBody(
121+
invocationContext: pointer,
122+
): { object: undefined; transfer: Transferable[] } {
123+
return { object: undefined, transfer: [] };
124+
}
125+
120126
release(objectRef: ref): { object: undefined; transfer: Transferable[] } {
121127
this.memory.release(objectRef);
122128
return { object: undefined, transfer: [] };
@@ -163,6 +169,8 @@ export type ResponseMessage = {
163169
sourceTid: number;
164170
/** The context pointer of the request */
165171
context: pointer;
172+
/** The request method this response corresponds to */
173+
requestMethod: keyof ITCInterface;
166174
/** The response content */
167175
response:
168176
| {

Runtime/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export interface ExportedFunctions {
2222
swjs_wake_worker_thread(): void;
2323
swjs_receive_response(object: ref, transferring: pointer): void;
2424
swjs_receive_error(error: ref, context: number): void;
25+
swjs_invoke_remote_jsobject_body(context: pointer): number;
2526
}
2627

2728
export const enum LibraryFeatures {

0 commit comments

Comments
 (0)