Skip to content

Commit

Permalink
Basic WebWorker implementation.
Browse files Browse the repository at this point in the history
Only accessible from Rust currently.

fixes

wip

wip

test_rs passes
  • Loading branch information
ry committed Jan 2, 2019
1 parent 2c477dd commit f6e67ae
Show file tree
Hide file tree
Showing 13 changed files with 639 additions and 69 deletions.
1 change: 1 addition & 0 deletions BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ ts_sources = [
"js/url.ts",
"js/url_search_params.ts",
"js/util.ts",
"js/workers.ts",
"js/write_file.ts",
"tsconfig.json",

Expand Down
28 changes: 17 additions & 11 deletions js/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const LIB_RUNTIME = "lib.deno_runtime.d.ts";
* like `.`, or it could be a module specifier like
* `http://gist.github.com/somefile.ts`
*/
type ContainingFile = string;
export type ContainingFile = string;
/** The internal local filename of a compiled module. It will often be something
* like `/home/ry/.deno/gen/f7b4605dfbc4d3bb356e98fda6ceb1481e4a8df5.js`
*/
Expand All @@ -27,7 +27,7 @@ type ModuleId = string;
/** The external name of a module - could be a URL or could be a relative path.
* Examples `http://gist.github.com/somefile.ts` or `./somefile.ts`
*/
type ModuleSpecifier = string;
export type ModuleSpecifier = string;
/** The compiled source code which is cached in `.deno/gen/` */
type OutputCode = string;
/** The original source code */
Expand Down Expand Up @@ -170,9 +170,7 @@ export class Compiler
* TypeScript compiler, but the TypeScript compiler shouldn't be asking about
* external modules that we haven't told it about yet.
*/
private _getModuleMetaData(
fileName: ModuleFileName
): ModuleMetaData | undefined {
getModuleMetaData(fileName: ModuleFileName): ModuleMetaData | undefined {
return this._moduleMetaDataMap.has(fileName)
? this._moduleMetaDataMap.get(fileName)
: fileName.startsWith(ASSETS)
Expand Down Expand Up @@ -401,7 +399,7 @@ export class Compiler
* this ensures the module is loaded.
*/
getOutput(filename: ModuleFileName): OutputCode {
const moduleMetaData = this._getModuleMetaData(filename)!;
const moduleMetaData = this.getModuleMetaData(filename)!;
assert(moduleMetaData != null, `Module not loaded: "${filename}"`);
this._scriptFileNames = [moduleMetaData.fileName];
return this.compile(moduleMetaData);
Expand All @@ -412,11 +410,19 @@ export class Compiler
* this ensures the module is loaded.
*/
getSource(filename: ModuleFileName): SourceCode {
const moduleMetaData = this._getModuleMetaData(filename)!;
const moduleMetaData = this.getModuleMetaData(filename)!;
assert(moduleMetaData != null, `Module not loaded: "${filename}"`);
return moduleMetaData.sourceCode;
}

getJavaScriptSource(filename: ModuleFileName): OutputCode {
let s = this.getOutput(filename);
if (!s) {
s = this.getSource(filename);
}
return s;
}

// TypeScript Language Service and Format Diagnostic Host API

getCanonicalFileName(fileName: string): string {
Expand All @@ -442,7 +448,7 @@ export class Compiler

getScriptKind(fileName: ModuleFileName): ts.ScriptKind {
this._log("getScriptKind()", fileName);
const moduleMetaData = this._getModuleMetaData(fileName);
const moduleMetaData = this.getModuleMetaData(fileName);
if (moduleMetaData) {
switch (moduleMetaData.mediaType) {
case MediaType.TypeScript:
Expand All @@ -461,13 +467,13 @@ export class Compiler

getScriptVersion(fileName: ModuleFileName): string {
this._log("getScriptVersion()", fileName);
const moduleMetaData = this._getModuleMetaData(fileName);
const moduleMetaData = this.getModuleMetaData(fileName);
return (moduleMetaData && moduleMetaData.scriptVersion) || "";
}

getScriptSnapshot(fileName: ModuleFileName): ts.IScriptSnapshot | undefined {
this._log("getScriptSnapshot()", fileName);
return this._getModuleMetaData(fileName);
return this.getModuleMetaData(fileName);
}

getCurrentDirectory(): string {
Expand All @@ -493,7 +499,7 @@ export class Compiler
}

fileExists(fileName: string): boolean {
const moduleMetaData = this._getModuleMetaData(fileName);
const moduleMetaData = this.getModuleMetaData(fileName);
const exists = moduleMetaData != null;
this._log("fileExists()", fileName, exists);
return exists;
Expand Down
6 changes: 6 additions & 0 deletions js/globals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import * as textEncoding from "./text_encoding";
import * as timers from "./timers";
import * as url from "./url";
import * as urlSearchParams from "./url_search_params";
import * as workers from "./workers";

// These imports are not exposed and therefore are fine to just import the
// symbols required.
Expand Down Expand Up @@ -78,3 +79,8 @@ window.TextEncoder = textEncoding.TextEncoder;
export type TextEncoder = textEncoding.TextEncoder;
window.TextDecoder = textEncoding.TextDecoder;
export type TextDecoder = textEncoding.TextDecoder;

window.workerMain = workers.workerMain;
// TODO These shouldn't be available in main isolate.
window.postMessage = workers.postMessage;
window.close = workers.workerClose;
25 changes: 24 additions & 1 deletion js/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright 2018 the Deno authors. All rights reserved. MIT license.
// We need to make sure this module loads, for its side effects.
import "./globals";
import { window } from "./globals";

import * as flatbuffers from "./flatbuffers";
import * as msg from "gen/msg_generated";
Expand All @@ -26,6 +26,29 @@ function sendStart(): msg.StartRes {
return startRes;
}

import { postMessage } from "./workers";
import { TextDecoder, TextEncoder } from "./text_encoding";
import { ModuleSpecifier, ContainingFile } from "./compiler";
type CompilerLookup = { specifier: ModuleSpecifier; referrer: ContainingFile };

function compilerMain() {
// workerMain should have already been called since a compiler is a worker.
const compiler = Compiler.instance();
window.onmessage = function(e: { data: Uint8Array }) {
const json = new TextDecoder().decode(e.data);
const lookup = JSON.parse(json) as CompilerLookup;

const filename = compiler.getFilename(lookup.specifier, lookup.referrer);
const moduleMetaData = compiler.getModuleMetaData(filename);

const responseJson = JSON.stringify(moduleMetaData);
const response = new TextEncoder().encode(responseJson);
console.log("moduleMetaData", responseJson);
postMessage(response);
};
}
window["compilerMain"] = compilerMain;

/* tslint:disable-next-line:no-default-export */
export default function denoMain() {
libdeno.recv(handleAsyncMsgFromRust);
Expand Down
76 changes: 76 additions & 0 deletions js/workers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import * as dispatch from "./dispatch";
import * as msg from "gen/msg_generated";
import * as flatbuffers from "./flatbuffers";
import { libdeno } from "./libdeno";
import { assert, log, setLogDebug } from "./util";
import { globalEval } from "./global_eval";

export async function postMessage(data: Uint8Array): Promise<void> {
const builder = flatbuffers.createBuilder();
msg.WorkerPostMessage.startWorkerPostMessage(builder);
const inner = msg.WorkerPostMessage.endWorkerPostMessage(builder);
const baseRes = await dispatch.sendAsync(
builder,
msg.Any.WorkerPostMessage,
inner,
data
);
assert(baseRes != null);
}

export async function getMessage(): Promise<null | Uint8Array> {
log("getMessage");
// Send CodeFetch message
const builder = flatbuffers.createBuilder();
msg.WorkerGetMessage.startWorkerGetMessage(builder);
const inner = msg.WorkerGetMessage.endWorkerGetMessage(builder);
const baseRes = await dispatch.sendAsync(
builder,
msg.Any.WorkerGetMessage,
inner
);
assert(baseRes != null);
assert(
msg.Any.WorkerGetMessageRes === baseRes!.innerType(),
`base.innerType() unexpectedly is ${baseRes!.innerType()}`
);
const res = new msg.WorkerGetMessageRes();
assert(baseRes!.inner(res) != null);

const dataArray = res.dataArray();
if (dataArray == null) {
return null;
} else {
return new Uint8Array(dataArray!);
}
}

let isClosing = false;

export function workerClose(): void {
isClosing = true;
}

export async function workerMain() {
libdeno.recv(dispatch.handleAsyncMsgFromRust);
setLogDebug(true);
log("workerMain");

// TODO avoid using globalEval to get Window. But circular imports if getting
// it from globals.ts
const window = globalEval("this");

while (!isClosing) {
const data = await getMessage();
if (data == null) {
log("workerMain got null message. quitting.");
break;
}
if (window["onmessage"]) {
const event = { data };
window.onmessage(event);
} else {
break;
}
}
}
159 changes: 159 additions & 0 deletions src/compiler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#![allow(dead_code)]
#![allow(unused_imports)]
#![allow(unused_variables)]

use isolate::Buf;
use isolate::WorkerChannels;
use msg;
use resources;
use resources::add_worker;
use resources::Resource;
use resources::ResourceId;
use workers::spawn_worker;
use workers::Worker;

use futures::Future;
use serde_json;
use std::cell::Cell;
use std::cell::RefCell;
use std::sync::Arc;
use std::sync::Mutex;

lazy_static! {
static ref c_rid: Mutex<Option<ResourceId>> = Mutex::new(None);
}

// This corresponds to JS ModuleMetaData.
// TODO Rename one or the other so they correspond.
#[derive(Debug)]
pub struct CodeFetchOutput {
pub module_name: String,
pub filename: String,
pub media_type: msg::MediaType,
pub source_code: Vec<u8>,
pub maybe_output_code: Option<Vec<u8>>,
pub maybe_source_map: Option<Vec<u8>>,
}

impl CodeFetchOutput {
// TODO Use serde_derive? Use flatbuffers?
fn from_json(json_str: &str) -> Option<Self> {
match serde_json::from_str::<serde_json::Value>(json_str) {
Ok(serde_json::Value::Object(map)) => {
let module_name = match map["moduleId"].as_str() {
None => return None,
Some(s) => s.to_string(),
};

let filename = match map["fileName"].as_str() {
None => return None,
Some(s) => s.to_string(),
};

let source_code = match map["sourceCode"].as_str() {
None => return None,
Some(s) => s.to_string(),
};

let maybe_output_code =
map["outputCode"].as_str().map(|s| s.as_bytes().to_vec());

let maybe_source_map =
map["sourceMap"].as_str().map(|s| s.as_bytes().to_vec());

Some(CodeFetchOutput {
module_name,
filename,
media_type: msg::MediaType::JavaScript, // TODO
source_code: source_code.as_bytes().to_vec(),
maybe_output_code,
maybe_source_map,
})
}
_ => None,
}
}
}

fn lazy_start() -> Resource {
let mut cell = c_rid.lock().unwrap();
let rid = cell.get_or_insert_with(|| {
let (_t, c) = spawn_worker("compilerMain()".to_string());
let resource = add_worker(c);
resource.rid
});
Resource { rid: *rid }
}

fn stop() {
/*
if let Some(rid) = c_rid.lock().unwrap() {
println!("kill compiler");
}
*/
}

fn req(specifier: &str, referrer: &str) -> Buf {
String::from(format!(
r#"{{"specifier": "{}", "referrer": "{}"}}"#,
specifier, referrer
)).into_boxed_str()
.into_boxed_bytes()
}

fn compile_sync(specifier: &str, referrer: &str) -> Option<CodeFetchOutput> {
let req_msg = req(specifier, referrer);

let compiler = lazy_start();

let send_future = resources::worker_post_message(compiler.rid, req_msg);
send_future.wait().unwrap();

let recv_future = resources::worker_recv_message(compiler.rid);
let res_msg = recv_future.wait().unwrap().unwrap();

let res_json = std::str::from_utf8(&res_msg).unwrap();
CodeFetchOutput::from_json(res_json)
}

#[cfg(test)]
mod tests {
use super::*;
use futures::Sink;

#[test]
fn test_compile_sync() {
let cwd = std::env::current_dir().unwrap();
let cwd_string = cwd.to_str().unwrap().to_owned();

let specifier = "./tests/002_hello.ts";
let referrer = cwd_string + "/";

let cfo = compile_sync(specifier, &referrer);
println!("compile_sync {:?}", cfo);

stop();
}

#[test]
fn code_fetch_output_from_json() {
let json = r#"{
"moduleId":"/Users/rld/src/deno/tests/002_hello.ts",
"fileName":"/Users/rld/src/deno/tests/002_hello.ts",
"mediaType":1,
"sourceCode":"console.log(\"Hello World\");\n",
"outputCode":"yyy",
"sourceMap":"xxx",
"scriptVersion":"1"
}"#;
let actual = CodeFetchOutput::from_json(json).unwrap();
assert_eq!(actual.filename, "/Users/rld/src/deno/tests/002_hello.ts");
assert_eq!(actual.module_name, "/Users/rld/src/deno/tests/002_hello.ts");
assert_eq!(
actual.source_code,
"console.log(\"Hello World\");\n".as_bytes().to_vec()
);
assert_eq!(actual.maybe_output_code, Some("yyy".as_bytes().to_vec()));
assert_eq!(actual.maybe_source_map, Some("xxx".as_bytes().to_vec()));
}
}
Loading

0 comments on commit f6e67ae

Please sign in to comment.