plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
b858611
— Peter John
2026-11-02T13:57:47+05:30
T4: native host: Backend, wasmtime binding, macroquad, recorder
- plum-game/src/native.rs +17 -12
- plum-game/tests/host_test.rs +12 -0
plum-game/src/native.rs
CHANGED
|
@@ -45,12 +45,15 @@ fn str_bytes(store: &mut impl wasmtime::AsContextMut, s: &Rooted<AnyRef>) -> Res
|
|
|
45
45
|
let Val::AnyRef(Some(data)) = st.field(&mut *store, 0)? else {
|
|
46
46
|
return Err(wasmtime::Error::msg("Str.data is not a byte array"));
|
|
47
47
|
};
|
|
48
|
-
let start = st.field(&mut *store, 1)?.
|
|
48
|
+
let (Val::I64(start), Val::I64(len)) = (st.field(&mut *store, 1)?, st.field(&mut *store, 2)?) else {
|
|
49
|
+
return Err(wasmtime::Error::msg("Str.start/len are not Int"));
|
|
50
|
+
};
|
|
49
|
-
let len =
|
|
51
|
+
let (start, len) = (usize::try_from(start)?, usize::try_from(len)?);
|
|
52
|
+
let end = start.checked_add(len).ok_or_else(|| wasmtime::Error::msg("Str view out of range"))?;
|
|
50
53
|
let arr = data.unwrap_array(&*store)?;
|
|
51
54
|
let mut buf = vec![0u8; arr.len(&*store)? as usize];
|
|
52
55
|
arr.copy_to_i8_slice(&mut *store, &mut buf)?;
|
|
53
|
-
buf.get(start..
|
|
56
|
+
buf.get(start..end).map(|b| b.to_vec()).ok_or_else(|| wasmtime::Error::msg("Str view out of range"))
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
fn str_arg<B: Backend>(caller: &mut Caller<'_, HostState<B>>, p: &[Val], i: usize) -> Result<String> {
|
|
@@ -176,20 +179,22 @@ impl<B: Backend> PlumGame<B> {
|
|
|
176
179
|
Ok(())
|
|
177
180
|
}
|
|
178
181
|
|
|
182
|
+
/// Calls `name` with the state (and `extra`) inside a root scope, so the
|
|
183
|
+
/// per-frame `Rooted` handle is released instead of accumulating.
|
|
179
|
-
fn
|
|
184
|
+
fn call_with_state(&mut self, name: &str, extra: &[Val]) -> Result<()> {
|
|
185
|
+
let func = self.func(name)?;
|
|
180
|
-
let s = self.state.
|
|
186
|
+
let s = self.state.clone().ok_or_else(|| wasmtime::Error::msg("`init` has not run"))?;
|
|
187
|
+
let mut scope = wasmtime::RootScope::new(&mut self.store);
|
|
181
|
-
|
|
188
|
+
let mut args = vec![Val::AnyRef(Some(s.to_rooted(&mut scope)))];
|
|
189
|
+
args.extend_from_slice(extra);
|
|
190
|
+
func.call(&mut scope, &args, &mut [])
|
|
182
191
|
}
|
|
183
192
|
|
|
184
193
|
pub fn update(&mut self, dt: f32) -> Result<()> {
|
|
185
|
-
|
|
194
|
+
self.call_with_state("update", &[fv(dt)])
|
|
186
|
-
let g = self.state_val()?;
|
|
187
|
-
update.call(&mut self.store, &[g, fv(dt)], &mut [])
|
|
188
195
|
}
|
|
189
196
|
|
|
190
197
|
pub fn draw(&mut self) -> Result<()> {
|
|
191
|
-
|
|
198
|
+
self.call_with_state("draw", &[])
|
|
192
|
-
let g = self.state_val()?;
|
|
193
|
-
draw.call(&mut self.store, &[g], &mut [])
|
|
194
199
|
}
|
|
195
200
|
}
|
plum-game/tests/host_test.rs
CHANGED
|
@@ -139,3 +139,15 @@ fn atlasAndMapThroughHost() {
|
|
|
139
139
|
assert!(rec.map_terrain(m, 0, 0) < 512);
|
|
140
140
|
assert_eq!(rec.map_terrain(m, w, 0), 0);
|
|
141
141
|
}
|
|
142
|
+
|
|
143
|
+
#[test]
|
|
144
|
+
fn manyFramesDoNotAccumulateRoots() {
|
|
145
|
+
let mut g = game(Recorder::new());
|
|
146
|
+
for _ in 0..2000 {
|
|
147
|
+
g.update(0.016).unwrap();
|
|
148
|
+
g.draw().unwrap();
|
|
149
|
+
}
|
|
150
|
+
g.backend_mut().calls.clear();
|
|
151
|
+
g.draw().unwrap();
|
|
152
|
+
assert_eq!(g.backend().calls.last().unwrap(), "drawText(\"frames 2000\",0,0,8,1,1,1,1)");
|
|
153
|
+
}
|