Skip to content

Commit 0930b6b

Browse files
committed
Implement running a processor and rendering a display from JS
1 parent 40adc34 commit 0930b6b

25 files changed

+416
-211
lines changed

.prettierrc

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
{
2-
"tabWidth": 2,
2+
"tabWidth": 4,
33
"overrides": [
44
{
5-
"files": ["*.json"],
5+
"files": ["*.html"],
66
"options": {
7-
"tabWidth": 4
7+
"tabWidth": 2
88
}
99
}
1010
],
11+
"importOrder": ["mindy-website", "^[./]"],
12+
"importOrderSeparation": true,
1113
"plugins": ["@trivago/prettier-plugin-sort-imports"]
1214
}

Cargo.lock

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ rand = { version = "0.9.2", optional = true }
5353
regex = { version = "1.11.1", optional = true }
5454
serde_json = { version = "1.0.141", optional = true }
5555

56+
# wasm
57+
wasm-bindgen = { version = "0.2.100", optional = true }
58+
5659
# mlog
5760
clap = { version = "4.5.42", features = ["derive"], optional = true }
5861
clap-stdin = { version = "0.6.0", optional = true }
@@ -110,6 +113,10 @@ std = [
110113
"strum/std",
111114
"thiserror/std",
112115
]
116+
wasm = [
117+
"dep:wasm-bindgen",
118+
]
119+
113120
mlog = [
114121
"std",
115122
"dep:clap",

mindy-website/Cargo.toml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,17 @@ crate-type = ["cdylib"]
88

99
[dependencies]
1010
console_error_panic_hook = "0.1.7"
11+
embedded-graphics = "0.8.1"
12+
embedded-graphics-web-simulator = "0.4.0"
1113
getrandom = { version = "0.3.3", features = ["wasm_js"] }
12-
mindy = { path = "..", features = ["embedded_graphics"] }
14+
js-sys = "0.3.77"
15+
mindy = { path = "..", features = ["embedded_graphics", "wasm"] }
1316
wasm-bindgen = "0.2.100"
1417
wee_alloc = "0.4.5"
18+
19+
[dependencies.web-sys]
20+
version = "0.3"
21+
features = [
22+
"console",
23+
"Element",
24+
]

mindy-website/src/lib.rs

Lines changed: 194 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,34 @@
1+
#![allow(clippy::boxed_local)]
2+
3+
use std::time::Duration;
4+
5+
use embedded_graphics::pixelcolor::Rgb888;
6+
use embedded_graphics_web_simulator::{
7+
display::WebSimulatorDisplay, output_settings::OutputSettings,
8+
};
9+
use mindy::{
10+
parser::LogicParser,
11+
types::{PackedPoint2, ProcessorConfig, ProcessorLinkConfig, content},
12+
vm::{
13+
Building, BuildingData, EmbeddedDisplayData, InstructionResult, LVar, LogicVM,
14+
LogicVMBuilder,
15+
buildings::{HYPER_PROCESSOR, LOGIC_PROCESSOR, MICRO_PROCESSOR, WORLD_PROCESSOR},
16+
variables::Constants,
17+
},
18+
};
119
use wasm_bindgen::prelude::*;
20+
use web_sys::Element;
221

322
#[global_allocator]
423
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
524

6-
#[wasm_bindgen]
7-
extern "C" {
8-
fn alert(s: &str);
25+
const MAX_DELTA: f64 = 6.;
26+
27+
#[allow(unused_macros)]
28+
macro_rules! log {
29+
($($t:tt)*) => {
30+
web_sys::console::log_1(&format!($($t)*).into());
31+
};
932
}
1033

1134
#[wasm_bindgen]
@@ -14,6 +37,172 @@ pub fn init() {
1437
}
1538

1639
#[wasm_bindgen]
17-
pub fn greet() {
18-
alert("Hello world!");
40+
pub struct WebLogicVM {
41+
vm: LogicVM,
42+
globals: Constants,
43+
logic_parser: LogicParser,
44+
prev_timestamp: Option<f64>,
45+
}
46+
47+
impl WebLogicVM {
48+
fn new(vm: LogicVM, globals: Constants) -> Self {
49+
Self {
50+
vm,
51+
globals,
52+
logic_parser: LogicParser::new(),
53+
prev_timestamp: None,
54+
}
55+
}
56+
}
57+
58+
#[wasm_bindgen]
59+
impl WebLogicVM {
60+
pub fn do_tick(&mut self, timestamp: f64) {
61+
// convert to seconds
62+
let timestamp = timestamp / 1000.;
63+
64+
// nominal 60 ticks per second
65+
let delta = match self.prev_timestamp {
66+
Some(prev_timestamp) => (timestamp - prev_timestamp) * 60.,
67+
None => 1.,
68+
}
69+
.min(MAX_DELTA);
70+
71+
self.prev_timestamp = Some(timestamp);
72+
self.vm
73+
.do_tick_with_delta(Duration::from_secs_f64(timestamp), delta);
74+
}
75+
76+
#[wasm_bindgen(getter)]
77+
pub fn time(&self) -> f64 {
78+
self.vm.time().as_secs_f64()
79+
}
80+
81+
pub fn set_processor_config(
82+
&mut self,
83+
position: PackedPoint2,
84+
code: &str,
85+
links: Option<Box<[PackedPoint2]>>,
86+
) -> Result<(), String> {
87+
let ast = self.logic_parser.parse(code).map_err(|e| e.to_string())?;
88+
89+
let building = self
90+
.vm
91+
.building(position)
92+
.ok_or_else(|| format!("building does not exist: {position}"))?;
93+
94+
let BuildingData::Processor(processor) = &mut *building.data.borrow_mut() else {
95+
return Err(format!(
96+
"expected processor at {position} but got {}",
97+
building.block.name
98+
));
99+
};
100+
101+
processor
102+
.update_config(
103+
ast,
104+
links
105+
.map(|v| {
106+
v.iter()
107+
.map(|p| {
108+
ProcessorLinkConfig::unnamed(p.x - position.x, p.y - position.y)
109+
})
110+
.collect::<Vec<_>>()
111+
})
112+
.as_deref(),
113+
&self.vm,
114+
building,
115+
&self.globals,
116+
)
117+
.map_err(|e| e.to_string())
118+
}
119+
}
120+
121+
#[wasm_bindgen]
122+
pub struct WebLogicVMBuilder {
123+
builder: LogicVMBuilder,
124+
}
125+
126+
#[wasm_bindgen]
127+
impl WebLogicVMBuilder {
128+
#[wasm_bindgen(constructor)]
129+
pub fn new() -> Self {
130+
Self {
131+
builder: LogicVMBuilder::new(),
132+
}
133+
}
134+
135+
pub fn add_processor(&mut self, position: PackedPoint2, kind: ProcessorKind) {
136+
self.builder.add_building(
137+
Building::from_processor_config(
138+
kind.name(),
139+
position,
140+
&ProcessorConfig::default(),
141+
&self.builder,
142+
)
143+
.expect("failed to create processor"),
144+
);
145+
}
146+
147+
pub fn add_display(
148+
&mut self,
149+
position: PackedPoint2,
150+
width: u32,
151+
height: u32,
152+
parent: &Element,
153+
) {
154+
let display = WebSimulatorDisplay::<Rgb888>::new(
155+
(width, height),
156+
&OutputSettings::default(),
157+
Some(parent),
158+
);
159+
160+
let display_data = EmbeddedDisplayData::new(
161+
display,
162+
Some(Box::new(|display| {
163+
display.flush().expect("failed to flush display");
164+
InstructionResult::Yield
165+
})),
166+
)
167+
.expect("failed to initialize display");
168+
169+
self.builder.add_building(Building::new(
170+
content::blocks::FROM_NAME["tile-logic-display"],
171+
position,
172+
display_data.into(),
173+
));
174+
}
175+
176+
pub fn build(self) -> Result<WebLogicVM, String> {
177+
let globals = LVar::create_global_constants();
178+
match self.builder.build_with_globals(&globals) {
179+
Ok(vm) => Ok(WebLogicVM::new(vm, globals)),
180+
Err(e) => Err(e.to_string()),
181+
}
182+
}
183+
}
184+
185+
impl Default for WebLogicVMBuilder {
186+
fn default() -> Self {
187+
Self::new()
188+
}
189+
}
190+
191+
#[wasm_bindgen]
192+
pub enum ProcessorKind {
193+
Micro,
194+
Logic,
195+
Hyper,
196+
World,
197+
}
198+
199+
impl ProcessorKind {
200+
fn name(&self) -> &str {
201+
match self {
202+
Self::Micro => MICRO_PROCESSOR,
203+
Self::Logic => LOGIC_PROCESSOR,
204+
Self::Hyper => HYPER_PROCESSOR,
205+
Self::World => WORLD_PROCESSOR,
206+
}
207+
}
19208
}

mindy-website/www/eslint.config.js

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,29 @@ import globals from "globals";
88
import tseslint from "typescript-eslint";
99

1010
export default tseslint.config([
11-
globalIgnores(["dist"]),
12-
{
13-
files: ["**/*.{ts,tsx}"],
14-
extends: [
15-
js.configs.recommended,
16-
...tseslint.configs.recommendedTypeChecked,
17-
...tseslint.configs.stylisticTypeChecked,
18-
reactHooks.configs["recommended-latest"],
19-
reactRefresh.configs.vite,
20-
reactX.configs["recommended-typescript"],
21-
reactDom.configs.recommended,
22-
],
23-
languageOptions: {
24-
parserOptions: {
25-
project: ["./tsconfig.node.json", "./tsconfig.app.json"],
26-
tsconfigRootDir: import.meta.dirname,
27-
},
28-
ecmaVersion: 2020,
29-
globals: globals.browser,
11+
globalIgnores(["dist"]),
12+
{
13+
files: ["**/*.{ts,tsx}"],
14+
extends: [
15+
js.configs.recommended,
16+
...tseslint.configs.recommendedTypeChecked,
17+
...tseslint.configs.stylisticTypeChecked,
18+
reactHooks.configs["recommended-latest"],
19+
reactRefresh.configs.vite,
20+
reactX.configs["recommended-typescript"],
21+
reactDom.configs.recommended,
22+
],
23+
rules: {
24+
"no-unused-vars": "warn",
25+
"@typescript-eslint/no-unused-vars": "warn",
26+
},
27+
languageOptions: {
28+
parserOptions: {
29+
project: ["./tsconfig.node.json", "./tsconfig.app.json"],
30+
tsconfigRootDir: import.meta.dirname,
31+
},
32+
ecmaVersion: 2020,
33+
globals: globals.browser,
34+
},
3035
},
31-
},
3236
]);

mindy-website/www/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<html lang="en">
33
<head>
44
<meta charset="UTF-8" />
5-
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
5+
<link rel="icon" type="image/png" href="/world-processor.png" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
77
<title>Mindy</title>
88
</head>

mindy-website/www/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"dev": "vite",
88
"build": "tsc -b && vite build",
99
"lint": "eslint .",
10-
"preview": "vite preview"
10+
"preview": "vite preview",
11+
"wasm": "wasm-pack build .. --dev && yarn upgrade mindy-website"
1112
},
1213
"dependencies": {
1314
"@mantine/core": "^8.2.4",

0 commit comments

Comments
 (0)