@@ -6,18 +6,45 @@ import { |
6 | 6 | pointer, |
7 | 7 | TypedArray, |
8 | 8 | ImportedFunctions, |
| 9 | + MAIN_THREAD_TID, |
9 | 10 | } from "./types.js"; |
10 | 11 | import * as JSValue from "./js-value.js"; |
11 | 12 | import { Memory } from "./memory.js"; |
12 | 13 |
|
| 14 | +type TransferMessage = { |
| 15 | + type: "transfer"; |
| 16 | + data: { |
| 17 | + object: any; |
| 18 | + transferring: pointer; |
| 19 | + destinationTid: number; |
| 20 | + }; |
| 21 | +}; |
| 22 | + |
| 23 | +type RequestTransferMessage = { |
| 24 | + type: "requestTransfer"; |
| 25 | + data: { |
| 26 | + objectRef: ref; |
| 27 | + objectSourceTid: number; |
| 28 | + transferring: pointer; |
| 29 | + destinationTid: number; |
| 30 | + }; |
| 31 | +}; |
| 32 | + |
| 33 | +type TransferErrorMessage = { |
| 34 | + type: "transferError"; |
| 35 | + data: { |
| 36 | + error: string; |
| 37 | + }; |
| 38 | +}; |
| 39 | + |
13 | 40 | type MainToWorkerMessage = { |
14 | 41 | type: "wake"; |
15 | | -}; |
| 42 | +} | RequestTransferMessage | TransferMessage | TransferErrorMessage; |
16 | 43 |
|
17 | 44 | type WorkerToMainMessage = { |
18 | 45 | type: "job"; |
19 | 46 | data: number; |
20 | | -}; |
| 47 | +} | RequestTransferMessage | TransferMessage | TransferErrorMessage; |
21 | 48 |
|
22 | 49 | /** |
23 | 50 | * A thread channel is a set of functions that are used to communicate between |
@@ -60,8 +87,9 @@ export type SwiftRuntimeThreadChannel = |
60 | 87 | * This function is used to send messages from the worker thread to the main thread. |
61 | 88 | * The message submitted by this function is expected to be listened by `listenMessageFromWorkerThread`. |
62 | 89 | * @param message The message to be sent to the main thread. |
| 90 | + * @param transfer The array of objects to be transferred to the main thread. |
63 | 91 | */ |
64 | | - postMessageToMainThread: (message: WorkerToMainMessage) => void; |
| 92 | + postMessageToMainThread: (message: WorkerToMainMessage, transfer: any[]) => void; |
65 | 93 | /** |
66 | 94 | * This function is expected to be set in the worker thread and should listen |
67 | 95 | * to messages from the main thread sent by `postMessageToWorkerThread`. |
@@ -75,8 +103,9 @@ export type SwiftRuntimeThreadChannel = |
75 | 103 | * The message submitted by this function is expected to be listened by `listenMessageFromMainThread`. |
76 | 104 | * @param tid The thread ID of the worker thread. |
77 | 105 | * @param message The message to be sent to the worker thread. |
| 106 | + * @param transfer The array of objects to be transferred to the worker thread. |
78 | 107 | */ |
79 | | - postMessageToWorkerThread: (tid: number, message: MainToWorkerMessage) => void; |
| 108 | + postMessageToWorkerThread: (tid: number, message: MainToWorkerMessage, transfer: any[]) => void; |
80 | 109 | /** |
81 | 110 | * This function is expected to be set in the main thread and should listen |
82 | 111 | * to messages sent by `postMessageToMainThread` from the worker thread. |
@@ -610,8 +639,37 @@ export class SwiftRuntime { |
610 | 639 | case "wake": |
611 | 640 | this.exports.swjs_wake_worker_thread(); |
612 | 641 | break; |
| 642 | + case "requestTransfer": { |
| 643 | + const object = this.memory.getObject(message.data.objectRef); |
| 644 | + const messageToMainThread: TransferMessage = { |
| 645 | + type: "transfer", |
| 646 | + data: { |
| 647 | + object, |
| 648 | + destinationTid: message.data.destinationTid, |
| 649 | + transferring: message.data.transferring, |
| 650 | + }, |
| 651 | + }; |
| 652 | + try { |
| 653 | + this.postMessageToMainThread(messageToMainThread, [object]); |
| 654 | + } catch (error) { |
| 655 | + this.postMessageToMainThread({ |
| 656 | + type: "transferError", |
| 657 | + data: { error: String(error) }, |
| 658 | + }); |
| 659 | + } |
| 660 | + break; |
| 661 | + } |
| 662 | + case "transfer": { |
| 663 | + const objectRef = this.memory.retain(message.data.object); |
| 664 | + this.exports.swjs_receive_object(objectRef, message.data.transferring); |
| 665 | + break; |
| 666 | + } |
| 667 | + case "transferError": { |
| 668 | + console.error(message.data.error); // TODO: Handle the error |
| 669 | + break; |
| 670 | + } |
613 | 671 | default: |
614 | | - const unknownMessage: never = message.type; |
| 672 | + const unknownMessage: never = message; |
615 | 673 | throw new Error(`Unknown message type: ${unknownMessage}`); |
616 | 674 | } |
617 | 675 | }); |
@@ -632,8 +690,57 @@ export class SwiftRuntime { |
632 | 690 | case "job": |
633 | 691 | this.exports.swjs_enqueue_main_job_from_worker(message.data); |
634 | 692 | break; |
| 693 | + case "requestTransfer": { |
| 694 | + if (message.data.objectSourceTid == MAIN_THREAD_TID) { |
| 695 | + const object = this.memory.getObject(message.data.objectRef); |
| 696 | + if (message.data.destinationTid != tid) { |
| 697 | + throw new Error("Invariant violation: The destination tid of the transfer request must be the same as the tid of the worker thread that received the request."); |
| 698 | + } |
| 699 | + this.postMessageToWorkerThread(message.data.destinationTid, { |
| 700 | + type: "transfer", |
| 701 | + data: { |
| 702 | + object, |
| 703 | + transferring: message.data.transferring, |
| 704 | + destinationTid: message.data.destinationTid, |
| 705 | + }, |
| 706 | + }, [object]); |
| 707 | + } else { |
| 708 | + // Proxy the transfer request to the worker thread that owns the object |
| 709 | + this.postMessageToWorkerThread(message.data.objectSourceTid, { |
| 710 | + type: "requestTransfer", |
| 711 | + data: { |
| 712 | + objectRef: message.data.objectRef, |
| 713 | + objectSourceTid: tid, |
| 714 | + transferring: message.data.transferring, |
| 715 | + destinationTid: message.data.destinationTid, |
| 716 | + }, |
| 717 | + }); |
| 718 | + } |
| 719 | + break; |
| 720 | + } |
| 721 | + case "transfer": { |
| 722 | + if (message.data.destinationTid == MAIN_THREAD_TID) { |
| 723 | + const objectRef = this.memory.retain(message.data.object); |
| 724 | + this.exports.swjs_receive_object(objectRef, message.data.transferring); |
| 725 | + } else { |
| 726 | + // Proxy the transfer response to the destination worker thread |
| 727 | + this.postMessageToWorkerThread(message.data.destinationTid, { |
| 728 | + type: "transfer", |
| 729 | + data: { |
| 730 | + object: message.data.object, |
| 731 | + transferring: message.data.transferring, |
| 732 | + destinationTid: message.data.destinationTid, |
| 733 | + }, |
| 734 | + }, [message.data.object]); |
| 735 | + } |
| 736 | + break; |
| 737 | + } |
| 738 | + case "transferError": { |
| 739 | + console.error(message.data.error); // TODO: Handle the error |
| 740 | + break; |
| 741 | + } |
635 | 742 | default: |
636 | | - const unknownMessage: never = message.type; |
| 743 | + const unknownMessage: never = message; |
637 | 744 | throw new Error(`Unknown message type: ${unknownMessage}`); |
638 | 745 | } |
639 | 746 | }, |
@@ -649,27 +756,47 @@ export class SwiftRuntime { |
649 | 756 | // Main thread's tid is always -1 |
650 | 757 | return this.tid || -1; |
651 | 758 | }, |
| 759 | + swjs_request_transferring_object: ( |
| 760 | + object_ref: ref, |
| 761 | + object_source_tid: number, |
| 762 | + transferring: pointer, |
| 763 | + ) => { |
| 764 | + if (this.tid == object_source_tid) { |
| 765 | + // Fast path: The object is already in the same thread |
| 766 | + this.exports.swjs_receive_object(object_ref, transferring); |
| 767 | + return; |
| 768 | + } |
| 769 | + this.postMessageToMainThread({ |
| 770 | + type: "requestTransfer", |
| 771 | + data: { |
| 772 | + objectRef: object_ref, |
| 773 | + objectSourceTid: object_source_tid, |
| 774 | + transferring, |
| 775 | + destinationTid: this.tid ?? MAIN_THREAD_TID, |
| 776 | + }, |
| 777 | + }); |
| 778 | + }, |
652 | 779 | }; |
653 | 780 | } |
654 | 781 |
|
655 | | - private postMessageToMainThread(message: WorkerToMainMessage) { |
| 782 | + private postMessageToMainThread(message: WorkerToMainMessage, transfer: any[] = []) { |
656 | 783 | const threadChannel = this.options.threadChannel; |
657 | 784 | if (!(threadChannel && "postMessageToMainThread" in threadChannel)) { |
658 | 785 | throw new Error( |
659 | 786 | "postMessageToMainThread is not set in options given to SwiftRuntime. Please set it to send messages to the main thread." |
660 | 787 | ); |
661 | 788 | } |
662 | | - threadChannel.postMessageToMainThread(message); |
| 789 | + threadChannel.postMessageToMainThread(message, transfer); |
663 | 790 | } |
664 | 791 |
|
665 | | - private postMessageToWorkerThread(tid: number, message: MainToWorkerMessage) { |
| 792 | + private postMessageToWorkerThread(tid: number, message: MainToWorkerMessage, transfer: any[] = []) { |
666 | 793 | const threadChannel = this.options.threadChannel; |
667 | 794 | if (!(threadChannel && "postMessageToWorkerThread" in threadChannel)) { |
668 | 795 | throw new Error( |
669 | 796 | "postMessageToWorkerThread is not set in options given to SwiftRuntime. Please set it to send messages to worker threads." |
670 | 797 | ); |
671 | 798 | } |
672 | | - threadChannel.postMessageToWorkerThread(tid, message); |
| 799 | + threadChannel.postMessageToWorkerThread(tid, message, transfer); |
673 | 800 | } |
674 | 801 | } |
675 | 802 |
|
|