Skip to content

Commit 0435e07

Browse files
committed
create godot_thread_local
d
1 parent 033a131 commit 0435e07

File tree

1 file changed

+267
-2
lines changed

1 file changed

+267
-2
lines changed

godot-core/src/private.rs

+267-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,272 @@ 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+
/// 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+
354619
#[cfg(all(debug_assertions, not(wasm_nothreads)))]
355620
thread_local! {
356621
static ERROR_CONTEXT_STACK: RefCell<ScopedFunctionStack> = const {

0 commit comments

Comments
 (0)