Skip to content

Commit 26db942

Browse files
committed
add support for nothreads build to single threaded init
1 parent 79edae3 commit 26db942

File tree

5 files changed

+87
-10
lines changed

5 files changed

+87
-10
lines changed

godot-core/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ codegen-lazy-fptrs = [
2121
double-precision = ["godot-codegen/double-precision"]
2222
experimental-godot-api = ["godot-codegen/experimental-godot-api"]
2323
experimental-threads = ["godot-ffi/experimental-threads"]
24+
experimental-wasm-nothreads = ["godot-ffi/experimental-wasm-nothreads"]
2425
debug-log = ["godot-ffi/debug-log"]
2526
trace = []
2627

godot-ffi/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ codegen-rustfmt = ["godot-codegen/codegen-rustfmt"]
1515
codegen-lazy-fptrs = ["godot-codegen/codegen-lazy-fptrs"]
1616
experimental-godot-api = ["godot-codegen/experimental-godot-api"]
1717
experimental-threads = []
18+
experimental-wasm-nothreads = []
1819
debug-log = []
1920

2021
api-custom = ["godot-bindings/api-custom"]

godot-ffi/src/binding/single_threaded.rs

+77-9
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,27 @@
1010
//! If used from different threads then there will be runtime errors in debug mode and UB in release mode.
1111
1212
use std::cell::Cell;
13+
14+
#[cfg(any(
15+
not(target_family = "wasm"),
16+
not(feature = "experimental-wasm-nothreads")
17+
))]
1318
use std::thread::ThreadId;
1419

1520
use super::GodotBinding;
1621
use crate::ManualInitCell;
1722

1823
pub(super) struct BindingStorage {
24+
// No threading when linking against Godot with a nothreads Wasm build.
25+
// Therefore, we just need to check if the bindings were initialized, as all accesses are from the main thread.
26+
#[cfg(all(target_family = "wasm", feature = "experimental-wasm-nothreads"))]
27+
initialized: Cell<bool>,
28+
1929
// Is used in to check that we've been called from the right thread, so must be thread-safe to access.
30+
#[cfg(any(
31+
not(target_family = "wasm"),
32+
not(feature = "experimental-wasm-nothreads")
33+
))]
2034
main_thread_id: Cell<Option<ThreadId>>,
2135
binding: ManualInitCell<GodotBinding>,
2236
}
@@ -30,6 +44,13 @@ impl BindingStorage {
3044
#[inline(always)]
3145
unsafe fn storage() -> &'static Self {
3246
static BINDING: BindingStorage = BindingStorage {
47+
#[cfg(all(target_family = "wasm", feature = "experimental-wasm-nothreads"))]
48+
initialized: Cell::new(false),
49+
50+
#[cfg(any(
51+
not(target_family = "wasm"),
52+
not(feature = "experimental-wasm-nothreads")
53+
))]
3354
main_thread_id: Cell::new(None),
3455
binding: ManualInitCell::new(),
3556
};
@@ -49,9 +70,24 @@ impl BindingStorage {
4970
// in which case we can tell that the storage has been initialized, and we don't access `binding`.
5071
let storage = unsafe { Self::storage() };
5172

52-
storage
53-
.main_thread_id
54-
.set(Some(std::thread::current().id()));
73+
// 'std::thread::current()' fails when linking to a Godot build without threads. When this feature is enabled,
74+
// we assume it is impossible to have multi-threading, so checking if we are in the main thread is not needed.
75+
// Therefore, we assign a bogus value for the thread ID instead, so we can still check for prior initialization
76+
// through this field later.
77+
#[cfg(all(target_family = "wasm", feature = "experimental-wasm-nothreads"))]
78+
{
79+
storage.initialized.set(true);
80+
}
81+
82+
#[cfg(any(
83+
not(target_family = "wasm"),
84+
not(feature = "experimental-wasm-nothreads")
85+
))]
86+
{
87+
storage
88+
.main_thread_id
89+
.set(Some(std::thread::current().id()));
90+
}
5591

