From fa9004d12cc94a05feabfb3afef2badf22b83641 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bel=C3=A9n=20Albeza?= <belen@hey.com>
Date: Fri, 4 Oct 2024 12:50:16 +0200
Subject: [PATCH] Pass struct to wasm (rust)

---
 frontend/render_v2/rs/build        |   3 -
 frontend/render_v2/rs/build.sh     |   6 ++
 frontend/render_v2/rs/src/main.rs  |  71 ++++++++++++------
 frontend/src/app/render_v2/rs.cljs |  10 ++-
 frontend/src/app/render_v2/rs.js   | 112 +++++++++++++++--------------
 5 files changed, 122 insertions(+), 80 deletions(-)
 delete mode 100755 frontend/render_v2/rs/build
 create mode 100755 frontend/render_v2/rs/build.sh

diff --git a/frontend/render_v2/rs/build b/frontend/render_v2/rs/build
deleted file mode 100755
index e5636ac08..000000000
--- a/frontend/render_v2/rs/build
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/bash
-EMCC_CFLAGS="--no-entry -s ERROR_ON_UNDEFINED_SYMBOLS=0 -s MAX_WEBGL_VERSION=2 -s MODULARIZE=1 -s EXPORT_NAME=createRustSkiaModule -s EXPORTED_RUNTIME_METHODS=GL -s ENVIRONMENT=webgl" cargo build --target=wasm32-unknown-emscripten
-
diff --git a/frontend/render_v2/rs/build.sh b/frontend/render_v2/rs/build.sh
new file mode 100755
index 000000000..2c79fd96c
--- /dev/null
+++ b/frontend/render_v2/rs/build.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+EMSDK_QUIET=1 . "/usr/local/emsdk/emsdk_env.sh"
+
+EMCC_CFLAGS="--no-entry -s ERROR_ON_UNDEFINED_SYMBOLS=0 -s MAX_WEBGL_VERSION=2 -s MODULARIZE=1 -s EXPORT_NAME=createRustSkiaModule -s EXPORTED_RUNTIME_METHODS=GL -s ENVIRONMENT=web" cargo build --target=wasm32-unknown-emscripten
+
diff --git a/frontend/render_v2/rs/src/main.rs b/frontend/render_v2/rs/src/main.rs
index 780de2d77..3df297255 100644
--- a/frontend/render_v2/rs/src/main.rs
+++ b/frontend/render_v2/rs/src/main.rs
@@ -2,9 +2,11 @@ use std::boxed::Box;
 
 use skia_safe::{
     gpu::{self, gl::FramebufferInfo, DirectContext},
-    Color, Paint, PaintStyle, Rect, Surface,
+    // Color, Paint, PaintStyle, Rect, Surface,
 };
 
