1#[cfg(debug_assertions)] #[cfg_attr(published_docs, doc(cfg(debug_assertions)))]
9use std::cell::RefCell;
10use std::io::Write;
11use std::sync::atomic;
12
13use sys::Global;
14
15use crate::global::godot_error;
16use crate::meta::error::CallError;
17use crate::meta::CallContext;
18use crate::obj::Gd;
19use crate::{classes, sys};
20
21mod reexport_pub {
25 pub use crate::gen::classes::class_macros;
26 #[cfg(feature = "trace")] #[cfg_attr(published_docs, doc(cfg(feature = "trace")))]
27 pub use crate::meta::trace;
28 pub use crate::obj::rtti::ObjectRtti;
29 pub use crate::registry::callbacks;
30 pub use crate::registry::plugin::{
31 ClassPlugin, DynTraitImpl, ErasedDynGd, ErasedRegisterFn, ITraitImpl, InherentImpl,
32 PluginItem, Struct,
33 };
34 #[cfg(since_api = "4.2")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.2")))]
35 pub use crate::registry::signal::priv_re_export::*;
36 pub use crate::storage::{as_storage, Storage};
37 pub use crate::sys::out;
38}
39pub use reexport_pub::*;
40
41static CALL_ERRORS: Global<CallErrors> = Global::default();
45
46static ERROR_PRINT_LEVEL: atomic::AtomicU8 = atomic::AtomicU8::new(2);
51
52sys::plugin_registry!(pub __GODOT_PLUGIN_REGISTRY: ClassPlugin);
53
54struct CallErrors {
60 ring_buffer: Vec<Option<CallError>>,
61 next_id: u8,
62 generation: u16,
63}
64
65impl Default for CallErrors {
66 fn default() -> Self {
67 Self {
68 ring_buffer: [const { None }; Self::MAX_ENTRIES as usize].into(),
69 next_id: 0,
70 generation: 0,
71 }
72 }
73}
74
75impl CallErrors {
76 const MAX_ENTRIES: u8 = 32;
77
78 fn insert(&mut self, err: CallError) -> i32 {
79 let id = self.next_id;
80
81 self.next_id = self.next_id.wrapping_add(1) % Self::MAX_ENTRIES;
82 if self.next_id == 0 {
83 self.generation = self.generation.wrapping_add(1);
84 }
85
86 self.ring_buffer[id as usize] = Some(err);
87
88 (self.generation as i32) << 16 | id as i32
89 }
90
91 fn remove(&mut self, id: i32) -> Option<CallError> {
93 let generation = (id >> 16) as u16;
94 let id = id as u8;
95
96 if id < self.next_id {
98 if generation != self.generation {
99 return None;
100 }
101 } else if generation != self.generation.wrapping_sub(1) {
102 return None;
103 }
104
105 self.ring_buffer[id as usize].take()
107 }
108}
109
110fn call_error_insert(err: CallError) -> i32 {
112 let id = CALL_ERRORS.lock().insert(err);
115 id
116}
117
118pub(crate) fn call_error_remove(in_error: &sys::GDExtensionCallError) -> Option<CallError> {
119 if in_error.error != sys::GODOT_RUST_CUSTOM_CALL_ERROR {
122 godot_error!("Tried to remove non-godot-rust call error {in_error:?}");
123 return None;
124 }
125
126 let call_error = CALL_ERRORS.lock().remove(in_error.argument);
127 if call_error.is_none() {
128 godot_error!("Failed to remove call error {in_error:?}");
130 }
131
132 call_error
133}
134
135pub fn next_class_id() -> u16 {
139 static NEXT_CLASS_ID: atomic::AtomicU16 = atomic::AtomicU16::new(0);
140 NEXT_CLASS_ID.fetch_add(1, atomic::Ordering::Relaxed)
141}
142
143pub(crate) fn iterate_plugins(mut visitor: impl FnMut(&ClassPlugin)) {
144 sys::plugin_foreach!(__GODOT_PLUGIN_REGISTRY; visitor);
145}
146
147#[cfg(feature = "codegen-full")] pub(crate) fn find_inherent_impl(class_name: crate::meta::ClassName) -> Option<InherentImpl> {
149 let plugins = __GODOT_PLUGIN_REGISTRY.lock().unwrap();
151
152 plugins.iter().find_map(|elem| {
153 if elem.class_name == class_name {
154 if let PluginItem::InherentImpl(inherent_impl) = &elem.item {
155 return Some(inherent_impl.clone());
156 }
157 }
158
159 None
160 })
161}
162
163#[allow(non_camel_case_types)]
168#[diagnostic::on_unimplemented(
169 message = "`impl` blocks for Godot classes require the `#[godot_api]` attribute",
170 label = "missing `#[godot_api]` before `impl`",
171 note = "see also: https://godot-rust.github.io/book/register/functions.html#godot-special-functions"
172)]
173pub trait You_forgot_the_attribute__godot_api {}
174
175pub struct ClassConfig {
176 pub is_tool: bool,
177}
178
179pub fn auto_init<T>(l: &mut crate::obj::OnReady<T>, base: &crate::obj::Gd<crate::classes::Node>) {
183 l.init_auto(base);
184}
185
186#[cfg(since_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.3")))]
187pub unsafe fn has_virtual_script_method(
188 object_ptr: sys::GDExtensionObjectPtr,
189 method_sname: sys::GDExtensionConstStringNamePtr,
190) -> bool {
191 sys::interface_fn!(object_has_script_method)(sys::to_const_ptr(object_ptr), method_sname) != 0
192}
193
194pub const fn is_editor_plugin<T: crate::obj::Inherits<crate::classes::EditorPlugin>>() {}
196
197#[cfg(before_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.3")))]
199pub fn is_class_inactive(is_tool: bool) -> bool {
200 if is_tool {
201 return false;
202 }
203
204 let global_config = unsafe { sys::config() };
206 let is_editor = || crate::classes::Engine::singleton().is_editor_hint();
207
208 global_config.tool_only_in_editor && global_config.is_editor_or_init(is_editor)
210}
211
212#[cfg(since_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.3")))]
214pub fn is_class_runtime(is_tool: bool) -> bool {
215 if is_tool {
216 return false;
217 }
218
219 let global_config = unsafe { sys::config() };
221
222 global_config.tool_only_in_editor
225}
226
227pub fn extract_panic_message(err: &(dyn Send + std::any::Any)) -> String {
231 if let Some(s) = err.downcast_ref::<&'static str>() {
232 s.to_string()
233 } else if let Some(s) = err.downcast_ref::<String>() {
234 s.clone()
235 } else {
236 format!("(panic of type ID {:?})", err.type_id())
237 }
238}
239
240pub fn format_panic_message(panic_info: &std::panic::PanicHookInfo) -> String {
241 let mut msg = extract_panic_message(panic_info.payload());
242
243 if let Some(context) = get_gdext_panic_context() {
244 msg = format!("{msg}\nContext: {context}");
245 }
246
247 let prefix = if let Some(location) = panic_info.location() {
248 format!("panic {}:{}", location.file(), location.line())
249 } else {
250 "panic".to_string()
251 };
252
253 let lbegin = "\n ";
255 let indented = msg.replace('\n', lbegin);
256
257 if indented.len() != msg.len() {
258 format!("[{prefix}]{lbegin}{indented}")
259 } else {
260 format!("[{prefix}] {msg}")
261 }
262}
263
264#[cfg(debug_assertions)] #[cfg_attr(published_docs, doc(cfg(debug_assertions)))]
266#[macro_export]
267macro_rules! format_backtrace {
268 ($prefix:expr, $backtrace:expr) => {{
269 use std::backtrace::BacktraceStatus;
270
271 let backtrace = $backtrace;
272
273 match backtrace.status() {
274 BacktraceStatus::Captured => format!("\n[{}]\n{}\n", $prefix, backtrace),
275 BacktraceStatus::Disabled => {
276 "(backtrace disabled, run application with `RUST_BACKTRACE=1` environment variable)"
277 .to_string()
278 }
279 BacktraceStatus::Unsupported => {
280 "(backtrace unsupported for current platform)".to_string()
281 }
282 _ => "(backtrace status unknown)".to_string(),
283 }
284 }};
285
286 ($prefix:expr) => {
287 $crate::format_backtrace!($prefix, std::backtrace::Backtrace::capture())
288 };
289}
290
291#[cfg(not(debug_assertions))] #[cfg_attr(published_docs, doc(cfg(not(debug_assertions))))]
292#[macro_export]
293macro_rules! format_backtrace {
294 ($prefix:expr $(, $backtrace:expr)? ) => {
295 String::new()
296 };
297}
298
299pub fn set_gdext_hook<F>(godot_print: F)
300where
301 F: Fn() -> bool + Send + Sync + 'static,
302{
303 std::panic::set_hook(Box::new(move |panic_info| {
304 let _ignored_result = std::io::stdout().flush();
306
307 let message = format_panic_message(panic_info);
308 if godot_print() {
309 godot_error!("{message}");
311 } else {
312 eprintln!("{message}");
313 }
314
315 let backtrace = format_backtrace!("panic backtrace");
316 eprintln!("{backtrace}");
317 let _ignored_result = std::io::stderr().flush();
318 }));
319}
320
321pub fn set_error_print_level(level: u8) -> u8 {
322 assert!(level <= 2);
323 ERROR_PRINT_LEVEL.swap(level, atomic::Ordering::Relaxed)
324}
325
326pub(crate) fn has_error_print_level(level: u8) -> bool {
327 assert!(level <= 2);
328 ERROR_PRINT_LEVEL.load(atomic::Ordering::Relaxed) >= level
329}
330
331#[cfg(debug_assertions)] #[cfg_attr(published_docs, doc(cfg(debug_assertions)))]
335struct ScopedFunctionStack {
336 functions: Vec<*const dyn Fn() -> String>,
337}
338
339#[cfg(debug_assertions)] #[cfg_attr(published_docs, doc(cfg(debug_assertions)))]
340impl ScopedFunctionStack {
341 unsafe fn push_function(&mut self, function: &dyn Fn() -> String) {
344 let function = std::ptr::from_ref(function);
345 #[allow(clippy::unnecessary_cast)]
346 let function = function as *const (dyn Fn() -> String + 'static);
347 self.functions.push(function);
348 }
349
350 fn pop_function(&mut self) {
351 self.functions.pop().expect("function stack is empty!");
352 }
353
354 fn get_last(&self) -> Option<String> {
355 self.functions.last().cloned().map(|pointer| {
356 unsafe { (*pointer)() }
360 })
361 }
362}
363
364#[cfg(debug_assertions)] #[cfg_attr(published_docs, doc(cfg(debug_assertions)))]
365thread_local! {
366 static ERROR_CONTEXT_STACK: RefCell<ScopedFunctionStack> = const {
367 RefCell::new(ScopedFunctionStack { functions: Vec::new() })
368 }
369}
370
371pub fn get_gdext_panic_context() -> Option<String> {
373 #[cfg(debug_assertions)] #[cfg_attr(published_docs, doc(cfg(debug_assertions)))]
374 return ERROR_CONTEXT_STACK.with(|cell| cell.borrow().get_last());
375
376 #[cfg(not(debug_assertions))] #[cfg_attr(published_docs, doc(cfg(not(debug_assertions))))]
377 None
378}
379
380pub struct PanicPayload {
384 payload: Box<dyn std::any::Any + Send + 'static>,
385}
386
387impl PanicPayload {
388 pub fn new(payload: Box<dyn std::any::Any + Send + 'static>) -> Self {
389 Self { payload }
390 }
391
392 pub fn into_panic_message(self) -> String {
394 extract_panic_message(self.payload.as_ref())
395 }
396
397 pub fn repanic(self) -> ! {
398 std::panic::resume_unwind(self.payload)
399 }
400}
401
402pub fn handle_panic<E, F, R>(error_context: E, code: F) -> Result<R, PanicPayload>
409where
410 E: Fn() -> String,
411 F: FnOnce() -> R + std::panic::UnwindSafe,
412{
413 #[cfg(not(debug_assertions))] #[cfg_attr(published_docs, doc(cfg(not(debug_assertions))))]
414 let _ = error_context; #[cfg(debug_assertions)] #[cfg_attr(published_docs, doc(cfg(debug_assertions)))]
417 ERROR_CONTEXT_STACK.with(|cell| unsafe {
418 cell.borrow_mut().push_function(&error_context)
420 });
421
422 let result = std::panic::catch_unwind(code).map_err(PanicPayload::new);
423
424 #[cfg(debug_assertions)] #[cfg_attr(published_docs, doc(cfg(debug_assertions)))]
425 ERROR_CONTEXT_STACK.with(|cell| cell.borrow_mut().pop_function());
426 result
427}
428
429pub fn handle_varcall_panic<F, R>(
432 call_ctx: &CallContext,
433 out_err: &mut sys::GDExtensionCallError,
434 code: F,
435) where
436 F: FnOnce() -> Result<R, CallError> + std::panic::UnwindSafe,
437{
438 let outcome: Result<Result<R, CallError>, PanicPayload> =
439 handle_panic(|| call_ctx.to_string(), code);
440
441 let call_error = match outcome {
442 Ok(Ok(_result)) => return,
444
445 Ok(Err(err)) => err,
447
448 Err(panic_msg) => CallError::failed_by_user_panic(call_ctx, panic_msg),
450 };
451
452 let error_id = report_call_error(call_error, true);
453
454 *out_err = sys::GDExtensionCallError {
456 error: sys::GODOT_RUST_CUSTOM_CALL_ERROR,
457 argument: error_id,
458 expected: 0,
459 };
460
461 }
463
464pub fn handle_ptrcall_panic<F, R>(call_ctx: &CallContext, code: F)
465where
466 F: FnOnce() -> R + std::panic::UnwindSafe,
467{
468 let outcome: Result<R, PanicPayload> = handle_panic(|| call_ctx.to_string(), code);
469
470 let call_error = match outcome {
471 Ok(_result) => return,
473
474 Err(payload) => CallError::failed_by_user_panic(call_ctx, payload),
476 };
477
478 let _id = report_call_error(call_error, false);
479}
480
481fn report_call_error(call_error: CallError, track_globally: bool) -> i32 {
482 if has_error_print_level(2) {
486 godot_error!("{call_error}");
487 }
488
489 if track_globally {
491 call_error_insert(call_error)
492 } else {
493 0
494 }
495}
496
497pub fn rebuild_gd(object_ref: &classes::Object) -> Gd<classes::Object> {
499 let ptr = object_ref.__object_ptr();
500
501 unsafe { Gd::from_obj_sys(ptr) }
503}
504
505#[cfg(test)] #[cfg_attr(published_docs, doc(cfg(test)))]
508mod tests {
509 use super::{CallError, CallErrors, PanicPayload};
510 use crate::meta::CallContext;
511
512 fn make(index: usize) -> CallError {
513 let method_name = format!("method_{index}");
514 let ctx = CallContext::func("Class", &method_name);
515 let payload = PanicPayload::new(Box::new("some panic reason".to_string()));
516
517 CallError::failed_by_user_panic(&ctx, payload)
518 }
519
520 #[test]
521 fn test_call_errors() {
522 let mut store = CallErrors::default();
523
524 let mut id07 = 0;
525 let mut id13 = 0;
526 let mut id20 = 0;
527 for i in 0..24 {
528 let id = store.insert(make(i));
529 match i {
530 7 => id07 = id,
531 13 => id13 = id,
532 20 => id20 = id,
533 _ => {}
534 }
535 }
536
537 let e = store.remove(id20).expect("must be present");
538 assert_eq!(e.method_name(), "method_20");
539
540 let e = store.remove(id20);
541 assert!(e.is_none());
542
543 for i in 24..CallErrors::MAX_ENTRIES as usize {
544 store.insert(make(i));
545 }
546 for i in 0..10 {
547 store.insert(make(i));
548 }
549
550 let e = store.remove(id07);
551 assert!(e.is_none(), "generation overwritten");
552
553 let e = store.remove(id13).expect("generation not yet overwritten");
554 assert_eq!(e.method_name(), "method_13");
555 }
556}