Skip to content

Commit

Permalink
Improve webworker architecture (#98)
Browse files Browse the repository at this point in the history
- Move script evaluation into worker, to avoid blocking the UI thread
- Add evaluation limits to `Engine`
- Use transfers to move buffers (instead of copying them)
  • Loading branch information
mkeeter authored Apr 29, 2024
1 parent 814f095 commit 95221ca
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 27 deletions.
11 changes: 11 additions & 0 deletions fidget/src/rhai/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,17 @@ impl Engine {
Self { engine, context }
}

/// Sets the operation limit (e.g. for untrusted scripts)
pub fn set_limit(&mut self, limit: u64) {
self.engine.on_progress(move |count| {
if count > limit {
Some("script runtime exceeded".into())
} else {
None
}
});
}

/// Executes a full script
pub fn run(&mut self, script: &str) -> Result<ScriptContext, Error> {
self.context.lock().unwrap().clear();
Expand Down
42 changes: 18 additions & 24 deletions wasm-demo/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import { defaultKeymap } from "@codemirror/commands";

import {
ResponseKind,
ScriptRequest,
ShapeRequest,
StartRequest,
WorkerResponse,
ScriptResponse,
WorkerRequest,
WorkerResponse,
} from "./message";

import { RENDER_SIZE, WORKERS_PER_SIDE, WORKER_COUNT } from "./constants";
Expand Down Expand Up @@ -54,28 +56,7 @@ class App {
}

onScriptChanged(text: string) {
let shape = null;
let result = "Ok(..)";
try {
shape = fidget.eval_script(text);
} catch (error) {
// Do some string formatting to make errors cleaner
result = error
.toString()
.replace("Rhai evaluation error: ", "Rhai evaluation error:\n")
.replace(" (line ", "\n(line ")
.replace(" (expecting ", "\n(expecting ");
}
this.output.setText(result);

if (shape) {
const tape = fidget.serialize_into_tape(shape);
this.start_time = performance.now();
this.workers_done = 0;
this.workers.forEach((w) => {
w.postMessage(new ShapeRequest(tape));
});
}
this.workers[0].postMessage(new ScriptRequest(text));
}

onWorkerMessage(i: number, req: WorkerResponse) {
Expand Down Expand Up @@ -103,6 +84,19 @@ class App {
}
break;
}
case ResponseKind.Script: {
let r = req as ScriptResponse;
this.output.setText(r.output);
if (r.tape) {
this.start_time = performance.now();
this.workers_done = 0;
this.workers.forEach((w) => {
w.postMessage(new ShapeRequest(r.tape));
});
}
break;
}

default: {
console.error(`unknown worker req ${req}`);
}
Expand Down Expand Up @@ -131,7 +125,7 @@ class Editor {
window.clearTimeout(this.timeout);
}
const text = v.state.doc.toString();
this.timeout = window.setTimeout(() => cb(text), 500);
this.timeout = window.setTimeout(() => cb(text), 250);
}
}),
],
Expand Down
28 changes: 26 additions & 2 deletions wasm-demo/message.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
export enum RequestKind {
Script,
Start,
Shape,
}

export class ScriptRequest {
kind: RequestKind.Script;
script: string;

constructor(script: string) {
this.script = script;
this.kind = RequestKind.Script;
}
}

export class StartRequest {
kind: RequestKind.Start;
index: number;
Expand All @@ -23,12 +34,13 @@ export class ShapeRequest {
}
}

export type WorkerRequest = ShapeRequest | StartRequest;
export type WorkerRequest = ScriptRequest | ShapeRequest | StartRequest;

////////////////////////////////////////////////////////////////////////////////

export enum ResponseKind {
Started,
Script,
Image,
}

Expand All @@ -40,6 +52,18 @@ export class StartedResponse {
}
}

export class ScriptResponse {
kind: ResponseKind.Script;
output: string;
tape: Uint8Array | null;

constructor(output: string, tape: Uint8Array | null) {
this.output = output;
this.tape = tape;
this.kind = ResponseKind.Script;
}
}

export class ImageResponse {
kind: ResponseKind.Image;
data: Uint8Array;
Expand All @@ -50,4 +74,4 @@ export class ImageResponse {
}
}

export type WorkerResponse = StartedResponse | ImageResponse;
export type WorkerResponse = StartedResponse | ScriptResponse | ImageResponse;
1 change: 1 addition & 0 deletions wasm-demo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub struct JsVmShape(VmShape);
#[wasm_bindgen]
pub fn eval_script(s: &str) -> Result<JsTree, String> {
let mut engine = fidget::rhai::Engine::new();
engine.set_limit(50_000); // ¯\_(ツ)_/¯
let out = engine.eval(s);
out.map(JsTree).map_err(|e| format!("{e}"))
}
Expand Down
33 changes: 32 additions & 1 deletion wasm-demo/worker.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {
ImageResponse,
RequestKind,
ScriptRequest,
ScriptResponse,
ShapeRequest,
StartRequest,
StartedResponse,
Expand All @@ -27,7 +29,32 @@ class Worker {
this.index,
WORKERS_PER_SIDE,
);
postMessage(new ImageResponse(out));
postMessage(new ImageResponse(out), { transfer: [out.buffer] });
}

run(s: ScriptRequest) {
let shape = null;
let result = "Ok(..)";
try {
shape = fidget.eval_script(s.script);
} catch (error) {
// Do some string formatting to make errors cleaner
result = error
.toString()
.replace("Rhai evaluation error: ", "Rhai evaluation error:\n")
.replace(" (line ", "\n(line ")
.replace(" (expecting ", "\n(expecting ");
}

let tape = null;
if (shape) {
tape = fidget.serialize_into_tape(shape);
postMessage(new ScriptResponse(result, tape), {
transfer: [tape.buffer],
});
} else {
postMessage(new ScriptResponse(result, tape));
}
}
}

Expand All @@ -46,6 +73,10 @@ async function run() {
worker!.render(req as ShapeRequest);
break;
}
case RequestKind.Script: {
worker!.run(req as ScriptRequest);
break;
}
default:
console.error(`unknown worker request ${req}`);
}
Expand Down

0 comments on commit 95221ca

Please sign in to comment.