+use skia_safe as skia;
+
 extern "C" {
     pub fn emscripten_GetProcAddress(
         name: *const ::std::os::raw::c_char,
@@ -22,15 +24,15 @@ struct GpuState {
 /// structures are not thread safe, so a state must not be shared between different Web Workers.
 pub struct State {
     gpu_state: GpuState,
-    surface: Surface,
+    surface: skia::Surface,
 }
 
 impl State {
-    fn new(gpu_state: GpuState, surface: Surface) -> Self {
+    fn new(gpu_state: GpuState, surface: skia::Surface) -> Self {
         State { gpu_state, surface }
     }
 
-    fn set_surface(&mut self, surface: Surface) {
+    fn set_surface(&mut self, surface: skia::Surface) {
         self.surface = surface;
     }
 }
@@ -66,7 +68,7 @@ fn create_gpu_state() -> GpuState {
 }
 
 /// Create the Skia surface that will be used for rendering.
-fn create_surface(gpu_state: &mut GpuState, width: i32, height: i32) -> Surface {
+fn create_surface(gpu_state: &mut GpuState, width: i32, height: i32) -> skia::Surface {
     let backend_render_target =
         gpu::backend_render_targets::make_gl((width, height), 1, 8, gpu_state.framebuffer_info);
 
@@ -81,9 +83,9 @@ fn create_surface(gpu_state: &mut GpuState, width: i32, height: i32) -> Surface
     .unwrap()
 }
 
-fn render_rect(surface: &mut Surface, rect: Rect, color: Color) {
-    let mut paint = Paint::default();
-    paint.set_style(PaintStyle::Fill);
+fn render_rect(surface: &mut skia::Surface, rect: skia::Rect, color: skia::Color) {
+    let mut paint = skia::Paint::default();
+    paint.set_style(skia::PaintStyle::Fill);
     paint.set_color(color);
     paint.set_anti_alias(true);
     surface.canvas().draw_rect(rect, &paint);
@@ -107,24 +109,51 @@ pub unsafe extern "C" fn resize_surface(state: *mut State, width: i32, height: i
     state.set_surface(surface);
 }
 
-/// Draw a black rect at the specified coordinates.
-/// # Safety
 #[no_mangle]
-pub unsafe extern "C" fn draw_rect(
-    state: *mut State,
-    left: i32,
-    top: i32,
-    right: i32,
-    bottom: i32,
+pub extern "C" fn make_color(r: i32, g: i32, b: i32, a: f32) -> Box<Color> {
+    Box::new(Color {
+        r: r as u8,
+        g: g as u8,
+        b: b as u8,
+        a,
+    })
+}
+
+#[repr(C)]
+pub struct Color {
     r: u8,
     g: u8,
     b: u8,
-) {
-    let state = unsafe { state.as_mut() }.expect("got an invalid state pointer");
-    let rect = Rect::new(left as f32, top as f32, right as f32, bottom as f32);
-    let color = Color::from_rgb(r, g, b);
+    a: f32,
+}
 
-    render_rect(&mut state.surface, rect, color);
+#[repr(C)]
+pub struct Rect {
+    left: f32,
+    top: f32,
+    right: f32,
+    bottom: f32,
+}
+
+#[no_mangle]
+pub extern "C" fn make_rect(left: f32, top: f32, right: f32, bottom: f32) -> Box<Rect> {
+    Box::new(Rect {
+        left,
+        top,
+        right,
+        bottom,
+    })
+}
+
+/// Draws a rect at the specified coordinates with the give ncolor
+/// # Safety
+#[no_mangle]
+pub unsafe extern "C" fn draw_rect(state: *mut State, rect: &Rect, color: &Color) {
+    let state = unsafe { state.as_mut() }.expect("got an invalid state pointer");
+    let r = skia::Rect::new(rect.left, rect.top, rect.right, rect.bottom);
+    let color = skia::Color::from_argb((color.a * 255.0) as u8, color.r, color.g, color.b);
+
+    render_rect(&mut state.surface, r, color);
 }
 
 #[no_mangle]
diff --git a/frontend/src/app/render_v2/rs.cljs b/frontend/src/app/render_v2/rs.cljs
index 372b26297..b0b9cc6ed 100644
--- a/frontend/src/app/render_v2/rs.cljs
+++ b/frontend/src/app/render_v2/rs.cljs
@@ -23,6 +23,8 @@
         reset-canvas (gobj/get ^js internal-module "_reset_canvas")
         scale (gobj/get ^js internal-module "_scale")
         flush (gobj/get ^js internal-module "_flush")
+        make-color (gobj/get ^js internal-module "_make_color")
+        make-rect (gobj/get ^js internal-module "_make_rect")
         supported-shapes (filter (fn [shape] (not= (:type shape) :frame)) (vals objects))]
 
         (js/requestAnimationFrame (fn []
@@ -31,9 +33,11 @@
                                     (translate gpu-state (- (:x vbox)) (- (:y vbox)))
                                     (doseq [shape supported-shapes]
                                       (let [sr (:selrect shape)
-                                            [r g b] (cc/hex->rgb (-> shape :fills first :fill-color))]
-                                        ;; (js/console.log (clj->js shape))
-                                        (draw-rect gpu-state (:x1 sr) (:y1 sr) (:x2 sr) (:y2 sr) r g b)))
+                                            [r g b] (cc/hex->rgb (-> shape :fills first :fill-color))
+                                            alpha (-> shape :fills first :fill-opacity)
+                                            color (make-color r g b alpha)
+                                            rect (make-rect (:x1 sr) (:y1 sr) (:x2 sr) (:y2 sr))]
+                                        (draw-rect gpu-state rect color)))
                                     (flush gpu-state)))))
 
 (defn set-canvas
diff --git a/frontend/src/app/render_v2/rs.js b/frontend/src/app/render_v2/rs.js
index 83ebc638e..46c24e869 100644
--- a/frontend/src/app/render_v2/rs.js
+++ b/frontend/src/app/render_v2/rs.js
@@ -28,7 +28,7 @@ var readyPromise = new Promise((resolve, reject) => {
   readyPromiseResolve = resolve;
   readyPromiseReject = reject;
 });
-["_draw_rect","_flush","_init","_main","_reset_canvas","_resize_surface","_scale","_translate","getExceptionMessage","incrementExceptionRefcount","decrementExceptionRefcount","_memory","___indirect_function_table","onRuntimeInitialized"].forEach((prop) => {
+["_draw_rect","_flush","_init","_main","_make_color","_make_rect","_reset_canvas","_resize_surface","_scale","_translate","getExceptionMessage","incrementExceptionRefcount","decrementExceptionRefcount","_memory","___indirect_function_table","onRuntimeInitialized"].forEach((prop) => {
   if (!Object.getOwnPropertyDescriptor(readyPromise, prop)) {
     Object.defineProperty(readyPromise, prop, {
       get: () => abort('You are getting ' + prop + ' on the Promise object, instead of the instance. Use .then() to get called back with the instance, see the MODULARIZE docs in src/settings.js'),
@@ -700,7 +700,7 @@ function createWasm() {
     }
   }
 
-  if (!wasmBinaryFile) wasmBinaryFile = findWasmBinary();
+  wasmBinaryFile ??= findWasmBinary();
 
   // If instantiation fails, reject the module ready promise.
   instantiateAsync(wasmBinary, wasmBinaryFile, info, receiveInstantiationResult).catch(readyPromiseReject);
@@ -752,45 +752,51 @@ function isExportedByForceFilesystem(name) {
          name === 'removeRunDependency';
 }
 
-function missingGlobal(sym, msg) {
-  if (typeof globalThis != 'undefined') {
+/**
+ * Intercept access to a global symbol.  This enables us to give informative
+ * warnings/errors when folks attempt to use symbols they did not include in
+ * their build, or no symbols that no longer exist.
+ */
+function hookGlobalSymbolAccess(sym, func) {
+  if (typeof globalThis != 'undefined' && !Object.getOwnPropertyDescriptor(globalThis, sym)) {
     Object.defineProperty(globalThis, sym, {
       configurable: true,
       get() {
-        warnOnce(`\`${sym}\` is not longer defined by emscripten. ${msg}`);
+        func();
         return undefined;
       }
     });
   }
 }
 
+function missingGlobal(sym, msg) {
+  hookGlobalSymbolAccess(sym, () => {
+    warnOnce(`\`${sym}\` is not longer defined by emscripten. ${msg}`);
+  });
+}
+
 missingGlobal('buffer', 'Please use HEAP8.buffer or wasmMemory.buffer');
 missingGlobal('asm', 'Please use wasmExports instead');
 
 function missingLibrarySymbol(sym) {
-  if (typeof globalThis != 'undefined' && !Object.getOwnPropertyDescriptor(globalThis, sym)) {
-    Object.defineProperty(globalThis, sym, {
-      configurable: true,
-      get() {
-        // Can't `abort()` here because it would break code that does runtime
-        // checks.  e.g. `if (typeof SDL === 'undefined')`.
-        var msg = `\`${sym}\` is a library symbol and not included by default; add it to your library.js __deps or to DEFAULT_LIBRARY_FUNCS_TO_INCLUDE on the command line`;
-        // DEFAULT_LIBRARY_FUNCS_TO_INCLUDE requires the name as it appears in
-        // library.js, which means $name for a JS name with no prefix, or name
-        // for a JS name like _name.
-        var librarySymbol = sym;
-        if (!librarySymbol.startsWith('_')) {
-          librarySymbol = '$' + sym;
-        }
-        msg += ` (e.g. -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE='${librarySymbol}')`;
-        if (isExportedByForceFilesystem(sym)) {
-          msg += '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you';
-        }
-        warnOnce(msg);
-        return undefined;
-      }
-    });
-  }
+  hookGlobalSymbolAccess(sym, () => {
+    // Can't `abort()` here because it would break code that does runtime
+    // checks.  e.g. `if (typeof SDL === 'undefined')`.
+    var msg = `\`${sym}\` is a library symbol and not included by default; add it to your library.js __deps or to DEFAULT_LIBRARY_FUNCS_TO_INCLUDE on the command line`;
+    // DEFAULT_LIBRARY_FUNCS_TO_INCLUDE requires the name as it appears in
+    // library.js, which means $name for a JS name with no prefix, or name
+    // for a JS name like _name.
+    var librarySymbol = sym;
+    if (!librarySymbol.startsWith('_')) {
+      librarySymbol = '$' + sym;
+    }
+    msg += ` (e.g. -sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE='${librarySymbol}')`;
+    if (isExportedByForceFilesystem(sym)) {
+      msg += '. Alternatively, forcing filesystem support (-sFORCE_FILESYSTEM) can export this for you';
+    }
+    warnOnce(msg);
+  });
+
   // Any symbol that is not included from the JS library is also (by definition)
   // not exported on the Module object.
   unexportedRuntimeSymbol(sym);
@@ -1305,12 +1311,7 @@ function dbg(...args) {
 
   var _emscripten_date_now = () => Date.now();
 
-  var _emscripten_get_now;
-      // Modern environment where performance.now() is supported:
-      // N.B. a shorter form "_emscripten_get_now = performance.now;" is
-      // unfortunately not allowed even in current browsers (e.g. FF Nightly 75).
-      _emscripten_get_now = () => performance.now();
-  ;
+  var _emscripten_get_now = () => performance.now();
 
   var GLctx;
   
@@ -5641,6 +5642,8 @@ function dbg(...args) {
   },
   filesystems:null,
   syncFSRequests:0,
+  readFiles:{
+  },
   FSStream:class {
         constructor() {
           // TODO(https://github.com/emscripten-core/emscripten/issues/21414):
@@ -6540,7 +6543,6 @@ function dbg(...args) {
           stream.stream_ops.open(stream);
         }
         if (Module['logReadFiles'] && !(flags & 1)) {
-          if (!FS.readFiles) FS.readFiles = {};
           if (!(path in FS.readFiles)) {
             FS.readFiles[path] = 1;
           }
@@ -6961,7 +6963,7 @@ function dbg(...args) {
   createDevice(parent, name, input, output) {
         var path = PATH.join2(typeof parent == 'string' ? parent : FS.getPath(parent), name);
         var mode = FS_getMode(!!input, !!output);
-        if (!FS.createDevice.major) FS.createDevice.major = 64;
+        FS.createDevice.major ??= 64;
         var dev = FS.makedev(FS.createDevice.major++, 0);
         // Create a fake device that a set of stream ops to emulate
         // the old behavior.
@@ -8563,7 +8565,9 @@ var wasmExports = createWasm();
 var ___wasm_call_ctors = createExportWrapper('__wasm_call_ctors', 0);
 var _init = Module['_init'] = createExportWrapper('init', 2);
 var _resize_surface = Module['_resize_surface'] = createExportWrapper('resize_surface', 3);
-var _draw_rect = Module['_draw_rect'] = createExportWrapper('draw_rect', 8);
+var _make_color = Module['_make_color'] = createExportWrapper('make_color', 4);
+var _make_rect = Module['_make_rect'] = createExportWrapper('make_rect', 4);
+var _draw_rect = Module['_draw_rect'] = createExportWrapper('draw_rect', 3);
 var _flush = Module['_flush'] = createExportWrapper('flush', 1);
 var _translate = Module['_translate'] = createExportWrapper('translate', 3);
 var _scale = Module['_scale'] = createExportWrapper('scale', 3);
@@ -8613,10 +8617,10 @@ var dynCall_iiiiij = Module['dynCall_iiiiij'] = createExportWrapper('dynCall_iii
 var dynCall_iiiiijj = Module['dynCall_iiiiijj'] = createExportWrapper('dynCall_iiiiijj', 9);
 var dynCall_iiiiiijj = Module['dynCall_iiiiiijj'] = createExportWrapper('dynCall_iiiiiijj', 10);
 
-function invoke_vii(index,a1,a2) {
+function invoke_iii(index,a1,a2) {
   var sp = stackSave();
   try {
-    getWasmTableEntry(index)(a1,a2);
+    return getWasmTableEntry(index)(a1,a2);
   } catch(e) {
     stackRestore(sp);
     if (!(e instanceof EmscriptenEH)) throw e;
@@ -8635,6 +8639,17 @@ function invoke_ii(index,a1) {
   }
 }
 
+function invoke_vii(index,a1,a2) {
+  var sp = stackSave();
+  try {
+    getWasmTableEntry(index)(a1,a2);
+  } catch(e) {
+    stackRestore(sp);
+    if (!(e instanceof EmscriptenEH)) throw e;
+    _setThrew(1, 0);
+  }
+}
+
 function invoke_viii(index,a1,a2,a3) {
   var sp = stackSave();
   try {
@@ -8657,10 +8672,10 @@ function invoke_vi(index,a1) {
   }
 }
 
-function invoke_iii(index,a1,a2) {
+function invoke_iiii(index,a1,a2,a3) {
   var sp = stackSave();
   try {
-    return getWasmTableEntry(index)(a1,a2);
+    return getWasmTableEntry(index)(a1,a2,a3);
   } catch(e) {
     stackRestore(sp);
     if (!(e instanceof EmscriptenEH)) throw e;
@@ -8690,17 +8705,6 @@ function invoke_iiiiiii(index,a1,a2,a3,a4,a5,a6) {
   }
 }
 
-function invoke_iiii(index,a1,a2,a3) {
-  var sp = stackSave();
-  try {
-    return getWasmTableEntry(index)(a1,a2,a3);
-  } catch(e) {
-    stackRestore(sp);
-    if (!(e instanceof EmscriptenEH)) throw e;
-    _setThrew(1, 0);
-  }
-}
-
 function invoke_iiiii(index,a1,a2,a3,a4) {
   var sp = stackSave();
   try {
@@ -9048,12 +9052,14 @@ var missingLibrarySymbols = [
   'setImmediateWrapped',
   'clearImmediateWrapped',
   'polyfillSetImmediate',
+  'registerPostMainLoop',
+  'registerPreMainLoop',
   'getPromise',
   'makePromise',
   'idsToPromises',
   'makePromiseCallback',
   'Browser_asyncPrepareDataCounter',
-  'setMainLoop',
+  'safeRequestAnimationFrame',
   'isLeapYear',
   'ydayFromDate',
   'arraySum',