use std::{fmt, ptr};
use godot_ffi as sys;
use sys::{GodotFfi, ffi_methods, interface_fn};
use crate::builtin::{
GString, StringName, VarArray, VariantDispatch, VariantOperator, VariantType,
};
use crate::classes;
use crate::meta::error::{ConvertError, FromVariantError};
use crate::meta::{
AsArg, Element, EngineFromGodot, ExtVariantType, FromGodot, GodotType, ToGodot, arg_into_ref,
ffi_variant_type,
};
mod impls;
#[repr(transparent)]
pub struct Variant {
_opaque: sys::types::OpaqueVariant,
}
impl Variant {
pub fn nil() -> Self {
Self::default()
}
pub fn from<T: ToGodot>(value: T) -> Self {
value.to_variant()
}
pub fn to<T: FromGodot>(&self) -> T {
T::from_variant(self)
}
pub fn try_to<T: FromGodot>(&self) -> Result<T, ConvertError> {
T::try_from_variant(self)
}
pub fn try_to_relaxed<T: FromGodot>(&self) -> Result<T, ConvertError> {
try_from_variant_relaxed(self)
}
pub(crate) fn engine_try_to_relaxed<T: EngineFromGodot>(&self) -> Result<T, ConvertError> {
try_from_variant_relaxed(self)
}
pub(crate) fn to_relaxed_or_panic<T, F>(&self, context: F) -> T
where
T: EngineFromGodot,
F: FnOnce() -> String,
{
self.engine_try_to_relaxed::<T>()
.unwrap_or_else(|err| panic!("{}: {err}", context()))
}
pub fn is_nil(&self) -> bool {
self.get_type() == VariantType::NIL
}
pub fn get_type(&self) -> VariantType {
let sys_type = self.sys_type();
let is_null_object = if sys_type == sys::GDEXTENSION_VARIANT_TYPE_OBJECT {
let object_ptr = unsafe {
crate::obj::raw_object_init(|type_ptr| {
let converter = sys::builtin_fn!(object_from_variant);
converter(type_ptr, sys::SysPtr::force_mut(self.var_sys()));
})
};
object_ptr.is_null()
} else {
false
};
if is_null_object {
VariantType::NIL
} else {
VariantType::from_sys(sys_type)
}
}
pub fn object_id(&self) -> Option<crate::obj::InstanceId> {
#[cfg(since_api = "4.4")]
{
assert!(
self.get_type() != VariantType::OBJECT || self.is_object_alive(),
"Variant::object_id(): object has been freed"
);
self.object_id_unchecked()
}
#[cfg(before_api = "4.4")]
{
use crate::meta::error::{ErrorKind, FromVariantError};
match self.try_to::<crate::obj::Gd<crate::classes::Object>>() {
Ok(obj) => Some(obj.instance_id_unchecked()),
Err(c)
if matches!(
c.kind(),
ErrorKind::FromVariant(FromVariantError::DeadObject)
) =>
{
panic!("Variant::object_id(): object has been freed")
}
_ => None, }
}
}
#[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
pub fn object_id_unchecked(&self) -> Option<crate::obj::InstanceId> {
let raw_id: u64 = unsafe { interface_fn!(variant_get_object_instance_id)(self.var_sys()) };
crate::obj::InstanceId::try_from_u64(raw_id)
}
#[inline]
pub fn call(&self, method: impl AsArg<StringName>, args: &[Variant]) -> Variant {
arg_into_ref!(method);
self.call_inner(method, args)
}
fn call_inner(&self, method: &StringName, args: &[Variant]) -> Variant {
let args_sys: Vec<_> = args.iter().map(|v| v.var_sys()).collect();
let mut error = sys::default_call_error();
let result = unsafe {
Variant::new_with_var_uninit(|variant_ptr| {
interface_fn!(variant_call)(
sys::SysPtr::force_mut(self.var_sys()),
method.string_sys(),
args_sys.as_ptr(),
args_sys.len() as i64,
variant_ptr,
ptr::addr_of_mut!(error),
)
})
};
if error.error != sys::GDEXTENSION_CALL_OK {
let arg_types: Vec<_> = args.iter().map(Variant::get_type).collect();
sys::panic_call_error(&error, "call", &arg_types);
}
result
}
pub fn evaluate(&self, rhs: &Variant, op: VariantOperator) -> Option<Variant> {
use crate::obj::EngineEnum;
let op_sys = op.ord() as sys::GDExtensionVariantOperator;
let mut is_valid = false as u8;
let result = unsafe {
Self::new_with_var_uninit(|variant_ptr| {
interface_fn!(variant_evaluate)(
op_sys,
self.var_sys(),
rhs.var_sys(),
variant_ptr,
ptr::addr_of_mut!(is_valid),
)
})
};
if is_valid == 1 { Some(result) } else { None }
}
pub(crate) fn sys_type(&self) -> sys::GDExtensionVariantType {
unsafe {
let ty: sys::GDExtensionVariantType = interface_fn!(variant_get_type)(self.var_sys());
ty
}
}
#[allow(unused_mut)] pub fn stringify(&self) -> GString {
let mut result = GString::new();
unsafe {
interface_fn!(variant_stringify)(self.var_sys(), result.string_sys_mut());
}
result
}
pub fn hash_u32(&self) -> u32 {
unsafe { interface_fn!(variant_hash)(self.var_sys()) }
.try_into()
.expect("Godot hashes are uint32_t")
}
pub fn booleanize(&self) -> bool {
unsafe { interface_fn!(variant_booleanize)(self.var_sys()) != 0 }
}
pub(crate) fn is_object_alive(&self) -> bool {
sys::strict_assert_eq!(self.get_type(), VariantType::OBJECT);
crate::global::is_instance_valid(self)
}
pub(crate) unsafe fn destroy_unchecked_thread(self) {
let mut this = self;
unsafe {
let variant_destroy = sys::thread_safe().variant_destroy;
variant_destroy(this.var_sys_mut());
}
std::mem::forget(this);
}
ffi_methods! {
type sys::GDExtensionVariantPtr = *mut Self;
fn new_from_var_sys = new_from_sys;
fn new_with_var_uninit = new_with_uninit;
fn new_with_var_init = new_with_init;
fn var_sys = sys;
fn var_sys_mut = sys_mut;
}
}
#[doc(hidden)]
impl Variant {
pub(crate) unsafe fn move_into_var_ptr(self, dst: sys::GDExtensionVariantPtr) {
let dst: sys::GDExtensionTypePtr = dst.cast();
unsafe {
self.move_return_ptr(dst, sys::PtrcallType::Standard);
}
}
#[doc(hidden)]
pub unsafe fn new_with_var_uninit_result<E>(
init_fn: impl FnOnce(sys::GDExtensionUninitializedVariantPtr) -> Result<(), E>,
) -> Result<Self, E> {
let mut raw = std::mem::MaybeUninit::<Variant>::uninit();
let var_uninit_ptr =
raw.as_mut_ptr() as <sys::GDExtensionVariantPtr as sys::SysPtr>::Uninit;
init_fn(var_uninit_ptr).map(|_success| unsafe { raw.assume_init() })
}
pub(crate) unsafe fn borrow_var_sys<'a>(ptr: sys::GDExtensionConstVariantPtr) -> &'a Variant {
sys::static_assert_eq_size_align!(Variant, sys::types::OpaqueVariant);
unsafe { &*(ptr.cast::<Variant>()) }
}
pub(crate) unsafe fn borrow_ref_slice<'a>(
variant_ptr_array: *const sys::GDExtensionConstVariantPtr,
length: usize,
) -> &'a [&'a Variant] {
sys::static_assert_eq_size_align!(Variant, sys::types::OpaqueVariant);
if variant_ptr_array.is_null() {
Self::strict_ensure_zero_length(length);
return &[];
}
let variant_ptr_array = variant_ptr_array.cast::<&Variant>();
unsafe { std::slice::from_raw_parts(variant_ptr_array, length) }
}
pub(crate) unsafe fn borrow_slice<'a>(
variant_array: sys::GDExtensionConstVariantPtr,
length: usize,
) -> &'a [Variant] {
sys::static_assert_eq_size_align!(Variant, sys::types::OpaqueVariant);
if variant_array.is_null() {
Self::strict_ensure_zero_length(length);
return &[];
}
let variant_array = variant_array.cast::<Variant>();
unsafe { std::slice::from_raw_parts(variant_array, length) }
}
pub(crate) unsafe fn borrow_slice_mut<'a>(
variant_array: sys::GDExtensionVariantPtr,
length: usize,
) -> &'a mut [Variant] {
sys::static_assert_eq_size_align!(Variant, sys::types::OpaqueVariant);
if variant_array.is_null() {
Self::strict_ensure_zero_length(length);
return &mut [];
}
let variant_array = variant_array.cast::<Variant>();
unsafe { std::slice::from_raw_parts_mut(variant_array, length) }
}
fn strict_ensure_zero_length(_length: usize) {
sys::strict_assert_eq!(
_length,
0,
"Variant::borrow_slice*(): pointer is null but length is not 0"
);
}
pub(crate) fn into_owned_var_sys(self) -> sys::GDExtensionVariantPtr {
sys::static_assert_eq_size_align!(Variant, sys::types::OpaqueVariant);
let leaked = Box::into_raw(Box::new(self));
leaked.cast()
}
pub(crate) unsafe fn from_owned_var_sys(ptr: sys::GDExtensionVariantPtr) -> Self {
sys::static_assert_eq_size_align!(Variant, sys::types::OpaqueVariant);
let ptr = ptr.cast::<Self>();
let boxed = unsafe { Box::from_raw(ptr) };
*boxed
}
}
impl Element for Variant {}
unsafe impl GodotFfi for Variant {
const VARIANT_TYPE: ExtVariantType = ExtVariantType::Variant;
ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
}
crate::meta::impl_godot_as_self!(Variant: ByVariant);
impl Default for Variant {
fn default() -> Self {
unsafe {
Self::new_with_var_uninit(|variant_ptr| {
interface_fn!(variant_new_nil)(variant_ptr);
})
}
}
}
impl Clone for Variant {
fn clone(&self) -> Self {
unsafe {
Self::new_with_var_uninit(|variant_ptr| {
interface_fn!(variant_new_copy)(variant_ptr, self.var_sys());
})
}
}
}
impl Drop for Variant {
fn drop(&mut self) {
unsafe {
interface_fn!(variant_destroy)(self.var_sys_mut());
}
}
}
impl PartialEq for Variant {
fn eq(&self, other: &Self) -> bool {
Self::evaluate(self, other, VariantOperator::EQUAL) .is_some_and(|v| v.to::<bool>())
}
}
impl fmt::Display for Variant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = self.stringify();
write!(f, "{s}")
}
}
impl fmt::Debug for Variant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.get_type() {
VariantType::ARRAY => {
let array = unsafe { VarArray::from_variant_unchecked(self) };
array.fmt(f)
}
VariantType::OBJECT => classes::debug_string_variant(self, f, "VariantGd"),
_ => VariantDispatch::from_variant(self).fmt(f),
}
}
}
fn try_from_variant_relaxed<T: EngineFromGodot>(variant: &Variant) -> Result<T, ConvertError> {
let from_type = variant.get_type();
let to_type = match ffi_variant_type::<T>() {
ExtVariantType::Variant => {
return T::engine_try_from_variant(variant);
}
ExtVariantType::Concrete(to_type) if from_type == to_type => {
return T::engine_try_from_variant(variant);
}
ExtVariantType::Concrete(to_type) => to_type,
};
if to_type == VariantType::NIL || !can_convert_godot_strict(from_type, to_type) {
return Err(FromVariantError::BadType {
expected: to_type,
actual: from_type,
}
.into_error(variant.clone()));
}
let converter = unsafe {
let get_constructor = interface_fn!(get_variant_to_type_constructor);
get_constructor(to_type.sys())
};
let converter =
converter.unwrap_or_else(|| panic!("missing converter for {from_type:?} -> {to_type:?}"));
let ffi_result = unsafe {
<<T::Via as GodotType>::Ffi as GodotFfi>::new_with_uninit(|result_ptr| {
converter(result_ptr, sys::SysPtr::force_mut(variant.var_sys()));
})
};
let via = <T::Via as GodotType>::try_from_ffi(ffi_result)?;
let concrete = T::engine_try_from_godot(via)?;
Ok(concrete)
}
fn can_convert_godot_strict(from_type: VariantType, to_type: VariantType) -> bool {
unsafe {
let can_convert_fn = interface_fn!(variant_can_convert_strict);
can_convert_fn(from_type.sys(), to_type.sys()) == sys::conv::SYS_TRUE
}
}