Skip to content

Commit f52b02e

Browse files
committed
create godot_thread_local
1 parent 6e21024 commit f52b02e

File tree

1 file changed

+245
-2
lines changed

1 file changed

+245
-2
lines changed

godot-core/src/private.rs

+245-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ pub use sys::out;
1717

1818
#[cfg(feature = "trace")]
1919
pub use crate::meta::trace;
20-
#[cfg(all(debug_assertions, not(wasm_nothreads)))]
21-
use std::cell::RefCell;
20+
use std::cell::{Cell, RefCell};
2221

2322
use crate::global::godot_error;
2423
use crate::meta::error::CallError;
@@ -351,6 +350,250 @@ impl ScopedFunctionStack {
351350
}
352351
}
353352

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+
354597
#[cfg(all(debug_assertions, not(wasm_nothreads)))]
355598
thread_local! {
356599
static ERROR_CONTEXT_STACK: RefCell<ScopedFunctionStack> = const {

0 commit comments

Comments
 (0)