Skip to content

Commit 90bce1b

Browse files
committed
Move VM into a web worker
1 parent 15c5a52 commit 90bce1b

File tree

18 files changed

+519
-182
lines changed

18 files changed

+519
-182
lines changed

Cargo.lock

Lines changed: 1 addition & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mindy-website/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ crate-type = ["cdylib"]
99
[dependencies]
1010
console_error_panic_hook = "0.1.7"
1111
embedded-graphics = "0.8.1"
12-
embedded-graphics-web-simulator = "0.4.0"
12+
embedded-graphics-web-simulator = { version = "0.4.0", git = "https://github.com/object-Object/embedded-graphics-web-simulator", branch = "offscreen_canvas" }
1313
getrandom = { version = "0.3.3", features = ["wasm_js"] }
1414
js-sys = "0.3.77"
1515
mindy = { path = "..", features = ["embedded_graphics", "wasm"] }
@@ -20,5 +20,6 @@ wee_alloc = "0.4.5"
2020
version = "0.3"
2121
features = [
2222
"console",
23-
"Element",
23+
"Performance",
24+
"WorkerGlobalScope",
2425
]

mindy-website/src/lib.rs

Lines changed: 68 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use mindy::{
1717
},
1818
};
1919
use wasm_bindgen::prelude::*;
20-
use web_sys::Element;
20+
use web_sys::{OffscreenCanvas, Performance, WorkerGlobalScope};
2121

2222
#[global_allocator]
2323
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
@@ -32,12 +32,11 @@ macro_rules! log {
3232
}
3333

