use std::fmt::Write;
use crate::builtin::{GString, StringName, Variant};
use crate::obj::{Bounds, EngineBitfield, Gd, GodotClass, InstanceId, RawGd, bounds};
use crate::sys;
#[cfg(safeguards_strict)] #[cfg_attr(published_docs, doc(cfg(safeguards_strict)))]
mod strict {
pub use crate::builtin::VariantType;
pub use crate::classes::{ClassDb, Object};
pub use crate::meta::ClassId;
pub use crate::obj::Singleton;
}
#[cfg(safeguards_balanced)] #[cfg_attr(published_docs, doc(cfg(safeguards_balanced)))]
mod balanced {
pub(crate) use crate::meta::CallContext;
}
#[cfg(safeguards_balanced)] #[cfg_attr(published_docs, doc(cfg(safeguards_balanced)))]
use balanced::*;
#[cfg(safeguards_strict)] #[cfg_attr(published_docs, doc(cfg(safeguards_strict)))]
use strict::*;
pub(crate) fn debug_string<T: GodotClass>(
obj: &Gd<T>,
f: &mut std::fmt::Formatter<'_>,
ty: &str,
) -> std::fmt::Result {
if let Some(id) = obj.instance_id_or_none() {
let class: StringName = obj.dynamic_class_string();
debug_string_parts(f, ty, id, class, obj.maybe_refcount(), None)
} else {
write!(f, "{ty} {{ freed obj }}")
}
}
#[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
pub(crate) fn debug_string_variant(
obj: &Variant,
f: &mut std::fmt::Formatter<'_>,
ty: &str,
) -> std::fmt::Result {
sys::strict_assert_eq!(obj.get_type(), VariantType::OBJECT);
let id = obj
.object_id_unchecked()
.expect("Variant must be of type OBJECT");
if id.lookup_validity() {
let class = obj
.call("get_class", &[])
.try_to_relaxed::<StringName>()
.expect("get_class() must be compatible with StringName");
let refcount = id.is_ref_counted().then(|| {
let count = obj
.call("get_reference_count", &[])
.try_to_relaxed::<i32>()
.expect("get_reference_count() must return integer");
Ok(count as usize)
});
debug_string_parts(f, ty, id, class, refcount, None)
} else {
write!(f, "{ty} {{ freed obj }}")
}
}
#[cfg(before_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.4")))]
pub(crate) fn debug_string_variant(
obj: &Variant,
f: &mut std::fmt::Formatter<'_>,
ty: &str,
) -> std::fmt::Result {
sys::strict_assert_eq!(obj.get_type(), VariantType::OBJECT);
match obj.try_to::<Gd<crate::classes::Object>>() {
Ok(obj) => {
let id = obj.instance_id(); let class = obj.dynamic_class_string();
let refcount = match obj.maybe_refcount() {
Some(Ok(rc)) => Some(Ok(rc.saturating_sub(1))),
Some(Err(e)) => Some(Err(e)),
None => None,
};
debug_string_parts(f, ty, id, class, refcount, None)
}
Err(_) => {
write!(f, "{ty} {{ freed obj }}")
}
}
}
pub(crate) fn debug_string_nullable<T: GodotClass>(
obj: &RawGd<T>,
f: &mut std::fmt::Formatter<'_>,
ty: &str,
) -> std::fmt::Result {
if obj.is_null() {
write!(f, "{ty} {{ null }}")
} else {
let obj = unsafe { obj.as_non_null() };
debug_string(obj, f, ty)
}
}
pub(crate) fn debug_string_with_trait<T: GodotClass>(
obj: &Gd<T>,
f: &mut std::fmt::Formatter<'_>,
ty: &str,
trt: &str,
) -> std::fmt::Result {
if let Some(id) = obj.instance_id_or_none() {
let class: StringName = obj.dynamic_class_string();
debug_string_parts(f, ty, id, class, obj.maybe_refcount(), Some(trt))
} else {
write!(f, "{ty} {{ freed obj }}")
}
}
fn debug_string_parts(
f: &mut std::fmt::Formatter<'_>,
ty: &str,
id: InstanceId,
class: StringName,
refcount: Option<Result<usize, ()>>,
trait_name: Option<&str>,
) -> std::fmt::Result {
let mut builder = f.debug_struct(ty);
builder
.field("id", &id.to_i64())
.field("class", &format_args!("{class}"));
if let Some(trait_name) = trait_name {
builder.field("trait", &format_args!("{trait_name}"));
}
match refcount {
Some(Ok(refcount)) => {
builder.field("refc", &refcount);
}
Some(Err(_)) => {
builder.field("refc", &"(N/A during init or drop)");
}
None => {}
}
builder.finish()
}
pub(crate) fn display_string<T: GodotClass>(
obj: &Gd<T>,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
let string: GString = obj.raw.as_object_ref().to_string();
<GString as std::fmt::Display>::fmt(&string, f)
}
pub(crate) fn debug_bitfield<T: EngineBitfield>(
bitfield: T,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
let value_bits = bitfield.ord();
let mut remaining_bits = value_bits;
let mut string = String::new();
let mut first = true;
for &c in T::all_constants() {
let mask = c.value().ord();
if mask == 0 && value_bits != 0 {
continue;
}
if value_bits & mask == mask {
remaining_bits &= !mask;
if first {
first = false;
} else {
string.push_str(" | ");
}
string.push_str(c.rust_name());
}
}
if remaining_bits != 0 {
if !first {
string.push_str(" | ");
}
write!(string, "Unknown(0x{remaining_bits:X})")?;
}
let bitfield_name = sys::short_type_name::<T>();
write!(f, "{bitfield_name} {{ {string} }}")
}
pub(crate) fn object_ptr_from_id(instance_id: InstanceId) -> sys::GDExtensionObjectPtr {
unsafe { sys::interface_fn!(object_get_instance_from_id)(instance_id.to_u64()) }
}
pub(crate) fn construct_engine_object<T>() -> Gd<T>
where
T: GodotClass + Bounds<Declarer = bounds::DeclEngine>,
{
let mut obj = unsafe {
let object_ptr = sys::classdb_construct_object(T::class_id().string_sys());
Gd::<T>::from_obj_sys(object_ptr)
};
#[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
obj.upcast_object_mut()
.notify(crate::classes::notify::ObjectNotification::POSTINITIALIZE);
obj
}
pub(crate) unsafe fn singleton_unchecked<T>(class_name: &StringName) -> Gd<T>
where
T: GodotClass,
{
unsafe {
let object_ptr = sys::interface_fn!(global_get_singleton)(class_name.string_sys());
Gd::<T>::from_obj_sys(object_ptr)
}
}
#[cfg(safeguards_balanced)] #[cfg_attr(published_docs, doc(cfg(safeguards_balanced)))]
pub(crate) fn ensure_object_alive(
instance_id: InstanceId,
old_object_ptr: sys::GDExtensionObjectPtr,
call_ctx: &CallContext,
) {
let new_object_ptr = object_ptr_from_id(instance_id);
assert!(
!new_object_ptr.is_null(),
"{call_ctx}: access to instance with ID {instance_id} after it has been freed"
);
assert_eq!(
new_object_ptr, old_object_ptr,
"{call_ctx}: instance ID {instance_id} points to a stale, reused object. Please report this to godot-rust maintainers."
);
}
#[cfg(safeguards_strict)] #[cfg_attr(published_docs, doc(cfg(safeguards_strict)))]
pub(crate) fn ensure_object_inherits(derived: ClassId, base: ClassId, instance_id: InstanceId) {
if derived == base
|| base == Object::class_id() || is_derived_base_cached(derived, base)
{
return;
}
panic!(
"Instance of ID {instance_id} has type {derived} but is incorrectly stored in a Gd<{base}>.\n\
This may happen if you change an object's identity through DerefMut."
)
}
#[cfg(safeguards_strict)] #[cfg_attr(published_docs, doc(cfg(safeguards_strict)))]
pub(crate) fn ensure_binding_not_null<T>(binding: sys::GDExtensionClassInstancePtr)
where
T: GodotClass + Bounds<Declarer = bounds::DeclUser>,
{
if !binding.is_null() {
return;
}
if crate::classes::Engine::singleton().is_editor_hint() {
panic!(
"Class {} -- null instance; does the class have a Godot creator function? \
Ensure that the given class is a tool class with #[class(tool)], if it is being accessed in the editor.",
std::any::type_name::<T>()
)
} else {
panic!(
"Class {} -- null instance; does the class have a Godot creator function?",
std::any::type_name::<T>()
);
}
}
#[cfg(safeguards_strict)] #[cfg_attr(published_docs, doc(cfg(safeguards_strict)))]
fn is_derived_base_cached(derived: ClassId, base: ClassId) -> bool {
use std::collections::HashSet;
use sys::Global;
static CACHE: Global<HashSet<(ClassId, ClassId)>> = Global::default();
let mut cache = CACHE.lock();
let key = (derived, base);
if cache.contains(&key) {
return true;
}
let is_parent_class =
ClassDb::singleton().is_parent_class(&derived.to_string_name(), &base.to_string_name());
if is_parent_class {
cache.insert(key);
}
is_parent_class
}