1#![cfg_attr(published_docs, feature(doc_cfg))]
2#![cfg_attr(test, allow(unused))]
19
20#[cfg(all(feature = "codegen-lazy-fptrs", feature = "experimental-threads"))] #[cfg_attr(published_docs, doc(cfg(all(feature = "codegen-lazy-fptrs", feature = "experimental-threads"))))]
26compile_error!(
27 "Cannot combine `lazy-function-tables` and `experimental-threads` features;\n\
28 thread safety for lazy-loaded function pointers is not yet implemented."
29);
30
31#[cfg(all(
32 feature = "experimental-wasm-nothreads",
33 feature = "experimental-threads"
34))]
35compile_error!("Cannot use 'experimental-threads' with a nothreads Wasm build yet.");
36
37#[rustfmt::skip]
41#[allow(
42 non_camel_case_types,
43 non_upper_case_globals,
44 non_snake_case,
45 deref_nullptr,
46 clippy::redundant_static_lifetimes,
47)]
48pub(crate) mod gen {
49 include!(concat!(env!("OUT_DIR"), "/mod.rs"));
50}
51
52pub mod conv;
53
54mod assertions;
55mod extras;
56mod global;
57mod godot_ffi;
58mod interface_init;
59#[cfg(target_os = "linux")] #[cfg_attr(published_docs, doc(cfg(target_os = "linux")))]
60pub mod linux_reload_workaround;
61mod opaque;
62mod plugins;
63mod string_cache;
64mod toolbox;
65
66#[doc(hidden)]
67#[cfg(target_family = "wasm")] #[cfg_attr(published_docs, doc(cfg(target_family = "wasm")))]
68pub use godot_macros::wasm_declare_init_fn;
69
70#[doc(hidden)]
72#[cfg(not(target_family = "wasm"))] #[cfg_attr(published_docs, doc(cfg(not(target_family = "wasm"))))]
73#[macro_export]
74macro_rules! wasm_declare_init_fn {
75 () => {};
76}
77
78pub use extras::*;
80pub use gen::central::*;
81pub use gen::gdextension_interface::*;
82pub use gen::interface::*;
83pub use gen::table_builtins::*;
85pub use gen::table_builtins_lifecycle::*;
86pub use gen::table_core_classes::*;
87pub use gen::table_editor_classes::*;
88pub use gen::table_scene_classes::*;
89pub use gen::table_servers_classes::*;
90pub use gen::table_utilities::*;
91pub use global::*;
92pub use init_level::*;
93pub use string_cache::StringCache;
94pub use toolbox::*;
95
96pub use crate::godot_ffi::{
97 ExtVariantType, GodotFfi, GodotNullableFfi, PrimitiveConversionError, PtrcallType,
98};
99
100mod binding;
104mod init_level;
105
106pub use binding::*;
107use binding::{
108 initialize_binding, initialize_builtin_method_table, initialize_class_core_method_table,
109 initialize_class_editor_method_table, initialize_class_scene_method_table,
110 initialize_class_server_method_table, runtime_metadata,
111};
112
113#[cfg(not(wasm_nothreads))] #[cfg_attr(published_docs, doc(cfg(not(wasm_nothreads))))]
114static MAIN_THREAD_ID: ManualInitCell<std::thread::ThreadId> = ManualInitCell::new();
115
116pub struct GdextRuntimeMetadata {
119 version_string: String,
120 version_triple: (u8, u8, u8),
121}
122
123impl GdextRuntimeMetadata {
124 pub fn load(sys_version: GDExtensionGodotVersion) -> Self {
125 let version_string = unsafe { read_version_string(sys_version.string) };
127
128 let version_triple = (
129 sys_version.major as u8,
130 sys_version.minor as u8,
131 sys_version.patch as u8,
132 );
133
134 Self {
135 version_string,
136 version_triple,
137 }
138 }
139
140 pub fn version_string(&self) -> &str {
142 &self.version_string
143 }
144
145 pub fn version_triple(&self) -> (u8, u8, u8) {
146 self.version_triple
147 }
148}
149
150unsafe impl Sync for GdextRuntimeMetadata {}
152unsafe impl Send for GdextRuntimeMetadata {}
154
155pub unsafe fn initialize(
164 get_proc_address: GDExtensionInterfaceGetProcAddress,
165 library: GDExtensionClassLibraryPtr,
166 config: GdextConfig,
167) {
168 out!("Initialize godot-rust...");
169
170 out!(
171 "Godot version against which godot-rust was compiled: {}",
172 GdextBuild::godot_static_version_string()
173 );
174
175 #[cfg(not(wasm_nothreads))] #[cfg_attr(published_docs, doc(cfg(not(wasm_nothreads))))]
179 unsafe {
180 MAIN_THREAD_ID.set(std::thread::current().id())
181 };
182
183 interface_init::ensure_static_runtime_compatibility(get_proc_address);
185
186 let version = unsafe { interface_init::runtime_version(get_proc_address) };
188 out!("Godot version of GDExtension API at runtime: {version:?}");
189
190 let interface = unsafe { interface_init::load_interface(get_proc_address) };
192 out!("Loaded interface.");
193
194 let global_method_table = unsafe { BuiltinLifecycleTable::load(&interface) };
196 out!("Loaded global method table.");
197
198 let mut string_names = StringCache::new(&interface, &global_method_table);
199
200 let utility_function_table =
202 unsafe { UtilityFunctionTable::load(&interface, &mut string_names) };
203 out!("Loaded utility function table.");
204
205 let runtime_metadata = GdextRuntimeMetadata::load(version);
206
207 let builtin_method_table = {
208 #[cfg(feature = "codegen-lazy-fptrs")]
209 {
210 None }
212 #[cfg(not(feature = "codegen-lazy-fptrs"))]
213 {
214 let table = unsafe { BuiltinMethodTable::load(&interface, &mut string_names) };
216 out!("Loaded builtin method table.");
217 Some(table)
218 }
219 };
220
221 drop(string_names);
222
223 unsafe {
225 initialize_binding(GodotBinding::new(
226 interface,
227 library,
228 global_method_table,
229 utility_function_table,
230 runtime_metadata,
231 config,
232 ))
233 }
234
235 if let Some(table) = builtin_method_table {
236 unsafe { initialize_builtin_method_table(table) }
238 }
239
240 out!("Assigned binding.");
241
242 #[cfg(feature = "codegen-lazy-fptrs")]
244 {
245 let table = unsafe { BuiltinMethodTable::load() };
247
248 unsafe { initialize_builtin_method_table(table) }
249
250 out!("Loaded builtin method table (lazily).");
251 }
252
253 print_preamble(version);
254}
255
256pub unsafe fn deinitialize() {
265 deinitialize_binding();
266
267 #[cfg(not(wasm_nothreads))]
269 {
270 if MAIN_THREAD_ID.is_initialized() {
271 MAIN_THREAD_ID.clear();
272 }
273 }
274}
275
276fn safeguards_level_string() -> &'static str {
277 if cfg!(safeguards_strict) {
278 "strict"
279 } else if cfg!(safeguards_balanced) {
280 "balanced"
281 } else {
282 "disengaged"
283 }
284}
285
286fn print_preamble(version: GDExtensionGodotVersion) {
287 let runtime_version = unsafe { read_version_string(version.string) };
289
290 let api_version: &'static str = GdextBuild::godot_static_version_string();
291 let safeguards_level = safeguards_level_string();
292 println!("Initialize godot-rust (API {api_version}, runtime {runtime_version}, safeguards {safeguards_level})");
293}
294
295#[inline]
301pub unsafe fn load_class_method_table(api_level: InitLevel) {
302 out!("Load class method table for level '{:?}'...", api_level);
303 let begin = std::time::Instant::now();
304
305 #[cfg(not(feature = "codegen-lazy-fptrs"))]
306 let interface = unsafe { get_interface() };
308
309 #[cfg(not(feature = "codegen-lazy-fptrs"))]
310 let mut string_names = StringCache::new(interface, unsafe { builtin_lifecycle_api() });
312
313 let (class_count, method_count);
314 match api_level {
315 InitLevel::Core => {
316 unsafe {
318 #[cfg(feature = "codegen-lazy-fptrs")] #[cfg_attr(published_docs, doc(cfg(feature = "codegen-lazy-fptrs")))]
319 initialize_class_core_method_table(ClassCoreMethodTable::load());
320 #[cfg(not(feature = "codegen-lazy-fptrs"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "codegen-lazy-fptrs"))))]
321 initialize_class_core_method_table(ClassCoreMethodTable::load(
322 interface,
323 &mut string_names,
324 ));
325 }
326 class_count = ClassCoreMethodTable::CLASS_COUNT;
327 method_count = ClassCoreMethodTable::METHOD_COUNT;
328 }
329 InitLevel::Servers => {
330 unsafe {
332 #[cfg(feature = "codegen-lazy-fptrs")] #[cfg_attr(published_docs, doc(cfg(feature = "codegen-lazy-fptrs")))]
333 initialize_class_server_method_table(ClassServersMethodTable::load());
334 #[cfg(not(feature = "codegen-lazy-fptrs"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "codegen-lazy-fptrs"))))]
335 initialize_class_server_method_table(ClassServersMethodTable::load(
336 interface,
337 &mut string_names,
338 ));
339 }
340 class_count = ClassServersMethodTable::CLASS_COUNT;
341 method_count = ClassServersMethodTable::METHOD_COUNT;
342 }
343 InitLevel::Scene => {
344 unsafe {
346 #[cfg(feature = "codegen-lazy-fptrs")] #[cfg_attr(published_docs, doc(cfg(feature = "codegen-lazy-fptrs")))]
347 initialize_class_scene_method_table(ClassSceneMethodTable::load());
348 #[cfg(not(feature = "codegen-lazy-fptrs"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "codegen-lazy-fptrs"))))]
349 initialize_class_scene_method_table(ClassSceneMethodTable::load(
350 interface,
351 &mut string_names,
352 ));
353 }
354 class_count = ClassSceneMethodTable::CLASS_COUNT;
355 method_count = ClassSceneMethodTable::METHOD_COUNT;
356 }
357 InitLevel::Editor => {
358 unsafe {
360 #[cfg(feature = "codegen-lazy-fptrs")] #[cfg_attr(published_docs, doc(cfg(feature = "codegen-lazy-fptrs")))]
361 initialize_class_editor_method_table(ClassEditorMethodTable::load());
362 #[cfg(not(feature = "codegen-lazy-fptrs"))] #[cfg_attr(published_docs, doc(cfg(not(feature = "codegen-lazy-fptrs"))))]
363 initialize_class_editor_method_table(ClassEditorMethodTable::load(
364 interface,
365 &mut string_names,
366 ));
367 }
368 class_count = ClassEditorMethodTable::CLASS_COUNT;
369 method_count = ClassEditorMethodTable::METHOD_COUNT;
370 }
371 }
372
373 let _elapsed = std::time::Instant::now() - begin;
374 out!(
375 "{:?} level: loaded {} classes and {} methods in {}s.",
376 api_level,
377 class_count,
378 method_count,
379 _elapsed.as_secs_f64()
380 );
381}
382
383#[inline]
391pub unsafe fn godot_has_feature(
392 os_class_sname: GDExtensionConstStringNamePtr,
393 tag_string: GDExtensionConstTypePtr,
394) -> bool {
395 let method_bind = unsafe { class_core_api() }.os__has_feature();
399
400 let interface = unsafe { get_interface() };
402 let get_singleton = interface.global_get_singleton.unwrap();
403 let class_ptrcall = interface.object_method_bind_ptrcall.unwrap();
404
405 let object_ptr = unsafe { get_singleton(os_class_sname) };
408 let mut return_ptr = false;
409 let type_ptrs = [tag_string];
410
411 unsafe {
413 class_ptrcall(
414 method_bind.0,
415 object_ptr,
416 type_ptrs.as_ptr(),
417 return_ptr.sys_mut(),
418 )
419 }
420
421 return_ptr
422}
423
424#[cfg(not(wasm_nothreads))] #[cfg_attr(published_docs, doc(cfg(not(wasm_nothreads))))]
429pub fn main_thread_id() -> std::thread::ThreadId {
430 assert!(
431 MAIN_THREAD_ID.is_initialized(),
432 "Godot engine not available; make sure you are not calling it from unit/doc tests"
433 );
434
435 let thread_id = unsafe { MAIN_THREAD_ID.get_unchecked() };
437
438 *thread_id
439}
440
441pub fn is_main_thread() -> bool {
446 #[cfg(not(wasm_nothreads))]
447 {
448 std::thread::current().id() == main_thread_id()
449 }
450
451 #[cfg(wasm_nothreads)]
452 {
453 true
454 }
455}
456
457pub unsafe fn discover_main_thread() {
466 #[cfg(not(wasm_nothreads))]
467 {
468 if is_main_thread() {
469 return;
471 }
472
473 let thread_id = std::thread::current().id();
474
475 unsafe {
477 MAIN_THREAD_ID.clear();
478 MAIN_THREAD_ID.set(thread_id);
479 }
480 }
481}
482
483pub unsafe fn classdb_construct_object(
490 class_name: GDExtensionConstStringNamePtr,
491) -> GDExtensionObjectPtr {
492 #[cfg(before_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.4")))]
493 return interface_fn!(classdb_construct_object)(class_name);
494
495 #[cfg(since_api = "4.4")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.4")))]
496 return interface_fn!(classdb_construct_object2)(class_name);
497}
498
499#[macro_export]
503#[doc(hidden)]
504macro_rules! builtin_fn {
505 ($name:ident $(@1)?) => {
506 $crate::builtin_lifecycle_api().$name
507 };
508}
509
510#[macro_export]
511#[doc(hidden)]
512macro_rules! builtin_call {
513 ($name:ident ( $($args:expr),* $(,)? )) => {
514 ($crate::builtin_lifecycle_api().$name)( $($args),* )
515 };
516 }
517
518#[macro_export]
519#[doc(hidden)]
520macro_rules! interface_fn {
521 ($name:ident) => {{
522 unsafe { $crate::get_interface().$name.unwrap_unchecked() }
523 }};
524}