Demo web without PyScript and JS + improve and fix jsffi#484
Conversation
from jsffi import init as js_init, get_GlobalThis, js_i32, get_Console
from jsffi import js_u8array_from_ptr, js_new_ImageData, js_request_animation_frame, js_to_f64
from unsafe import gc_alloc, gc_ptr
WIDTH: i32 = 800
HEIGHT: i32 = 600
SIZE: i32 = WIDTH * HEIGHT * 4
def simulate_step(buf: gc_ptr[u8], speed: f64) -> None:
for idx in range(SIZE):
buf[idx] = 100
def frame(timestamp: f64, buf: gc_ptr[u8]) -> None:
console = get_Console()
console.log("hello")
window = get_GlobalThis()
speed = js_to_f64(window.document.getElementById("speed").value)
simulate_step(buf, speed)
u8arr = js_u8array_from_ptr(buf, SIZE)
image_data = js_new_ImageData(u8arr, WIDTH, HEIGHT)
canvas = window.document.getElementById("canvas")
ctx = canvas.getContext("2d")
ctx.putImageData(image_data, js_i32(0), js_i32(0))
def _frame(_timestamp: f64) -> None:
frame(_timestamp, buf)
js_request_animation_frame(_frame)
def main() -> None:
js_init()
buf = gc_alloc[u8](SIZE)
frame(1.25, buf)
def _frame(timestamp: f64) -> None:
frame(timestamp, buf)
js_request_animation_frame(_frame)gives |
I tried things like that: from jsffi import init as js_init, get_GlobalThis, js_i32, get_Console
from jsffi import js_u8array_from_ptr, js_new_ImageData, js_request_animation_frame, js_to_f64
from unsafe import gc_alloc, gc_ptr
from _list import list
WIDTH: i32 = 800
HEIGHT: i32 = 600
SIZE: i32 = WIDTH * HEIGHT * 4
def simulate_step(buf: gc_ptr[u8], speed: f64) -> None:
for idx in range(SIZE):
buf[idx] = 100
# var glob_list: list[gc_ptr[u8]] = list[gc_ptr[u8]]()
# var glob_var: gc_ptr[u8] = gc_alloc[u8](SIZE)
var buf: gc_ptr[u8] = gc_alloc[u8](SIZE)
def frame(timestamp: f64) -> None:
# buf: gc_ptr[u8] = glob_list[0]
# buf: gc_ptr[u8] = glob_var
console = get_Console()
console.log("hello")
window = get_GlobalThis()
speed = js_to_f64(window.document.getElementById("speed").value)
simulate_step(buf, speed)
u8arr = js_u8array_from_ptr(buf, SIZE)
image_data = js_new_ImageData(u8arr, WIDTH, HEIGHT)
canvas = window.document.getElementById("canvas")
ctx = canvas.getContext("2d")
ctx.putImageData(image_data, js_i32(0), js_i32(0))
js_request_animation_frame(frame)
def main() -> None:
js_init()
# buf = gc_alloc[u8](SIZE)
# glob_var = buf
# glob_list.append(buf)
js_request_animation_frame(frame)This fails with I'm not too far from something really cool but it seems SPy is not yet ready for that. |
|
@paugier I managed to get this working with the following diff. It's still a hack because it uses a global variable, but it probably lets you to proceed. diff --git a/examples/jsffi-canvas/demo.spy b/examples/jsffi-canvas/demo.spy
index 042ec744..aadad061 100644
--- a/examples/jsffi-canvas/demo.spy
+++ b/examples/jsffi-canvas/demo.spy
@@ -8,6 +8,8 @@ WIDTH: i32 = 800
HEIGHT: i32 = 600
SIZE: i32 = WIDTH * HEIGHT * 4
+var BUF: gc_ptr[u8] = gc_ptr[u8].NULL
+
def simulate_step(buf: gc_ptr[u8], speed: f64) -> None:
for idx in range(SIZE):
@@ -36,9 +38,9 @@ def frame(timestamp: f64, buf: gc_ptr[u8]) -> None:
def main() -> None:
js_init()
- buf = gc_alloc[u8](SIZE)
+ BUF = gc_alloc[u8](SIZE)
- frame(1.25, buf)
+ frame(1.25, BUF)
# def _frame(timestamp: f64) -> None:
# frame(timestamp, buf)
diff --git a/spy/backend/c/cmodwriter.py b/spy/backend/c/cmodwriter.py
index 98ec8f88..6fe8346b 100644
--- a/spy/backend/c/cmodwriter.py
+++ b/spy/backend/c/cmodwriter.py
@@ -252,22 +252,24 @@ class CModuleWriter:
w_content = w_obj.get()
w_T = self.ctx.vm.dynamic_type(w_content)
# we support only int global variables for now
- assert isinstance(w_content, W_I32), "WIP: var type not supported"
- intval = self.ctx.vm.unwrap(w_content)
- c_type = self.ctx.w2c(w_T)
- self.tbh_globals.wl(f"extern {c_type} {fqn.c_name};")
- self.tbc_globals.wl(f"{c_type} {fqn.c_name} = {intval};")
-
- # ==== misc consts ====
- elif isinstance(w_T, W_PtrType):
- # for now, we only support NULL constnts
- assert isinstance(w_obj, W_Ptr)
- assert w_obj.addr == 0, (
- "only NULL pointers can be stored in constants for now"
- )
- c_type = self.ctx.w2c(w_T)
- self.tbh_globals.wl(f"extern {c_type} {fqn.c_name};")
- self.tbc_globals.wl(f"{c_type} {fqn.c_name} = {{0}};")
+ if isinstance(w_content, W_I32):
+ intval = self.ctx.vm.unwrap(w_content)
+ c_type = self.ctx.w2c(w_T)
+ self.tbh_globals.wl(f"extern {c_type} {fqn.c_name};")
+ self.tbc_globals.wl(f"{c_type} {fqn.c_name} = {intval};")
+
+ elif isinstance(w_T, W_PtrType):
+ # for now, we only support NULL constnts
+ assert isinstance(w_content, W_Ptr)
+ assert w_content.addr == 0, (
+ "only NULL pointers can be stored in constants for now"
+ )
+ c_type = self.ctx.w2c(w_T)
+ self.tbh_globals.wl(f"extern {c_type} {fqn.c_name};")
+ self.tbc_globals.wl(f"{c_type} {fqn.c_name} = {{0}};")
+
+ else:
+ raise WIP("var type `{w_T}` not supported")
else:
raise NotImplementedError("WIP") |
a1a04a6 to
6036cf4
Compare
f894b8f to
b1971a1
Compare
2bea565 to
e161a50
Compare
3f5513a to
ba106d1
Compare
fe8f639 to
8c4618b
Compare
|
My plans on this work:
Independently, the particle demo should also be improved with using random (#490)... Finally, communicate about these SPy demos. |
c74b8c3 to
e872a90
Compare
c8e4094 to
a0eb879
Compare
f3bba2c to
ece079f
Compare
Fixes #483 and co-authored with Claude.
I didn't clean the history (commits). Please tell me @antocuni if you think it is useful.
Description produced with Claude:
This PR (fixes #483) adds the primitives needed to drive 60fps canvas animations from SPy/WebAssembly without relying on PyScript, Pyodide, or any hand-written JavaScript. The changes span the C/WASM runtime (
libspy), the SPy Python-level module (jsffi.py), new tests, and two canvas demo applications.1. New
JsValtagged-union type (C side)The old design passed JavaScript values around purely as
JsRef(an opaque integer handle into a JS object table). The new design introduces aJsValstruct — a tagged union encoding a value along with its type tag (JSREF,F64,I32,STR,BOOL,FUNCPTR). This avoids unnecessary JS object allocations for primitive arguments. EachJsValis split into(int32_t tag, double payload)when crossing the WASM→JS boundary viaEM_JS.2.
jsffi_call_method.h/.c— variadic method calls (0–6 args)Previously only
call_method_1existed. A new header/source pair generatesjsffi_call_method_0throughjsffi_call_method_6, all takingJsVal-based arguments. The C file is#include-d directly intojsffi.cso allEM_JSfunctions share the same translation unit and can access thejsffiglobal object set up byjsffi_init().3.
jsffi.c— extended JS runtime object table and new primitivesjsffi_init()now pre-populates well-known singleton values:document(id 2),undefined(3),null(4),true(5),false(6).jsffi.from_jsval(tag, val)is added to decodeJsValpairs on the JS side.jsffi_f64,jsffi_drop_ref,jsffi_setattr(now takes aJsValpair instead of aJsRef),jsffi_u8array_from_ptr,jsffi_new_ImageData,jsffi_to_i32,jsffi_to_f64,jsffi_request_animation_frame,jsffi_debug_n_jsrefs.jsffi_wrap_funcis removed and replaced byJsVal-based function pointer passing.$wasmMemoryis added toEM_JS_DEPS(needed forUint8ClampedArrayfrom a raw WASM pointer).Note that
jsffi_u8array_from_ptrandjsffi_new_ImageDataare needed because there is no equivalent for the JSnewkeyword.jsffi_request_animation_frameavoids the creation of fewJsRefs.4.
spy/vm/modules/jsffi.py— SPy-side moduleW_JsValtype with__convert_from__that auto-convertsf64,i32,str,JsRef, or a function pointer into aJsVal.W_JsRef.__call_method__now supports 0–6 arguments via a generated dispatch (w_js_call_method_0…w_js_call_method_6), created dynamically usinginspect.Signature.W_JsRef.__convert_from__now also handlesf64 → JsRef.W_JsRef.__convert_to__added for converting aJsReftoi32,f64, orJsVal.w_get_Document,w_js_f64,w_js_to_i32,w_js_to_f64,w_drop_ref,w_js_u8array_from_ptr,w_js_new_ImageData,w_request_animation_frame,w__debug_n_jsrefs.5.
unsafe/misc.pyA small fix:
sizeof()now importsW_JsRefso it can compute its size correctly (needed for global pointer variables ofJsReftype).6. Tests (
tests/compiler/test_jsffi.py)New tests for all the above:
call_method_{0..6},to_i32,to_f64,u8array_from_ptr,get_Document, andrequest_animation_frame.7. New examples (
examples/jsffi/canvas/)Two complete browser demos, both written entirely in SPy:
demo_image_data.spy— a colour animation driven by RGB sliders, rendered viaputImageDatainto a canvas pixel buffer.demo_particles.spy— a 60fps bouncing-particle simulation using the Canvas 2D API (arc,fillRect,createRadialGradient), with controls for particle count, speed, and radius.A
create_html.pyscript generates HTML pages using FastHTML + Tailwind/DaisyUI. In release mode, the total payload (.wasm+ Emscripten glue.mjs) is ~91 KB — vs. ~10 MB for a PyScript/Pyodide deployment.The existing
examples/jsffi/files were reorganised into aminimal/subdirectory to keep things clean.