mirror of
https://github.com/penpot/penpot.git
synced 2026-02-24 18:56:28 -05:00
Compare commits
2 Commits
develop
...
ladybenko-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1495240914 | ||
|
|
2562d31fa3 |
@@ -116,6 +116,17 @@
|
||||
(ex/print-throwable cause :prefix "Unexpected Error")
|
||||
(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
|
||||
;; workspace, dashboard, viewer or settings, then assign the exception
|
||||
;; 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 "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]
|
||||
(.preventDefault ^js event)
|
||||
(when-let [cause (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)))))
|
||||
(handle-uncaught (unchecked-get event "error")))
|
||||
|
||||
(on-unhandled-rejection [event]
|
||||
(.preventDefault ^js event)
|
||||
(when-let [cause (unchecked-get event "reason")]
|
||||
(set! last-exception cause)
|
||||
(ex/print-throwable cause :prefix "Uncaught Rejection")
|
||||
(ts/schedule #(show-not-blocking-error cause))))]
|
||||
(handle-uncaught (unchecked-get event "reason")))]
|
||||
|
||||
(.addEventListener g/window "error" on-unhandled-error)
|
||||
(.addEventListener g/window "unhandledrejection" on-unhandled-rejection)
|
||||
|
||||
@@ -1368,8 +1368,10 @@
|
||||
([base-objects zoom vbox background callback]
|
||||
(let [rgba (sr-clr/hex->u32argb background 1)
|
||||
shapes (into [] (vals base-objects))
|
||||
total-shapes (count shapes)]
|
||||
(h/call wasm/internal-module "_set_canvas_background" rgba)
|
||||
total-shapes (count shapes)
|
||||
result (h/call wasm/internal-module "_set_canvas_background" rgba)]
|
||||
(println "set-canvas-background result:" result)
|
||||
;; (h/call wasm/internal-module "_set_canvas_background" rgba)
|
||||
(h/call wasm/internal-module "_set_view" zoom (- (:x vbox)) (- (:y vbox)))
|
||||
(h/call wasm/internal-module "_init_shapes_pool" total-shapes)
|
||||
(set-objects base-objects callback))))
|
||||
|
||||
@@ -7,11 +7,28 @@
|
||||
(ns 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
|
||||
"A helper for easy call wasm defined function in a module."
|
||||
"A helper for easy call wasm defined function in a module.
|
||||
Catches any exception thrown by the WASM function, reads the error code from
|
||||
WASM when available, and rethrows ex-info with :type (:wasm-non-blocking or
|
||||
:wasm-critical) in ex-data. The uncaught-error-handler in app.main.errors
|
||||
routes these to on-error so critical shows Internal Error, non-blocking shows toast."
|
||||
[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)]
|
||||
;; DEBUG
|
||||
;; (println "##" ~name)
|
||||
(~fn-sym ~@params))))
|
||||
(try
|
||||
(~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)]
|
||||
(throw ex#)))))))
|
||||
|
||||
30
render-wasm/Cargo.lock
generated
30
render-wasm/Cargo.lock
generated
@@ -17,6 +17,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
@@ -303,6 +309,8 @@ name = "macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
@@ -425,6 +433,7 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
name = "render"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"bezier-rs",
|
||||
"gl",
|
||||
@@ -432,6 +441,7 @@ dependencies = [
|
||||
"indexmap",
|
||||
"macros",
|
||||
"skia-safe",
|
||||
"thiserror",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
@@ -577,6 +587,26 @@ dependencies = [
|
||||
"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]]
|
||||
name = "toml"
|
||||
version = "0.8.19"
|
||||
|
||||
@@ -19,6 +19,7 @@ name = "render_wasm"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.102"
|
||||
base64 = "0.22.1"
|
||||
bezier-rs = "0.4.0"
|
||||
gl = "0.14.0"
|
||||
@@ -32,6 +33,7 @@ skia-safe = { version = "0.87.0", default-features = false, features = [
|
||||
"binary-cache",
|
||||
"webp",
|
||||
] }
|
||||
thiserror = "2.0.18"
|
||||
uuid = { version = "1.11.0", features = ["v4", "js"] }
|
||||
|
||||
[profile.release]
|
||||
|
||||
2
render-wasm/macros/Cargo.lock
generated
2
render-wasm/macros/Cargo.lock
generated
@@ -13,6 +13,8 @@ name = "macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
[package]
|
||||
name = "macros"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
heck = "0.5.0"
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = "2.0.106"
|
||||
|
||||
@@ -6,9 +6,149 @@ use std::sync;
|
||||
|
||||
use heck::{ToKebabCase, ToPascalCase};
|
||||
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>;
|
||||
|
||||
/// If the type is std::result::Result<T, E> or core::result::Result<T, E>, returns Some((T, E)).
|
||||
/// Otherwise None. The error type E is required to implement std::error::Error and Into<u8>.
|
||||
fn std_result_types(ty: &Type) -> Option<(&Type, &Type)> {
|
||||
let path = match ty {
|
||||
Type::Path(tp) => &tp.path,
|
||||
_ => return None,
|
||||
};
|
||||
let segs: Vec<_> = path.segments.iter().collect();
|
||||
if segs.len() < 3 {
|
||||
return None;
|
||||
}
|
||||
let (first, second, last) = (
|
||||
segs[0].ident.to_string(),
|
||||
segs[1].ident.to_string(),
|
||||
&segs[segs.len() - 1],
|
||||
);
|
||||
if last.ident != "Result" {
|
||||
return None;
|
||||
}
|
||||
let ok_std = first == "std" && second == "result";
|
||||
let ok_core = first == "core" && second == "result";
|
||||
if !ok_std && !ok_core {
|
||||
return None;
|
||||
}
|
||||
let args = match &last.arguments {
|
||||
syn::PathArguments::AngleBracketed(a) => &a.args,
|
||||
_ => return None,
|
||||
};
|
||||
if args.len() != 2 {
|
||||
return None;
|
||||
}
|
||||
match (&args[0], &args[1]) {
|
||||
(GenericArgument::Type(t), GenericArgument::Type(e)) => Some((t, e)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 std_result_types(boxed_ty) {
|
||||
Some((t, e)) => (t, quote!(#e)),
|
||||
None => 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 std::result::Result<T, E> (or core::result::Result<T, E>), \
|
||||
or crate::error::Result<T>. E must implement std::error::Error and Into<u8>. T must be a C ABI type (u32, u8, bool, (), etc.)"
|
||||
);
|
||||
}
|
||||
.into();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let block: Block = syn::parse2(quote! {
|
||||
{
|
||||
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;
|
||||
mem::set_error_code(__e.into());
|
||||
panic!("WASM error");
|
||||
}
|
||||
},
|
||||
Err(__payload) => {
|
||||
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()
|
||||
}
|
||||
|
||||
#[proc_macro_derive(ToJs)]
|
||||
pub fn derive_to_cljs(input: TokenStream) -> TokenStream {
|
||||
let input = syn::parse_macro_input!(input as syn::DeriveInput);
|
||||
|
||||
22
render-wasm/src/error.rs
Normal file
22
render-wasm/src/error.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
use thiserror::Error;
|
||||
|
||||
// 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(anyhow::Error),
|
||||
#[error("[Critical] {0}")]
|
||||
CriticalError(anyhow::Error),
|
||||
}
|
||||
|
||||
impl From<Error> for u8 {
|
||||
fn from(error: Error) -> Self {
|
||||
match error {
|
||||
Error::RecoverableError(_) => 0x01,
|
||||
Error::CriticalError(_) => 0x02,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod emscripten;
|
||||
mod error;
|
||||
mod math;
|
||||
mod mem;
|
||||
mod options;
|
||||
@@ -14,6 +15,9 @@ mod view;
|
||||
mod wapi;
|
||||
mod wasm;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use crate::error::{Error, Result};
|
||||
use macros::wasm_error;
|
||||
use math::{Bounds, Matrix};
|
||||
use mem::SerializableResult;
|
||||
use shapes::{StructureEntry, StructureEntryType, TransformEntry};
|
||||
@@ -103,10 +107,12 @@ pub extern "C" fn init(width: i32, height: i32) {
|
||||
}
|
||||
|
||||
#[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, {
|
||||
state.set_browser(browser);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -131,12 +137,14 @@ pub extern "C" fn set_render_options(debug: u32, dpr: f32) {
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn set_canvas_background(raw_color: u32) {
|
||||
pub extern "C" fn set_canvas_background(raw_color: u32) -> u8 {
|
||||
with_state_mut!(state, {
|
||||
let color = skia::Color::new(raw_color);
|
||||
state.set_background_color(color);
|
||||
state.rebuild_tiles_shallow();
|
||||
});
|
||||
|
||||
0x0f
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
@@ -335,11 +343,13 @@ pub extern "C" fn init_shapes_pool(capacity: usize) {
|
||||
}
|
||||
|
||||
#[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, {
|
||||
let id = uuid_from_u32_quartet(a, b, c, d);
|
||||
state.use_shape(id);
|
||||
});
|
||||
Err(Error::CriticalError(anyhow::anyhow!("Shape not found")))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
||||
@@ -5,6 +5,24 @@ use std::sync::Mutex;
|
||||
const LAYOUT_ALIGN: usize = 4;
|
||||
|
||||
static BUFFERU8: Mutex<Option<Vec<u8>>> = Mutex::new(None);
|
||||
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]
|
||||
pub extern "C" fn read_error_code() -> u8 {
|
||||
let guard = BUFFER_ERROR.lock().unwrap();
|
||||
*guard
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn alloc_bytes(len: usize) -> *mut u8 {
|
||||
|
||||
Reference in New Issue
Block a user