1010//! If used from different threads then there will be runtime errors in debug mode and UB in release mode.
1111
1212use std:: cell:: Cell ;
13+
14+ #[ cfg( not( wasm_nothreads) ) ]
1315use std:: thread:: ThreadId ;
1416
1517use super :: GodotBinding ;
1618use crate :: ManualInitCell ;
1719
1820pub ( 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+
1926 // 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) ) ]
2028 main_thread_id : Cell < Option < ThreadId > > ,
2129 binding : ManualInitCell < GodotBinding > ,
2230}
@@ -30,13 +38,59 @@ impl BindingStorage {
3038 #[ inline( always) ]
3139 unsafe fn storage ( ) -> & ' static Self {
3240 static BINDING : BindingStorage = BindingStorage {
41+ #[ cfg( wasm_nothreads) ]
42+ initialized : Cell :: new ( false ) ,
43+
44+ #[ cfg( not( wasm_nothreads) ) ]
3345 main_thread_id : Cell :: new ( None ) ,
3446 binding : ManualInitCell :: new ( ) ,
3547 } ;
3648
3749 & BINDING
3850 }
3951
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+
4094 /// Initialize the binding storage, this must be called before any other public functions.
4195 ///
4296 /// # Safety
@@ -49,9 +103,10 @@ impl BindingStorage {
49103 // in which case we can tell that the storage has been initialized, and we don't access `binding`.
50104 let storage = unsafe { Self :: storage ( ) } ;
51105
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 ) } ;
55110
56111 // SAFETY: We are the first thread to set this binding (possibly after deinitialize), as otherwise the above set() would fail and
57112 // 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 {
70125 // SAFETY: We only call this once no other operations happen anymore, i.e. no other access to the binding.
71126 let storage = unsafe { Self :: storage ( ) } ;
72127
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 ) } ;
79132
80133 // SAFETY: We are the only thread that can access the binding, and we know that it's initialized.
81134 unsafe {
@@ -92,7 +145,10 @@ impl BindingStorage {
92145 pub unsafe fn get_binding_unchecked ( ) -> & ' static GodotBinding {
93146 let storage = Self :: storage ( ) ;
94147
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+ {
96152 let main_thread_id = storage. main_thread_id . get ( ) . expect (
97153 "Godot engine not available; make sure you are not calling it from unit/doc tests" ,
98154 ) ;
@@ -111,7 +167,8 @@ impl BindingStorage {
111167 pub fn is_initialized ( ) -> bool {
112168 // SAFETY: We don't access the binding.
113169 let storage = unsafe { Self :: storage ( ) } ;
114- storage. main_thread_id . get ( ) . is_some ( )
170+
171+ storage. initialized ( )
115172 }
116173}
117174
0 commit comments