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( not( wasm_nothreads) ) ]
13
15
use std:: thread:: ThreadId ;
14
16
15
17
use super :: GodotBinding ;
16
18
use crate :: ManualInitCell ;
17
19
18
20
pub ( super ) struct BindingStorage {
21
+ // No threading when linking against Godot with a nothreads Wasm build.
22
+ // Therefore, we just need to check if the bindings were initialized, as all accesses are from the main thread.
23
+ #[ cfg( wasm_nothreads) ]
24
+ initialized : Cell < bool > ,
25
+
19
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) ) ]
20
28
main_thread_id : Cell < Option < ThreadId > > ,
21
29
binding : ManualInitCell < GodotBinding > ,
22
30
}
@@ -30,13 +38,53 @@ impl BindingStorage {
30
38
#[ inline( always) ]
31
39
unsafe fn storage ( ) -> & ' static Self {
32
40
static BINDING : BindingStorage = BindingStorage {
41
+ #[ cfg( wasm_nothreads) ]
42
+ initialized : Cell :: new ( false ) ,
43
+
44
+ #[ cfg( not( wasm_nothreads) ) ]
33
45
main_thread_id : Cell :: new ( None ) ,
34
46
binding : ManualInitCell :: new ( ) ,
35
47
} ;
36
48
37
49
& BINDING
38
50
}
39
51
52
+ /// Marks the binding storage as initialized or deinitialized.
53
+ /// We store the thread ID to ensure future accesses to the binding only come from the main thread.
54
+ ///
55
+ /// # Safety
56
+ /// Must be called from the main thread. Additionally, the binding storage must be initialized immediately
57
+ /// after this function if `initialized` is `true`, or deinitialized if it is `false`.
58
+ ///
59
+ /// # Panics
60
+ /// If attempting to deinitialize before initializing, or vice-versa.
61
+ unsafe fn set_initialized ( & self , initialized : bool ) {
62
+ #[ cfg( wasm_nothreads) ]
63
+ {
64
+ if !initialized && !self . initialized . get ( ) {
65
+ panic ! ( "deinitialize without prior initialize" ) ;
66
+ }
67
+
68
+ // 'std::thread::current()' fails when linking to a Godot web build without threads. When compiling to wasm-nothreads,
69
+ // we assume it is impossible to have multi-threading, so checking if we are in the main thread is not needed.
70
+ // Therefore, we don't store the thread ID, but rather just whether initialization already occurred.
71
+ self . initialized . set ( initialized) ;
72
+ }
73
+
74
+ #[ cfg( not( wasm_nothreads) ) ]
75
+ {
76
+ if initialized {
77
+ self . main_thread_id . set ( Some ( std:: thread:: current ( ) . id ( ) ) ) ;
78
+ } else {
79
+ self . main_thread_id
80
+ . get ( )
81
+ . expect ( "deinitialize without prior initialize" ) ;
82
+
83
+ self . main_thread_id . set ( None ) ;
84
+ }
85
+ }
86
+ }
87
+
40
88
/// Initialize the binding storage, this must be called before any other public functions.
41
89
///
42
90
/// # Safety
@@ -49,9 +97,10 @@ impl BindingStorage {
49
97
// in which case we can tell that the storage has been initialized, and we don't access `binding`.
50
98
let storage = unsafe { Self :: storage ( ) } ;
51
99
52
- storage
53
- . main_thread_id
54
- . set ( Some ( std:: thread:: current ( ) . id ( ) ) ) ;
100
+ // SAFETY: We are about to initialize the binding below, so marking the binding as initialized is correct.
101
+ // If we can't initialize the binding at this point, we get a panic before changing the status, thus the
102
+ // binding won't be set.
103
+ unsafe { storage. set_initialized ( true ) } ;
55
104
56
105
// SAFETY: We are the first thread to set this binding (possibly after deinitialize), as otherwise the above set() would fail and
57
106
// return early. We also know initialize() is not called concurrently with anything else that can call another method on the binding,
@@ -70,12 +119,10 @@ impl BindingStorage {
70
119
// SAFETY: We only call this once no other operations happen anymore, i.e. no other access to the binding.
71
120
let storage = unsafe { Self :: storage ( ) } ;
72
121
73
- storage
74
- . main_thread_id
75
- . get ( )
76
- . expect ( "deinitialize without prior initialize" ) ;
77
-
78
- storage. main_thread_id . set ( None ) ;
122
+ // SAFETY: We are about to deinitialize the binding below, so marking the binding as deinitialized is correct.
123
+ // If we can't deinitialize the binding at this point, we get a panic before changing the status, thus the
124
+ // binding won't be deinitialized.
125
+ unsafe { storage. set_initialized ( false ) } ;
79
126
80
127
// SAFETY: We are the only thread that can access the binding, and we know that it's initialized.
81
128
unsafe {
@@ -92,7 +139,10 @@ impl BindingStorage {
92
139
pub unsafe fn get_binding_unchecked ( ) -> & ' static GodotBinding {
93
140
let storage = Self :: storage ( ) ;
94
141
95
- if cfg ! ( debug_assertions) {
142
+ // We only check if we are in the main thread in debug builds if we aren't building for a non-threaded Godot build,
143
+ // since we could otherwise assume there won't be multi-threading.
144
+ #[ cfg( all( debug_assertions, not( wasm_nothreads) ) ) ]
145
+ {
96
146
let main_thread_id = storage. main_thread_id . get ( ) . expect (
97
147
"Godot engine not available; make sure you are not calling it from unit/doc tests" ,
98
148
) ;
@@ -111,6 +161,11 @@ impl BindingStorage {
111
161
pub fn is_initialized ( ) -> bool {
112
162
// SAFETY: We don't access the binding.
113
163
let storage = unsafe { Self :: storage ( ) } ;
164
+
165
+ #[ cfg( wasm_nothreads) ]
166
+ return storage. initialized . get ( ) ;
167
+
168
+ #[ cfg( not( wasm_nothreads) ) ]
114
169
storage. main_thread_id . get ( ) . is_some ( )
115
170
}
116
171
}
0 commit comments