diff --git a/.gitignore b/.gitignore index 5577d80..289cfa6 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ Cargo.lock *.pdb node_modules/ +dist/ diff --git a/simulator/Cargo.toml b/simulator/Cargo.toml index 7465d0c..3a40e55 100644 --- a/simulator/Cargo.toml +++ b/simulator/Cargo.toml @@ -15,7 +15,9 @@ crate-type = ["cdylib", "rlib"] default = ["console_error_panic_hook"] [dependencies] +serde = { version = "1.0", features = ["derive"] } wasm-bindgen = "0.2.63" +serde-wasm-bindgen = "0.4" # The `console_error_panic_hook` crate provides better debugging of panics by # logging them with `console.error`. This is great for development, but requires diff --git a/simulator/src/lib.rs b/simulator/src/lib.rs index 3c86bec..48ee07e 100644 --- a/simulator/src/lib.rs +++ b/simulator/src/lib.rs @@ -1,3 +1,6 @@ +pub mod program; +pub mod runner; + use wasm_bindgen::prelude::*; // When the `wee_alloc` feature is enabled, use `wee_alloc` as the global diff --git a/simulator/src/program.rs b/simulator/src/program.rs new file mode 100644 index 0000000..689fefc --- /dev/null +++ b/simulator/src/program.rs @@ -0,0 +1,85 @@ +use serde::{Serialize, Deserialize}; +use wasm_bindgen::prelude::*; +use serde_wasm_bindgen::{to_value, from_value}; + +pub mod instruction; +use instruction::*; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[wasm_bindgen] +pub struct Node { + pub x: f32, + pub y: f32, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[wasm_bindgen] +pub struct Program { + nodes: Vec, + instructions: Vec +} + +impl Program { + pub fn new(nodes: Vec, instructions: Vec) -> Option { + if instructions.len() == 0 { + return None + } + + Some(Program { + nodes, + instructions, + }) + } + + pub fn nodes(&self) -> &Vec { + &self.nodes + } + + pub fn instructions(&self) -> &Vec { + &self.instructions + } +} + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "Node[]")] + pub type NodeArray; + + #[wasm_bindgen(typescript_type = "Instruction[]")] + pub type InstructionArray; + + #[wasm_bindgen(typescript_type = "Error")] + pub type IError; +} + +impl IError { + pub fn from_err(err: E) -> IError where JsValue: From { + IError::from(JsValue::from(err)) + } +} + +#[wasm_bindgen] +impl Program { + pub fn from(nodes: NodeArray, instructions: InstructionArray) -> Result { + let nodes: Vec = from_value(nodes.into()).map_err(IError::from_err)?; + let instructions: Vec = from_value(instructions.into()).map_err(IError::from_err)?; + + Program::new(nodes, instructions).ok_or_else(|| { + unimplemented!(); + }) + } + + pub fn empty() -> Program { + Self::new(vec![], vec![Instruction::noop()]).unwrap() + } + + #[wasm_bindgen(getter = nodes)] + pub fn nodes_js(&self) -> JsValue { + to_value(&self.nodes).unwrap() + } + + #[wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> JsValue { + to_value(&self).unwrap() + } +} diff --git a/simulator/src/program/instruction.rs b/simulator/src/program/instruction.rs new file mode 100644 index 0000000..dff556a --- /dev/null +++ b/simulator/src/program/instruction.rs @@ -0,0 +1,30 @@ +use serde::{Serialize, Deserialize}; +use wasm_bindgen::prelude::*; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum Action { + Noop, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Instruction { + pub next: usize, + pub action: Action +} + +impl Instruction { + pub fn noop() -> Instruction { + Instruction { + next: 0, + action: Action::Noop + } + } +} + +#[wasm_bindgen(typescript_custom_section)] +const TS_INSTRUCTION: &'static str = r#" +export interface Instruction { + next: number; + action: "Noop"; +} +"#; diff --git a/simulator/src/runner.rs b/simulator/src/runner.rs new file mode 100644 index 0000000..b547895 --- /dev/null +++ b/simulator/src/runner.rs @@ -0,0 +1,22 @@ +use wasm_bindgen::prelude::*; +use crate::program::Program; + +#[wasm_bindgen] +pub struct Runner { + program: Program, + + width: usize, + height: usize, +} + +#[wasm_bindgen] +impl Runner { + #[wasm_bindgen(constructor)] + pub fn new(program: Program, width: usize, height: usize) -> Runner { + Runner { + program, + width, + height + } + } +} diff --git a/src/App.tsx b/src/App.tsx index c701cec..31897e4 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,12 @@ +import { Program } from "chaos-toy-rs"; +import * as instruction from "./instruction"; import type { Component } from "solid-js"; import Simulator from "./Simulator"; const App: Component = () => { return ( <> - + ); }; diff --git a/src/Simulator.tsx b/src/Simulator.tsx index 727d201..75064f3 100644 --- a/src/Simulator.tsx +++ b/src/Simulator.tsx @@ -1,11 +1,71 @@ -import { hello_world } from "chaos-toy-rs"; -import type { Component } from "solid-js"; - -const Simulator: Component = () => { - return (<> -
{hello_world()}
- Sorry, your browser needs to support canvases - ); +import { Program, Node, Instruction, Runner } from "chaos-toy-rs"; +import { batch, Component, createEffect, createSignal, onCleanup } from "solid-js"; + +const Simulator: Component<{nodes: Node[], instructions: Instruction[]}> = (props) => { + const [getRunner, setRunner] = createSignal(); + const [getWidth, setWidth] = createSignal(100); + const [getHeight, setHeight] = createSignal(100); + let canvas: HTMLCanvasElement; + let parent: HTMLDivElement; + let ctx: CanvasRenderingContext2D; + + createEffect((prev) => { + if (prev) { + prev.free(); + } + // TODO: avoid re-creating a program when nodes and instructions are the same + const program = Program.from(props.nodes, props.instructions); + const runner = new Runner(program, getWidth(), getHeight()); + setRunner(runner); + + return runner; + }); + + function resizeListener() { + batch(() => { + const dpr = window.devicePixelRatio ?? 1; + if (parent.clientWidth * dpr !== getWidth()) { + setWidth(Math.ceil(parent.clientWidth * dpr)); + } + if (parent.clientHeight * dpr !== getHeight()) { + setHeight(Math.ceil(parent.clientHeight * dpr)); + } + }); + } + + function draw() { + const width = getWidth(); + const height = getHeight(); + ctx.fillStyle = "gray"; + ctx.fillRect(0, 0, width, height); + } + + createEffect(() => { + window.addEventListener("resize", resizeListener); + resizeListener(); + ctx = canvas.getContext("2d") ?? (() => { + throw new Error("Couldn't create canvas context"); + })(); + draw(); + + onCleanup(() => window.removeEventListener("resize", resizeListener)); + }); + + return (
+ Sorry, your browser needs to support canvases +
); }; export default Simulator; diff --git a/src/instruction.ts b/src/instruction.ts new file mode 100644 index 0000000..53e4ce4 --- /dev/null +++ b/src/instruction.ts @@ -0,0 +1,8 @@ +import {Instruction} from "chaos-toy-rs"; + +export function noop(): Instruction { + return { + next: 0, + action: "Noop" + }; +} diff --git a/tsconfig.json b/tsconfig.json index 9b0a619..89e6987 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ "jsx": "preserve", "jsxImportSource": "solid-js", "types": ["vite/client"], + "strict": true, "noEmit": true, "isolatedModules": true }