3434
#[wasm_bindgen]
35-
pub fn init() {
35+
pub fn init_logging() {
3636
console_error_panic_hook::set_once();
3737
}
3838

39-
#[wasm_bindgen]
40-
pub fn pack_point(x: i16, y: i16) -> u32 {
39+
fn pack_point(x: i16, y: i16) -> u32 {
4140
((y as u32) << 16) | (x as u32)
4241
}
4342

@@ -48,23 +47,48 @@ fn unpack_point(position: u32) -> PackedPoint2 {
4847
}
4948
}
5049

50+
fn fps_to_delta(fps: f64) -> f64 {
51+
// nominal 60 fps
52+
// 60 / 60 -> 1
53+
// 60 / 120 -> 0.5
54+
// 60 / 30 -> 2
55+
(60. / fps).min(MAX_DELTA)
56+
}
57+
58+
fn delta_to_time(delta: f64) -> f64 {
59+
// 60 ticks per second
60+
delta / 60.
61+
}
62+
5163
#[wasm_bindgen]
5264
pub struct WebLogicVM {
5365
vm: LogicVM,
5466
globals: Constants,
5567
logic_parser: LogicParser,
56-
prev_timestamp: Option<f64>,
68+
performance: Performance,
69+
delta: f64,
70+
tick_secs: f64,
71+
next_tick_end: f64,
5772
}
5873

5974
#[wasm_bindgen]
6075
impl WebLogicVM {
6176
#[wasm_bindgen(constructor)]
62-
pub fn new() -> Self {
77+
pub fn new(target_fps: f64) -> Self {
78+
let delta = fps_to_delta(target_fps);
79+
let tick_secs = delta_to_time(delta);
6380
Self {
6481
vm: LogicVM::new(),
6582
globals: LVar::create_global_constants(),
6683
logic_parser: LogicParser::new(),
67-
prev_timestamp: None,
84+
performance: js_sys::global()
85+
.dyn_into::<WorkerGlobalScope>()
86+
.expect("failed to cast global to WorkerGlobalScope")
87+
.performance()
88+
.expect("failed to get performance object"),
89+
delta,
90+
tick_secs,
91+
next_tick_end: 0.,
6892
}
6993
}
7094

@@ -102,12 +126,12 @@ impl WebLogicVM {
102126
position: u32,
103127
width: u32,
104128
height: u32,
105-
parent: &Element,
129+
canvas: &OffscreenCanvas,
106130
) -> Result<(), String> {
107-
let display = WebSimulatorDisplay::<Rgb888>::new(
131+
let display = WebSimulatorDisplay::<Rgb888, _>::from_offscreen_canvas(
108132
(width, height),
109133
&OutputSettings::default(),
110-
Some(parent),
134+
canvas,
111135
);
112136

113137
let display_data = EmbeddedDisplayData::new(
@@ -185,32 +209,50 @@ impl WebLogicVM {
185209
))
186210
}
187211

212+
pub fn remove_building(&mut self, position: u32) {
213+
self.vm.remove_building(unpack_point(position));
214+
}
215+
188216
pub fn building_name(&self, position: u32) -> Option<JsString> {
189217
self.vm
190218
.building(unpack_point(position))
191219
.map(|b| JsString::from_char_code(b.block.name.as_u16str().as_slice()))
192220
}
193221

194-
pub fn do_tick(&mut self, timestamp: f64) {
195-
// convert to seconds
196-
let timestamp = timestamp / 1000.;
197-
198-
// nominal 60 ticks per second
199-
let delta = match self.prev_timestamp {
200-
Some(prev_timestamp) => (timestamp - prev_timestamp) * 60.,
201-
None => 1.,
222+
pub fn processor_links(&self, position: u32) -> Option<Vec<JsString>> {
223+
if let Some(building) = self.vm.building(unpack_point(position))
224+
&& let BuildingData::Processor(processor) = &*building.data.borrow()
225+
{
226+
Some(
227+
processor
228+
.state
229+
.links()
230+
.iter()
231+
.map(|l| JsString::from(l.name.as_str()))
232+
.collect(),
233+
)
234+
} else {
235+
None
202236
}
203-
.min(MAX_DELTA);
237+
}
204238

205-
self.prev_timestamp = Some(timestamp);
206-
self.vm
207-
.do_tick_with_delta(Duration::from_secs_f64(timestamp), delta);
239+
pub fn set_target_fps(&mut self, target_fps: f64) {
240+
self.delta = fps_to_delta(target_fps);
241+
self.tick_secs = delta_to_time(self.delta);
242+
self.next_tick_end = 0.;
208243
}
209-
}
210244

211-
impl Default for WebLogicVM {
212-
fn default() -> Self {
213-
Self::new()
245+
pub fn do_tick(&mut self) {
246+
let mut time;
247+
loop {
248+
time = self.performance.now() / 1000.;
249+
self.vm
250+
.do_tick_with_delta(Duration::from_secs_f64(time), self.delta);
251+
if time >= self.next_tick_end {
252+
break;
253+
}
254+
}
255+
self.next_tick_end = time + self.tick_secs;
214256
}
215257
}
216258

mindy-website/www/eslint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export default tseslint.config([
3131
},
3232
],
3333
"@typescript-eslint/consistent-type-definitions": "off",
34+
"@typescript-eslint/switch-exhaustiveness-check": "warn",
3435
},
3536
languageOptions: {
3637
parserOptions: {

mindy-website/www/package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
"type": "module",
66
"scripts": {
77
"dev": "vite",
8-
"build": "tsc -b && vite build",
8+
"build": "tsc -b && yarn wasm-build && vite build",
99
"lint": "eslint .",
1010
"preview": "vite preview",
11-
"wasm": "wasm-pack build .. --dev && yarn upgrade mindy-website"
11+
"wasm": "wasm-pack build .. --target web --dev && yarn upgrade mindy-website",
12+
"wasm-build": "wasm-pack build .. --target web --release && yarn upgrade mindy-website"
1213
},
1314
"dependencies": {
1415
"@mantine/core": "^8.2.4",
@@ -37,7 +38,6 @@
3738
"prettier": "^3.6.2",
3839
"typescript": "~5.8.3",
3940
"typescript-eslint": "^8.39.1",
40-
"vite": "^7.1.2",
41-
"vite-plugin-wasm": "^3.5.0"
41+
"vite": "^7.1.2"
4242
}
4343
}

mindy-website/www/src/App.tsx

Lines changed: 11 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,25 @@ import { Center, Loader, MantineProvider } from "@mantine/core";
22
import "@mantine/core/styles.css";
33
import { ReactFlowProvider } from "@xyflow/react";
44
import "@xyflow/react/dist/style.css";
5-
import { useEffect, useRef, useState } from "react";
6-
7-
import { init, WebLogicVM } from "mindy-website";
5+
import { Suspense } from "react";
86

97
import LogicVMFlow from "./components/LogicVMFlow";
8+
import LogicVMProvider from "./components/LogicVMProvider";
109
import "./global.css";
1110
import { theme } from "./theme";
1211

1312
export default function App() {
14-
const [vm, setVM] = useState<WebLogicVM>();
15-
const frameRef = useRef(0);
16-
17-
useEffect(() => {
18-
init();
19-
20-
const newVM = new WebLogicVM();
21-
setVM(newVM);
22-
23-
// start tick loop
24-
const callback = (time: number) => {
25-
newVM.do_tick(time);
26-
frameRef.current = requestAnimationFrame(callback);
27-
};
28-
frameRef.current = requestAnimationFrame(callback);
29-
30-
return () => {
31-
cancelAnimationFrame(frameRef.current);
32-
};
33-
}, []);
34-
3513
return (
3614
<MantineProvider theme={theme} defaultColorScheme="dark">
37-
<ReactFlowProvider>
38-
<Center h="100vh">
39-
{vm == null ? (
40-
<Loader color="indigo" size="lg" />
41-
) : (
42-
<LogicVMFlow vm={vm} />
43-
)}
44-
</Center>
45-
</ReactFlowProvider>
15+
<Center h="100vh">
16+
<Suspense fallback={<Loader color="indigo" size="lg" />}>
17+
<LogicVMProvider>
18+
<ReactFlowProvider>
19+
<LogicVMFlow />
20+
</ReactFlowProvider>
21+
</LogicVMProvider>
22+
</Suspense>
23+
</Center>
4624
</MantineProvider>
4725
);
4826
}

0 commit comments

Comments
 (0)