summaryrefslogtreecommitdiff
path: root/cli/js/workers.ts
diff options
context:
space:
mode:
authorRyan Dahl <ry@tinyclouds.org>2019-10-04 20:28:51 -0400
committerGitHub <noreply@github.com>2019-10-04 20:28:51 -0400
commitb81e5db17aa8b3088d6034ddf86b79c69410f012 (patch)
tree579e4c23d60d1b0d038156bc28a04f74ea87b2f0 /cli/js/workers.ts
parent9049213867d30f7df090a83b6baf3e0717a4d2d2 (diff)
Merge deno_cli_snapshots into deno_cli (#3064)
Diffstat (limited to 'cli/js/workers.ts')
-rw-r--r--cli/js/workers.ts193
1 files changed, 193 insertions, 0 deletions
diff --git a/cli/js/workers.ts b/cli/js/workers.ts
new file mode 100644
index 000000000..281fe619f
--- /dev/null
+++ b/cli/js/workers.ts
@@ -0,0 +1,193 @@
+// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
+/* eslint-disable @typescript-eslint/no-explicit-any */
+import * as dispatch from "./dispatch.ts";
+import { sendAsync, sendSync } from "./dispatch_json.ts";
+import { log } from "./util.ts";
+import { TextDecoder, TextEncoder } from "./text_encoding.ts";
+import { window } from "./window.ts";
+import { blobURLMap } from "./url.ts";
+import { blobBytesWeakMap } from "./blob.ts";
+
+const encoder = new TextEncoder();
+const decoder = new TextDecoder();
+
+export function encodeMessage(data: any): Uint8Array {
+ const dataJson = JSON.stringify(data);
+ return encoder.encode(dataJson);
+}
+
+export function decodeMessage(dataIntArray: Uint8Array): any {
+ const dataJson = decoder.decode(dataIntArray);
+ return JSON.parse(dataJson);
+}
+
+function createWorker(
+ specifier: string,
+ includeDenoNamespace: boolean,
+ hasSourceCode: boolean,
+ sourceCode: Uint8Array
+): number {
+ return sendSync(dispatch.OP_CREATE_WORKER, {
+ specifier,
+ includeDenoNamespace,
+ hasSourceCode,
+ sourceCode: new TextDecoder().decode(sourceCode)
+ });
+}
+
+async function hostGetWorkerClosed(rid: number): Promise<void> {
+ await sendAsync(dispatch.OP_HOST_GET_WORKER_CLOSED, { rid });
+}
+
+function hostPostMessage(rid: number, data: any): void {
+ const dataIntArray = encodeMessage(data);
+ sendSync(dispatch.OP_HOST_POST_MESSAGE, { rid }, dataIntArray);
+}
+
+async function hostGetMessage(rid: number): Promise<any> {
+ const res = await sendAsync(dispatch.OP_HOST_GET_MESSAGE, { rid });
+
+ if (res.data != null) {
+ return decodeMessage(new Uint8Array(res.data));
+ } else {
+ return null;
+ }
+}
+
+// Stuff for workers
+export const onmessage: (e: { data: any }) => void = (): void => {};
+
+export function postMessage(data: any): void {
+ const dataIntArray = encodeMessage(data);
+ sendSync(dispatch.OP_WORKER_POST_MESSAGE, {}, dataIntArray);
+}
+
+export async function getMessage(): Promise<any> {
+ log("getMessage");
+ const res = await sendAsync(dispatch.OP_WORKER_GET_MESSAGE);
+
+ if (res.data != null) {
+ return decodeMessage(new Uint8Array(res.data));
+ } else {
+ return null;
+ }
+}
+
+export let isClosing = false;
+
+export function workerClose(): void {
+ isClosing = true;
+}
+
+export async function workerMain(): Promise<void> {
+ log("workerMain");
+
+ while (!isClosing) {
+ const data = await getMessage();
+ if (data == null) {
+ log("workerMain got null message. quitting.");
+ break;
+ }
+
+ if (window["onmessage"]) {
+ const event = { data };
+ const result: void | Promise<void> = window.onmessage(event);
+ if (result && "then" in result) {
+ await result;
+ }
+ }
+
+ if (!window["onmessage"]) {
+ break;
+ }
+ }
+}
+
+export interface Worker {
+ onerror?: () => void;
+ onmessage?: (e: { data: any }) => void;
+ onmessageerror?: () => void;
+ postMessage(data: any): void;
+ closed: Promise<void>;
+}
+
+// TODO(kevinkassimo): Maybe implement reasonable web worker options?
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface WorkerOptions {}
+
+/** Extended Deno Worker initialization options.
+ * `noDenoNamespace` hides global `window.Deno` namespace for
+ * spawned worker and nested workers spawned by it (default: false).
+ */
+export interface DenoWorkerOptions extends WorkerOptions {
+ noDenoNamespace?: boolean;
+}
+
+export class WorkerImpl implements Worker {
+ private readonly rid: number;
+ private isClosing = false;
+ private readonly isClosedPromise: Promise<void>;
+ public onerror?: () => void;
+ public onmessage?: (data: any) => void;
+ public onmessageerror?: () => void;
+
+ constructor(specifier: string, options?: DenoWorkerOptions) {
+ let hasSourceCode = false;
+ let sourceCode = new Uint8Array();
+
+ let includeDenoNamespace = true;
+ if (options && options.noDenoNamespace) {
+ includeDenoNamespace = false;
+ }
+ // Handle blob URL.
+ if (specifier.startsWith("blob:")) {
+ hasSourceCode = true;
+ const b = blobURLMap.get(specifier);
+ if (!b) {
+ throw new Error("No Blob associated with the given URL is found");
+ }
+ const blobBytes = blobBytesWeakMap.get(b!);
+ if (!blobBytes) {
+ throw new Error("Invalid Blob");
+ }
+ sourceCode = blobBytes!;
+ }
+
+ this.rid = createWorker(
+ specifier,
+ includeDenoNamespace,
+ hasSourceCode,
+ sourceCode
+ );
+ this.run();
+ this.isClosedPromise = hostGetWorkerClosed(this.rid);
+ this.isClosedPromise.then(
+ (): void => {
+ this.isClosing = true;
+ }
+ );
+ }
+
+ get closed(): Promise<void> {
+ return this.isClosedPromise;
+ }
+
+ postMessage(data: any): void {
+ hostPostMessage(this.rid, data);
+ }
+
+ private async run(): Promise<void> {
+ while (!this.isClosing) {
+ const data = await hostGetMessage(this.rid);
+ if (data == null) {
+ log("worker got null message. quitting.");
+ break;
+ }
+ // TODO(afinch7) stop this from eating messages before onmessage has been assigned
+ if (this.onmessage) {
+ const event = { data };
+ this.onmessage(event);
+ }
+ }
+ }
+}