5692
// SAFETY: We are the first thread to set this binding (possibly after deinitialize), as otherwise the above set() would fail and
5793
// return early. We also know initialize() is not called concurrently with anything else that can call another method on the binding,
@@ -70,12 +106,27 @@ impl BindingStorage {
70106
// SAFETY: We only call this once no other operations happen anymore, i.e. no other access to the binding.
71107
let storage = unsafe { Self::storage() };
72108

73-
storage
74-
.main_thread_id
75-
.get()
76-
.expect("deinitialize without prior initialize");
109+
#[cfg(all(target_family = "wasm", feature = "experimental-wasm-nothreads"))]
110+
{
111+
if !storage.initialized.get() {
112+
panic!("deinitialize without prior initialize");
113+
}
77114

78-
storage.main_thread_id.set(None);
115+
storage.initialized.set(false);
116+
}
117+
118+
#[cfg(any(
119+
not(target_family = "wasm"),
120+
not(feature = "experimental-wasm-nothreads")
121+
))]
122+
{
123+
storage
124+
.main_thread_id
125+
.get()
126+
.expect("deinitialize without prior initialize");
127+
128+
storage.main_thread_id.set(None);
129+
}
79130

80131
// SAFETY: We are the only thread that can access the binding, and we know that it's initialized.
81132
unsafe {
@@ -92,7 +143,16 @@ impl BindingStorage {
92143
pub unsafe fn get_binding_unchecked() -> &'static GodotBinding {
93144
let storage = Self::storage();
94145

95-
if cfg!(debug_assertions) {
146+
// We only check if we are in the main thread in debug builds if we aren't building for a non-threaded Godot build,
147+
// since we could otherwise assume there won't be multi-threading.
148+
#[cfg(all(
149+
debug_assertions,
150+
any(
151+
not(target_family = "wasm"),
152+
not(feature = "experimental-wasm-nothreads")
153+
)
154+
))]
155+
{
96156
let main_thread_id = storage.main_thread_id.get().expect(
97157
"Godot engine not available; make sure you are not calling it from unit/doc tests",
98158
);
@@ -111,6 +171,14 @@ impl BindingStorage {
111171
pub fn is_initialized() -> bool {
112172
// SAFETY: We don't access the binding.
113173
let storage = unsafe { Self::storage() };
174+
175+
#[cfg(all(target_family = "wasm", feature = "experimental-wasm-nothreads"))]
176+
return storage.initialized.get();
177+
178+
#[cfg(any(
179+
not(target_family = "wasm"),
180+
not(feature = "experimental-wasm-nothreads")
181+
))]
114182
storage.main_thread_id.get().is_some()
115183
}
116184
}

godot/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ double-precision = ["godot-core/double-precision"]
1818
experimental-godot-api = ["godot-core/experimental-godot-api"]
1919
experimental-threads = ["godot-core/experimental-threads"]
2020
experimental-wasm = []
21+
experimental-wasm-nothreads = ["godot-core/experimental-wasm-nothreads"]
2122
codegen-rustfmt = ["godot-core/codegen-rustfmt"]
2223
lazy-function-tables = ["godot-core/codegen-lazy-fptrs"]
2324
serde = ["godot-core/serde"]

godot/src/lib.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
//! * **`api-custom`**
6969
//!
7070
//! Sets the [**API level**](https://godot-rust.github.io/book/toolchain/godot-version.html) to the specified Godot version,
71-
//! or a custom-built local binary.
71+
//! or a custom-built local binary.
7272
//! You can use at most one `api-*` feature. If absent, the current Godot minor version is used, with patch level 0.<br><br>
7373
//!
7474
//! * **`double-precision`**
@@ -124,6 +124,12 @@ pub mod __docs;
124124
#[cfg(all(feature = "lazy-function-tables", feature = "experimental-threads"))]
125125
compile_error!("Thread safety for lazy function pointers is not yet implemented.");
126126

127+
#[cfg(all(
128+
feature = "experimental-wasm-nothreads",
129+
feature = "experimental-threads"
130+
))]
131+
compile_error!("Cannot use 'experimental-threads' with a nothreads Wasm build yet.");
132+
127133
#[cfg(all(target_family = "wasm", not(feature = "experimental-wasm")))]
128134
compile_error!("Must opt-in using `experimental-wasm` Cargo feature; keep in mind that this is work in progress");
129135

0 commit comments

Comments
 (0)