@@ -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,272 @@ 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
+ /// Acquires a reference to the value in this TLS key.
399
+ ///
400
+ /// See [`std::thread::LocalKey::try_with`] for details.
401
+ #[ allow( dead_code) ]
402
+ #[ inline]
403
+ pub fn try_with < F , R > ( & ' static self , f : F ) -> Result < R , std:: thread:: AccessError >
404
+ where
405
+ F : FnOnce ( & T ) -> R ,
406
+ {
407
+ #[ cfg( not( wasm_nothreads) ) ]
408
+ return self . threaded_val . try_with ( f) ;
409
+
410
+ #[ cfg( wasm_nothreads) ]
411
+ Ok ( self . with ( f) )
412
+ }
413
+ }
414
+
415
+ #[ allow( dead_code) ]
416
+ impl < T : ' static > GodotThreadLocal < Cell < T > > {
417
+ /// Sets or initializes the contained value.
418
+ ///
419
+ /// See [`std::thread::LocalKey::set`] for details.
420
+ pub fn set ( & ' static self , value : T ) {
421
+ #[ cfg( not( wasm_nothreads) ) ]
422
+ return self . threaded_val . set ( value) ;
423
+
424
+ // According to `LocalKey` docs, this method must not call the default initializer.
425
+ #[ cfg( wasm_nothreads) ]
426
+ if let Some ( initialized) = self . non_threaded_val . get ( ) {
427
+ initialized. set ( value) ;
428
+ } else {
429
+ self . non_threaded_val . get_or_init ( || Cell :: new ( value) ) ;
430
+ }
431
+ }
432
+
433
+ /// Returns a copy of the contained value.
434
+ ///
435
+ /// See [`std::thread::LocalKey::get`] for details.
436
+ pub fn get ( & ' static self ) -> T
437
+ where
438
+ T : Copy ,
439
+ {
440
+ #[ cfg( not( wasm_nothreads) ) ]
441
+ return self . threaded_val . get ( ) ;
442
+
443
+ #[ cfg( wasm_nothreads) ]
444
+ self . with ( Cell :: get)
445
+ }
446
+
447
+ /// Takes the contained value, leaving `Default::default()` in its place.
448
+ ///
449
+ /// See [`std::thread::LocalKey::take`] for details.
450
+ pub fn take ( & ' static self ) -> T
451
+ where
452
+ T : Default ,
453
+ {
454
+ #[ cfg( not( wasm_nothreads) ) ]
455
+ return self . threaded_val . take ( ) ;
456
+
457
+ #[ cfg( wasm_nothreads) ]
458
+ self . with ( Cell :: take)
459
+ }
460
+
461
+ /// Replaces the contained value, returning the old value.
462
+ ///
463
+ /// See [`std::thread::LocalKey::replace`] for details.
464
+ pub fn replace ( & ' static self , value : T ) -> T {
465
+ #[ cfg( not( wasm_nothreads) ) ]
466
+ return self . threaded_val . replace ( value) ;
467
+
468
+ #[ cfg( wasm_nothreads) ]
469
+ self . with ( |cell| cell. replace ( value) )
470
+ }
471
+ }
472
+
473
+ #[ allow( dead_code) ]
474
+ impl < T : ' static > GodotThreadLocal < RefCell < T > > {
475
+ /// Acquires a reference to the contained value.
476
+ ///
477
+ /// See [`std::thread::LocalKey::with_borrow`] for details.
478
+ pub fn with_borrow < F , R > ( & ' static self , f : F ) -> R
479
+ where
480
+ F : FnOnce ( & T ) -> R ,
481
+ {
482
+ #[ cfg( not( wasm_nothreads) ) ]
483
+ return self . threaded_val . with_borrow ( f) ;
484
+
485
+ #[ cfg( wasm_nothreads) ]
486
+ self . with ( |cell| f ( & cell. borrow ( ) ) )
487
+ }
488
+
489
+ /// Acquires a mutable reference to the contained value.
490
+ ///
491
+ /// See [`std::thread::LocalKey::with_borrow_mut`] for details.
492
+ pub fn with_borrow_mut < F , R > ( & ' static self , f : F ) -> R
493
+ where
494
+ F : FnOnce ( & mut T ) -> R ,
495
+ {
496
+ #[ cfg( not( wasm_nothreads) ) ]
497
+ return self . threaded_val . with_borrow_mut ( f) ;
498
+
499
+ #[ cfg( wasm_nothreads) ]
500
+ self . with ( |cell| f ( & mut cell. borrow_mut ( ) ) )
501
+ }
502
+
503
+ /// Sets or initializes the contained value.
504
+ ///
505
+ /// See [`std::thread::LocalKey::set`] for details.
506
+ pub fn set ( & ' static self , value : T ) {
507
+ #[ cfg( not( wasm_nothreads) ) ]
508
+ return self . threaded_val . set ( value) ;
509
+
510
+ // According to `LocalKey` docs, this method must not call the default initializer.
511
+ #[ cfg( wasm_nothreads) ]
512
+ if let Some ( initialized) = self . non_threaded_val . get ( ) {
513
+ * initialized. borrow_mut ( ) = value;
514
+ } else {
515
+ self . non_threaded_val . get_or_init ( || RefCell :: new ( value) ) ;
516
+ }
517
+ }
518
+
519
+ /// Takes the contained value, leaving `Default::default()` in its place.
520
+ ///
521
+ /// See [`std::thread::LocalKey::take`] for details.
522
+ pub fn take ( & ' static self ) -> T
523
+ where
524
+ T : Default ,
525
+ {
526
+ #[ cfg( not( wasm_nothreads) ) ]
527
+ return self . threaded_val . take ( ) ;
528
+
529
+ #[ cfg( wasm_nothreads) ]
530
+ self . with ( RefCell :: take)
531
+ }
532
+
533
+ /// Replaces the contained value, returning the old value.
534
+ ///
535
+ /// See [`std::thread::LocalKey::replace`] for details.
536
+ pub fn replace ( & ' static self , value : T ) -> T {
537
+ #[ cfg( not( wasm_nothreads) ) ]
538
+ return self . threaded_val . replace ( value) ;
539
+
540
+ #[ cfg( wasm_nothreads) ]
541
+ self . with ( |cell| cell. replace ( value) )
542
+ }
543
+ }
544
+
545
+ impl < T : ' static > std:: fmt:: Debug for GodotThreadLocal < T > {
546
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
547
+ f. debug_struct ( "GodotThreadLocal" ) . finish_non_exhaustive ( )
548
+ }
549
+ }
550
+
551
+ #[ cfg( not( wasm_nothreads) ) ]
552
+ macro_rules! godot_thread_local {
553
+ // empty (base case for the recursion)
554
+ ( ) => { } ;
555
+
556
+ ( $( #[ $attr: meta] ) * $vis: vis static $name: ident: $ty: ty = const $init: block; $( $rest: tt) * ) => {
557
+ $crate:: private:: godot_thread_local!( $( #[ $attr] ) * $vis static $name: $ty = const $init) ;
558
+ $crate:: private:: godot_thread_local!( $( $rest) * ) ;
559
+ } ;
560
+
561
+ ( $( #[ $attr: meta] ) * $vis: vis static $name: ident: $ty: ty = const $init: block) => {
562
+ $( #[ $attr] ) *
563
+ $vis static $name: $crate:: private:: GodotThreadLocal <$ty> = {
564
+ :: std:: thread_local! {
565
+ static $name: $ty = const $init
566
+ }
567
+
568
+ $crate:: private:: GodotThreadLocal :: new_threads( & $name)
569
+ } ;
570
+ } ;
571
+
572
+ ( $( #[ $attr: meta] ) * $vis: vis static $name: ident: $ty: ty = $init: expr; $( $rest: tt) * ) => {
573
+ $crate:: private:: godot_thread_local!( $( #[ $attr] ) * $vis static $name: $ty = $init) ;
574
+ $crate:: private:: godot_thread_local!( $( $rest) * ) ;
575
+ } ;
576
+
577
+ ( $( #[ $attr: meta] ) * $vis: vis static $name: ident: $ty: ty = $init: expr) => {
578
+ $( #[ $attr] ) *
579
+ $vis static $name: $crate:: private:: GodotThreadLocal <$ty> = {
580
+ :: std:: thread_local! {
581
+ static $name: $ty = $init
582
+ }
583
+
584
+ $crate:: private:: GodotThreadLocal :: new_threads( & $name)
585
+ } ;
586
+ } ;
587
+ }
588
+
589
+ #[ cfg( wasm_nothreads) ]
590
+ macro_rules! godot_thread_local {
591
+ // empty (base case for the recursion)
592
+ ( ) => { } ;
593
+
594
+ ( $( #[ $attr: meta] ) * $vis: vis static $name: ident: $ty: ty = const $init: block; $( $rest: tt) * ) => {
595
+ $crate:: private:: godot_thread_local!( $( #[ $attr] ) * $vis static $name: $ty = const $init) ;
596
+ $crate:: private:: godot_thread_local!( $( $rest) * ) ;
597
+ } ;
598
+
599
+ ( $( #[ $attr: meta] ) * $vis: vis static $name: ident: $ty: ty = const $init: block) => {
600
+ $( #[ $attr] ) *
601
+ $vis static $name: $crate:: private:: GodotThreadLocal <$ty> =
602
+ $crate:: private:: GodotThreadLocal :: new_nothreads( || $init) ;
603
+ } ;
604
+
605
+ ( $( #[ $attr: meta] ) * $vis: vis static $name: ident: $ty: ty = $init: expr; $( $rest: tt) * ) => {
606
+ $crate:: private:: godot_thread_local!( $( #[ $attr] ) * $vis static $name: $ty = $init) ;
607
+ $crate:: private:: godot_thread_local!( $( $rest) * ) ;
608
+ } ;
609
+
610
+ ( $( #[ $attr: meta] ) * $vis: vis static $name: ident: $ty: ty = $init: expr) => {
611
+ $( #[ $attr] ) *
612
+ $vis static $name: $crate:: private:: GodotThreadLocal <$ty> =
613
+ $crate:: private:: GodotThreadLocal :: new_nothreads( || $init) ;
614
+ } ;
615
+ }
616
+
617
+ pub ( crate ) use godot_thread_local;
618
+
354
619
#[ cfg( all( debug_assertions, not( wasm_nothreads) ) ) ]
355
620
thread_local ! {
356
621
static ERROR_CONTEXT_STACK : RefCell <ScopedFunctionStack > = const {
0 commit comments