Create wasm_error macro to handle Wasm errors differentiating critical vs recoverable

This commit is contained in:
Belén Albeza 2026-02-24 14:55:04 +01:00 committed by Alejandro Alonso
parent 208b3329fd
commit 2ace44c9e5
22 changed files with 560 additions and 175 deletions

View File

@ -116,6 +116,17 @@
(ex/print-throwable cause :prefix "Unexpected Error") (ex/print-throwable cause :prefix "Unexpected Error")
(show-not-blocking-error cause)))) (show-not-blocking-error cause))))
(defmethod ptk/handle-error :wasm-non-blocking
[error]
(when-let [cause (::instance error)]
(show-not-blocking-error cause)))
(defmethod ptk/handle-error :wasm-critical
[error]
(when-let [cause (::instance error)]
(ex/print-throwable cause :prefix "WASM critical error"))
(st/emit! (rt/assign-exception error)))
;; We receive a explicit authentication error; If the uri is for ;; We receive a explicit authentication error; If the uri is for
;; workspace, dashboard, viewer or settings, then assign the exception ;; workspace, dashboard, viewer or settings, then assign the exception
;; for show the error page. Otherwise this explicitly clears all ;; for show the error page. Otherwise this explicitly clears all
@ -327,20 +338,24 @@
(str/starts-with? message "invalid props on component") (str/starts-with? message "invalid props on component")
(str/starts-with? message "Unexpected token ")))) (str/starts-with? message "Unexpected token "))))
(handle-uncaught [cause]
(when cause
(set! last-exception cause)
(let [data (ex-data cause)
type (get data :type)]
(if (#{:wasm-critical :wasm-non-blocking} type)
(on-error cause)
(when-not (is-ignorable-exception? cause)
(ex/print-throwable cause :prefix "Uncaught Exception")
(ts/schedule #(show-not-blocking-error cause)))))))
(on-unhandled-error [event] (on-unhandled-error [event]
(.preventDefault ^js event) (.preventDefault ^js event)
(when-let [cause (unchecked-get event "error")] (handle-uncaught (unchecked-get event "error")))
(set! last-exception cause)
(when-not (is-ignorable-exception? cause)
(ex/print-throwable cause :prefix "Uncaught Exception")
(ts/schedule #(show-not-blocking-error cause)))))
(on-unhandled-rejection [event] (on-unhandled-rejection [event]
(.preventDefault ^js event) (.preventDefault ^js event)
(when-let [cause (unchecked-get event "reason")] (handle-uncaught (unchecked-get event "reason")))]
(set! last-exception cause)
(ex/print-throwable cause :prefix "Uncaught Rejection")
(ts/schedule #(show-not-blocking-error cause))))]
(.addEventListener g/window "error" on-unhandled-error) (.addEventListener g/window "error" on-unhandled-error)
(.addEventListener g/window "unhandledrejection" on-unhandled-rejection) (.addEventListener g/window "unhandledrejection" on-unhandled-rejection)

View File

@ -7,11 +7,30 @@
(ns app.render-wasm.helpers (ns app.render-wasm.helpers
#?(:cljs (:require-macros [app.render-wasm.helpers]))) #?(:cljs (:require-macros [app.render-wasm.helpers])))
(def ^:export error-code
"WASM error code constants (must match render-wasm/src/error.rs and mem.rs)."
{0x01 :wasm-non-blocking 0x02 :wasm-critical})
(defmacro call (defmacro call
"A helper for easy call wasm defined function in a module." "A helper for calling a wasm function.
Catches any exception thrown by the WASM function, reads the error code from
WASM when available, and routes it based on the error type:
- :wasm-non-blocking: call app.main.errors/on-error (eventually, shows a toast and logs the error)
- :wasm-critical or unknown: throws an exception to be handled by the global error handler (eventually, shows the internal error page)"
[module name & params] [module name & params]
(let [fn-sym (with-meta (gensym "fn-") {:tag 'function})] (let [fn-sym (with-meta (gensym "fn-") {:tag 'function})
e-sym (gensym "e")
code-sym (gensym "code")]
`(let [~fn-sym (cljs.core/unchecked-get ~module ~name)] `(let [~fn-sym (cljs.core/unchecked-get ~module ~name)]
;; DEBUG (try
;; (println "##" ~name) (~fn-sym ~@params)
(~fn-sym ~@params)))) (catch :default ~e-sym
(let [read-code# (cljs.core/unchecked-get ~module "_read_error_code")
~code-sym (when read-code# (read-code#))
type# (or (get app.render-wasm.helpers/error-code ~code-sym) :wasm-critical)
ex# (ex-info (str "WASM error (type: " type# ")")
{:fn ~name :type type# :message (.-message ~e-sym) :error-code ~code-sym}
~e-sym)]
(if (= type# :wasm-non-blocking)
(@~'app.main.store/on-error ex#)
(throw ex#))))))))

23
render-wasm/Cargo.lock generated
View File

@ -297,6 +297,8 @@ name = "macros"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2",
"quote",
"syn", "syn",
] ]
@ -426,6 +428,7 @@ dependencies = [
"indexmap", "indexmap",
"macros", "macros",
"skia-safe", "skia-safe",
"thiserror",
"uuid", "uuid",
] ]
@ -579,6 +582,26 @@ dependencies = [
"xattr", "xattr",
] ]
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "toml" name = "toml"
version = "1.0.3+spec-1.1.0" version = "1.0.3+spec-1.1.0"

View File

@ -32,6 +32,7 @@ skia-safe = { version = "0.93.1", default-features = false, features = [
"binary-cache", "binary-cache",
"webp", "webp",
] } ] }
thiserror = "2.0.18"
uuid = { version = "1.11.0", features = ["v4", "js"] } uuid = { version = "1.11.0", features = ["v4", "js"] }
[profile.release] [profile.release]

View File

@ -13,6 +13,8 @@ name = "macros"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2",
"quote",
"syn", "syn",
] ]

View File

@ -1,11 +1,13 @@
[package] [package]
name = "macros" name = "macros"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2021"
[lib] [lib]
proc-macro = true proc-macro = true
[dependencies] [dependencies]
heck = "0.5.0" heck = "0.5.0"
proc-macro2 = "1.0"
quote = "1.0"
syn = "2.0.106" syn = "2.0.106"

View File

@ -6,9 +6,109 @@ use std::sync;
use heck::{ToKebabCase, ToPascalCase}; use heck::{ToKebabCase, ToPascalCase};
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Block, GenericArgument, ItemFn, ReturnType, Type};
type Result<T> = std::result::Result<T, String>; type Result<T> = std::result::Result<T, String>;
/// Attribute macro for WASM-exported functions. The function **must** return
/// `std::result::Result<T, E>` where T is a C ABI type and E implements
/// `std::error::Error` and `Into<u8>`. The macro:
/// - Clears the error code at entry.
/// - Runs the body in `std::panic::catch_unwind`.
/// - Unwraps the Result: `Ok(x)` → return x; `Err(e)` → set error code in memory and panic
/// (so ClojureScript can catch the exception and read the code via `read_error_code`).
/// - On panic from the body: sets critical error code (0x02) and resumes unwind.
#[proc_macro_attribute]
pub fn wasm_error(_attr: TokenStream, item: TokenStream) -> TokenStream {
let mut input = parse_macro_input!(item as ItemFn);
let body = (*input.block).clone();
let (attrs, boxed_ty) = match &input.sig.output {
ReturnType::Type(attrs, boxed_ty) => (attrs, boxed_ty),
ReturnType::Default => {
return quote! {
compile_error!(
"#[wasm_error] requires the function to return std::result::Result<T, E> where E: std::error::Error + Into<u8>"
);
}
.into();
}
};
let (inner_ty, error_ty) = match crate_error_result_inner_type(boxed_ty) {
Some(t) => (t, quote!(crate::error::Error)),
None => {
return quote! {
compile_error!(
"#[wasm_error] requires the function to return crate::error::Result<T>. T must be a C ABI type (u32, u8, bool, (), etc.)"
);
}
.into();
}
};
let block: Block = syn::parse2(quote! {
{
crate::mem::clear_error_code();
let __wasm_err_result = std::panic::catch_unwind(|| -> std::result::Result<#inner_ty, #error_ty> {
#body
});
match __wasm_err_result {
Ok(__inner) => match __inner {
Ok(__val) => __val,
Err(__e) => {
let _: &dyn std::error::Error = &__e;
let __msg = __e.to_string();
crate::mem::set_error_code(__e.into());
panic!("WASM error: {}",__msg);
}
},
Err(__payload) => {
crate::mem::set_error_code(0x02); // critical, same as Error::Critical
std::panic::resume_unwind(__payload);
}
}
}
})
.expect("block parse");
input.sig.output = ReturnType::Type(attrs.clone(), Box::new(inner_ty.clone()));
input.block = Box::new(block);
quote! { #input }.into()
}
/// If the type is crate::error::Result<T> or a single-segment Result<T> (e.g. with
/// `use crate::error::Result`), returns Some(T). Otherwise None.
fn crate_error_result_inner_type(ty: &Type) -> Option<&Type> {
let path = match ty {
Type::Path(tp) => &tp.path,
_ => return None,
};
let segs: Vec<_> = path.segments.iter().collect();
let last = path.segments.last()?;
if last.ident != "Result" {
return None;
}
let args = match &last.arguments {
syn::PathArguments::AngleBracketed(a) => &a.args,
_ => return None,
};
if args.len() != 1 {
return None;
}
// Accept crate::error::Result<T> or bare Result<T> (from use)
let ok = segs.len() == 1
|| (segs.len() == 3 && segs[0].ident == "crate" && segs[1].ident == "error");
if !ok {
return None;
}
match &args[0] {
GenericArgument::Type(t) => Some(t),
_ => None,
}
}
#[proc_macro_derive(ToJs)] #[proc_macro_derive(ToJs)]
pub fn derive_to_cljs(input: TokenStream) -> TokenStream { pub fn derive_to_cljs(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as syn::DeriveInput); let input = syn::parse_macro_input!(input as syn::DeriveInput);

25
render-wasm/src/error.rs Normal file
View File

@ -0,0 +1,25 @@
use thiserror::Error;
pub const RECOVERABLE_ERROR: u8 = 0x01;
pub const CRITICAL_ERROR: u8 = 0x02;
// This is not really dead code, #[wasm_error] macro replaces this by something else.
#[allow(dead_code)]
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Error, Debug)]
pub enum Error {
#[error("[Recoverable] {0}")]
RecoverableError(String),
#[error("[Critical] {0}")]
CriticalError(String),
}
impl From<Error> for u8 {
fn from(error: Error) -> Self {
match error {
Error::RecoverableError(_) => RECOVERABLE_ERROR,
Error::CriticalError(_) => CRITICAL_ERROR,
}
}
}

View File

@ -1,5 +1,6 @@
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
mod emscripten; mod emscripten;
mod error;
mod math; mod math;
mod mem; mod mem;
mod options; mod options;
@ -14,12 +15,16 @@ mod view;
mod wapi; mod wapi;
mod wasm; mod wasm;
use std::collections::HashMap;
#[allow(unused_imports)]
use crate::error::{Error, Result};
use macros::wasm_error;
use math::{Bounds, Matrix}; use math::{Bounds, Matrix};
use mem::SerializableResult; use mem::SerializableResult;
use shapes::{StructureEntry, StructureEntryType, TransformEntry}; use shapes::{StructureEntry, StructureEntryType, TransformEntry};
use skia_safe as skia; use skia_safe as skia;
use state::State; use state::State;
use std::collections::HashMap;
use utils::uuid_from_u32_quartet; use utils::uuid_from_u32_quartet;
use uuid::Uuid; use uuid::Uuid;
@ -95,22 +100,27 @@ macro_rules! with_state_mut_current_shape {
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn init(width: i32, height: i32) { #[wasm_error]
pub extern "C" fn init(width: i32, height: i32) -> Result<()> {
let state_box = Box::new(State::new(width, height)); let state_box = Box::new(State::new(width, height));
unsafe { unsafe {
STATE = Some(state_box); STATE = Some(state_box);
} }
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_browser(browser: u8) { #[wasm_error]
pub extern "C" fn set_browser(browser: u8) -> Result<()> {
with_state_mut!(state, { with_state_mut!(state, {
state.set_browser(browser); state.set_browser(browser);
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn clean_up() { #[wasm_error]
pub extern "C" fn clean_up() -> Result<()> {
with_state_mut!(state, { with_state_mut!(state, {
// Cancel the current animation frame if it exists so // Cancel the current animation frame if it exists so
// it won't try to render without context // it won't try to render without context
@ -118,49 +128,60 @@ pub extern "C" fn clean_up() {
render_state.cancel_animation_frame(); render_state.cancel_animation_frame();
}); });
unsafe { STATE = None } unsafe { STATE = None }
mem::free_bytes(); mem::free_bytes()?;
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_render_options(debug: u32, dpr: f32) { #[wasm_error]
pub extern "C" fn set_render_options(debug: u32, dpr: f32) -> Result<()> {
with_state_mut!(state, { with_state_mut!(state, {
let render_state = state.render_state_mut(); let render_state = state.render_state_mut();
render_state.set_debug_flags(debug); render_state.set_debug_flags(debug);
render_state.set_dpr(dpr); render_state.set_dpr(dpr);
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_canvas_background(raw_color: u32) { #[wasm_error]
pub extern "C" fn set_canvas_background(raw_color: u32) -> Result<()> {
with_state_mut!(state, { with_state_mut!(state, {
let color = skia::Color::new(raw_color); let color = skia::Color::new(raw_color);
state.set_background_color(color); state.set_background_color(color);
state.rebuild_tiles_shallow(); state.rebuild_tiles_shallow();
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn render(_: i32) { #[wasm_error]
pub extern "C" fn render(_: i32) -> Result<()> {
with_state_mut!(state, { with_state_mut!(state, {
state.rebuild_touched_tiles(); state.rebuild_touched_tiles();
state state
.start_render_loop(performance::get_time()) .start_render_loop(performance::get_time())
.expect("Error rendering"); .expect("Error rendering");
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn render_sync() { #[wasm_error]
pub extern "C" fn render_sync() -> Result<()> {
with_state_mut!(state, { with_state_mut!(state, {
state.rebuild_tiles(); state.rebuild_tiles();
state state
.render_sync(performance::get_time()) .render_sync(performance::get_time())
.expect("Error rendering"); .expect("Error rendering");
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn render_sync_shape(a: u32, b: u32, c: u32, d: u32) { #[wasm_error]
pub extern "C" fn render_sync_shape(a: u32, b: u32, c: u32, d: u32) -> Result<()> {
with_state_mut!(state, { with_state_mut!(state, {
let id = uuid_from_u32_quartet(a, b, c, d); let id = uuid_from_u32_quartet(a, b, c, d);
state.use_shape(id); state.use_shape(id);
@ -179,34 +200,42 @@ pub extern "C" fn render_sync_shape(a: u32, b: u32, c: u32, d: u32) {
state.rebuild_tiles_from(Some(&id)); state.rebuild_tiles_from(Some(&id));
state state
.render_sync_shape(&id, performance::get_time()) .render_sync_shape(&id, performance::get_time())
.expect("Error rendering"); .map_err(|e| Error::RecoverableError(e.to_string()))?;
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn render_from_cache(_: i32) { #[wasm_error]
pub extern "C" fn render_from_cache(_: i32) -> Result<()> {
with_state_mut!(state, { with_state_mut!(state, {
state.render_state.cancel_animation_frame(); state.render_state.cancel_animation_frame();
state.render_from_cache(); state.render_from_cache();
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_preview_mode(enabled: bool) { #[wasm_error]
pub extern "C" fn set_preview_mode(enabled: bool) -> Result<()> {
with_state_mut!(state, { with_state_mut!(state, {
state.render_state.set_preview_mode(enabled); state.render_state.set_preview_mode(enabled);
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn render_preview() { #[wasm_error]
pub extern "C" fn render_preview() -> Result<()> {
with_state_mut!(state, { with_state_mut!(state, {
state.render_preview(performance::get_time()); state.render_preview(performance::get_time());
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn process_animation_frame(timestamp: i32) { #[wasm_error]
pub extern "C" fn process_animation_frame(timestamp: i32) -> Result<()> {
let result = std::panic::catch_unwind(|| { let result = std::panic::catch_unwind(|| {
with_state_mut!(state, { with_state_mut!(state, {
state state
@ -225,37 +254,45 @@ pub extern "C" fn process_animation_frame(timestamp: i32) {
std::panic::resume_unwind(err); std::panic::resume_unwind(err);
} }
} }
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn reset_canvas() { #[wasm_error]
pub extern "C" fn reset_canvas() -> Result<()> {
with_state_mut!(state, { with_state_mut!(state, {
state.render_state_mut().reset_canvas(); state.render_state_mut().reset_canvas();
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn resize_viewbox(width: i32, height: i32) { #[wasm_error]
pub extern "C" fn resize_viewbox(width: i32, height: i32) -> Result<()> {
with_state_mut!(state, { with_state_mut!(state, {
state.resize(width, height); state.resize(width, height);
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_view(zoom: f32, x: f32, y: f32) { #[wasm_error]
pub extern "C" fn set_view(zoom: f32, x: f32, y: f32) -> Result<()> {
with_state_mut!(state, { with_state_mut!(state, {
performance::begin_measure!("set_view"); performance::begin_measure!("set_view");
let render_state = state.render_state_mut(); let render_state = state.render_state_mut();
render_state.set_view(zoom, x, y); render_state.set_view(zoom, x, y);
performance::end_measure!("set_view"); performance::end_measure!("set_view");
}); });
Ok(())
} }
#[cfg(feature = "profile-macros")] #[cfg(feature = "profile-macros")]
static mut VIEW_INTERACTION_START: i32 = 0; static mut VIEW_INTERACTION_START: i32 = 0;
#[no_mangle] #[no_mangle]
pub extern "C" fn set_view_start() { #[wasm_error]
pub extern "C" fn set_view_start() -> Result<()> {
with_state_mut!(state, { with_state_mut!(state, {
#[cfg(feature = "profile-macros")] #[cfg(feature = "profile-macros")]
unsafe { unsafe {
@ -265,10 +302,12 @@ pub extern "C" fn set_view_start() {
state.render_state.options.set_fast_mode(true); state.render_state.options.set_fast_mode(true);
performance::end_measure!("set_view_start"); performance::end_measure!("set_view_start");
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_view_end() { #[wasm_error]
pub extern "C" fn set_view_end() -> Result<()> {
with_state_mut!(state, { with_state_mut!(state, {
let _end_start = performance::begin_timed_log!("set_view_end"); let _end_start = performance::begin_timed_log!("set_view_end");
performance::begin_measure!("set_view_end"); performance::begin_measure!("set_view_end");
@ -304,17 +343,21 @@ pub extern "C" fn set_view_end() {
performance::console_log!("[PERF] view_interaction: {}ms", total_time); performance::console_log!("[PERF] view_interaction: {}ms", total_time);
} }
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn clear_focus_mode() { #[wasm_error]
pub extern "C" fn clear_focus_mode() -> Result<()> {
with_state_mut!(state, { with_state_mut!(state, {
state.clear_focus_mode(); state.clear_focus_mode();
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_focus_mode() { #[wasm_error]
pub extern "C" fn set_focus_mode() -> Result<()> {
let bytes = mem::bytes(); let bytes = mem::bytes();
let entries: Vec<Uuid> = bytes let entries: Vec<Uuid> = bytes
@ -325,83 +368,111 @@ pub extern "C" fn set_focus_mode() {
with_state_mut!(state, { with_state_mut!(state, {
state.set_focus_mode(entries); state.set_focus_mode(entries);
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn init_shapes_pool(capacity: usize) { #[wasm_error]
pub extern "C" fn init_shapes_pool(capacity: usize) -> Result<()> {
with_state_mut!(state, { with_state_mut!(state, {
state.init_shapes_pool(capacity); state.init_shapes_pool(capacity);
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn use_shape(a: u32, b: u32, c: u32, d: u32) { #[wasm_error]
pub extern "C" fn use_shape(a: u32, b: u32, c: u32, d: u32) -> Result<()> {
with_state_mut!(state, { with_state_mut!(state, {
let id = uuid_from_u32_quartet(a, b, c, d); let id = uuid_from_u32_quartet(a, b, c, d);
state.use_shape(id); state.use_shape(id);
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn touch_shape(a: u32, b: u32, c: u32, d: u32) { #[wasm_error]
pub extern "C" fn touch_shape(a: u32, b: u32, c: u32, d: u32) -> Result<()> {
with_state_mut!(state, { with_state_mut!(state, {
let shape_id = uuid_from_u32_quartet(a, b, c, d); let shape_id = uuid_from_u32_quartet(a, b, c, d);
state.touch_shape(shape_id); state.touch_shape(shape_id);
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_parent(a: u32, b: u32, c: u32, d: u32) { #[wasm_error]
pub extern "C" fn set_parent(a: u32, b: u32, c: u32, d: u32) -> Result<()> {
with_state_mut!(state, { with_state_mut!(state, {
let id = uuid_from_u32_quartet(a, b, c, d); let id = uuid_from_u32_quartet(a, b, c, d);
state.set_parent_for_current_shape(id); state.set_parent_for_current_shape(id);
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_shape_masked_group(masked: bool) { #[wasm_error]
pub extern "C" fn set_shape_masked_group(masked: bool) -> Result<()> {
with_current_shape_mut!(state, |shape: &mut Shape| { with_current_shape_mut!(state, |shape: &mut Shape| {
shape.set_masked(masked); shape.set_masked(masked);
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_shape_selrect(left: f32, top: f32, right: f32, bottom: f32) { #[wasm_error]
pub extern "C" fn set_shape_selrect(left: f32, top: f32, right: f32, bottom: f32) -> Result<()> {
with_current_shape_mut!(state, |shape: &mut Shape| { with_current_shape_mut!(state, |shape: &mut Shape| {
shape.set_selrect(left, top, right, bottom); shape.set_selrect(left, top, right, bottom);
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_shape_clip_content(clip_content: bool) { #[wasm_error]
pub extern "C" fn set_shape_clip_content(clip_content: bool) -> Result<()> {
with_current_shape_mut!(state, |shape: &mut Shape| { with_current_shape_mut!(state, |shape: &mut Shape| {
shape.set_clip(clip_content); shape.set_clip(clip_content);
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_shape_rotation(rotation: f32) { #[wasm_error]
pub extern "C" fn set_shape_rotation(rotation: f32) -> Result<()> {
with_current_shape_mut!(state, |shape: &mut Shape| { with_current_shape_mut!(state, |shape: &mut Shape| {
shape.set_rotation(rotation); shape.set_rotation(rotation);
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_shape_transform(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) { #[wasm_error]
pub extern "C" fn set_shape_transform(
a: f32,
b: f32,
c: f32,
d: f32,
e: f32,
f: f32,
) -> Result<()> {
with_current_shape_mut!(state, |shape: &mut Shape| { with_current_shape_mut!(state, |shape: &mut Shape| {
shape.set_transform(a, b, c, d, e, f); shape.set_transform(a, b, c, d, e, f);
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn add_shape_child(a: u32, b: u32, c: u32, d: u32) { #[wasm_error]
pub extern "C" fn add_shape_child(a: u32, b: u32, c: u32, d: u32) -> Result<()> {
with_current_shape_mut!(state, |shape: &mut Shape| { with_current_shape_mut!(state, |shape: &mut Shape| {
let id = uuid_from_u32_quartet(a, b, c, d); let id = uuid_from_u32_quartet(a, b, c, d);
shape.add_child(id); shape.add_child(id);
}); });
Ok(())
} }
fn set_children_set(entries: Vec<Uuid>) { fn set_children_set(entries: Vec<Uuid>) -> Result<()> {
let mut deleted = Vec::new(); let mut deleted = Vec::new();
let mut parent_id = None; let mut parent_id = None;
@ -420,7 +491,9 @@ fn set_children_set(entries: Vec<Uuid>) {
with_state_mut!(state, { with_state_mut!(state, {
let Some(parent_id) = parent_id else { let Some(parent_id) = parent_id else {
return; return Err(Error::RecoverableError(
"set_children_set: Parent ID not found".to_string(),
));
}; };
for id in deleted { for id in deleted {
@ -428,21 +501,27 @@ fn set_children_set(entries: Vec<Uuid>) {
state.touch_shape(id); state.touch_shape(id);
} }
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_children_0() { #[wasm_error]
pub extern "C" fn set_children_0() -> Result<()> {
let entries = vec![]; let entries = vec![];
set_children_set(entries); set_children_set(entries)?;
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_children_1(a1: u32, b1: u32, c1: u32, d1: u32) { #[wasm_error]
pub extern "C" fn set_children_1(a1: u32, b1: u32, c1: u32, d1: u32) -> Result<()> {
let entries = vec![uuid_from_u32_quartet(a1, b1, c1, d1)]; let entries = vec![uuid_from_u32_quartet(a1, b1, c1, d1)];
set_children_set(entries); set_children_set(entries)?;
Ok(())
} }
#[no_mangle] #[no_mangle]
#[wasm_error]
pub extern "C" fn set_children_2( pub extern "C" fn set_children_2(
a1: u32, a1: u32,
b1: u32, b1: u32,
@ -452,15 +531,17 @@ pub extern "C" fn set_children_2(
b2: u32, b2: u32,
c2: u32, c2: u32,
d2: u32, d2: u32,
) { ) -> Result<()> {
let entries = vec![ let entries = vec![
uuid_from_u32_quartet(a1, b1, c1, d1), uuid_from_u32_quartet(a1, b1, c1, d1),
uuid_from_u32_quartet(a2, b2, c2, d2), uuid_from_u32_quartet(a2, b2, c2, d2),
]; ];
set_children_set(entries); set_children_set(entries)?;
Ok(())
} }
#[no_mangle] #[no_mangle]
#[wasm_error]
pub extern "C" fn set_children_3( pub extern "C" fn set_children_3(
a1: u32, a1: u32,
b1: u32, b1: u32,
@ -474,16 +555,18 @@ pub extern "C" fn set_children_3(
b3: u32, b3: u32,
c3: u32, c3: u32,
d3: u32, d3: u32,
) { ) -> Result<()> {
let entries = vec![ let entries = vec![
uuid_from_u32_quartet(a1, b1, c1, d1), uuid_from_u32_quartet(a1, b1, c1, d1),
uuid_from_u32_quartet(a2, b2, c2, d2), uuid_from_u32_quartet(a2, b2, c2, d2),
uuid_from_u32_quartet(a3, b3, c3, d3), uuid_from_u32_quartet(a3, b3, c3, d3),
]; ];
set_children_set(entries); set_children_set(entries)?;
Ok(())
} }
#[no_mangle] #[no_mangle]
#[wasm_error]
pub extern "C" fn set_children_4( pub extern "C" fn set_children_4(
a1: u32, a1: u32,
b1: u32, b1: u32,
@ -501,17 +584,19 @@ pub extern "C" fn set_children_4(
b4: u32, b4: u32,
c4: u32, c4: u32,
d4: u32, d4: u32,
) { ) -> Result<()> {
let entries = vec![ let entries = vec![
uuid_from_u32_quartet(a1, b1, c1, d1), uuid_from_u32_quartet(a1, b1, c1, d1),
uuid_from_u32_quartet(a2, b2, c2, d2), uuid_from_u32_quartet(a2, b2, c2, d2),
uuid_from_u32_quartet(a3, b3, c3, d3), uuid_from_u32_quartet(a3, b3, c3, d3),
uuid_from_u32_quartet(a4, b4, c4, d4), uuid_from_u32_quartet(a4, b4, c4, d4),
]; ];
set_children_set(entries); set_children_set(entries)?;
Ok(())
} }
#[no_mangle] #[no_mangle]
#[wasm_error]
pub extern "C" fn set_children_5( pub extern "C" fn set_children_5(
a1: u32, a1: u32,
b1: u32, b1: u32,
@ -533,7 +618,7 @@ pub extern "C" fn set_children_5(
b5: u32, b5: u32,
c5: u32, c5: u32,
d5: u32, d5: u32,
) { ) -> Result<()> {
let entries = vec![ let entries = vec![
uuid_from_u32_quartet(a1, b1, c1, d1), uuid_from_u32_quartet(a1, b1, c1, d1),
uuid_from_u32_quartet(a2, b2, c2, d2), uuid_from_u32_quartet(a2, b2, c2, d2),
@ -541,11 +626,13 @@ pub extern "C" fn set_children_5(
uuid_from_u32_quartet(a4, b4, c4, d4), uuid_from_u32_quartet(a4, b4, c4, d4),
uuid_from_u32_quartet(a5, b5, c5, d5), uuid_from_u32_quartet(a5, b5, c5, d5),
]; ];
set_children_set(entries); set_children_set(entries)?;
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_children() { #[wasm_error]
pub extern "C" fn set_children() -> Result<()> {
let bytes = mem::bytes_or_empty(); let bytes = mem::bytes_or_empty();
let entries: Vec<Uuid> = bytes let entries: Vec<Uuid> = bytes
@ -553,58 +640,76 @@ pub extern "C" fn set_children() {
.map(|data| Uuid::try_from(data).unwrap()) .map(|data| Uuid::try_from(data).unwrap())
.collect(); .collect();
set_children_set(entries); set_children_set(entries)?;
if !bytes.is_empty() { if !bytes.is_empty() {
mem::free_bytes(); mem::free_bytes()?;
} }
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn is_image_cached(a: u32, b: u32, c: u32, d: u32, is_thumbnail: bool) -> bool { #[wasm_error]
pub extern "C" fn is_image_cached(
a: u32,
b: u32,
c: u32,
d: u32,
is_thumbnail: bool,
) -> Result<bool> {
with_state_mut!(state, { with_state_mut!(state, {
let id = uuid_from_u32_quartet(a, b, c, d); let id = uuid_from_u32_quartet(a, b, c, d);
state.render_state().has_image(&id, is_thumbnail) let result = state.render_state().has_image(&id, is_thumbnail);
Ok(result)
}) })
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_shape_svg_raw_content() { #[wasm_error]
pub extern "C" fn set_shape_svg_raw_content() -> Result<()> {
with_current_shape_mut!(state, |shape: &mut Shape| { with_current_shape_mut!(state, |shape: &mut Shape| {
let bytes = mem::bytes(); let bytes = mem::bytes();
let svg_raw_content = String::from_utf8(bytes) let svg_raw_content = String::from_utf8(bytes)
.unwrap() .map_err(|e| Error::RecoverableError(e.to_string()))?
.trim_end_matches('\0') .trim_end_matches('\0')
.to_string(); .to_string();
shape shape.set_svg_raw_content(svg_raw_content);
.set_svg_raw_content(svg_raw_content)
.expect("Failed to set svg raw content");
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_shape_opacity(opacity: f32) { #[wasm_error]
pub extern "C" fn set_shape_opacity(opacity: f32) -> Result<()> {
with_current_shape_mut!(state, |shape: &mut Shape| { with_current_shape_mut!(state, |shape: &mut Shape| {
shape.set_opacity(opacity); shape.set_opacity(opacity);
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_shape_hidden(hidden: bool) { #[wasm_error]
pub extern "C" fn set_shape_hidden(hidden: bool) -> Result<()> {
with_current_shape_mut!(state, |shape: &mut Shape| { with_current_shape_mut!(state, |shape: &mut Shape| {
shape.set_hidden(hidden); shape.set_hidden(hidden);
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_shape_corners(r1: f32, r2: f32, r3: f32, r4: f32) { #[wasm_error]
pub extern "C" fn set_shape_corners(r1: f32, r2: f32, r3: f32, r4: f32) -> Result<()> {
with_current_shape_mut!(state, |shape: &mut Shape| { with_current_shape_mut!(state, |shape: &mut Shape| {
shape.set_corners((r1, r2, r3, r4)); shape.set_corners((r1, r2, r3, r4));
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn get_selection_rect() -> *mut u8 { #[wasm_error]
pub extern "C" fn get_selection_rect() -> Result<*mut u8> {
let bytes = mem::bytes(); let bytes = mem::bytes();
let entries: Vec<Uuid> = bytes let entries: Vec<Uuid> = bytes
@ -619,40 +724,41 @@ pub extern "C" fn get_selection_rect() -> *mut u8 {
}) })
.collect(); .collect();
with_state_mut!(state, { let result_bound = with_state_mut!(state, {
let bbs: Vec<_> = entries let bbs: Vec<_> = entries
.iter() .iter()
.flat_map(|id| state.shapes.get(id).map(|b| b.bounds())) .flat_map(|id| state.shapes.get(id).map(|b| b.bounds()))
.collect(); .collect();
let result_bound = if bbs.len() == 1 { if bbs.len() == 1 {
bbs[0] bbs[0]
} else { } else {
Bounds::join_bounds(&bbs) Bounds::join_bounds(&bbs)
}; }
});
let width = result_bound.width(); let width = result_bound.width();
let height = result_bound.height(); let height = result_bound.height();
let center = result_bound.center(); let center = result_bound.center();
let transform = result_bound.transform_matrix().unwrap_or(Matrix::default()); let transform = result_bound.transform_matrix().unwrap_or(Matrix::default());
let mut bytes = vec![0; 40]; let mut bytes = vec![0; 40];
bytes[0..4].clone_from_slice(&width.to_le_bytes()); bytes[0..4].clone_from_slice(&width.to_le_bytes());
bytes[4..8].clone_from_slice(&height.to_le_bytes()); bytes[4..8].clone_from_slice(&height.to_le_bytes());
bytes[8..12].clone_from_slice(&center.x.to_le_bytes()); bytes[8..12].clone_from_slice(&center.x.to_le_bytes());
bytes[12..16].clone_from_slice(&center.y.to_le_bytes()); bytes[12..16].clone_from_slice(&center.y.to_le_bytes());
bytes[16..20].clone_from_slice(&transform[0].to_le_bytes()); bytes[16..20].clone_from_slice(&transform[0].to_le_bytes());
bytes[20..24].clone_from_slice(&transform[3].to_le_bytes()); bytes[20..24].clone_from_slice(&transform[3].to_le_bytes());
bytes[24..28].clone_from_slice(&transform[1].to_le_bytes()); bytes[24..28].clone_from_slice(&transform[1].to_le_bytes());
bytes[28..32].clone_from_slice(&transform[4].to_le_bytes()); bytes[28..32].clone_from_slice(&transform[4].to_le_bytes());
bytes[32..36].clone_from_slice(&transform[2].to_le_bytes()); bytes[32..36].clone_from_slice(&transform[2].to_le_bytes());
bytes[36..40].clone_from_slice(&transform[5].to_le_bytes()); bytes[36..40].clone_from_slice(&transform[5].to_le_bytes());
mem::write_bytes(bytes) Ok(mem::write_bytes(bytes))
})
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_structure_modifiers() { #[wasm_error]
pub extern "C" fn set_structure_modifiers() -> Result<()> {
let bytes = mem::bytes(); let bytes = mem::bytes();
let entries: Vec<_> = bytes let entries: Vec<_> = bytes
@ -690,18 +796,22 @@ pub extern "C" fn set_structure_modifiers() {
} }
}); });
mem::free_bytes(); mem::free_bytes()?;
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn clean_modifiers() { #[wasm_error]
pub extern "C" fn clean_modifiers() -> Result<()> {
with_state_mut!(state, { with_state_mut!(state, {
state.shapes.clean_all(); state.shapes.clean_all();
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_modifiers() { #[wasm_error]
pub extern "C" fn set_modifiers() -> Result<()> {
let bytes = mem::bytes(); let bytes = mem::bytes();
let entries: Vec<_> = bytes let entries: Vec<_> = bytes
@ -720,26 +830,31 @@ pub extern "C" fn set_modifiers() {
state.set_modifiers(modifiers); state.set_modifiers(modifiers);
state.rebuild_modifier_tiles(ids); state.rebuild_modifier_tiles(ids);
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn start_temp_objects() { #[wasm_error]
pub extern "C" fn start_temp_objects() -> Result<()> {
unsafe { unsafe {
#[allow(static_mut_refs)] #[allow(static_mut_refs)]
let mut state = STATE.take().expect("Got an invalid state pointer"); let mut state = STATE.take().expect("Got an invalid state pointer");
state = Box::new(state.start_temp_objects()); state = Box::new(state.start_temp_objects());
STATE = Some(state); STATE = Some(state);
} }
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn end_temp_objects() { #[wasm_error]
pub extern "C" fn end_temp_objects() -> Result<()> {
unsafe { unsafe {
#[allow(static_mut_refs)] #[allow(static_mut_refs)]
let mut state = STATE.take().expect("Got an invalid state pointer"); let mut state = STATE.take().expect("Got an invalid state pointer");
state = Box::new(state.end_temp_objects()); state = Box::new(state.end_temp_objects());
STATE = Some(state); STATE = Some(state);
} }
Ok(())
} }
fn main() { fn main() {

View File

@ -1,29 +1,29 @@
use std::alloc::{alloc, Layout};
use std::ptr;
use std::sync::Mutex; use std::sync::Mutex;
const LAYOUT_ALIGN: usize = 4; use crate::error::{Error, Result, CRITICAL_ERROR};
static BUFFERU8: Mutex<Option<Vec<u8>>> = Mutex::new(None); pub const LAYOUT_ALIGN: usize = 4;
pub static BUFFERU8: Mutex<Option<Vec<u8>>> = Mutex::new(None);
pub static BUFFER_ERROR: Mutex<u8> = Mutex::new(0x00);
pub fn clear_error_code() {
let mut guard = BUFFER_ERROR.lock().unwrap();
*guard = 0x00;
}
/// Sets the error buffer from a byte. Used by #[wasm_error] when E: Into<u8>.
pub fn set_error_code(code: u8) {
let mut guard = BUFFER_ERROR.lock().unwrap();
*guard = code;
}
#[no_mangle] #[no_mangle]
pub extern "C" fn alloc_bytes(len: usize) -> *mut u8 { pub extern "C" fn read_error_code() -> u8 {
let mut guard = BUFFERU8.lock().unwrap(); if let Ok(guard) = BUFFER_ERROR.lock() {
*guard
if guard.is_some() { } else {
panic!("Bytes already allocated"); CRITICAL_ERROR
}
unsafe {
let layout = Layout::from_size_align_unchecked(len, LAYOUT_ALIGN);
let ptr = alloc(layout);
if ptr.is_null() {
panic!("Allocation failed");
}
// TODO: Maybe this could be removed.
ptr::write_bytes(ptr, 0, len);
*guard = Some(Vec::from_raw_parts(ptr, len, len));
ptr
} }
} }
@ -40,13 +40,6 @@ pub fn write_bytes(mut bytes: Vec<u8>) -> *mut u8 {
ptr ptr
} }
#[no_mangle]
pub extern "C" fn free_bytes() {
let mut guard = BUFFERU8.lock().unwrap();
*guard = None;
std::mem::drop(guard);
}
pub fn bytes() -> Vec<u8> { pub fn bytes() -> Vec<u8> {
let mut guard = BUFFERU8.lock().unwrap(); let mut guard = BUFFERU8.lock().unwrap();
guard.take().expect("Buffer is not initialized") guard.take().expect("Buffer is not initialized")
@ -57,6 +50,15 @@ pub fn bytes_or_empty() -> Vec<u8> {
guard.take().unwrap_or_default() guard.take().unwrap_or_default()
} }
pub fn free_bytes() -> Result<()> {
let mut guard = BUFFERU8
.lock()
.map_err(|_| Error::CriticalError("Failed to lock buffer".to_string()))?;
*guard = None;
std::mem::drop(guard);
Ok(())
}
pub trait SerializableResult: From<Self::BytesType> + Into<Self::BytesType> { pub trait SerializableResult: From<Self::BytesType> + Into<Self::BytesType> {
type BytesType; type BytesType;
fn clone_to_slice(&self, slice: &mut [u8]); fn clone_to_slice(&self, slice: &mut [u8]);

View File

@ -705,9 +705,8 @@ impl Shape {
self.invalidate_extrect(); self.invalidate_extrect();
} }
pub fn set_svg_raw_content(&mut self, content: String) -> Result<(), String> { pub fn set_svg_raw_content(&mut self, content: String) {
self.shape_type = Type::SVGRaw(SVGRaw::from_content(content)); self.shape_type = Type::SVGRaw(SVGRaw::from_content(content));
Ok(())
} }
pub fn set_blend_mode(&mut self, mode: BlendMode) { pub fn set_blend_mode(&mut self, mode: BlendMode) {

View File

@ -3,6 +3,7 @@ pub mod blurs;
pub mod fills; pub mod fills;
pub mod fonts; pub mod fonts;
pub mod layouts; pub mod layouts;
pub mod mem;
pub mod paths; pub mod paths;
pub mod shadows; pub mod shadows;
pub mod shapes; pub mod shapes;

View File

@ -1,4 +1,4 @@
use macros::ToJs; use macros::{wasm_error, ToJs};
use crate::mem; use crate::mem;
use crate::shapes; use crate::shapes;
@ -67,7 +67,8 @@ pub fn parse_fills_from_bytes(buffer: &[u8], num_fills: usize) -> Vec<shapes::Fi
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_shape_fills() { #[wasm_error]
pub extern "C" fn set_shape_fills() -> Result<()> {
with_current_shape_mut!(state, |shape: &mut Shape| { with_current_shape_mut!(state, |shape: &mut Shape| {
let bytes = mem::bytes(); let bytes = mem::bytes();
// The first byte contains the actual number of fills // The first byte contains the actual number of fills
@ -75,8 +76,9 @@ pub extern "C" fn set_shape_fills() {
// Skip the first 4 bytes (header with fill count) and parse only the actual fills // Skip the first 4 bytes (header with fill count) and parse only the actual fills
let fills = parse_fills_from_bytes(&bytes[4..], num_fills); let fills = parse_fills_from_bytes(&bytes[4..], num_fills);
shape.set_fills(fills); shape.set_fills(fills);
mem::free_bytes(); mem::free_bytes()?;
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]

View File

@ -1,5 +1,7 @@
use crate::mem; use crate::mem;
use macros::wasm_error;
// use crate::mem::SerializableResult; // use crate::mem::SerializableResult;
use crate::error::Error;
use crate::uuid::Uuid; use crate::uuid::Uuid;
use crate::with_state_mut; use crate::with_state_mut;
use crate::STATE; use crate::STATE;
@ -65,7 +67,8 @@ impl TryFrom<Vec<u8>> for ShapeImageIds {
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn store_image() { #[wasm_error]
pub extern "C" fn store_image() -> crate::error::Result<()> {
let bytes = mem::bytes(); let bytes = mem::bytes();
let ids = ShapeImageIds::try_from(bytes[0..IMAGE_IDS_SIZE].to_vec()).unwrap(); let ids = ShapeImageIds::try_from(bytes[0..IMAGE_IDS_SIZE].to_vec()).unwrap();
@ -87,7 +90,8 @@ pub extern "C" fn store_image() {
state.touch_shape(ids.shape_id); state.touch_shape(ids.shape_id);
}); });
mem::free_bytes(); mem::free_bytes()?;
Ok(())
} }
/// Stores an image from an existing WebGL texture, avoiding re-decoding /// Stores an image from an existing WebGL texture, avoiding re-decoding
@ -99,13 +103,17 @@ pub extern "C" fn store_image() {
/// - bytes 40-43: width (i32) /// - bytes 40-43: width (i32)
/// - bytes 44-47: height (i32) /// - bytes 44-47: height (i32)
#[no_mangle] #[no_mangle]
pub extern "C" fn store_image_from_texture() { #[wasm_error]
pub extern "C" fn store_image_from_texture() -> crate::error::Result<()> {
let bytes = mem::bytes(); let bytes = mem::bytes();
if bytes.len() < 48 { if bytes.len() < 48 {
// FIXME: Review if this should be an critical or a recoverable error.
eprintln!("store_image_from_texture: insufficient data"); eprintln!("store_image_from_texture: insufficient data");
mem::free_bytes(); mem::free_bytes()?;
return; return Err(Error::RecoverableError(
"store_image_from_texture: insufficient data".to_string(),
));
} }
let ids = ShapeImageIds::try_from(bytes[0..IMAGE_IDS_SIZE].to_vec()).unwrap(); let ids = ShapeImageIds::try_from(bytes[0..IMAGE_IDS_SIZE].to_vec()).unwrap();
@ -139,5 +147,6 @@ pub extern "C" fn store_image_from_texture() {
state.touch_shape(ids.shape_id); state.touch_shape(ids.shape_id);
}); });
mem::free_bytes(); mem::free_bytes()?;
Ok(())
} }

View File

@ -1,4 +1,4 @@
use macros::ToJs; use macros::{wasm_error, ToJs};
use crate::mem; use crate::mem;
use crate::shapes::{FontFamily, FontStyle}; use crate::shapes::{FontFamily, FontStyle};
@ -30,6 +30,7 @@ impl From<RawFontStyle> for FontStyle {
} }
#[no_mangle] #[no_mangle]
#[wasm_error]
pub extern "C" fn store_font( pub extern "C" fn store_font(
a: u32, a: u32,
b: u32, b: u32,
@ -39,7 +40,7 @@ pub extern "C" fn store_font(
style: u8, style: u8,
is_emoji: bool, is_emoji: bool,
is_fallback: bool, is_fallback: bool,
) { ) -> Result<()> {
with_state_mut!(state, { with_state_mut!(state, {
let id = uuid_from_u32_quartet(a, b, c, d); let id = uuid_from_u32_quartet(a, b, c, d);
let font_bytes = mem::bytes(); let font_bytes = mem::bytes();
@ -52,8 +53,9 @@ pub extern "C" fn store_font(
.fonts_mut() .fonts_mut()
.add(family, &font_bytes, is_emoji, is_fallback); .add(family, &font_bytes, is_emoji, is_fallback);
mem::free_bytes(); mem::free_bytes()?;
}); });
Ok(())
} }
#[no_mangle] #[no_mangle]

View File

@ -1,4 +1,4 @@
use macros::ToJs; use macros::{wasm_error, ToJs};
use crate::mem; use crate::mem;
use crate::shapes::{GridCell, GridDirection, GridTrack, GridTrackType}; use crate::shapes::{GridCell, GridDirection, GridTrack, GridTrackType};
@ -7,6 +7,9 @@ use crate::{uuid_from_u32_quartet, with_current_shape_mut, with_state, with_stat
use super::align; use super::align;
#[allow(unused_imports)]
use crate::error::Result;
#[derive(Debug)] #[derive(Debug)]
#[repr(C, align(1))] #[repr(C, align(1))]
struct RawGridCell { struct RawGridCell {
@ -168,7 +171,8 @@ pub extern "C" fn set_grid_layout_data(
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_grid_columns() { #[wasm_error]
pub extern "C" fn set_grid_columns() -> Result<()> {
let bytes = mem::bytes(); let bytes = mem::bytes();
let entries: Vec<GridTrack> = bytes let entries: Vec<GridTrack> = bytes
@ -181,11 +185,13 @@ pub extern "C" fn set_grid_columns() {
shape.set_grid_columns(entries); shape.set_grid_columns(entries);
}); });
mem::free_bytes(); mem::free_bytes()?;
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_grid_rows() { #[wasm_error]
pub extern "C" fn set_grid_rows() -> Result<()> {
let bytes = mem::bytes(); let bytes = mem::bytes();
let entries: Vec<GridTrack> = bytes let entries: Vec<GridTrack> = bytes
@ -198,11 +204,13 @@ pub extern "C" fn set_grid_rows() {
shape.set_grid_rows(entries); shape.set_grid_rows(entries);
}); });
mem::free_bytes(); mem::free_bytes()?;
Ok(())
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_grid_cells() { #[wasm_error]
pub extern "C" fn set_grid_cells() -> Result<()> {
let bytes = mem::bytes(); let bytes = mem::bytes();
let cells: Vec<RawGridCell> = bytes let cells: Vec<RawGridCell> = bytes
@ -215,7 +223,8 @@ pub extern "C" fn set_grid_cells() {
shape.set_grid_cells(cells.into_iter().map(|raw| raw.into()).collect()); shape.set_grid_cells(cells.into_iter().map(|raw| raw.into()).collect());
}); });
mem::free_bytes(); mem::free_bytes()?;
Ok(())
} }
#[no_mangle] #[no_mangle]

View File

@ -0,0 +1,38 @@
use std::alloc::{alloc, Layout};
use std::ptr;
#[allow(unused_imports)]
use crate::error::{Error, Result};
use crate::mem::{BUFFERU8, LAYOUT_ALIGN};
use macros::wasm_error;
#[no_mangle]
#[wasm_error]
pub extern "C" fn alloc_bytes(len: usize) -> Result<*mut u8> {
let mut guard = BUFFERU8
.lock()
.map_err(|_| Error::CriticalError("Failed to lock buffer".to_string()))?;
if guard.is_some() {
return Err(Error::CriticalError("Bytes already allocated".to_string()));
}
unsafe {
let layout = Layout::from_size_align_unchecked(len, LAYOUT_ALIGN);
let ptr = alloc(layout);
if ptr.is_null() {
return Err(Error::CriticalError("Allocation failed".to_string()));
}
// TODO: Maybe this could be removed.
ptr::write_bytes(ptr, 0, len);
*guard = Some(Vec::from_raw_parts(ptr, len, len));
Ok(ptr)
}
}
#[no_mangle]
#[wasm_error]
pub extern "C" fn free_bytes() -> Result<()> {
crate::mem::free_bytes()?;
Ok(())
}

View File

@ -1,5 +1,5 @@
#![allow(unused_mut, unused_variables)] #![allow(unused_mut, unused_variables)]
use macros::ToJs; use macros::{wasm_error, ToJs};
use mem::SerializableResult; use mem::SerializableResult;
use std::mem::size_of; use std::mem::size_of;
use std::sync::{Mutex, OnceLock}; use std::sync::{Mutex, OnceLock};
@ -161,12 +161,14 @@ pub extern "C" fn start_shape_path_buffer() {
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_shape_path_chunk_buffer() { #[wasm_error]
pub extern "C" fn set_shape_path_chunk_buffer() -> Result<()> {
let bytes = mem::bytes(); let bytes = mem::bytes();
let buffer = get_path_upload_buffer(); let buffer = get_path_upload_buffer();
let mut buffer = buffer.lock().unwrap(); let mut buffer = buffer.lock().unwrap();
buffer.extend_from_slice(&bytes); buffer.extend_from_slice(&bytes);
mem::free_bytes(); mem::free_bytes()?;
Ok(())
} }
#[no_mangle] #[no_mangle]

View File

@ -1,4 +1,4 @@
use macros::ToJs; use macros::{wasm_error, ToJs};
use super::RawSegmentData; use super::RawSegmentData;
use crate::math; use crate::math;
@ -8,6 +8,9 @@ use crate::{mem, SerializableResult};
use crate::{with_current_shape_mut, with_state, STATE}; use crate::{with_current_shape_mut, with_state, STATE};
use std::mem::size_of; use std::mem::size_of;
#[allow(unused_imports)]
use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, ToJs)] #[derive(Debug, Clone, Copy, PartialEq, ToJs)]
#[repr(u8)] #[repr(u8)]
#[allow(dead_code)] #[allow(dead_code)]
@ -43,15 +46,19 @@ pub extern "C" fn set_shape_bool_type(raw_bool_type: u8) {
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn calculate_bool(raw_bool_type: u8) -> *mut u8 { #[wasm_error]
pub extern "C" fn calculate_bool(raw_bool_type: u8) -> Result<*mut u8> {
let bytes = mem::bytes_or_empty(); let bytes = mem::bytes_or_empty();
let entries: Vec<Uuid> = bytes let entries: Vec<Uuid> = bytes
.chunks(size_of::<<Uuid as SerializableResult>::BytesType>()) .chunks(size_of::<<Uuid as SerializableResult>::BytesType>())
.map(|data| Uuid::try_from(data).unwrap()) .map(|data| {
.collect(); // FIXME: Review if this should be an critical or a recoverable error.
Uuid::try_from(data).map_err(|_| Error::RecoverableError("Invalid UUID".to_string()))
})
.collect::<Result<Vec<Uuid>>>()?;
mem::free_bytes(); mem::free_bytes()?;
let bool_type = RawBoolType::from(raw_bool_type).into(); let bool_type = RawBoolType::from(raw_bool_type).into();
let result; let result;
@ -64,5 +71,5 @@ pub extern "C" fn calculate_bool(raw_bool_type: u8) -> *mut u8 {
.map(RawSegmentData::from_segment) .map(RawSegmentData::from_segment)
.collect(); .collect();
}); });
mem::write_vec(result) Ok(mem::write_vec(result))
} }

View File

@ -1,4 +1,4 @@
use macros::ToJs; use macros::{wasm_error, ToJs};
use super::{fills::RawFillData, fonts::RawFontStyle}; use super::{fills::RawFillData, fonts::RawFontStyle};
@ -9,6 +9,8 @@ use crate::shapes::{
use crate::utils::{uuid_from_u32, uuid_from_u32_quartet}; use crate::utils::{uuid_from_u32, uuid_from_u32_quartet};
use crate::{with_current_shape, with_current_shape_mut, with_state, with_state_mut, STATE}; use crate::{with_current_shape, with_current_shape_mut, with_state, with_state_mut, STATE};
use crate::error::Error;
const RAW_SPAN_DATA_SIZE: usize = std::mem::size_of::<RawTextSpan>(); const RAW_SPAN_DATA_SIZE: usize = std::mem::size_of::<RawTextSpan>();
const RAW_PARAGRAPH_DATA_SIZE: usize = std::mem::size_of::<RawParagraphData>(); const RAW_PARAGRAPH_DATA_SIZE: usize = std::mem::size_of::<RawParagraphData>();
@ -285,16 +287,22 @@ pub extern "C" fn clear_shape_text() {
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn set_shape_text_content() { #[wasm_error]
pub extern "C" fn set_shape_text_content() -> crate::error::Result<()> {
let bytes = mem::bytes(); let bytes = mem::bytes();
with_current_shape_mut!(state, |shape: &mut Shape| { with_current_shape_mut!(state, |shape: &mut Shape| {
let raw_text_data = RawParagraph::try_from(&bytes).unwrap(); let raw_text_data = RawParagraph::try_from(&bytes).unwrap();
if shape.add_paragraph(raw_text_data.into()).is_err() { shape.add_paragraph(raw_text_data.into()).map_err(|_| {
println!("Error with set_shape_text_content on {:?}", shape.id); Error::RecoverableError(format!(
} "Error with set_shape_text_content on {:?}",
shape.id
))
})?;
}); });
mem::free_bytes();
mem::free_bytes()?;
Ok(())
} }
#[no_mangle] #[no_mangle]

View File

@ -1,3 +1,5 @@
use macros::{wasm_error, ToJs};
use crate::math::{Matrix, Point, Rect}; use crate::math::{Matrix, Point, Rect};
use crate::mem; use crate::mem;
use crate::shapes::{Paragraph, Shape, TextContent, TextPositionWithAffinity, Type, VerticalAlign}; use crate::shapes::{Paragraph, Shape, TextContent, TextPositionWithAffinity, Type, VerticalAlign};
@ -5,7 +7,6 @@ use crate::state::TextSelection;
use crate::utils::uuid_from_u32_quartet; use crate::utils::uuid_from_u32_quartet;
use crate::utils::uuid_to_u32_quartet; use crate::utils::uuid_to_u32_quartet;
use crate::{with_state, with_state_mut, STATE}; use crate::{with_state, with_state_mut, STATE};
use macros::ToJs;
use skia_safe::Color; use skia_safe::Color;
#[derive(PartialEq, ToJs)] #[derive(PartialEq, ToJs)]
@ -263,29 +264,31 @@ pub extern "C" fn text_editor_set_cursor_from_point(x: f32, y: f32) {
// TEXT OPERATIONS // TEXT OPERATIONS
// ============================================================================ // ============================================================================
// FIXME: Review if all the return Ok(()) should be Err instead.
#[no_mangle] #[no_mangle]
pub extern "C" fn text_editor_insert_text() { #[wasm_error]
pub extern "C" fn text_editor_insert_text() -> Result<()> {
let bytes = crate::mem::bytes(); let bytes = crate::mem::bytes();
let text = match String::from_utf8(bytes) { let text = match String::from_utf8(bytes) {
Ok(s) => s, Ok(text) => text,
Err(_) => return, Err(_) => return Ok(()),
}; };
with_state_mut!(state, { with_state_mut!(state, {
if !state.text_editor_state.is_active { if !state.text_editor_state.is_active {
return; return Ok(());
} }
let Some(shape_id) = state.text_editor_state.active_shape_id else { let Some(shape_id) = state.text_editor_state.active_shape_id else {
return; return Ok(());
}; };
let Some(shape) = state.shapes.get_mut(&shape_id) else { let Some(shape) = state.shapes.get_mut(&shape_id) else {
return; return Ok(());
}; };
let Type::Text(text_content) = &mut shape.shape_type else { let Type::Text(text_content) = &mut shape.shape_type else {
return; return Ok(());
}; };
let selection = state.text_editor_state.selection; let selection = state.text_editor_state.selection;
@ -316,7 +319,8 @@ pub extern "C" fn text_editor_insert_text() {
state.render_state.mark_touched(shape_id); state.render_state.mark_touched(shape_id);
}); });
crate::mem::free_bytes(); crate::mem::free_bytes()?;
Ok(())
} }
#[no_mangle] #[no_mangle]