@@ -17,8 +17,7 @@ pub use sys::out;
17
17
18
18
#[ cfg( feature = "trace" ) ]
19
19
pub use crate :: meta:: trace;
20
- #[ cfg( all( debug_assertions, not( wasm_nothreads) ) ) ]
21
- use std:: cell:: RefCell ;
20
+ use std:: cell:: { Cell , RefCell } ;
22
21
23
22
use crate :: global:: godot_error;
24
23
use crate :: meta:: error:: CallError ;
@@ -351,6 +350,250 @@ impl ScopedFunctionStack {
351
350
}
352
351
}
353
352
353
+ /// A thread local which adequately behaves as a global variable when compiling under `experimental-wasm-nothreads`, as it does
354
+ /// not support thread locals. Aims to support similar APIs as [`std::thread::LocalKey`].
355
+ pub ( crate ) struct GodotThreadLocal < T : ' static > {
356
+ #[ cfg( not( wasm_nothreads) ) ]
357
+ threaded_val : & ' static std:: thread:: LocalKey < T > ,
358
+
359
+ #[ cfg( wasm_nothreads) ]
360
+ non_threaded_val : std:: cell:: OnceCell < T > ,
361
+
362
+ #[ cfg( wasm_nothreads) ]
363
+ initializer : fn ( ) -> T ,
364
+ }
365
+
366
+ // SAFETY: there can only be one thread with `wasm_nothreads`.
367
+ #[ cfg( wasm_nothreads) ]
368
+ unsafe impl < T : ' static > Sync for GodotThreadLocal < T > { }
369
+
370
+ impl < T : ' static > GodotThreadLocal < T > {
371
+ #[ cfg( not( wasm_nothreads) ) ]
372
+ pub const fn new_threads ( key : & ' static std:: thread:: LocalKey < T > ) -> Self {
373
+ Self { threaded_val : key }
374
+ }
375
+
376
+ #[ cfg( wasm_nothreads) ]
377
+ pub const fn new_nothreads ( initializer : fn ( ) -> T ) -> Self {
378
+ Self {
379
+ non_threaded_val : std:: cell:: OnceCell :: new ( ) ,
380
+ initializer,
381
+ }
382
+ }
383
+
384
+ /// Acquires a reference to the value in this TLS key.
385
+ ///
386
+ /// See [`std::thread::LocalKey::with`] for details.
387
+ pub fn with < F , R > ( & ' static self , f : F ) -> R
388
+ where
389
+ F : FnOnce ( & T ) -> R ,
390
+ {
391
+ #[ cfg( not( wasm_nothreads) ) ]
392
+ return self . threaded_val . with ( f) ;
393
+
394
+ #[ cfg( wasm_nothreads) ]
395
+ f ( self . non_threaded_val . get_or_init ( self . initializer ) )
396
+ }
397
+ }
398
+
399
+ #[ allow( dead_code) ]
400
+ impl < T : ' static > GodotThreadLocal < Cell < T > > {
401
+ /// Sets or initializes the contained value.
402
+ ///
403
+ /// See [`std::thread::LocalKey::set`] for details.
404
+ pub fn set ( & ' static self , value : T ) {
405
+ #[ cfg( not( wasm_nothreads) ) ]
406
+ return self . threaded_val . set ( value) ;
407
+
408
+ // According to `LocalKey` docs, this method must not call the default initializer.
409
+ #[ cfg( wasm_nothreads) ]
410
+ if let Some ( initialized) = self . non_threaded_val . get ( ) {
411
+ initialized. set ( value) ;
412
+ } else {
413
+ self . non_threaded_val . get_or_init ( || Cell :: new ( value) ) ;
414
+ }
415
+ }
416
+
417
+ /// Returns a copy of the contained value.
418
+ ///
419
+ /// See [`std::thread::LocalKey::get`] for details.
420
+ pub fn get ( & ' static self ) -> T
421
+ where
422
+ T : Copy ,
423
+ {
424
+ #[ cfg( not( wasm_nothreads) ) ]
425
+ return self . threaded_val . get ( ) ;
426
+
427
+ #[ cfg( wasm_nothreads) ]
428
+ self . with ( Cell :: get)
429
+ }
430
+
431
+ /// Takes the contained value, leaving `Default::default()` in its place.
432
+ ///
433
+ /// See [`std::thread::LocalKey::take`] for details.
434
+ pub fn take ( & ' static self ) -> T
435
+ where
436
+ T : Default ,
437
+ {
438
+ #[ cfg( not( wasm_nothreads) ) ]
439
+ return self . threaded_val . take ( ) ;
440
+
441
+ #[ cfg( wasm_nothreads) ]
442
+ self . with ( Cell :: take)
443
+ }
444
+
445
+ /// Replaces the contained value, returning the old value.
446
+ ///
447
+ /// See [`std::thread::LocalKey::replace`] for details.
448
+ pub fn replace ( & ' static self , value : T ) -> T {
449
+ #[ cfg( not( wasm_nothreads) ) ]
450
+ return self . threaded_val . replace ( value) ;
451
+
452
+ #[ cfg( wasm_nothreads) ]
453
+ self . with ( |cell| cell. replace ( value) )
454
+ }
455
+ }
456
+
457
+ #[ allow( dead_code) ]
458
+ impl < T : ' static > GodotThreadLocal < RefCell < T > > {
459
+ /// Acquires a reference to the contained value.
460
+ ///
461
+ /// See [`std::thread::LocalKey::with_borrow`] for details.
462
+ pub fn with_borrow < F , R > ( & ' static self , f : F ) -> R
463
+ where
464
+ F : FnOnce ( & T ) -> R ,
465
+ {
466
+ #[ cfg( not( wasm_nothreads) ) ]
467
+ return self . threaded_val . with_borrow ( f) ;
468
+
469
+ #[ cfg( wasm_nothreads) ]
470
+ self . with ( |cell| f ( & cell. borrow ( ) ) )
471
+ }
472
+
473
+ /// Acquires a mutable reference to the contained value.
474
+ ///
475
+ /// See [`std::thread::LocalKey::with_borrow_mut`] for details.
476
+ pub fn with_borrow_mut < F , R > ( & ' static self , f : F ) -> R
477
+ where
478
+ F : FnOnce ( & mut T ) -> R ,
479
+ {
480
+ #[ cfg( not( wasm_nothreads) ) ]
481
+ return self . threaded_val . with_borrow_mut ( f) ;
482
+
483
+ #[ cfg( wasm_nothreads) ]
484
+ self . with ( |cell| f ( & mut cell. borrow_mut ( ) ) )
485
+ }
486
+
487
+ /// Sets or initializes the contained value.
488
+ ///
489
+ /// See [`std::thread::LocalKey::set`] for details.
490
+ pub fn set ( & ' static self , value : T ) {
491
+ #[ cfg( not( wasm_nothreads) ) ]
492
+ return self . threaded_val . set ( value) ;
493
+
494
+ // According to `LocalKey` docs, this method must not call the default initializer.
495
+ #[ cfg( wasm_nothreads) ]
496
+ if let Some ( initialized) = self . non_threaded_val . get ( ) {
497
+ * initialized. borrow_mut ( ) = value;
498
+ } else {
499
+ self . non_threaded_val . get_or_init ( || RefCell :: new ( value) ) ;
500
+ }
501
+ }
502
+
503
+ /// Takes the contained value, leaving `Default::default()` in its place.
504
+ ///
505
+ /// See [`std::thread::LocalKey::take`] for details.
506
+ pub fn take ( & ' static self ) -> T
507
+ where
508
+ T : Default ,
509
+ {
510
+ #[ cfg( not( wasm_nothreads) ) ]
511
+ return self . threaded_val . take ( ) ;
512
+
513
+ #[ cfg( wasm_nothreads) ]
514
+ self . with ( RefCell :: take)
515
+ }
516
+
517
+ /// Replaces the contained value, returning the old value.
518
+ ///
519
+ /// See [`std::thread::LocalKey::replace`] for details.
520
+ pub fn replace ( & ' static self , value : T ) -> T {
521
+ #[ cfg( not( wasm_nothreads) ) ]
522
+ return self . threaded_val . replace ( value) ;
523
+
524
+ #[ cfg( wasm_nothreads) ]
525
+ self . with ( |cell| cell. replace ( value) )
526
+ }
527
+ }
528
+
529
+ #[ cfg( not( wasm_nothreads) ) ]
530
+ macro_rules! godot_thread_local {
531
+ // empty (base case for the recursion)
532
+ ( ) => { } ;
533
+
534
+ ( $( #[ $attr: meta] ) * $vis: vis static $name: ident: $ty: ty = const $init: block; $( $rest: tt) * ) => {
535
+ $crate:: private:: godot_thread_local!( $( #[ $attr] ) * $vis static $name: $ty = const $init) ;
536
+ $crate:: private:: godot_thread_local!( $( $rest) * ) ;
537
+ } ;
538
+
539
+ ( $( #[ $attr: meta] ) * $vis: vis static $name: ident: $ty: ty = const $init: block) => {
540
+ $( #[ $attr] ) *
541
+ $vis static $name: $crate:: private:: GodotThreadLocal <$ty> = {
542
+ :: std:: thread_local! {
543
+ static $name: $ty = const $init
544
+ }
545
+
546
+ $crate:: private:: GodotThreadLocal :: new_threads( & $name)
547
+ } ;
548
+ } ;
549
+
550
+ ( $( #[ $attr: meta] ) * $vis: vis static $name: ident: $ty: ty = $init: expr; $( $rest: tt) * ) => {
551
+ $crate:: private:: godot_thread_local!( $( #[ $attr] ) * $vis static $name: $ty = $init) ;
552
+ $crate:: private:: godot_thread_local!( $( $rest) * ) ;
553
+ } ;
554
+
555
+ ( $( #[ $attr: meta] ) * $vis: vis static $name: ident: $ty: ty = $init: expr) => {
556
+ $( #[ $attr] ) *
557
+ $vis static $name: $crate:: private:: GodotThreadLocal <$ty> = {
558
+ :: std:: thread_local! {
559
+ static $name: $ty = $init
560
+ }
561
+
562
+ $crate:: private:: GodotThreadLocal :: new_threads( & $name)
563
+ } ;
564
+ } ;
565
+ }
566
+
567
+ #[ cfg( wasm_nothreads) ]
568
+ macro_rules! godot_thread_local {
569
+ // empty (base case for the recursion)
570
+ ( ) => { } ;
571
+
572
+ ( $( #[ $attr: meta] ) * $vis: vis static $name: ident: $ty: ty = const $init: block; $( $rest: tt) * ) => {
573
+ $crate:: private:: godot_thread_local!( $( #[ $attr] ) * $vis static $name: $ty = const $init) ;
574
+ $crate:: private:: godot_thread_local!( $( $rest) * ) ;
575
+ } ;
576
+
577
+ ( $( #[ $attr: meta] ) * $vis: vis static $name: ident: $ty: ty = const $init: block) => {
578
+ $( #[ $attr] ) *
579
+ $vis static $name: $crate:: private:: GodotThreadLocal <$ty> =
580
+ $crate:: private:: GodotThreadLocal :: new_nothreads( || $init) ;
581
+ } ;
582
+
583
+ ( $( #[ $attr: meta] ) * $vis: vis static $name: ident: $ty: ty = $init: expr; $( $rest: tt) * ) => {
584
+ $crate:: private:: godot_thread_local!( $( #[ $attr] ) * $vis static $name: $ty = $init) ;
585
+ $crate:: private:: godot_thread_local!( $( $rest) * ) ;
586
+ } ;
587
+
588
+ ( $( #[ $attr: meta] ) * $vis: vis static $name: ident: $ty: ty = $init: expr) => {
589
+ $( #[ $attr] ) *
590
+ $vis static $name: $crate:: private:: GodotThreadLocal <$ty> =
591
+ $crate:: private:: GodotThreadLocal :: new_nothreads( || $init) ;
592
+ } ;
593
+ }
594
+
595
+ pub ( crate ) use godot_thread_local;
596
+
354
597
#[ cfg( all( debug_assertions, not( wasm_nothreads) ) ) ]
355
598
thread_local ! {
356
599
static ERROR_CONTEXT_STACK : RefCell <ScopedFunctionStack > = const {
0 commit comments