use std::sync::atomic::{AtomicBool, Ordering};
use godot_ffi as sys;
use sys::GodotFfi;
use crate::builtin::{GString, StringName};
use crate::obj::Singleton;
use crate::out;
mod reexport_pub {
#[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
pub use super::sys::is_editor_hint;
#[cfg(not(wasm_nothreads))] #[cfg_attr(published_docs, doc(cfg(not(wasm_nothreads))))]
pub use super::sys::main_thread_id;
pub use super::sys::{GdextBuild, InitStage, is_main_thread};
}
pub use reexport_pub::*;
use crate::obj::signal::prune_stored_signal_connections;
#[repr(C)]
struct InitUserData {
library: sys::GDExtensionClassLibraryPtr,
#[cfg(since_api = "4.5")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.5")))]
main_loop_callbacks: sys::GDExtensionMainLoopCallbacks,
}
#[cfg(since_api = "4.5")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.5")))]
unsafe extern "C" fn startup_func<E: ExtensionLibrary>() {
let ctx = || "ExtensionLibrary::on_stage_init(MainLoop)".to_string();
swallow_panics(ctx, || {
E::on_stage_init(InitStage::MainLoop);
});
sys::print_deferred_startup_messages();
}
#[cfg(since_api = "4.5")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.5")))]
unsafe extern "C" fn frame_func<E: ExtensionLibrary>() {
let ctx = || "ExtensionLibrary::on_main_loop_frame()".to_string();
swallow_panics(ctx, || {
E::on_main_loop_frame();
});
}
#[cfg(since_api = "4.5")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.5")))]
unsafe extern "C" fn shutdown_func<E: ExtensionLibrary>() {
let ctx = || "ExtensionLibrary::on_stage_deinit(MainLoop)".to_string();
swallow_panics(ctx, || {
E::on_stage_deinit(InitStage::MainLoop);
});
}
#[doc(hidden)]
pub unsafe fn __gdext_load_library<E: ExtensionLibrary>(
get_proc_address: sys::GDExtensionInterfaceGetProcAddress,
library: sys::GDExtensionClassLibraryPtr,
init: *mut sys::GDExtensionInitialization,
) -> sys::GDExtensionBool {
let init_code = || {
#[cfg(target_os = "linux")] #[cfg_attr(published_docs, doc(cfg(target_os = "linux")))]
sys::linux_reload_workaround::default_set_hot_reload();
let tool_only_in_editor = match E::editor_run_behavior() {
EditorRunBehavior::ToolClassesOnly => true,
EditorRunBehavior::AllClasses => false,
};
let config = sys::GdextConfig::new(tool_only_in_editor);
unsafe {
sys::initialize(get_proc_address, library, config);
}
#[cfg(feature = "experimental-threads")] #[cfg_attr(published_docs, doc(cfg(feature = "experimental-threads")))]
crate::private::set_gdext_hook(|| true);
#[cfg(not(feature = "experimental-threads"))]
{
let main_thread = std::thread::current().id();
crate::private::set_gdext_hook(move || std::thread::current().id() == main_thread);
}
let success = true;
let userdata = Box::into_raw(Box::new(InitUserData {
library,
#[cfg(since_api = "4.5")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.5")))]
main_loop_callbacks: sys::GDExtensionMainLoopCallbacks {
startup_func: Some(startup_func::<E>),
frame_func: Some(frame_func::<E>),
shutdown_func: Some(shutdown_func::<E>),
},
}));
let godot_init_params = sys::GDExtensionInitialization {
minimum_initialization_level: E::min_level().to_sys(),
userdata: userdata.cast::<std::ffi::c_void>(),
initialize: Some(ffi_initialize_layer::<E>),
deinitialize: Some(ffi_deinitialize_layer::<E>),
};
unsafe {
*init = godot_init_params;
}
success as u8
};
let is_success = std::panic::catch_unwind(init_code);
is_success.unwrap_or(0)
}
static LEVEL_SERVERS_CORE_LOADED: AtomicBool = AtomicBool::new(false);
unsafe extern "C" fn ffi_initialize_layer<E: ExtensionLibrary>(
userdata: *mut std::ffi::c_void,
init_level: sys::GDExtensionInitializationLevel,
) {
unsafe {
let userdata = userdata.cast::<InitUserData>().as_ref().unwrap();
let level = InitLevel::from_sys(init_level);
let ctx = || format!("ExtensionLibrary::on_stage_init({level:?})");
fn try_load<E: ExtensionLibrary>(level: InitLevel, userdata: &InitUserData) {
if level == InitLevel::Scene {
if !LEVEL_SERVERS_CORE_LOADED.load(Ordering::Relaxed) {
try_load::<E>(InitLevel::Core, userdata);
try_load::<E>(InitLevel::Servers, userdata);
}
} else if level == InitLevel::Core {
LEVEL_SERVERS_CORE_LOADED.store(true, Ordering::Relaxed);
}
unsafe { gdext_on_level_init(level, userdata) };
E::on_stage_init(level.to_stage());
}
swallow_panics(ctx, || {
try_load::<E>(level, userdata);
});
}
}
unsafe extern "C" fn ffi_deinitialize_layer<E: ExtensionLibrary>(
userdata: *mut std::ffi::c_void,
init_level: sys::GDExtensionInitializationLevel,
) {
unsafe {
let level = InitLevel::from_sys(init_level);
let ctx = || format!("ExtensionLibrary::on_stage_deinit({level:?})");
swallow_panics(ctx, || {
if level == InitLevel::Core {
LEVEL_SERVERS_CORE_LOADED.store(false, Ordering::Relaxed);
drop(Box::from_raw(userdata.cast::<InitUserData>()));
}
E::on_stage_deinit(level.to_stage());
gdext_on_level_deinit(level);
});
}
}
unsafe fn gdext_on_level_init(level: InitLevel, _userdata: &InitUserData) {
unsafe { sys::load_class_method_table(level) };
match level {
InitLevel::Core => {
#[cfg(since_api = "4.5")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.5")))]
unsafe {
sys::interface_fn!(register_main_loop_callbacks)(
_userdata.library,
&raw const _userdata.main_loop_callbacks,
)
};
#[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
sys::set_editor_hint(crate::classes::Engine::singleton().is_editor_hint());
}
InitLevel::Servers => {
unsafe { sys::discover_main_thread() };
}
InitLevel::Scene => {
unsafe { ensure_godot_features_compatible() };
}
InitLevel::Editor => {
#[cfg(all(since_api = "4.3", feature = "register-docs"))]
unsafe {
crate::docs::register();
}
}
}
crate::registry::class::auto_register_classes(level);
}
fn gdext_on_level_deinit(level: InitLevel) {
if level == InitLevel::Editor {
prune_stored_signal_connections();
}
crate::registry::class::unregister_classes(level);
if level == InitLevel::Core {
crate::task::cleanup();
crate::tools::cleanup();
unsafe {
crate::meta::cleanup();
}
unsafe {
sys::deinitialize();
}
}
}
fn swallow_panics<E, F>(error_context: E, code: F)
where
E: Fn() -> String,
F: FnOnce() + std::panic::UnwindSafe,
{
let _ = crate::private::handle_panic(error_context, code);
}
#[doc(alias = "entry_symbol", alias = "entry_point")]
pub unsafe trait ExtensionLibrary {
fn editor_run_behavior() -> EditorRunBehavior {
EditorRunBehavior::ToolClassesOnly
}
fn min_level() -> InitLevel {
InitLevel::Scene
}
#[allow(unused_variables)]
fn on_stage_init(stage: InitStage) {}
#[allow(unused_variables)]
fn on_stage_deinit(stage: InitStage) {}
#[cfg(since_api = "4.5")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.5")))]
fn on_main_loop_frame() {}
}
#[derive(Copy, Clone, Debug)]
#[non_exhaustive]
pub enum EditorRunBehavior {
ToolClassesOnly,
AllClasses,
}
pub use sys::InitLevel;
unsafe fn ensure_godot_features_compatible() {
out!("Check Godot precision setting...");
#[cfg(feature = "debug-log")] let safeguards_level = if cfg!(safeguards_strict) {
"strict"
} else if cfg!(safeguards_balanced) {
"balanced"
} else {
"disengaged"
};
out!("Safeguards: {safeguards_level}");
let os_class = StringName::from("OS");
let single = GString::from("single");
let double = GString::from("double");
let gdext_is_double = cfg!(feature = "double-precision");
let godot_is_double = unsafe {
let is_single = sys::godot_has_feature(os_class.string_sys(), single.sys());
let is_double = sys::godot_has_feature(os_class.string_sys(), double.sys());
assert_ne!(
is_single, is_double,
"Godot has invalid configuration: single={is_single}, double={is_double}"
);
is_double
};
let s = |is_double: bool| -> &'static str { if is_double { "double" } else { "single" } };
out!(
"Is double precision: Godot={}, gdext={}",
s(godot_is_double),
s(gdext_is_double)
);
if godot_is_double != gdext_is_double {
panic!(
"Godot runs with {} precision, but gdext was compiled with {} precision.\n\
Cargo feature `double-precision` must be used if and only if Godot is compiled with `precision=double`.\n",
s(godot_is_double),
s(gdext_is_double),
);
}
}