1#[cfg(safeguards_strict)] #[cfg_attr(published_docs, doc(cfg(safeguards_strict)))]
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, CallResult};
17use crate::meta::CallContext;
18use crate::obj::Gd;
19use crate::{classes, sys};
20
21mod reexport_pub {
25 #[cfg(all(since_api = "4.3", feature = "register-docs"))] #[cfg_attr(published_docs, doc(cfg(all(since_api = "4.3", feature = "register-docs"))))]
26 pub use crate::docs::{DocsItem, DocsPlugin, InherentImplDocs, StructDocs};
27 pub use crate::gen::classes::class_macros;
28 pub use crate::gen::virtuals; #[cfg(feature = "trace")] #[cfg_attr(published_docs, doc(cfg(feature = "trace")))]
30 pub use crate::meta::trace;
31 pub use crate::obj::rtti::ObjectRtti;
32 pub use crate::registry::callbacks;
33 pub use crate::registry::plugin::{
34 ClassPlugin, DynTraitImpl, ErasedDynGd, ErasedRegisterFn, ITraitImpl, InherentImpl,
35 PluginItem, Struct,
36 };
37 pub use crate::registry::signal::priv_re_export::*;
38 pub use crate::storage::{
39 as_storage, IntoVirtualMethodReceiver, RecvGdSelf, RecvMut, RecvRef, Storage,
40 VirtualMethodReceiver,
41 };
42 pub use crate::sys::out;
43}
44pub use reexport_pub::*;
45
46static CALL_ERRORS: Global<CallErrors> = Global::default();
50
51static ERROR_PRINT_LEVEL: atomic::AtomicU8 = atomic::AtomicU8::new(2);
56
57sys::plugin_registry!(pub __GODOT_PLUGIN_REGISTRY: ClassPlugin);
58#[cfg(all(since_api = "4.3", feature = "register-docs"))] #[cfg_attr(published_docs, doc(cfg(all(since_api = "4.3", feature = "register-docs"))))]
59sys::plugin_registry!(pub __GODOT_DOCS_REGISTRY: DocsPlugin);
60
61struct CallErrors {
67 ring_buffer: Vec<Option<CallError>>,
68 next_id: u8,
69 generation: u16,
70}
71
72impl Default for CallErrors {
73 fn default() -> Self {
74 Self {
75 ring_buffer: [const { None }; Self::MAX_ENTRIES as usize].into(),
76 next_id: 0,
77 generation: 0,
78 }
79 }
80}
81
82impl CallErrors {
83 const MAX_ENTRIES: u8 = 32;
84
85 fn insert(&mut self, err: CallError) -> i32 {
86 let id = self.next_id;
87
88 self.next_id = self.next_id.wrapping_add(1) % Self::MAX_ENTRIES;
89 if self.next_id == 0 {
90 self.generation = self.generation.wrapping_add(1);
91 }
92
93 self.ring_buffer[id as usize] = Some(err);
94
95 (self.generation as i32) << 16 | id as i32
96 }
97
98 fn remove(&mut self, id: i32) -> Option<CallError> {
100 let generation = (id >> 16) as u16;
101 let id = id as u8;
102
103 if id < self.next_id {
105 if generation != self.generation {
106 return None;
107 }
108 } else if generation != self.generation.wrapping_sub(1) {
109 return None;
110 }
111
112 self.ring_buffer[id as usize].take()
114 }
115}
116
117fn call_error_insert(err: CallError) -> i32 {
119 let id = CALL_ERRORS.lock().insert(err);
122 id
123}
124
125pub(crate) fn call_error_remove(in_error: &sys::GDExtensionCallError) -> Option<CallError> {
126 if in_error.error != sys::GODOT_RUST_CUSTOM_CALL_ERROR {
129 godot_error!("Tried to remove non-godot-rust call error {in_error:?}");
130 return None;
131 }
132
133 let call_error = CALL_ERRORS.lock().remove(in_error.argument);
134 if call_error.is_none() {
135 godot_error!("Failed to remove call error {in_error:?}");
137 }
138
139 call_error
140}
141
142pub fn next_class_id() -> u16 {
146 static NEXT_CLASS_ID: atomic::AtomicU16 = atomic::AtomicU16::new(0);
147 NEXT_CLASS_ID.fetch_add(1, atomic::Ordering::Relaxed)
148}
149
150pub(crate) fn iterate_plugins(mut visitor: impl FnMut(&ClassPlugin)) {
151 sys::plugin_foreach!(__GODOT_PLUGIN_REGISTRY; visitor);
152}
153
154#[cfg(all(since_api = "4.3", feature = "register-docs"))] #[cfg_attr(published_docs, doc(cfg(all(since_api = "4.3", feature = "register-docs"))))]
155pub(crate) fn iterate_docs_plugins(mut visitor: impl FnMut(&DocsPlugin)) {
156 sys::plugin_foreach!(__GODOT_DOCS_REGISTRY; visitor);
157}
158
159#[cfg(feature = "codegen-full")] pub(crate) fn find_inherent_impl(class_name: crate::meta::ClassId) -> Option<InherentImpl> {
161 let plugins = __GODOT_PLUGIN_REGISTRY.lock().unwrap();
163
164 plugins.iter().find_map(|elem| {
165 if elem.class_name == class_name {
166 if let PluginItem::InherentImpl(inherent_impl) = &elem.item {
167 return Some(inherent_impl.clone());
168 }
169 }
170
171 None
172 })
173}
174
175#[allow(non_camel_case_types)]
180#[diagnostic::on_unimplemented(
181 message = "`impl` blocks for Godot classes require the `#[godot_api]` attribute",
182 label = "missing `#[godot_api]` before `impl`",
183 note = "see also: https://godot-rust.github.io/book/register/functions.html#godot-special-functions"
184)]
185pub trait You_forgot_the_attribute__godot_api {}
186
187pub struct ClassConfig {
188 pub is_tool: bool,
189}
190
191pub fn auto_init<T>(l: &mut crate::obj::OnReady<T>, base: &crate::obj::Gd<crate::classes::Node>) {
195 l.init_auto(base);
196}
197
198#[cfg(since_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.3")))]
199pub unsafe fn has_virtual_script_method(
200 object_ptr: sys::GDExtensionObjectPtr,
201 method_sname: sys::GDExtensionConstStringNamePtr,
202) -> bool {
203 sys::interface_fn!(object_has_script_method)(sys::to_const_ptr(object_ptr), method_sname) != 0
204}
205
206pub const fn is_editor_plugin<T: crate::obj::Inherits<crate::classes::EditorPlugin>>() {}
208
209#[cfg(before_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.3")))]
211pub fn is_class_inactive(is_tool: bool) -> bool {
212 use crate::obj::Singleton;
213
214 if is_tool {
215 return false;
216 }
217
218 let global_config = unsafe { sys::config() };
220 let is_editor = || crate::classes::Engine::singleton().is_editor_hint();
221
222 global_config.tool_only_in_editor && global_config.is_editor_or_init(is_editor)
224}
225
226#[cfg(since_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.3")))]
228pub fn is_class_runtime(is_tool: bool) -> bool {
229 if is_tool {
230 return false;
231 }
232
233 let global_config = unsafe { sys::config() };
235
236 global_config.tool_only_in_editor
239}
240
241pub fn opt_default_value<T>(value: impl crate::meta::AsArg<T>) -> crate::builtin::Variant
248where
249 T: crate::meta::GodotImmutable + crate::meta::ToGodot + Clone,
250{
251 let value = crate::meta::AsArg::<T>::into_arg(value);
255 let value = value.cow_into_owned();
256 let value = <T as crate::meta::GodotImmutable>::into_runtime_immutable(value);
257 crate::builtin::Variant::from(value)
258}
259
260pub fn extract_panic_message(err: &(dyn Send + std::any::Any)) -> String {
264 if let Some(s) = err.downcast_ref::<&'static str>() {
265 s.to_string()
266 } else if let Some(s) = err.downcast_ref::<String>() {
267 s.clone()
268 } else {
269 format!("(panic of type ID {:?})", err.type_id())
270 }
271}
272
273pub fn format_panic_message(panic_info: &std::panic::PanicHookInfo) -> String {
274 let mut msg = extract_panic_message(panic_info.payload());
275
276 if let Some(context) = fetch_last_panic_context() {
277 msg = format!("{msg}\nContext: {context}");
278 }
279
280 let prefix = if let Some(location) = panic_info.location() {
281 format!("panic {}:{}", location.file(), location.line())
282 } else {
283 "panic".to_string()
284 };
285
286 let lbegin = "\n ";
288 let indented = msg.replace('\n', lbegin);
289
290 if indented.len() != msg.len() {
291 format!("[{prefix}]{lbegin}{indented}")
292 } else {
293 format!("[{prefix}] {msg}")
294 }
295}
296
297#[cfg(safeguards_strict)] #[cfg_attr(published_docs, doc(cfg(safeguards_strict)))]
299#[macro_export]
300macro_rules! format_backtrace {
301 ($prefix:expr, $backtrace:expr) => {{
302 use std::backtrace::BacktraceStatus;
303
304 let backtrace = $backtrace;
305
306 match backtrace.status() {
307 BacktraceStatus::Captured => format!("\n[{}]\n{}\n", $prefix, backtrace),
308 BacktraceStatus::Disabled => {
309 "(backtrace disabled, run application with `RUST_BACKTRACE=1` environment variable)"
310 .to_string()
311 }
312 BacktraceStatus::Unsupported => {
313 "(backtrace unsupported for current platform)".to_string()
314 }
315 _ => "(backtrace status unknown)".to_string(),
316 }
317 }};
318
319 ($prefix:expr) => {
320 $crate::format_backtrace!($prefix, std::backtrace::Backtrace::capture())
321 };
322}
323
324#[cfg(not(safeguards_strict))] #[cfg_attr(published_docs, doc(cfg(not(safeguards_strict))))]
325#[macro_export]
326macro_rules! format_backtrace {
327 ($prefix:expr $(, $backtrace:expr)? ) => {
328 String::new()
329 };
330}
331
332pub fn set_gdext_hook<F>(godot_print: F)
333where
334 F: Fn() -> bool + Send + Sync + 'static,
335{
336 std::panic::set_hook(Box::new(move |panic_info| {
337 let _ignored_result = std::io::stdout().flush();
339
340 let message = format_panic_message(panic_info);
341 if godot_print() {
342 godot_error!("{message}");
344 } else {
345 eprintln!("{message}");
346 }
347
348 let backtrace = format_backtrace!("panic backtrace");
349 eprintln!("{backtrace}");
350 let _ignored_result = std::io::stderr().flush();
351 }));
352}
353
354pub fn set_error_print_level(level: u8) -> u8 {
355 assert!(level <= 2);
356 ERROR_PRINT_LEVEL.swap(level, atomic::Ordering::Relaxed)
357}
358
359pub(crate) fn has_error_print_level(level: u8) -> bool {
360 assert!(level <= 2);
361 ERROR_PRINT_LEVEL.load(atomic::Ordering::Relaxed) >= level
362}
363
364#[cfg(safeguards_strict)] #[cfg_attr(published_docs, doc(cfg(safeguards_strict)))]
368struct ScopedFunctionStack {
369 functions: Vec<*const dyn Fn() -> String>,
370}
371
372#[cfg(safeguards_strict)] #[cfg_attr(published_docs, doc(cfg(safeguards_strict)))]
373impl ScopedFunctionStack {
374 unsafe fn push_function<'a, 'b>(&'a mut self, function: &'b (dyn Fn() -> String + 'b)) {
377 let function = unsafe {
381 std::mem::transmute::<
382 *const (dyn Fn() -> String + 'b),
383 *const (dyn Fn() -> String + 'static),
384 >(function)
385 };
386
387 self.functions.push(function);
388 }
389
390 fn pop_function(&mut self) {
391 self.functions.pop().expect("function stack is empty!");
392 }
393
394 fn get_last(&self) -> Option<String> {
395 self.functions.last().cloned().map(|pointer| {
396 unsafe { (*pointer)() }
400 })
401 }
402}
403
404#[cfg(safeguards_strict)] #[cfg_attr(published_docs, doc(cfg(safeguards_strict)))]
405thread_local! {
406 static ERROR_CONTEXT_STACK: RefCell<ScopedFunctionStack> = const {
407 RefCell::new(ScopedFunctionStack { functions: Vec::new() })
408 }
409}
410
411pub fn fetch_last_panic_context() -> Option<String> {
413 #[cfg(safeguards_strict)] #[cfg_attr(published_docs, doc(cfg(safeguards_strict)))]
414 return ERROR_CONTEXT_STACK.with(|cell| cell.borrow().get_last());
415
416 #[cfg(not(safeguards_strict))] #[cfg_attr(published_docs, doc(cfg(not(safeguards_strict))))]
417 None
418}
419
420pub struct PanicPayload {
424 payload: Box<dyn std::any::Any + Send + 'static>,
425}
426
427impl PanicPayload {
428 pub fn new(payload: Box<dyn std::any::Any + Send + 'static>) -> Self {
429 Self { payload }
430 }
431
432 pub fn into_panic_message(self) -> String {
434 extract_panic_message(self.payload.as_ref())
435 }
436
437 pub fn repanic(self) -> ! {
438 std::panic::resume_unwind(self.payload)
439 }
440}
441
442pub fn handle_panic<E, F, R>(error_context: E, code: F) -> Result<R, PanicPayload>
449where
450 E: Fn() -> String,
451 F: FnOnce() -> R + std::panic::UnwindSafe,
452{
453 #[cfg(not(safeguards_strict))] #[cfg_attr(published_docs, doc(cfg(not(safeguards_strict))))]
454 let _ = error_context; #[cfg(safeguards_strict)] #[cfg_attr(published_docs, doc(cfg(safeguards_strict)))]
457 ERROR_CONTEXT_STACK.with(|cell| unsafe {
458 cell.borrow_mut().push_function(&error_context)
460 });
461
462 let result = std::panic::catch_unwind(code).map_err(PanicPayload::new);
463
464 #[cfg(safeguards_strict)] #[cfg_attr(published_docs, doc(cfg(safeguards_strict)))]
465 ERROR_CONTEXT_STACK.with(|cell| cell.borrow_mut().pop_function());
466 result
467}
468
469pub fn handle_fallible_varcall<F, R>(
471 call_ctx: &CallContext,
472 out_err: &mut sys::GDExtensionCallError,
473 code: F,
474) where
475 F: FnOnce() -> CallResult<R> + std::panic::UnwindSafe,
476{
477 if let Some(error_id) = handle_fallible_call(call_ctx, code, true) {
478 *out_err = sys::GDExtensionCallError {
480 error: sys::GODOT_RUST_CUSTOM_CALL_ERROR,
481 argument: error_id,
482 expected: 0,
483 };
484 };
485
486 }
488
489pub fn handle_fallible_ptrcall<F>(call_ctx: &CallContext, code: F)
491where
492 F: FnOnce() -> CallResult<()> + std::panic::UnwindSafe,
493{
494 handle_fallible_call(call_ctx, code, false);
495}
496
497fn handle_fallible_call<F, R>(call_ctx: &CallContext, code: F, track_globally: bool) -> Option<i32>
504where
505 F: FnOnce() -> CallResult<R> + std::panic::UnwindSafe,
506{
507 let outcome: Result<CallResult<R>, PanicPayload> = handle_panic(|| call_ctx.to_string(), code);
508
509 let call_error = match outcome {
510 Ok(Ok(_result)) => return None,
512
513 Ok(Err(err)) => err,
515
516 Err(panic_msg) => CallError::failed_by_user_panic(call_ctx, panic_msg),
518 };
519
520 if has_error_print_level(2) {
524 godot_error!("{call_error}");
525 }
526
527 let error_id = if track_globally {
529 call_error_insert(call_error)
530 } else {
531 0
532 };
533
534 Some(error_id)
535}
536
537pub fn rebuild_gd(object_ref: &classes::Object) -> Gd<classes::Object> {
539 let ptr = object_ref.__object_ptr();
540
541 unsafe { Gd::from_obj_sys(ptr) }
543}
544
545#[cfg(test)] #[cfg_attr(published_docs, doc(cfg(test)))]
548mod tests {
549 use super::{CallError, CallErrors, PanicPayload};
550 use crate::meta::CallContext;
551
552 fn make(index: usize) -> CallError {
553 let method_name = format!("method_{index}");
554 let ctx = CallContext::func("Class", &method_name);
555 let payload = PanicPayload::new(Box::new("some panic reason".to_string()));
556
557 CallError::failed_by_user_panic(&ctx, payload)
558 }
559
560 #[test]
561 fn test_call_errors() {
562 let mut store = CallErrors::default();
563
564 let mut id07 = 0;
565 let mut id13 = 0;
566 let mut id20 = 0;
567 for i in 0..24 {
568 let id = store.insert(make(i));
569 match i {
570 7 => id07 = id,
571 13 => id13 = id,
572 20 => id20 = id,
573 _ => {}
574 }
575 }
576
577 let e = store.remove(id20).expect("must be present");
578 assert_eq!(e.method_name(), "method_20");
579
580 let e = store.remove(id20);
581 assert!(e.is_none());
582
583 for i in 24..CallErrors::MAX_ENTRIES as usize {
584 store.insert(make(i));
585 }
586 for i in 0..10 {
587 store.insert(make(i));
588 }
589
590 let e = store.remove(id07);
591 assert!(e.is_none(), "generation overwritten");
592
593 let e = store.remove(id13).expect("generation not yet overwritten");
594 assert_eq!(e.method_name(), "method_13");
595 }
596}