Skip to content

Commit b2bff88

Browse files
authoredFeb 12, 2025
Merge pull request #1045 from TitanNano/jovan/main_thread_id
Global main thread ID
2 parents 187a289 + 9718ffc commit b2bff88

File tree

4 files changed

+43
-32
lines changed

4 files changed

+43
-32
lines changed
 

‎godot-core/build.rs

+1
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ fn main() {
1717
println!("cargo:rerun-if-changed=build.rs");
1818

1919
godot_bindings::emit_godot_version_cfg();
20+
godot_bindings::emit_wasm_nothreads_cfg();
2021
}

‎godot-core/src/init/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ use crate::out;
1616

1717
pub use sys::GdextBuild;
1818

19+
#[cfg(not(wasm_nothreads))]
20+
pub use sys::{is_main_thread, main_thread_id};
21+
1922
#[doc(hidden)]
2023
#[deny(unsafe_op_in_unsafe_fn)]
2124
pub unsafe fn __gdext_load_library<E: ExtensionLibrary>(

‎godot-ffi/src/binding/single_threaded.rs

+2-32
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,13 @@
1111
1212
use std::cell::Cell;
1313

14-
#[cfg(not(wasm_nothreads))]
15-
use std::thread::ThreadId;
16-
1714
use super::GodotBinding;
1815
use crate::ManualInitCell;
1916

2017
pub(super) struct BindingStorage {
2118
// No threading when linking against Godot with a nothreads Wasm build.
2219
// Therefore, we just need to check if the bindings were initialized, as all accesses are from the main thread.
23-
#[cfg(wasm_nothreads)]
2420
initialized: Cell<bool>,
25-
26-
// Is used in to check that we've been called from the right thread, so must be thread-safe to access.
27-
#[cfg(not(wasm_nothreads))]
28-
main_thread_id: Cell<Option<ThreadId>>,
2921
binding: ManualInitCell<GodotBinding>,
3022
}
3123

@@ -38,11 +30,7 @@ impl BindingStorage {
3830
#[inline(always)]
3931
unsafe fn storage() -> &'static Self {
4032
static BINDING: BindingStorage = BindingStorage {
41-
#[cfg(wasm_nothreads)]
4233
initialized: Cell::new(false),
43-
44-
#[cfg(not(wasm_nothreads))]
45-
main_thread_id: Cell::new(None),
4634
binding: ManualInitCell::new(),
4735
};
4836

@@ -53,11 +41,7 @@ impl BindingStorage {
5341
///
5442
/// It is recommended to use this function for that purpose as the field to check varies depending on the compilation target.
5543
fn initialized(&self) -> bool {
56-
#[cfg(wasm_nothreads)]
57-
return self.initialized.get();
58-
59-
#[cfg(not(wasm_nothreads))]
60-
self.main_thread_id.get().is_some()
44+
self.initialized.get()
6145
}
6246

6347
/// Marks the binding storage as initialized or deinitialized.
@@ -78,17 +62,7 @@ impl BindingStorage {
7862
}
7963
}
8064

81-
// 'std::thread::current()' fails when linking to a Godot web build without threads. When compiling to wasm-nothreads,
82-
// we assume it is impossible to have multi-threading, so checking if we are in the main thread is not needed.
83-
// Therefore, we don't store the thread ID, but rather just whether initialization already occurred.
84-
#[cfg(wasm_nothreads)]
8565
self.initialized.set(initialized);
86-
87-
#[cfg(not(wasm_nothreads))]
88-
{
89-
let thread_id = initialized.then(|| std::thread::current().id());
90-
self.main_thread_id.set(thread_id);
91-
}
9266
}
9367

9468
/// Initialize the binding storage, this must be called before any other public functions.
@@ -152,11 +126,7 @@ impl BindingStorage {
152126
// TODO: figure out why the panic happens on Android, and how to resolve it. See https://github.com/godot-rust/gdext/pull/780.
153127
#[cfg(all(debug_assertions, not(wasm_nothreads), not(target_os = "android")))]
154128
{
155-
let main_thread_id = storage.main_thread_id.get().expect(
156-
"Godot engine not available; make sure you are not calling it from unit/doc tests",
157-
);
158-
159-
if main_thread_id != std::thread::current().id() {
129+
if !crate::is_main_thread() {
160130
// If a binding is accessed the first time, this will panic and start unwinding. It can then happen that during unwinding,
161131
// another FFI call happens (e.g. Godot destructor), which would cause immediate abort, swallowing the error message.
162132
// Thus check if a panic is already in progress.

‎godot-ffi/src/lib.rs

+37
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ use binding::{
8484
initialize_class_scene_method_table, initialize_class_server_method_table, runtime_metadata,
8585
};
8686

87+
#[cfg(not(wasm_nothreads))]
88+
static MAIN_THREAD_ID: ManualInitCell<std::thread::ThreadId> = ManualInitCell::new();
89+
8790
/// Stage of the Godot initialization process.
8891
///
8992
/// Godot's initialization and deinitialization processes are split into multiple stages, like a stack. At each level,
@@ -167,6 +170,14 @@ pub unsafe fn initialize(
167170
GdextBuild::godot_static_version_string()
168171
);
169172

173+
// We want to initialize the main thread ID as early as possible.
174+
//
175+
// SAFETY: We set the main thread ID exactly once here and never again.
176+
#[cfg(not(wasm_nothreads))]
177+
unsafe {
178+
MAIN_THREAD_ID.set(std::thread::current().id())
179+
};
180+
170181
// Before anything else: if we run into a Godot binary that's compiled differently from gdext, proceeding would be UB -> panic.
171182
interface_init::ensure_static_runtime_compatibility(get_proc_address);
172183

@@ -380,6 +391,32 @@ pub unsafe fn godot_has_feature(
380391
return_ptr
381392
}
382393

394+
/// Get the [`ThreadId`](std::thread::ThreadId) of the main thread.
395+
///
396+
/// # Panics
397+
/// - If it is called before the engine bindings have been initialized.
398+
#[cfg(not(wasm_nothreads))]
399+
pub fn main_thread_id() -> std::thread::ThreadId {
400+
assert!(
401+
MAIN_THREAD_ID.is_initialized(),
402+
"Godot engine not available; make sure you are not calling it from unit/doc tests"
403+
);
404+
405+
// SAFETY: We initialized the cell during library initialization, before any other code is executed.
406+
let thread_id = unsafe { MAIN_THREAD_ID.get_unchecked() };
407+
408+
*thread_id
409+
}
410+
411+
/// Check if the current thread is the main thread.
412+
///
413+
/// # Panics
414+
/// - If it is called before the engine bindings have been initialized.
415+
#[cfg(not(wasm_nothreads))]
416+
pub fn is_main_thread() -> bool {
417+
std::thread::current().id() == main_thread_id()
418+
}
419+
383420
// ----------------------------------------------------------------------------------------------------------------------------------------------
384421
// Macros to access low-level function bindings
385422

0 commit comments

Comments
 (0)