#![cfg_attr(published_docs, feature(doc_cfg))]
#![cfg_attr(test, allow(unused))]
#[cfg(all(feature = "codegen-lazy-fptrs", feature = "experimental-threads"))] #[cfg_attr(published_docs, doc(cfg(all(feature = "codegen-lazy-fptrs", feature = "experimental-threads"))))]
compile_error!(
"Cannot combine `lazy-function-tables` and `experimental-threads` features;\n\
thread safety for lazy-loaded function pointers is not yet implemented."
);
#[cfg(all(
feature = "experimental-wasm-nothreads",
feature = "experimental-threads"
))]
compile_error!("Cannot use 'experimental-threads' with a nothreads Wasm build yet.");
#[rustfmt::skip]
#[allow(
non_camel_case_types,
non_upper_case_globals,
non_snake_case,
deref_nullptr,
clippy::redundant_static_lifetimes,
unsafe_op_in_unsafe_fn, // FFI delegation, safety delegated to Godot C API contract
)]
pub(crate) mod r#gen {
include!(concat!(env!("OUT_DIR"), "/mod.rs"));
}
pub mod conv;
mod assertions;
mod extras;
mod global;
mod godot_ffi;
mod interface_init;
#[cfg(target_os = "linux")] #[cfg_attr(published_docs, doc(cfg(target_os = "linux")))]
pub mod linux_reload_workaround;
mod opaque;
mod plugins;
mod string_cache;
mod toolbox;
use std::sync::atomic::{AtomicBool, Ordering};
pub use extras::*;
pub use r#gen::central::*;
pub use r#gen::gdextension_interface::*;
pub use r#gen::interface::*;
pub use r#gen::table_builtins::*;
pub use r#gen::table_builtins_lifecycle::*;
pub use r#gen::table_core_classes::*;
pub use r#gen::table_editor_classes::*;
pub use r#gen::table_scene_classes::*;
pub use r#gen::table_servers_classes::*;
pub use r#gen::table_utilities::*;
pub use global::*;
pub use init_level::*;
pub use string_cache::StringCache;
pub use toolbox::*;
pub use crate::godot_ffi::{ExtVariantType, GodotFfi, PrimitiveConversionError, PtrcallType};
mod binding;
mod init_level;
pub use binding::*;
use binding::{
initialize_binding, initialize_builtin_method_table, initialize_class_core_method_table,
initialize_class_editor_method_table, initialize_class_scene_method_table,
initialize_class_server_method_table, runtime_metadata,
};
#[cfg(not(wasm_nothreads))] #[cfg_attr(published_docs, doc(cfg(not(wasm_nothreads))))]
static MAIN_THREAD_ID: ManualInitCell<std::thread::ThreadId> = ManualInitCell::new();
static STARTUP_MESSAGES: Global<Vec<StartupMessage>> = Global::default();
struct StartupMessage {
message: std::ffi::CString,
function: std::ffi::CString,
file: std::ffi::CString,
line: i32,
level: StartupMessageLevel,
}
#[derive(Clone, Debug)]
pub enum StartupMessageLevel {
Warn { id: &'static str },
Error,
}
pub struct GdextRuntimeMetadata {
version_string: String,
version_triple: (u8, u8, u8),
supports_deprecated_apis: bool,
}
impl GdextRuntimeMetadata {
pub fn load(version: GDExtensionGodotVersion, supports_deprecated_apis: bool) -> Self {
let version_string = unsafe { read_version_string(version.string) };
let version_triple = (
version.major as u8,
version.minor as u8,
version.patch as u8,
);
Self {
version_string,
version_triple,
supports_deprecated_apis,
}
}
pub fn version_string(&self) -> &str {
&self.version_string
}
pub fn version_triple(&self) -> (u8, u8, u8) {
self.version_triple
}
pub fn supports_deprecated_apis(&self) -> bool {
self.supports_deprecated_apis
}
}
unsafe impl Sync for GdextRuntimeMetadata {}
unsafe impl Send for GdextRuntimeMetadata {}
pub unsafe fn initialize(
get_proc_address: GDExtensionInterfaceGetProcAddress,
library: GDExtensionClassLibraryPtr,
config: GdextConfig,
) {
out!("Initialize godot-rust...");
out!(
"Godot version against which godot-rust was compiled: {}",
GdextBuild::godot_static_version_string()
);
#[cfg(not(wasm_nothreads))] #[cfg_attr(published_docs, doc(cfg(not(wasm_nothreads))))]
unsafe {
MAIN_THREAD_ID.set(std::thread::current().id())
};
interface_init::ensure_static_runtime_compatibility(get_proc_address);
let (version, supports_deprecated_apis) = {
let get_proc_address2 = get_proc_address.expect("get_proc_address unexpectedly null");
unsafe { interface_init::runtime_version(get_proc_address2) }
};
out!("Godot version of GDExtension API at runtime: {:?}", version);
let interface = unsafe { interface_init::load_interface(get_proc_address) };
out!("Loaded interface.");
let global_method_table = unsafe { BuiltinLifecycleTable::load(&interface) };
out!("Loaded global method table.");
let mut string_names = StringCache::new(&interface, &global_method_table);
let utility_function_table =
unsafe { UtilityFunctionTable::load(&interface, &mut string_names) };
out!("Loaded utility function table.");
let runtime_metadata = GdextRuntimeMetadata::load(version, supports_deprecated_apis);
let builtin_method_table = {
#[cfg(feature = "codegen-lazy-fptrs")]
{
None }
#[cfg(not(feature = "codegen-lazy-fptrs"))]
{
let table = unsafe { BuiltinMethodTable::load(&interface, &mut string_names) };
out!("Loaded builtin method table.");
Some(table)
}
};
drop(string_names);
unsafe {
initialize_binding(GodotBinding::new(
interface,
get_proc_address,
library,
global_method_table,
utility_function_table,
runtime_metadata,
config,
))
}
if let Some(table) = builtin_method_table {
unsafe { initialize_builtin_method_table(table) }
}
out!("Assigned binding.");
#[cfg(feature = "codegen-lazy-fptrs")]
{
let table = unsafe { BuiltinMethodTable::load() };
unsafe { initialize_builtin_method_table(table) }
out!("Loaded builtin method table (lazily).");
}
print_preamble(version);
}
pub unsafe fn deinitialize() {
unsafe { deinitialize_binding() };
#[cfg(not(wasm_nothreads))]
{
if MAIN_THREAD_ID.is_initialized() {
unsafe { MAIN_THREAD_ID.clear() };
}
}
}
fn safeguards_level_string() -> &'static str {
if cfg!(safeguards_strict) {
"strict"
} else if cfg!(safeguards_balanced) {
"balanced"
} else {
"disengaged"
}
}
#[doc(hidden)]
pub fn collect_startup_message(
mut message: String,
level: StartupMessageLevel,
file: &str,
line: u32,
module_path: &str,
) {
if let StartupMessageLevel::Warn { id } = &level {
if is_message_suppressed(id) {
return;
} else {
message = format!(
"{message}\n(Suppress this warning with env-var `GDRUST_SUPPRESSED_WARNINGS={id},...`)",
);
}
}
let msg = StartupMessage {
message: std::ffi::CString::new(message).expect("message contains null byte"),
function: std::ffi::CString::new(module_path).expect("module_path contains null byte"),
file: std::ffi::CString::new(file).expect("file contains null byte"),
line: line as i32,
level,
};
STARTUP_MESSAGES.lock().push(msg);
}
fn is_message_suppressed(id: &str) -> bool {
if let Ok(suppressed_warnings) = std::env::var("GDRUST_SUPPRESSED_WARNINGS") {
suppressed_warnings
.split(',')
.any(|suppressed_id| suppressed_id.trim() == id)
} else {
false
}
}
pub fn print_deferred_startup_messages() {
let mut messages = STARTUP_MESSAGES.lock();
if messages.is_empty() {
return;
}
for msg in messages.iter() {
let print_fn = match msg.level {
StartupMessageLevel::Warn { .. } => interface_fn!(print_warning),
StartupMessageLevel::Error => interface_fn!(print_error),
};
unsafe {
print_fn(
msg.message.as_ptr(),
msg.function.as_ptr(),
msg.file.as_ptr(),
msg.line,
conv::SYS_TRUE, );
}
}
messages.clear();
}
fn print_preamble(version: GDExtensionGodotVersion) {
let is_quiet = std::env::args()
.take_while(|arg| arg != "--")
.any(|arg| arg == "--quiet" || arg == "--no-header");
if is_quiet {
return;
}
let runtime_version = unsafe { read_version_string(version.string) };
let api_version: &'static str = GdextBuild::godot_static_version_string();
let safeguards_level = safeguards_level_string();
println!(
"Initialize godot-rust (API {api_version}, runtime {runtime_version}, safeguards {safeguards_level})"
);
}
#[inline]
pub unsafe fn load_class_method_table(api_level: InitLevel) {
out!("Load class method table for level '{:?}'...", api_level);
let begin = std::time::Instant::now();
#[cfg(not(feature = "codegen-lazy-fptrs"))]
let interface = unsafe { get_interface() };
#[cfg(not(feature = "codegen-lazy-fptrs"))]
let mut string_names = StringCache::new(interface, unsafe { builtin_lifecycle_api() });
let (class_count, method_count);
match api_level {
InitLevel::Core => {
unsafe {
#[cfg(feature = "codegen-lazy-fptrs")] #[cfg_attr(published_docs, doc(cfg(feature = "codegen-lazy-fptrs")))]
initialize_class_core_method_table(ClassCoreMethodTable::load());
#[cfg(not(feature = "codegen-lazy-fptrs"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "codegen-lazy-fptrs"))))]
initialize_class_core_method_table(ClassCoreMethodTable::load(
interface,
&mut string_names,
));
}
class_count = ClassCoreMethodTable::CLASS_COUNT;
method_count = ClassCoreMethodTable::METHOD_COUNT;
}
InitLevel::Servers => {
unsafe {
#[cfg(feature = "codegen-lazy-fptrs")] #[cfg_attr(published_docs, doc(cfg(feature = "codegen-lazy-fptrs")))]
initialize_class_server_method_table(ClassServersMethodTable::load());
#[cfg(not(feature = "codegen-lazy-fptrs"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "codegen-lazy-fptrs"))))]
initialize_class_server_method_table(ClassServersMethodTable::load(
interface,
&mut string_names,
));
}
class_count = ClassServersMethodTable::CLASS_COUNT;
method_count = ClassServersMethodTable::METHOD_COUNT;
}
InitLevel::Scene => {
unsafe {
#[cfg(feature = "codegen-lazy-fptrs")] #[cfg_attr(published_docs, doc(cfg(feature = "codegen-lazy-fptrs")))]
initialize_class_scene_method_table(ClassSceneMethodTable::load());
#[cfg(not(feature = "codegen-lazy-fptrs"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "codegen-lazy-fptrs"))))]
initialize_class_scene_method_table(ClassSceneMethodTable::load(
interface,
&mut string_names,
));
}
class_count = ClassSceneMethodTable::CLASS_COUNT;
method_count = ClassSceneMethodTable::METHOD_COUNT;
let supports_deprecated_apis = unsafe { runtime_metadata() }.supports_deprecated_apis();
if !supports_deprecated_apis {
defer_startup_warn!(
id: "GodotWithoutDeprecated",
"Your Godot version has disabled deprecated APIs (compiled with `deprecated=no`).\n\
This is generally a bad idea, as Godot can no longer run extensions compiled with older\n\
versions (e.g. from the asset store). Furthermore, godot-rust does not officially support\n\
non-standard builds and can break unexpectedly. This warning may become a hard error.\n\
To fix this, use an official stable release, or compile the engine with `deprecated=yes`."
);
}
}
InitLevel::Editor => {
unsafe {
#[cfg(feature = "codegen-lazy-fptrs")] #[cfg_attr(published_docs, doc(cfg(feature = "codegen-lazy-fptrs")))]
initialize_class_editor_method_table(ClassEditorMethodTable::load());
#[cfg(not(feature = "codegen-lazy-fptrs"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "codegen-lazy-fptrs"))))]
initialize_class_editor_method_table(ClassEditorMethodTable::load(
interface,
&mut string_names,
));
}
class_count = ClassEditorMethodTable::CLASS_COUNT;
method_count = ClassEditorMethodTable::METHOD_COUNT;
}
}
let _elapsed = std::time::Instant::now() - begin;
out!(
"{:?} level: loaded {} classes and {} methods in {}s.",
api_level,
class_count,
method_count,
_elapsed.as_secs_f64()
);
}
#[inline]
pub unsafe fn godot_has_feature(
os_class_sname: GDExtensionConstStringNamePtr,
tag_string: GDExtensionConstTypePtr,
) -> bool {
let method_bind = unsafe { class_core_api() }.os__has_feature();
let interface = unsafe { get_interface() };
let get_singleton = interface.global_get_singleton.unwrap();
let class_ptrcall = interface.object_method_bind_ptrcall.unwrap();
let object_ptr = unsafe { get_singleton(os_class_sname) };
let mut return_ptr = false;
let type_ptrs = [tag_string];
unsafe {
class_ptrcall(
method_bind.0,
object_ptr,
type_ptrs.as_ptr(),
return_ptr.sys_mut(),
)
}
return_ptr
}
#[cfg(not(wasm_nothreads))] #[cfg_attr(published_docs, doc(cfg(not(wasm_nothreads))))]
pub fn main_thread_id() -> std::thread::ThreadId {
assert!(
MAIN_THREAD_ID.is_initialized(),
"Godot engine not available; make sure you are not calling it from unit/doc tests"
);
let thread_id = unsafe { MAIN_THREAD_ID.get_unchecked() };
*thread_id
}
pub fn is_main_thread() -> bool {
#[cfg(not(wasm_nothreads))]
{
std::thread::current().id() == main_thread_id()
}
#[cfg(wasm_nothreads)]
{
true
}
}
static IS_EDITOR_HINT: AtomicBool = AtomicBool::new(false);
pub fn set_editor_hint(is_editor_hint: bool) {
IS_EDITOR_HINT.store(is_editor_hint, Ordering::Relaxed);
}
pub fn is_editor_hint() -> bool {
IS_EDITOR_HINT.load(Ordering::Relaxed)
}
pub unsafe fn discover_main_thread() {
#[cfg(not(wasm_nothreads))]
{
if is_main_thread() {
return;
}
let thread_id = std::thread::current().id();
unsafe {
MAIN_THREAD_ID.clear();
MAIN_THREAD_ID.set(thread_id);
}
}
}
pub unsafe fn classdb_construct_object(
class_name: GDExtensionConstStringNamePtr,
) -> GDExtensionObjectPtr {
#[cfg(before_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.4")))]
let f = interface_fn!(classdb_construct_object);
#[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
let f = interface_fn!(classdb_construct_object2);
unsafe { f(class_name) }
}
#[macro_export]
#[doc(hidden)]
macro_rules! builtin_fn {
($name:ident $(@1)?) => {
$crate::builtin_lifecycle_api().$name
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! builtin_call {
($name:ident ( $($args:expr_2021),* $(,)? )) => {
($crate::builtin_lifecycle_api().$name)( $($args),* )
};
}
#[macro_export]
#[doc(hidden)]
macro_rules! interface_fn {
($name:ident) => {{ unsafe { $crate::get_interface().$name.unwrap_unchecked() } }};
}
#[macro_export]
macro_rules! defer_startup_warn {
(id: $id:literal, $fmt:literal $(, $args:expr_2021)* $(,)?) => {{
let message = format!($fmt $(, $args)*);
$crate::collect_startup_message(
message,
$crate::StartupMessageLevel::Warn { id: $id },
file!(),
line!(),
module_path!(),
);
}};
}
#[macro_export]
macro_rules! defer_startup_error {
($fmt:literal $(, $args:expr_2021)* $(,)?) => {{
let message = format!($fmt $(, $args)*);
$crate::collect_startup_message(
message,
$crate::StartupMessageLevel::Error,
file!(),
line!(),
module_path!(),
);
}};
}