10
10
//! If used from different threads then there will be runtime errors in debug mode and UB in release mode.
11
11
12
12
use std:: cell:: Cell ;
13
+
14
+ #[ cfg( any(
15
+ not( target_family = "wasm" ) ,
16
+ not( feature = "experimental-wasm-nothreads" )
17
+ ) ) ]
13
18
use std:: thread:: ThreadId ;
14
19
15
20
use super :: GodotBinding ;
16
21
use crate :: ManualInitCell ;
17
22
18
23
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
+
19
29
// 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
+ ) ) ]
20
34
main_thread_id : Cell < Option < ThreadId > > ,
21
35
binding : ManualInitCell < GodotBinding > ,
22
36
}
@@ -30,6 +44,13 @@ impl BindingStorage {
30
44
#[ inline( always) ]
31
45
unsafe fn storage ( ) -> & ' static Self {
32
46
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
+ ) ) ]
33
54
main_thread_id : Cell :: new ( None ) ,
34
55
binding : ManualInitCell :: new ( ) ,
35
56
} ;
@@ -49,9 +70,24 @@ impl BindingStorage {
49
70
// in which case we can tell that the storage has been initialized, and we don't access `binding`.
50
71
let storage = unsafe { Self :: storage ( ) } ;
51
72
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
+ }
55
91
56
92
// SAFETY: We are the first thread to set this binding (possibly after deinitialize), as otherwise the above set() would fail and
57
93
// 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 {
70
106
// SAFETY: We only call this once no other operations happen anymore, i.e. no other access to the binding.
71
107
let storage = unsafe { Self :: storage ( ) } ;
72
108
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
+ }
77
114
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
+ }
79
130
80
131
// SAFETY: We are the only thread that can access the binding, and we know that it's initialized.
81
132
unsafe {
@@ -92,7 +143,16 @@ impl BindingStorage {
92
143
pub unsafe fn get_binding_unchecked ( ) -> & ' static GodotBinding {
93
144
let storage = Self :: storage ( ) ;
94
145
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
+ {
96
156
let main_thread_id = storage. main_thread_id . get ( ) . expect (
97
157
"Godot engine not available; make sure you are not calling it from unit/doc tests" ,
98
158
) ;
@@ -111,6 +171,14 @@ impl BindingStorage {
111
171
pub fn is_initialized ( ) -> bool {
112
172
// SAFETY: We don't access the binding.
113
173
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
+ ) ) ]
114
182
storage. main_thread_id . get ( ) . is_some ( )
115
183
}
116
184
}
0 commit comments