use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use crate::builtin::Variant;
use crate::classes::Engine;
use crate::obj::Singleton;
use crate::sys;
#[macro_export]
#[doc(hidden)]
macro_rules! inner_function {
() => {{
fn f() {}
fn type_name_of<T>(_: T) -> &'static str {
std::any::type_name::<T>()
}
let name = type_name_of(f);
name.strip_suffix("::f").unwrap()
}};
}
#[macro_export]
#[doc(hidden)]
macro_rules! inner_godot_msg {
($level:expr; $fmt:literal $(, $args:expr)* $(,)?) => {
{
let description = format!($fmt $(, $args)*);
$crate::global::print_custom($crate::global::PrintRecord {
level: $level,
message: &description,
rationale: None,
source: Some($crate::global::PrintSource {
function: $crate::inner_function!(),
file: file!(),
line: line!(),
}),
editor_notify: false,
});
}
};
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
#[non_exhaustive]
pub enum PrintLevel {
Info,
Warn,
Error,
ScriptError,
}
impl PrintLevel {
pub fn godot_title(self) -> Option<&'static str> {
match self {
Self::Warn => Some("WARNING"),
Self::Error => Some("ERROR"),
Self::ScriptError => Some("SCRIPT ERROR"),
Self::Info => None,
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct PrintSource<'a> {
pub function: &'a str,
pub file: &'a str,
pub line: u32,
}
impl<'a> PrintSource<'a> {
pub fn from_location(location: &'a std::panic::Location<'a>, function: &'a str) -> Self {
Self {
function,
file: location.file(),
line: location.line(),
}
}
#[track_caller]
pub fn caller() -> Self {
Self::from_location(std::panic::Location::caller(), "")
}
}
#[derive(Copy, Clone, Debug)]
pub struct PrintRecord<'a> {
pub level: PrintLevel,
pub message: &'a str,
pub rationale: Option<&'a str>,
pub source: Option<PrintSource<'a>>,
pub editor_notify: bool,
}
#[track_caller]
pub fn print_custom(record: PrintRecord<'_>) {
if !sys::is_initialized() {
let level = record
.level
.godot_title()
.map_or(String::new(), |t| format!("{t}:"));
match record.rationale {
Some(msg) => eprintln!("{level}{} ({msg})", record.message),
None => eprintln!("{level}{}", record.message),
}
return;
}
if record.level == PrintLevel::Info {
print_info(record.message, record.rationale, record.source);
return;
}
let source = record.source.unwrap_or_else(PrintSource::caller);
let PrintSource {
function,
file,
line,
} = source;
let desc_nul = format!("{}\0", record.message);
let func_nul = format!("{function}\0");
let file_nul = format!("{file}\0");
let msg_nul = record.rationale.map(|m| format!("{m}\0"));
let editor_notify = sys::conv::bool_to_sys(record.editor_notify);
let desc_ptr = sys::c_str_from_str(&desc_nul);
let func_ptr = sys::c_str_from_str(&func_nul);
let file_ptr = sys::c_str_from_str(&file_nul);
let line = line as i32;
unsafe {
let interface = sys::thread_safe();
if let Some(msg_z) = &msg_nul {
let godot_fn = match record.level {
PrintLevel::Warn => interface.print_warning_with_message,
PrintLevel::Error => interface.print_error_with_message,
PrintLevel::ScriptError => interface.print_script_error_with_message,
PrintLevel::Info => unreachable!(),
};
godot_fn(
desc_ptr,
sys::c_str_from_str(msg_z),
func_ptr,
file_ptr,
line,
editor_notify,
);
} else {
let godot_fn = match record.level {
PrintLevel::Warn => interface.print_warning,
PrintLevel::Error => interface.print_error,
PrintLevel::ScriptError => interface.print_script_error,
PrintLevel::Info => unreachable!(),
};
godot_fn(desc_ptr, func_ptr, file_ptr, line, editor_notify);
}
}
}
fn print_info(description: &str, message: Option<&str>, source: Option<PrintSource<'_>>) {
let full = match (message, source) {
(Some(m), None) => format!("{description}: {m}"),
(Some(m), Some(s)) => {
format!(
"{description}: {m}\n\tat: {} ({}:{})",
s.function, s.file, s.line
)
}
(None, Some(s)) => format!(
"{description}\n\tat: {} ({}:{})",
s.function, s.file, s.line
),
(None, None) => description.to_string(),
};
__threadsafe_print(full, false);
}
#[doc(hidden)]
pub fn __threadsafe_print(message: String, rich: bool) {
let variant = Variant::from(message);
if rich {
crate::global::print_rich(std::slice::from_ref(&variant));
} else {
crate::global::print(std::slice::from_ref(&variant));
}
unsafe { variant.destroy_unchecked_thread() };
}
#[macro_export]
macro_rules! godot_warn {
($fmt:literal $(, $args:expr_2021)* $(,)?) => {
$crate::inner_godot_msg!($crate::global::PrintLevel::Warn; $fmt $(, $args)*);
};
}
#[macro_export]
macro_rules! godot_error {
($fmt:literal $(, $args:expr_2021)* $(,)?) => {
$crate::inner_godot_msg!($crate::global::PrintLevel::Error; $fmt $(, $args)*);
};
}
#[macro_export]
macro_rules! godot_script_error {
($fmt:literal $(, $args:expr_2021)* $(,)?) => {
$crate::inner_godot_msg!($crate::global::PrintLevel::ScriptError; $fmt $(, $args)*);
};
}
#[macro_export]
macro_rules! godot_print {
($fmt:literal $(, $args:expr_2021)* $(,)?) => {
$crate::global::__threadsafe_print(format!($fmt $(, $args)*), false)
};
}
#[macro_export]
macro_rules! godot_print_rich {
($fmt:literal $(, $args:expr_2021)* $(,)?) => {
$crate::global::__threadsafe_print(format!($fmt $(, $args)*), true)
};
}
#[macro_export]
macro_rules! godot_str {
($fmt:literal $(, $args:expr_2021)* $(,)?) => {
$crate::global::str(&[
$crate::builtin::Variant::from(
format!($fmt $(, $args)*)
)
])
};
}
static SUPPRESS_DEPTH: AtomicU32 = AtomicU32::new(0);
static SUPPRESS_ORIG_STATE: AtomicBool = AtomicBool::new(true);
#[doc(hidden)]
#[must_use = "keep RAII guard alive, e.g. `let _guard = suppress_godot_errors();`"]
pub struct SuppressGuard {
_private: (),
}
impl Drop for SuppressGuard {
fn drop(&mut self) {
let prev = SUPPRESS_DEPTH.fetch_sub(1, Ordering::AcqRel);
sys::strict_assert!(prev >= 1, "suppress_godot_errors: depth underflow");
if prev == 1 {
let restore = SUPPRESS_ORIG_STATE.load(Ordering::Acquire);
Engine::singleton().set_print_error_messages(restore);
}
}
}
#[doc(hidden)]
pub fn suppress_godot_errors() -> SuppressGuard {
if SUPPRESS_DEPTH.fetch_add(1, Ordering::AcqRel) == 0 {
SUPPRESS_ORIG_STATE.store(
Engine::singleton().is_printing_error_messages(),
Ordering::Release,
);
Engine::singleton().set_print_error_messages(false);
}
SuppressGuard { _private: () }
}