use crate::builtin::Variant;
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 {
if let Some(msg_z) = &msg_nul {
let godot_fn = match record.level {
PrintLevel::Warn => sys::interface_fn!(print_warning_with_message),
PrintLevel::Error => sys::interface_fn!(print_error_with_message),
PrintLevel::ScriptError => sys::interface_fn!(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 => sys::interface_fn!(print_warning),
PrintLevel::Error => sys::interface_fn!(print_error),
PrintLevel::ScriptError => sys::interface_fn!(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(),
};
crate::global::print(&[Variant::from(full)]);
}
#[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::print(&[
$crate::builtin::Variant::from(
format!($fmt $(, $args)*)
)
])
};
}
#[macro_export]
macro_rules! godot_print_rich {
($fmt:literal $(, $args:expr_2021)* $(,)?) => {
$crate::global::print_rich(&[
$crate::builtin::Variant::from(
format!($fmt $(, $args)*)
)
])
};
}
#[macro_export]
macro_rules! godot_str {
($fmt:literal $(, $args:expr_2021)* $(,)?) => {
$crate::global::str(&[
$crate::builtin::Variant::from(
format!($fmt $(, $args)*)
)
])
};
}