Scaffolding for programs and runners

main
Shad Amethyst 1 year ago
parent 4a4824e268
commit b6f3a2b858

1
.gitignore vendored

@ -15,3 +15,4 @@ Cargo.lock
*.pdb
node_modules/
dist/

@ -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

@ -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

@ -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<Node>,
instructions: Vec<Instruction>
}
impl Program {
pub fn new(nodes: Vec<Node>, instructions: Vec<Instruction>) -> Option<Program> {
if instructions.len() == 0 {
return None
}
Some(Program {
nodes,
instructions,
})
}
pub fn nodes(&self) -> &Vec<Node> {
&self.nodes
}
pub fn instructions(&self) -> &Vec<Instruction> {
&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<E>(err: E) -> IError where JsValue: From<E> {
IError::from(JsValue::from(err))
}
}
#[wasm_bindgen]
impl Program {
pub fn from(nodes: NodeArray, instructions: InstructionArray) -> Result<Program, IError> {
let nodes: Vec<Node> = from_value(nodes.into()).map_err(IError::from_err)?;
let instructions: Vec<Instruction> = 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()
}
}

@ -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";
}
"#;

@ -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
}
}
}

@ -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 (
<>
<Simulator />
<Simulator nodes={[]} instructions={[instruction.noop()]} />
</>
);
};

@ -1,11 +1,71 @@
import { hello_world } from "chaos-toy-rs";
import type { Component } from "solid-js";
const Simulator: Component = () => {
return (<>
<div>{hello_world()}</div>
<canvas>Sorry, your browser needs to support canvases</canvas>
</>);
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<Runner>();
const [getWidth, setWidth] = createSignal<number>(100);
const [getHeight, setHeight] = createSignal<number>(100);
let canvas: HTMLCanvasElement;
let parent: HTMLDivElement;
let ctx: CanvasRenderingContext2D;
createEffect<Runner>((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 (<div ref={parent!} style={{
width: "100vw",
height: "100vh",
overflow: "hidden",
}}>
<canvas
ref={canvas!}
width={getWidth()}
height={getHeight()}
style={{
width: "100%",
height: "100%",
}}
>Sorry, your browser needs to support canvases</canvas>
</div>);
};
export default Simulator;

@ -0,0 +1,8 @@
import {Instruction} from "chaos-toy-rs";
export function noop(): Instruction {
return {
next: 0,
action: "Noop"
};
}

@ -8,6 +8,7 @@
"jsx": "preserve",
"jsxImportSource": "solid-js",
"types": ["vite/client"],
"strict": true,
"noEmit": true,
"isolatedModules": true
}

Loading…
Cancel
Save