diff --git a/Cargo.lock b/Cargo.lock index fd9bea5d..0f0d5b29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -893,6 +893,7 @@ dependencies = [ "num-traits", "ordered-float", "rhai", + "serde", "static_assertions", "thiserror", "windows", diff --git a/fidget/Cargo.toml b/fidget/Cargo.toml index 49529afc..e5979c3f 100644 --- a/fidget/Cargo.toml +++ b/fidget/Cargo.toml @@ -20,6 +20,7 @@ ordered-float = "3" static_assertions = "1" thiserror = "1" workspace-hack = { version = "0.1", path = "../workspace-hack" } +serde = { version = "1.0", features = ["derive"] } # JIT dynasmrt = { version = "2.0", optional = true } diff --git a/fidget/src/core/compiler/op.rs b/fidget/src/core/compiler/op.rs index 309f9359..193fb1dd 100644 --- a/fidget/src/core/compiler/op.rs +++ b/fidget/src/core/compiler/op.rs @@ -1,3 +1,5 @@ +use serde::{Deserialize, Serialize}; + /// Macro to generate a set of opcodes, using the given type for registers macro_rules! opcodes { ( @@ -139,7 +141,7 @@ opcodes!( /// - RHS register (or immediate for `*Imm`) /// /// Each "register" represents an SSA slot, which is never reused. - #[derive(Copy, Clone, Debug)] + #[derive(Copy, Clone, Debug, Serialize, Deserialize)] pub enum SsaOp { // default variants } @@ -250,7 +252,7 @@ opcodes!( /// /// We have a maximum of 256 registers, though some tapes (e.g. ones /// targeting physical hardware) may choose to use fewer. - #[derive(Copy, Clone, Debug, PartialEq)] + #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum RegOp { // default variants /// Read from a memory slot to a register diff --git a/fidget/src/core/compiler/reg_tape.rs b/fidget/src/core/compiler/reg_tape.rs index aace91f3..4887019b 100644 --- a/fidget/src/core/compiler/reg_tape.rs +++ b/fidget/src/core/compiler/reg_tape.rs @@ -1,9 +1,10 @@ //! Tape used for evaluation use crate::compiler::{RegOp, RegisterAllocator, SsaTape}; +use serde::{Deserialize, Serialize}; /// Low-level tape for use with the Fidget virtual machine (or to be lowered /// further into machine instructions). -#[derive(Clone, Default)] +#[derive(Clone, Default, Serialize, Deserialize)] pub struct RegTape { tape: Vec, diff --git a/fidget/src/core/compiler/ssa_tape.rs b/fidget/src/core/compiler/ssa_tape.rs index bf5a2f66..48535133 100644 --- a/fidget/src/core/compiler/ssa_tape.rs +++ b/fidget/src/core/compiler/ssa_tape.rs @@ -4,6 +4,7 @@ use crate::{ context::{BinaryOpcode, Node, Op, UnaryOpcode}, Context, Error, }; +use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; @@ -16,7 +17,7 @@ use std::collections::{HashMap, HashSet}; /// - 4-byte RHS register (or immediate `f32`) /// /// All register addressing is absolute. -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct SsaTape { /// The tape is stored in reverse order, such that the root of the tree is /// the first item in the tape. diff --git a/fidget/src/core/vm/data.rs b/fidget/src/core/vm/data.rs index 00148763..c5b12ab1 100644 --- a/fidget/src/core/vm/data.rs +++ b/fidget/src/core/vm/data.rs @@ -57,7 +57,7 @@ use crate::{ /// Despite this peek at its internals, users are unlikely to touch `VmData` /// directly; a [`VmShape`](crate::vm::VmShape) wraps the `VmData` and /// implements our common traits. -#[derive(Default)] +#[derive(Default, serde::Serialize, serde::Deserialize)] pub struct VmData { ssa: SsaTape, asm: RegTape, diff --git a/fidget/src/core/vm/mod.rs b/fidget/src/core/vm/mod.rs index 5518e73d..b82d5a6c 100644 --- a/fidget/src/core/vm/mod.rs +++ b/fidget/src/core/vm/mod.rs @@ -94,6 +94,12 @@ impl AsRef<[Choice]> for VmTrace { #[derive(Clone)] pub struct GenericVmShape(Arc>); +impl From> for GenericVmShape { + fn from(d: VmData) -> Self { + Self(d.into()) + } +} + impl GenericVmShape { pub(crate) fn simplify_inner( &self, diff --git a/wasm-demo/Cargo.lock b/wasm-demo/Cargo.lock index 587ef619..2abf5889 100644 --- a/wasm-demo/Cargo.lock +++ b/wasm-demo/Cargo.lock @@ -100,6 +100,15 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "2.5.0" @@ -259,6 +268,7 @@ dependencies = [ "num-traits", "ordered-float", "rhai", + "serde", "static_assertions", "thiserror", "windows", @@ -269,6 +279,7 @@ dependencies = [ name = "fidget-wasm-demo" version = "0.1.0" dependencies = [ + "bincode", "fidget", "nalgebra", "rhai", @@ -543,6 +554,26 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "serde" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.59", +] + [[package]] name = "simba" version = "0.7.3" diff --git a/wasm-demo/Cargo.toml b/wasm-demo/Cargo.toml index 749f5c1b..fa231c85 100644 --- a/wasm-demo/Cargo.toml +++ b/wasm-demo/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] +bincode = "1.3.3" fidget = {path = "../fidget", default-features = false, features = ["rhai", "mesh", "render"]} wasm-bindgen = "0.2.92" nalgebra = "0.31" diff --git a/wasm-demo/index.ts b/wasm-demo/index.ts index dce4e323..9d13f659 100644 --- a/wasm-demo/index.ts +++ b/wasm-demo/index.ts @@ -6,7 +6,7 @@ import { defaultKeymap } from "@codemirror/commands"; import { ResponseKind, - ScriptRequest, + ShapeRequest, StartRequest, WorkerResponse, WorkerRequest, @@ -58,12 +58,13 @@ class App { let result = null; try { const shapeTree = fidget.eval_script(text); + const tape = fidget.serialize_into_tape(shapeTree); result = "Ok(..)"; this.start_time = performance.now(); this.workers_done = 0; this.workers.forEach((w) => { - w.postMessage(new ScriptRequest(text)); + w.postMessage(new ShapeRequest(tape)); }); } catch (error) { // Do some string formatting to make errors cleaner diff --git a/wasm-demo/message.ts b/wasm-demo/message.ts index 70f04dbc..6b0182b1 100644 --- a/wasm-demo/message.ts +++ b/wasm-demo/message.ts @@ -1,6 +1,6 @@ export enum RequestKind { Start, - Script, + Shape, } export class StartRequest { @@ -13,17 +13,17 @@ export class StartRequest { } } -export class ScriptRequest { - kind: RequestKind.Script; - script: string; +export class ShapeRequest { + kind: RequestKind.Shape; + tape: Uint8Array; - constructor(s: string) { - this.script = s; - this.kind = RequestKind.Script; + constructor(tape: Uint8Array) { + this.tape = tape; + this.kind = RequestKind.Shape; } } -export type WorkerRequest = ScriptRequest | StartRequest; +export type WorkerRequest = ShapeRequest | StartRequest; //////////////////////////////////////////////////////////////////////////////// diff --git a/wasm-demo/src/lib.rs b/wasm-demo/src/lib.rs index 566f2b93..63d4e2e0 100644 --- a/wasm-demo/src/lib.rs +++ b/wasm-demo/src/lib.rs @@ -3,7 +3,7 @@ use fidget::{ eval::MathShape, render::{BitRenderMode, RenderConfig}, shape::Bounds, - vm::VmShape, + vm::{VmData, VmShape}, Error, }; use wasm_bindgen::prelude::*; @@ -12,6 +12,10 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen] pub struct JsTree(Tree); +#[derive(Clone)] +#[wasm_bindgen] +pub struct JsVmShape(VmShape); + #[wasm_bindgen] pub fn eval_script(s: &str) -> Result { let mut engine = fidget::rhai::Engine::new(); @@ -19,28 +23,21 @@ pub fn eval_script(s: &str) -> Result { out.map(JsTree).map_err(|e| format!("{e}")) } +/// Serializes a `JsTree` into a `bincode`-packed `VmData` #[wasm_bindgen] -pub fn render(t: JsTree, image_size: usize) -> Result, String> { - fn inner(t: Tree, image_size: usize) -> Result, Error> { - let mut ctx = Context::new(); - let root = ctx.import(&t); - - let cfg = RenderConfig::<2> { - image_size, - ..RenderConfig::default() - }; +pub fn serialize_into_tape(t: JsTree) -> Result, String> { + let mut ctx = Context::new(); + let root = ctx.import(&t.0); + let shape = VmShape::new(&ctx, root).map_err(|e| format!("{e}"))?; + bincode::serialize(shape.data()).map_err(|e| format!("{e}")) +} - let shape = VmShape::new(&ctx, root)?; - let out = cfg.run(shape, &BitRenderMode)?; - Ok(out - .into_iter() - .flat_map(|b| { - let b = b as u8 * u8::MAX; - [b, b, b, 255] - }) - .collect()) - } - inner(t.0, image_size).map_err(|e| format!("{e}")) +/// Deserialize a `bincode`-packed `VmData` into a `VmShape` +#[wasm_bindgen] +pub fn deserialize_tape(data: Vec) -> Result { + let d: VmData<255> = + bincode::deserialize(&data).map_err(|e| format!("{e}"))?; + Ok(JsVmShape(VmShape::from(d))) } /// Renders a subregion of an image, for webworker-based multithreading @@ -49,7 +46,7 @@ pub fn render(t: JsTree, image_size: usize) -> Result, String> { /// into `0 <= pos < workers_per_side^2` tiles. #[wasm_bindgen] pub fn render_region( - t: JsTree, + shape: JsVmShape, image_size: usize, index: usize, workers_per_side: usize, @@ -63,14 +60,11 @@ pub fn render_region( ); } fn inner( - t: Tree, + shape: VmShape, image_size: usize, index: usize, workers_per_side: usize, ) -> Result, Error> { - let mut ctx = Context::new(); - let root = ctx.import(&t); - // Corner position in [0, workers_per_side] coordinates let mut corner = nalgebra::Vector2::new( index / workers_per_side, @@ -95,7 +89,6 @@ pub fn render_region( ..RenderConfig::default() }; - let shape = VmShape::new(&ctx, root)?; let out = cfg.run(shape, &BitRenderMode)?; Ok(out .into_iter() @@ -105,5 +98,6 @@ pub fn render_region( }) .collect()) } - inner(t.0, image_size, index, workers_per_side).map_err(|e| format!("{e}")) + inner(shape.0, image_size, index, workers_per_side) + .map_err(|e| format!("{e}")) } diff --git a/wasm-demo/worker.ts b/wasm-demo/worker.ts index b1015557..2fabb8a2 100644 --- a/wasm-demo/worker.ts +++ b/wasm-demo/worker.ts @@ -1,7 +1,7 @@ import { ImageResponse, RequestKind, - ScriptRequest, + ShapeRequest, StartRequest, StartedResponse, WorkerRequest, @@ -19,10 +19,13 @@ class Worker { this.index = req.index; } - render(s: ScriptRequest) { - const tree = fidget.eval_script(s.script); + render(s: ShapeRequest) { + let start = performance.now(); + const shape = fidget.deserialize_tape(s.tape); + let end = performance.now(); + console.log(`deserialized in ${end - start} ms`); const out = fidget.render_region( - tree, + shape, RENDER_SIZE, this.index, WORKERS_PER_SIDE, @@ -42,8 +45,8 @@ async function run() { worker = new Worker(req as StartRequest); break; } - case RequestKind.Script: { - worker!.render(req as ScriptRequest); + case RequestKind.Shape: { + worker!.render(req as ShapeRequest); break; } default: