use std::collections::HashMap;
use std::{any, ptr};
use sys::{Global, GlobalGuard, GlobalLockError, interface_fn, out};
use crate::classes::ClassDb;
use crate::init::InitLevel;
use crate::meta::ClassId;
use crate::meta::error::FromGodotError;
use crate::obj::{DynGd, Gd, GodotClass, Singleton, cap};
use crate::private::{ClassPlugin, PluginItem};
use crate::registry::callbacks;
use crate::registry::plugin::{DynTraitImpl, ErasedRegisterFn, ITraitImpl, InherentImpl, Struct};
use crate::{godot_error, godot_warn, sys};
fn global_loaded_classes_by_init_level()
-> GlobalGuard<'static, HashMap<InitLevel, Vec<LoadedClass>>> {
static LOADED_CLASSES_BY_INIT_LEVEL: Global<
HashMap<InitLevel, Vec<LoadedClass>>, > = Global::default();
lock_or_panic(&LOADED_CLASSES_BY_INIT_LEVEL, "loaded classes")
}
fn global_loaded_classes_by_name() -> GlobalGuard<'static, HashMap<ClassId, ClassMetadata>> {
static LOADED_CLASSES_BY_NAME: Global<HashMap<ClassId, ClassMetadata>> = Global::default();
lock_or_panic(&LOADED_CLASSES_BY_NAME, "loaded classes (by name)")
}
fn global_dyn_traits_by_typeid() -> GlobalGuard<'static, HashMap<any::TypeId, Vec<DynTraitImpl>>> {
static DYN_TRAITS_BY_TYPEID: Global<HashMap<any::TypeId, Vec<DynTraitImpl>>> =
Global::default();
lock_or_panic(&DYN_TRAITS_BY_TYPEID, "dyn traits")
}
pub struct LoadedClass {
name: ClassId,
is_editor_plugin: bool,
unregister_singleton_fn: Option<fn()>,
}
pub struct ClassMetadata {}
#[cfg(before_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.3")))]
type GodotCreationInfo = sys::GDExtensionClassCreationInfo2;
#[cfg(all(since_api = "4.3", before_api = "4.4"))] #[cfg_attr(published_docs, doc(cfg(all(since_api = "4.3", before_api = "4.4"))))]
type GodotCreationInfo = sys::GDExtensionClassCreationInfo3;
#[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
type GodotCreationInfo = sys::GDExtensionClassCreationInfo4;
#[cfg(before_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.4")))]
pub(crate) type GodotGetVirtual = <sys::GDExtensionClassGetVirtual as sys::Inner>::FnPtr;
#[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
pub(crate) type GodotGetVirtual = <sys::GDExtensionClassGetVirtual2 as sys::Inner>::FnPtr;
#[derive(Debug)]
struct ClassRegistrationInfo {
class_name: ClassId,
parent_class_name: Option<ClassId>,
register_methods_constants_fn: Option<ErasedRegisterFn>,
register_properties_fn: Option<ErasedRegisterFn>,
user_register_fn: Option<ErasedRegisterFn>,
default_virtual_fn: Option<GodotGetVirtual>, user_virtual_fn: Option<GodotGetVirtual>, register_singleton_fn: Option<fn()>,
unregister_singleton_fn: Option<fn()>,
godot_params: GodotCreationInfo,
#[allow(dead_code)] init_level: InitLevel,
is_editor_plugin: bool,
dynify_fns_by_trait: HashMap<any::TypeId, DynTraitImpl>,
component_already_filled: [bool; 4],
}
impl ClassRegistrationInfo {
fn validate_unique(&mut self, item: &PluginItem) {
let index = match item {
PluginItem::Struct { .. } => 0,
PluginItem::InherentImpl(_) => 1,
PluginItem::ITraitImpl { .. } => 2,
PluginItem::DynTraitImpl { .. } => return,
};
if self.component_already_filled[index] {
panic!(
"Godot class `{}` is defined multiple times in Rust; you can rename it with #[class(rename=NewName)]",
self.class_name,
)
}
self.component_already_filled[index] = true;
}
}
#[expect(dead_code)] pub(crate) fn register_class<
T: cap::GodotDefault
+ cap::ImplementsGodotVirtual
+ cap::GodotToString
+ cap::GodotNotification
+ cap::GodotRegisterClass
+ GodotClass,
>() {
out!("Manually register class {}", std::any::type_name::<T>());
let godot_params = GodotCreationInfo {
to_string_func: Some(callbacks::to_string::<T>),
notification_func: Some(callbacks::on_notification::<T>),
reference_func: Some(callbacks::reference::<T>),
unreference_func: Some(callbacks::unreference::<T>),
create_instance_func: Some(callbacks::create::<T>),
free_instance_func: Some(callbacks::free::<T>),
get_virtual_func: Some(callbacks::get_virtual::<T>),
class_userdata: ptr::null_mut(), ..default_creation_info()
};
assert!(
!T::class_id().is_none(),
"cannot register () or unnamed class"
);
register_class_raw(ClassRegistrationInfo {
class_name: T::class_id(),
parent_class_name: Some(T::Base::class_id()),
register_methods_constants_fn: None,
register_properties_fn: None,
user_register_fn: Some(ErasedRegisterFn {
raw: callbacks::register_class_by_builder::<T>,
}),
user_virtual_fn: None,
default_virtual_fn: None,
godot_params,
init_level: T::INIT_LEVEL,
is_editor_plugin: false,
dynify_fns_by_trait: HashMap::new(),
component_already_filled: Default::default(), register_singleton_fn: None,
unregister_singleton_fn: None,
});
}
pub fn auto_register_classes(init_level: InitLevel) {
out!("Auto-register classes at level `{init_level:?}`...");
let mut map = HashMap::<ClassId, ClassRegistrationInfo>::new();
crate::private::iterate_plugins(|elem: &ClassPlugin| {
if elem.init_level != init_level {
return;
}
let name = elem.class_name;
let class_info = map
.entry(name)
.or_insert_with(|| default_registration_info(name));
fill_class_info(elem.item.clone(), class_info);
});
register_classes_and_dyn_traits(&mut map, init_level);
let mut editor_plugins: Vec<ClassId> = Vec::new();
let mut singletons: Vec<fn()> = Vec::new();
for info in map.into_values() {
#[cfg(feature = "debug-log")] #[cfg_attr(published_docs, doc(cfg(feature = "debug-log")))]
let class_name = info.class_name;
if info.is_editor_plugin {
editor_plugins.push(info.class_name);
}
if let Some(register_singleton_fn) = info.register_singleton_fn {
singletons.push(register_singleton_fn)
}
register_class_raw(info);
out!("Class {class_name} loaded.");
}
for register_singleton_fn in singletons {
register_singleton_fn()
}
for editor_plugin_class_name in editor_plugins {
unsafe { interface_fn!(editor_add_plugin)(editor_plugin_class_name.string_sys()) };
}
out!("All classes for level `{init_level:?}` auto-registered.");
}
fn register_classes_and_dyn_traits(
map: &mut HashMap<ClassId, ClassRegistrationInfo>,
init_level: InitLevel,
) {
let mut loaded_classes_by_level = global_loaded_classes_by_init_level();
let mut loaded_classes_by_name = global_loaded_classes_by_name();
let mut dyn_traits_by_typeid = global_dyn_traits_by_typeid();
for info in map.values_mut() {
let class_name = info.class_name;
out!("Register class: {class_name} at level `{init_level:?}`");
let loaded_class = LoadedClass {
name: class_name,
is_editor_plugin: info.is_editor_plugin,
unregister_singleton_fn: info.unregister_singleton_fn,
};
let metadata = ClassMetadata {};
for (trait_type_id, mut dyn_trait_impl) in info.dynify_fns_by_trait.drain() {
dyn_trait_impl.parent_class_name = info.parent_class_name;
dyn_traits_by_typeid
.entry(trait_type_id)
.or_default()
.push(dyn_trait_impl);
}
loaded_classes_by_level
.entry(init_level)
.or_default()
.push(loaded_class);
loaded_classes_by_name.insert(class_name, metadata);
}
}
pub fn unregister_classes(init_level: InitLevel) {
let mut loaded_classes_by_level = global_loaded_classes_by_init_level();
let mut loaded_classes_by_name = global_loaded_classes_by_name();
let loaded_classes_current_level = loaded_classes_by_level
.remove(&init_level)
.unwrap_or_default();
out!("Unregister classes of level {init_level:?}...");
for class in loaded_classes_current_level.into_iter().rev() {
loaded_classes_by_name.remove(&class.name);
unregister_class_raw(class);
}
}
#[cfg(feature = "codegen-full")] #[cfg_attr(published_docs, doc(cfg(feature = "codegen-full")))]
pub fn auto_register_rpcs<T: GodotClass>(object: &mut T) {
if let Some(InherentImpl {
register_rpcs_fn: Some(closure),
..
}) = crate::private::find_inherent_impl(T::class_id())
{
(closure.raw)(object);
}
}
pub(crate) fn try_dynify_object<T: GodotClass, D: ?Sized + 'static>(
mut object: Gd<T>,
) -> Result<DynGd<T, D>, (FromGodotError, Gd<T>)> {
let typeid = any::TypeId::of::<D>();
let trait_name = sys::short_type_name::<D>();
let dyn_traits_by_typeid = global_dyn_traits_by_typeid();
let Some(relations) = dyn_traits_by_typeid.get(&typeid) else {
return Err((FromGodotError::UnregisteredDynTrait { trait_name }, object));
};
for relation in relations {
match relation.get_dyn_gd(object) {
Ok(dyn_gd) => return Ok(dyn_gd),
Err(obj) => object = obj,
}
}
let error = FromGodotError::UnimplementedDynTrait {
trait_name,
class_name: object.dynamic_class_string().to_string(),
};
Err((error, object))
}
pub(crate) fn get_dyn_implementor_class_ids<T, D>() -> Vec<ClassId>
where
T: GodotClass,
D: ?Sized + 'static,
{
let typeid = any::TypeId::of::<D>();
let dyn_traits_by_typeid = global_dyn_traits_by_typeid();
let Some(relations) = dyn_traits_by_typeid.get(&typeid) else {
let trait_name = sys::short_type_name::<D>();
godot_warn!(
"godot-rust: No class has been linked to trait {trait_name} with #[godot_dyn]."
);
return Vec::new();
};
assert!(
!relations.is_empty(),
"Trait {trait_name} has been registered as DynGd Trait \
despite no class being related to it \n\
**this is a bug, please report it**",
trait_name = sys::short_type_name::<D>()
);
relations
.iter()
.filter_map(|implementor| {
if implementor.parent_class_name? == T::class_id()
|| ClassDb::singleton().is_parent_class(
&implementor.parent_class_name?.to_string_name(),
&T::class_id().to_string_name(),
)
{
Some(*implementor.class_name())
} else {
None
}
})
.collect()
}
fn fill_class_info(item: PluginItem, c: &mut ClassRegistrationInfo) {
c.validate_unique(&item);
match item {
PluginItem::Struct(Struct {
base_class_name,
generated_create_fn,
generated_recreate_fn,
register_properties_fn,
free_fn,
default_get_virtual_fn,
unregister_singleton_fn,
register_singleton_fn,
is_tool,
is_editor_plugin,
is_internal,
is_instantiable,
reference_fn,
unreference_fn,
}) => {
c.parent_class_name = Some(base_class_name);
c.default_virtual_fn = default_get_virtual_fn;
c.register_properties_fn = Some(register_properties_fn);
c.is_editor_plugin = is_editor_plugin;
c.register_singleton_fn = register_singleton_fn;
c.unregister_singleton_fn = unregister_singleton_fn;
c.godot_params.is_abstract = sys::conv::bool_to_sys(!is_instantiable);
c.godot_params.free_instance_func = Some(free_fn);
c.godot_params.reference_func = reference_fn;
c.godot_params.unreference_func = unreference_fn;
fill_into(
&mut c.godot_params.create_instance_func,
generated_create_fn,
)
.expect("duplicate: create_instance_func (def)");
fill_into(
&mut c.godot_params.recreate_instance_func,
generated_recreate_fn,
)
.expect("duplicate: recreate_instance_func (def)");
c.godot_params.is_exposed = sys::conv::bool_to_sys(!is_internal);
#[cfg(before_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.3")))]
let _ = is_tool; #[cfg(since_api = "4.3")]
{
c.godot_params.is_runtime =
sys::conv::bool_to_sys(crate::private::is_class_runtime(is_tool));
}
}
PluginItem::InherentImpl(InherentImpl {
register_methods_constants_fn,
register_rpcs_fn: _,
}) => {
c.register_methods_constants_fn = Some(register_methods_constants_fn);
}
PluginItem::ITraitImpl(ITraitImpl {
user_register_fn,
user_create_fn,
user_recreate_fn,
user_to_string_fn,
user_on_notification_fn,
user_set_fn,
user_get_fn,
get_virtual_fn,
user_get_property_list_fn,
user_free_property_list_fn,
user_property_can_revert_fn,
user_property_get_revert_fn,
validate_property_fn,
}) => {
c.user_register_fn = user_register_fn;
fill_into(&mut c.godot_params.create_instance_func, user_create_fn)
.expect("duplicate: create_instance_func (i)");
fill_into(&mut c.godot_params.recreate_instance_func, user_recreate_fn)
.expect("duplicate: recreate_instance_func (i)");
c.godot_params.to_string_func = user_to_string_fn;
c.godot_params.notification_func = user_on_notification_fn;
c.godot_params.set_func = user_set_fn;
c.godot_params.get_func = user_get_fn;
c.godot_params.get_property_list_func = user_get_property_list_fn;
c.godot_params.free_property_list_func = user_free_property_list_fn;
c.godot_params.property_can_revert_func = user_property_can_revert_fn;
c.godot_params.property_get_revert_func = user_property_get_revert_fn;
c.user_virtual_fn = get_virtual_fn;
{
c.godot_params.validate_property_func = validate_property_fn;
}
}
PluginItem::DynTraitImpl(dyn_trait_impl) => {
let type_id = dyn_trait_impl.dyn_trait_typeid();
let prev = c.dynify_fns_by_trait.insert(type_id, dyn_trait_impl);
assert!(
prev.is_none(),
"Duplicate registration of {:?} for class {}",
type_id,
c.class_name
);
}
}
}
fn fill_into<T>(dst: &mut Option<T>, src: Option<T>) -> Result<(), ()> {
match (dst, src) {
(dst @ None, src) => *dst = src,
(Some(_), Some(_)) => return Err(()),
(Some(_), None) => { }
}
Ok(())
}
fn register_class_raw(mut info: ClassRegistrationInfo) {
validate_class_constraints(&info);
let class_name = info.class_name;
let parent_class_name = info
.parent_class_name
.expect("class defined (parent_class_name)");
if info.godot_params.get_virtual_func.is_none() {
info.godot_params.get_virtual_func = info.user_virtual_fn.or(info.default_virtual_fn);
}
let registration_failed = unsafe {
#[cfg(before_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.3")))]
let register_fn = interface_fn!(classdb_register_extension_class2);
#[cfg(all(since_api = "4.3", before_api = "4.4"))] #[cfg_attr(published_docs, doc(cfg(all(since_api = "4.3", before_api = "4.4"))))]
let register_fn = interface_fn!(classdb_register_extension_class3);
#[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
let register_fn = interface_fn!(classdb_register_extension_class4);
let _: () = register_fn(
sys::get_library(),
class_name.string_sys(),
parent_class_name.string_sys(),
ptr::addr_of!(info.godot_params),
);
let tag = interface_fn!(classdb_get_class_tag)(class_name.string_sys());
tag.is_null()
};
if registration_failed {
godot_error!(
"Failed to register class `{class_name}`; check preceding Godot stderr messages."
);
}
let mut class_builder = 0;
if let Some(register_fn) = info.register_methods_constants_fn {
(register_fn.raw)(&mut class_builder);
}
if let Some(register_fn) = info.register_properties_fn {
(register_fn.raw)(&mut class_builder);
}
if let Some(register_fn) = info.user_register_fn {
(register_fn.raw)(&mut class_builder);
}
}
fn validate_class_constraints(_class: &ClassRegistrationInfo) {
}
fn unregister_class_raw(class: LoadedClass) {
let class_name = class.name;
out!("Unregister class: {class_name}");
if class.is_editor_plugin {
unsafe {
interface_fn!(editor_remove_plugin)(class_name.string_sys());
}
out!("> Editor plugin removed");
}
if let Some(unregister_singleton_fn) = class.unregister_singleton_fn {
unregister_singleton_fn();
}
#[allow(clippy::let_unit_value)]
let _: () = unsafe {
interface_fn!(classdb_unregister_extension_class)(
sys::get_library(),
class_name.string_sys(),
)
};
out!("Class {class_name} unloaded");
}
fn lock_or_panic<T>(global: &'static Global<T>, ctx: &str) -> GlobalGuard<'static, T> {
match global.try_lock() {
Ok(it) => it,
Err(err) => match err {
GlobalLockError::Poisoned { .. } => panic!(
"global lock for {ctx} poisoned; class registration or deregistration may have panicked"
),
GlobalLockError::WouldBlock => {
panic!("unexpected concurrent access to global lock for {ctx}")
}
GlobalLockError::InitFailed => unreachable!("global lock for {ctx} not initialized"),
},
}
}
fn default_registration_info(class_name: ClassId) -> ClassRegistrationInfo {
ClassRegistrationInfo {
class_name,
parent_class_name: None,
register_methods_constants_fn: None,
register_properties_fn: None,
user_register_fn: None,
default_virtual_fn: None,
user_virtual_fn: None,
register_singleton_fn: None,
unregister_singleton_fn: None,
godot_params: default_creation_info(),
init_level: InitLevel::Scene,
is_editor_plugin: false,
dynify_fns_by_trait: HashMap::new(),
component_already_filled: Default::default(), }
}
#[cfg(before_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.3")))]
fn default_creation_info() -> sys::GDExtensionClassCreationInfo2 {
sys::GDExtensionClassCreationInfo2 {
is_virtual: false as u8,
is_abstract: false as u8,
is_exposed: sys::conv::SYS_TRUE,
set_func: None,
get_func: None,
get_property_list_func: None,
free_property_list_func: None,
property_can_revert_func: None,
property_get_revert_func: None,
validate_property_func: None,
notification_func: None,
to_string_func: None,
reference_func: None,
unreference_func: None,
create_instance_func: None,
free_instance_func: None,
recreate_instance_func: None,
get_virtual_func: None,
get_virtual_call_data_func: None,
call_virtual_with_data_func: None,
get_rid_func: None,
class_userdata: ptr::null_mut(),
}
}
#[cfg(all(since_api = "4.3", before_api = "4.4"))] #[cfg_attr(published_docs, doc(cfg(all(since_api = "4.3", before_api = "4.4"))))]
fn default_creation_info() -> sys::GDExtensionClassCreationInfo3 {
sys::GDExtensionClassCreationInfo3 {
is_virtual: false as u8,
is_abstract: false as u8,
is_exposed: sys::conv::SYS_TRUE,
is_runtime: sys::conv::SYS_TRUE,
set_func: None,
get_func: None,
get_property_list_func: None,
free_property_list_func: None,
property_can_revert_func: None,
property_get_revert_func: None,
validate_property_func: None,
notification_func: None,
to_string_func: None,
reference_func: None,
unreference_func: None,
create_instance_func: None,
free_instance_func: None,
recreate_instance_func: None,
get_virtual_func: None,
get_virtual_call_data_func: None,
call_virtual_with_data_func: None,
get_rid_func: None,
class_userdata: ptr::null_mut(),
}
}
#[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
fn default_creation_info() -> sys::GDExtensionClassCreationInfo4 {
sys::GDExtensionClassCreationInfo4 {
is_virtual: false as u8,
is_abstract: false as u8,
is_exposed: sys::conv::SYS_TRUE,
is_runtime: sys::conv::SYS_TRUE,
icon_path: ptr::null(),
set_func: None,
get_func: None,
get_property_list_func: None,
free_property_list_func: None,
property_can_revert_func: None,
property_get_revert_func: None,
validate_property_func: None,
notification_func: None,
to_string_func: None,
reference_func: None,
unreference_func: None,
create_instance_func: None,
free_instance_func: None,
recreate_instance_func: None,
get_virtual_func: None,
get_virtual_call_data_func: None,
call_virtual_with_data_func: None,
class_userdata: ptr::null_mut(),
}
}