-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathlib.rs
More file actions
203 lines (171 loc) · 7.16 KB
/
lib.rs
File metadata and controls
203 lines (171 loc) · 7.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#![doc = include_str!("../README.md")]
// `macro_use` puts the log macros (`error!`, `warn!`, `debug!`, `info!` and `trace!`) in scope for the crate
#[macro_use]
extern crate log;
pub mod editor_wrapper;
pub mod helpers;
pub mod native_communication;
use crate::helpers::wrapper;
use editor::messages::prelude::*;
use std::panic;
use std::sync::Mutex;
use std::sync::atomic::{AtomicBool, Ordering};
use wasm_bindgen::prelude::*;
// Set up the persistent editor backend state
pub static EDITOR_HAS_CRASHED: AtomicBool = AtomicBool::new(false);
pub static FRONTEND_READY: AtomicBool = AtomicBool::new(false);
pub static NODE_GRAPH_ERROR_DISPLAYED: AtomicBool = AtomicBool::new(false);
pub static LOGGER: WasmLog = WasmLog;
thread_local! {
#[cfg(not(feature = "native"))]
pub static EDITOR: Mutex<Option<editor::application::Editor>> = const { Mutex::new(None) };
pub static MESSAGE_BUFFER: std::cell::RefCell<Vec<Message>> = const { std::cell::RefCell::new(Vec::new()) };
pub static EDITOR_WRAPPER: Mutex<Option<editor_wrapper::EditorWrapper>> = const { Mutex::new(None) };
pub static PANIC_DIALOG_MESSAGE_CALLBACK: std::cell::RefCell<Option<js_sys::Function>> = const { std::cell::RefCell::new(None) };
}
/// Initialize the backend
#[wasm_bindgen(start)]
pub fn init_graphite() {
// Set up the panic hook
panic::set_hook(Box::new(panic_hook));
// Set up the logger with a default level of debug
log::set_logger(&LOGGER).expect("Failed to set logger");
log::set_max_level(log::LevelFilter::Debug);
}
/// When a panic occurs, notify the user and log the error to the JS console before the backend dies
pub fn panic_hook(info: &panic::PanicHookInfo) {
let info = info.to_string();
let backtrace = Error::new("stack").stack().to_string();
if backtrace.contains("DynAnyNode") {
log::error!("Node graph evaluation panicked {info}");
// When the graph panics, the node runtime lock may not be released properly
if editor::node_graph_executor::NODE_RUNTIME.try_lock().is_none() {
unsafe { editor::node_graph_executor::NODE_RUNTIME.force_unlock() };
}
if !NODE_GRAPH_ERROR_DISPLAYED.load(Ordering::SeqCst) {
NODE_GRAPH_ERROR_DISPLAYED.store(true, Ordering::SeqCst);
wrapper(|wrapper| {
let error = r#"
<rect x="50%" y="50%" width="600" height="100" transform="translate(-300 -50)" rx="4" fill="var(--color-error-red)" />
<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-size="18" fill="var(--color-2-mildblack)">
<tspan x="50%" dy="-24" font-weight="bold">The document crashed while being rendered in its current state.</tspan>
<tspan x="50%" dy="24">The editor is now unstable! Undo your last action to restore the artwork,</tspan>
<tspan x="50%" dy="24">then save your document and restart the editor before continuing work.</tspan>
/text>"#
// It's a mystery why the `/text>` tag above needs to be missing its `<`, but when it exists it prints the `<` character in the text. However this works with it removed.
.to_string();
wrapper.send_frontend_message_to_js_rust_proxy(FrontendMessage::UpdateDocumentArtwork { svg: error });
});
}
return;
} else {
EDITOR_HAS_CRASHED.store(true, Ordering::SeqCst);
}
log::error!("{info}");
// Prefer using the raw JS callback to avoid mutex lock contention inside the panic hook.
if let Err(info) = send_panic_dialog_via_callback(info) {
send_panic_dialog_deferred(info);
}
}
fn send_panic_dialog_via_callback(panic_info: String) -> Result<(), String> {
let message = FrontendMessage::DisplayDialogPanic { panic_info };
let message_type = message.to_discriminant().local_name();
let Ok(message_data) = serde_wasm_bindgen::to_value(&message) else {
log::error!("Failed to serialize crash dialog panic message");
let FrontendMessage::DisplayDialogPanic { panic_info } = message else {
unreachable!("Message variant changed unexpectedly")
};
return Err(panic_info);
};
PANIC_DIALOG_MESSAGE_CALLBACK.with(|callback| {
let callback_ref = callback.borrow();
let Some(callback) = callback_ref.as_ref() else {
let FrontendMessage::DisplayDialogPanic { panic_info } = message else {
unreachable!("Message variant changed unexpectedly")
};
return Err(panic_info);
};
if let Err(error) = callback.call2(&JsValue::null(), &JsValue::from(message_type), &message_data) {
log::error!("Failed to send crash dialog panic message to JS: {:?}", error);
let FrontendMessage::DisplayDialogPanic { panic_info } = message else {
unreachable!("Message variant changed unexpectedly")
};
return Err(panic_info);
}
Ok(())
})
}
#[cfg(not(feature = "native"))]
fn send_panic_dialog_deferred(panic_info: String) {
let callback = Closure::once_into_js(move || {
if send_panic_dialog_via_callback(panic_info).is_err() {
log::error!("Failed to send crash dialog after panic because the editor wrapper is unavailable");
}
});
let Some(window) = web_sys::window() else {
log::error!("Failed to schedule crash dialog after panic because no window exists");
return;
};
if window.set_timeout_with_callback_and_timeout_and_arguments_0(callback.unchecked_ref(), 0).is_err() {
log::error!("Failed to schedule crash dialog after panic with setTimeout");
}
}
#[cfg(feature = "native")]
fn send_panic_dialog_deferred(_panic_info: String) {
// Native builds do not use `setTimeout`, so just log the failure in the caller's context.
}
#[wasm_bindgen]
extern "C" {
/// The JavaScript `Error` type
#[derive(Clone, Debug)]
pub type Error;
#[wasm_bindgen(constructor)]
pub fn new(msg: &str) -> Error;
#[wasm_bindgen(structural, method, getter)]
fn stack(error: &Error) -> String;
}
/// Logging to the JS console
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(msg: &str, format: &str);
#[wasm_bindgen(js_namespace = console)]
fn info(msg: &str, format: &str);
#[wasm_bindgen(js_namespace = console)]
fn warn(msg: &str, format: &str);
#[wasm_bindgen(js_namespace = console)]
fn error(msg: &str, format: &str);
#[wasm_bindgen(js_namespace = console)]
fn trace(msg: &str, format: &str);
}
#[derive(Default)]
pub struct WasmLog;
impl log::Log for WasmLog {
#[inline]
fn enabled(&self, metadata: &log::Metadata) -> bool {
metadata.level() <= log::max_level()
}
fn log(&self, record: &log::Record) {
if !self.enabled(record.metadata()) {
return;
}
let (log, name, color): (fn(&str, &str), &str, &str) = match record.level() {
log::Level::Trace => (log, "trace", "color:plum"),
log::Level::Debug => (log, "debug", "color:cyan"),
log::Level::Warn => (warn, "warn", "color:goldenrod"),
log::Level::Info => (info, "info", "color:mediumseagreen"),
log::Level::Error => (error, "error", "color:red"),
};
// The %c is replaced by the message color
if record.level() == log::Level::Info {
// We don't print the file name and line number for info-level logs because it's used for printing the message system logs
log(&format!("%c{}\t{}", name, record.args()), color);
} else {
let file = record.file().unwrap_or_else(|| record.target());
let line = record.line().map_or_else(|| "[Unknown]".to_string(), |line| line.to_string());
let args = record.args();
log(&format!("%c{name}\t{file}:{line}\n{args}"), color);
}
}
fn flush(&self) {}
}