Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Examples/ActorOnWebWorker/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ let package = Package(
name: "Example",
platforms: [.macOS("15"), .iOS("18"), .watchOS("11"), .tvOS("18"), .visionOS("2")],
dependencies: [
.package(path: "../../")
.package(name: "JavaScriptKit", path: "../../")
],
targets: [
.executableTarget(
Expand Down
2 changes: 1 addition & 1 deletion Examples/Multithreading/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ let package = Package(
name: "Example",
platforms: [.macOS("15"), .iOS("18"), .watchOS("11"), .tvOS("18"), .visionOS("2")],
dependencies: [
.package(path: "../../"),
.package(name: "JavaScriptKit", path: "../../"),
.package(
url: "https://github.com/kateinoigakukun/chibi-ray",
revision: "c8cab621a3338dd2f8e817d3785362409d3b8cf1"
Expand Down
2 changes: 1 addition & 1 deletion Examples/OffscrenCanvas/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ let package = Package(
name: "Example",
platforms: [.macOS("15"), .iOS("18"), .watchOS("11"), .tvOS("18"), .visionOS("2")],
dependencies: [
.package(path: "../../")
.package(name: "JavaScriptKit", path: "../../")
],
targets: [
.executableTarget(
Expand Down
6 changes: 6 additions & 0 deletions Plugins/PackageToJS/Templates/runtime.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ declare class ITCInterface {
sendingContext: pointer;
transfer: Transferable[];
};
invokeRemoteJSObjectBody(invocationContext: pointer): {
object: undefined;
transfer: Transferable[];
};
release(objectRef: ref): {
object: undefined;
transfer: Transferable[];
Expand Down Expand Up @@ -140,6 +144,8 @@ type ResponseMessage = {
sourceTid: number;
/** The context pointer of the request */
context: pointer;
/** The request method this response corresponds to */
requestMethod: keyof ITCInterface;
/** The response content */
response: {
ok: true;
Expand Down
80 changes: 68 additions & 12 deletions Plugins/PackageToJS/Templates/runtime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ class ITCInterface {
const transfer = transferringObjects.map((ref) => this.memory.getObject(ref));
return { object: objects, sendingContext, transfer };
}
invokeRemoteJSObjectBody(invocationContext) {
return { object: undefined, transfer: [] };
}
release(objectRef) {
this.memory.release(objectRef);
return { object: undefined, transfer: [] };
Expand Down Expand Up @@ -455,13 +458,51 @@ class SwiftRuntime {
if (broker)
return broker;
const itcInterface = new ITCInterface(this.memory);
const defaultRequestHandler = (message) => {
const request = message.data.request;
// @ts-ignore dynamic dispatch by method name
const result = itcInterface[request.method].apply(itcInterface, request.parameters);
return { ok: true, value: result };
};
const requestHandlers = {
invokeRemoteJSObjectBody: (message) => {
const invocationContext = message.data.request
.parameters[0];
const hasError = this.exports.swjs_invoke_remote_jsobject_body(invocationContext);
return {
ok: true,
value: {
object: hasError,
sendingContext: message.data.context,
transfer: [],
},
};
},
};
const defaultResponseHandler = (message) => {
if (message.data.response.ok) {
const object = this.memory.retain(message.data.response.value.object);
this.exports.swjs_receive_response(object, message.data.context);
}
else {
const error = deserializeError(message.data.response.error);
const errorObject = this.memory.retain(error);
this.exports.swjs_receive_error(errorObject, message.data.context);
}
};
const responseHandlers = {
invokeRemoteJSObjectBody: (_message) => {
// Swift continuation is resumed on the owner thread.
},
};
const newBroker = new MessageBroker((_a = this.tid) !== null && _a !== void 0 ? _a : -1, threadChannel, {
onRequest: (message) => {
var _a;
let returnValue;
try {
// @ts-ignore
const result = itcInterface[message.data.request.method](...message.data.request.parameters);
returnValue = { ok: true, value: result };
const method = message.data.request.method;
const handler = (_a = requestHandlers[method]) !== null && _a !== void 0 ? _a : defaultRequestHandler;
returnValue = handler(message);
}
catch (error) {
returnValue = {
Expand All @@ -474,6 +515,7 @@ class SwiftRuntime {
data: {
sourceTid: message.data.sourceTid,
context: message.data.context,
requestMethod: message.data.request.method,
response: returnValue,
},
};
Expand All @@ -489,15 +531,10 @@ class SwiftRuntime {
}
},
onResponse: (message) => {
if (message.data.response.ok) {
const object = this.memory.retain(message.data.response.value.object);
this.exports.swjs_receive_response(object, message.data.context);
}
else {
const error = deserializeError(message.data.response.error);
const errorObject = this.memory.retain(error);
this.exports.swjs_receive_error(errorObject, message.data.context);
}
var _a;
const method = message.data.requestMethod;
const handler = (_a = responseHandlers[method]) !== null && _a !== void 0 ? _a : defaultResponseHandler;
handler(message);
},
});
broker = newBroker;
Expand Down Expand Up @@ -842,6 +879,25 @@ class SwiftRuntime {
},
});
},
swjs_request_remote_jsobject_body: (object_source_tid, invocation_context) => {
var _a;
if (!this.options.threadChannel) {
throw new Error("threadChannel is not set in options given to SwiftRuntime. Please set it to request remote JSObject access.");
}
const broker = getMessageBroker(this.options.threadChannel);
broker.request({
type: "request",
data: {
sourceTid: (_a = this.tid) !== null && _a !== void 0 ? _a : MAIN_THREAD_TID,
targetTid: object_source_tid,
context: invocation_context,
request: {
method: "invokeRemoteJSObjectBody",
parameters: [invocation_context],
},
},
});
},
};
}
postMessageToMainThread(message, transfer = []) {
Expand Down
125 changes: 102 additions & 23 deletions Runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
deserializeError,
MainToWorkerMessage,
MessageBroker,
RequestMessage,
ResponseMessage,
ITCInterface,
serializeError,
Expand Down Expand Up @@ -265,29 +266,98 @@ export class SwiftRuntime {
const getMessageBroker = (threadChannel: SwiftRuntimeThreadChannel) => {
if (broker) return broker;
const itcInterface = new ITCInterface(this.memory);
type ITCMethodName = keyof ITCInterface;

const defaultRequestHandler = (
message: RequestMessage,
): ResponseMessage["data"]["response"] => {
const request = message.data.request;
// @ts-ignore dynamic dispatch by method name
const result = itcInterface[request.method].apply(
itcInterface,
request.parameters as any[],
);
return { ok: true, value: result };
};

const requestHandlers: Partial<
Record<
ITCMethodName,
(
message: RequestMessage,
) => ResponseMessage["data"]["response"]
>
> = {
invokeRemoteJSObjectBody: (message) => {
const invocationContext = message.data.request
.parameters[0] as pointer;
const hasError =
this.exports.swjs_invoke_remote_jsobject_body(
invocationContext,
);
return {
ok: true,
value: {
object: hasError,
sendingContext: message.data.context,
transfer: [],
},
};
},
};

const defaultResponseHandler = (message: ResponseMessage) => {
if (message.data.response.ok) {
const object = this.memory.retain(
message.data.response.value.object,
);
this.exports.swjs_receive_response(
object,
message.data.context,
);
} else {
const error = deserializeError(message.data.response.error);
const errorObject = this.memory.retain(error);
this.exports.swjs_receive_error(
errorObject,
message.data.context,
);
}
};

const responseHandlers: Partial<
Record<ITCMethodName, (message: ResponseMessage) => void>
> = {
invokeRemoteJSObjectBody: (_message) => {
// Swift continuation is resumed on the owner thread.
},
};

const newBroker = new MessageBroker(this.tid ?? -1, threadChannel, {
onRequest: (message) => {
let returnValue: ResponseMessage["data"]["response"];
try {
// @ts-ignore
const result = itcInterface[
message.data.request.method
](...message.data.request.parameters);
returnValue = { ok: true, value: result };
const method = message.data.request.method;
const handler =
requestHandlers[method] ?? defaultRequestHandler;
returnValue = handler(message);
} catch (error) {
returnValue = {
ok: false,
error: serializeError(error),
};
}

const responseMessage: ResponseMessage = {
type: "response",
data: {
sourceTid: message.data.sourceTid,
context: message.data.context,
requestMethod: message.data.request.method,
response: returnValue,
},
};

try {
newBroker.reply(responseMessage);
} catch (error) {
Expand All @@ -303,24 +373,10 @@ export class SwiftRuntime {
}
},
onResponse: (message) => {
if (message.data.response.ok) {
const object = this.memory.retain(
message.data.response.value.object,
);
this.exports.swjs_receive_response(
object,
message.data.context,
);
} else {
const error = deserializeError(
message.data.response.error,
);
const errorObject = this.memory.retain(error);
this.exports.swjs_receive_error(
errorObject,
message.data.context,
);
}
const method = message.data.requestMethod;
const handler =
responseHandlers[method] ?? defaultResponseHandler;
handler(message);
},
});
broker = newBroker;
Expand Down Expand Up @@ -934,6 +990,29 @@ export class SwiftRuntime {
},
});
},
swjs_request_remote_jsobject_body: (
object_source_tid: number,
invocation_context: pointer,
) => {
if (!this.options.threadChannel) {
throw new Error(
"threadChannel is not set in options given to SwiftRuntime. Please set it to request remote JSObject access.",
);
}
const broker = getMessageBroker(this.options.threadChannel);
broker.request({
type: "request",
data: {
sourceTid: this.tid ?? MAIN_THREAD_TID,
targetTid: object_source_tid,
context: invocation_context,
request: {
method: "invokeRemoteJSObjectBody",
parameters: [invocation_context],
},
},
});
},
};
}

Expand Down
9 changes: 9 additions & 0 deletions Runtime/src/itc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@ export class ITCInterface {
return { object: objects, sendingContext, transfer };
}

invokeRemoteJSObjectBody(invocationContext: pointer): {
object: undefined;
transfer: Transferable[];
} {
return { object: undefined, transfer: [] };
}

release(objectRef: ref): { object: undefined; transfer: Transferable[] } {
this.memory.release(objectRef);
return { object: undefined, transfer: [] };
Expand Down Expand Up @@ -163,6 +170,8 @@ export type ResponseMessage = {
sourceTid: number;
/** The context pointer of the request */
context: pointer;
/** The request method this response corresponds to */
requestMethod: keyof ITCInterface;
/** The response content */
response:
| {
Expand Down
1 change: 1 addition & 0 deletions Runtime/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface ExportedFunctions {
swjs_wake_worker_thread(): void;
swjs_receive_response(object: ref, transferring: pointer): void;
swjs_receive_error(error: ref, context: number): void;
swjs_invoke_remote_jsobject_body(context: pointer): number;
}

export const enum LibraryFeatures {
Expand Down
Loading
Loading