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,59 @@ 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
+ /// Returns whether the binding storage has already been initialized.
53
+ ///
54
+ /// It is recommended to use this function for that purpose as the field to check varies depending on the compilation target.
55
+ 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 ( )
61
+ }
62
+
63
+ /// Marks the binding storage as initialized or deinitialized.
64
+ /// We store the thread ID to ensure future accesses to the binding only come from the main thread.
65
+ ///
66
+ /// # Safety
67
+ /// Must be called from the main thread. Additionally, the binding storage must be initialized immediately
68
+ /// after this function if `initialized` is `true`, or deinitialized if it is `false`.
69
+ ///
70
+ /// # Panics
71
+ /// If attempting to deinitialize before initializing, or vice-versa.
72
+ unsafe fn set_initialized ( & self , initialized : bool ) {
73
+ if initialized == self . initialized ( ) {
74
+ if initialized {
75
+ panic ! ( "already initialized" ) ;
76
+ } else {
77
+ panic ! ( "deinitialize without prior initialize" ) ;
78
+ }
79
+ }
80
+
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) ]
85
+ 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
+ }
92
+ }
93
+
40
94
/// Initialize the binding storage, this must be called before any other public functions.
41
95
///
42
96
/// # Safety
@@ -49,9 +103,10 @@ impl BindingStorage {
49
103
// in which case we can tell that the storage has been initialized, and we don't access `binding`.
50
104
let storage = unsafe { Self :: storage ( ) } ;
51
105
52
- storage
53
- . main_thread_id
54
- . set ( Some ( std:: thread:: current ( ) . id ( ) ) ) ;
106
+ // SAFETY: We are about to initialize the binding below, so marking the binding as initialized is correct.
107
+ // If we can't initialize the binding at this point, we get a panic before changing the status, thus the
108
+ // binding won't be set.
109
+ unsafe { storage. set_initialized ( true ) } ;
55
110
56
111
// SAFETY: We are the first thread to set this binding (possibly after deinitialize), as otherwise the above set() would fail and
57
112
// return early. We also know initialize() is not called concurrently with anything else that can call another method on the binding,
@@ -70,12 +125,10 @@ impl BindingStorage {
70
125
// SAFETY: We only call this once no other operations happen anymore, i.e. no other access to the binding.
71
126
let storage = unsafe { Self :: storage ( ) } ;
72
127
73
- storage
74
- . main_thread_id
75
- . get ( )
76
- . expect ( "deinitialize without prior initialize" ) ;
77
-
78
- storage. main_thread_id . set ( None ) ;
128
+ // SAFETY: We are about to deinitialize the binding below, so marking the binding as deinitialized is correct.
129
+ // If we can't deinitialize the binding at this point, we get a panic before changing the status, thus the
130
+ // binding won't be deinitialized.
131
+ unsafe { storage. set_initialized ( false ) } ;
79
132
80
133
// SAFETY: We are the only thread that can access the binding, and we know that it's initialized.
81
134
unsafe {
@@ -92,7 +145,10 @@ impl BindingStorage {
92
145
pub unsafe fn get_binding_unchecked ( ) -> & ' static GodotBinding {
93
146
let storage = Self :: storage ( ) ;
94
147
95
- if cfg ! ( debug_assertions) {
148
+ // We only check if we are in the main thread in debug builds if we aren't building for a non-threaded Godot build,
149
+ // since we could otherwise assume there won't be multi-threading.
150
+ #[ cfg( all( debug_assertions, not( wasm_nothreads) ) ) ]
151
+ {
96
152
let main_thread_id = storage. main_thread_id . get ( ) . expect (
97
153
"Godot engine not available; make sure you are not calling it from unit/doc tests" ,
98
154
) ;
@@ -111,7 +167,8 @@ impl BindingStorage {
111
167
pub fn is_initialized ( ) -> bool {
112
168
// SAFETY: We don't access the binding.
113
169
let storage = unsafe { Self :: storage ( ) } ;
114
- storage. main_thread_id . get ( ) . is_some ( )
170
+
171
+ storage. initialized ( )
115
172
}
116
173
}
117
174
0 commit comments