Move spin to bevy_platform_support out of other crates (#17470)

# Objective

- Contributes to #16877

## Solution

- Expanded `bevy_platform_support::sync` module to provide
API-compatible replacements for `std` items such as `RwLock`, `Mutex`,
and `OnceLock`.
- Removed `spin` from all crates except `bevy_platform_support`.

## Testing

- CI

---

## Notes

- The sync primitives, while verbose, entirely rely on `spin` for their
implementation requiring no `unsafe` and not changing the status-quo on
_how_ locks actually work within Bevy. This is just a refactoring to
consolidate the "hacks" and workarounds required to get a consistent
experience when either using `std::sync` or `spin`.
- I have opted to rely on `std::sync` for `std` compatible locks,
maintaining the status quo. However, now that we have these locks
factored out into the own module, it would be trivial to investigate
alternate locking backends, such as `parking_lot`.
- API for these locking types is entirely based on `std`. I have
implemented methods and types which aren't currently in use within Bevy
(e.g., `LazyLock` and `Once`) for the sake of completeness. As the
standard library is highly stable, I don't expect the Bevy and `std`
implementations to drift apart much if at all.

---------

Co-authored-by: BD103 <59022059+BD103@users.noreply.github.com>
Co-authored-by: Benjamin Brienen <benjamin.brienen@outlook.com>
This commit is contained in:
Zachary Harrold 2025-01-23 16:27:02 +11:00 committed by GitHub
parent dd2d84b342
commit 04990fcd27
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 753 additions and 147 deletions

View File

@ -93,7 +93,6 @@ portable-atomic = [
"bevy_tasks?/portable-atomic",
"bevy_platform_support/portable-atomic",
"concurrent-queue/portable-atomic",
"spin/portable_atomic",
"bevy_reflect?/portable-atomic",
]
@ -128,10 +127,6 @@ arrayvec = { version = "0.7.4", default-features = false, optional = true }
smallvec = { version = "1", features = ["union", "const_generics"] }
indexmap = { version = "2.5.0", default-features = false }
variadics_please = { version = "1.1", default-features = false }
spin = { version = "0.9.8", default-features = false, features = [
"spin_mutex",
"rwlock",
] }
tracing = { version = "0.1", default-features = false, optional = true }
log = { version = "0.4", default-features = false }
bumpalo = "3"

View File

@ -7,12 +7,7 @@
use alloc::{borrow::ToOwned, boxed::Box};
use core::{fmt::Debug, hash::Hash, ops::Deref};
#[cfg(feature = "std")]
use std::sync::{PoisonError, RwLock};
#[cfg(not(feature = "std"))]
use spin::rwlock::RwLock;
use bevy_platform_support::sync::{PoisonError, RwLock};
use bevy_utils::{FixedHasher, HashSet};
/// An interned value. Will stay valid until the end of the program and will not drop.
@ -143,24 +138,16 @@ impl<T: Internable + ?Sized> Interner<T> {
/// will return [`Interned<T>`] using the same static reference.
pub fn intern(&self, value: &T) -> Interned<T> {
{
#[cfg(feature = "std")]
let set = self.0.read().unwrap_or_else(PoisonError::into_inner);
#[cfg(not(feature = "std"))]
let set = self.0.read();
if let Some(value) = set.get(value) {
return Interned(*value);
}
}
{
#[cfg(feature = "std")]
let mut set = self.0.write().unwrap_or_else(PoisonError::into_inner);
#[cfg(not(feature = "std"))]
let mut set = self.0.write();
if let Some(value) = set.get(value) {
Interned(*value)
} else {

View File

@ -21,6 +21,7 @@ std = [
"critical-section?/std",
"portable-atomic?/std",
"portable-atomic-util?/std",
"spin/std",
]
alloc = ["portable-atomic-util?/alloc"]
@ -31,7 +32,11 @@ critical-section = ["dep:critical-section", "portable-atomic?/critical-section"]
## `portable-atomic` provides additional platform support for atomic types and
## operations, even on targets without native support.
portable-atomic = ["dep:portable-atomic", "dep:portable-atomic-util"]
portable-atomic = [
"dep:portable-atomic",
"dep:portable-atomic-util",
"spin/portable-atomic",
]
[dependencies]
critical-section = { version = "1.2.0", default-features = false, optional = true }
@ -39,6 +44,14 @@ portable-atomic = { version = "1", default-features = false, features = [
"fallback",
], optional = true }
portable-atomic-util = { version = "0.2.4", default-features = false, optional = true }
spin = { version = "0.9.8", default-features = false, features = [
"mutex",
"spin_mutex",
"rwlock",
"once",
"lazy",
"barrier",
] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
web-time = { version = "1.1", default-features = false }

View File

@ -17,3 +17,28 @@ extern crate alloc;
pub mod sync;
pub mod time;
/// Frequently used items which would typically be included in most contexts.
///
/// When adding `no_std` support to a crate for the first time, often there's a substantial refactor
/// required due to the change in implicit prelude from `std::prelude` to `core::prelude`.
/// This unfortunately leaves out many items from `alloc`, even if the crate unconditionally
/// includes that crate.
///
/// This prelude aims to ease the transition by re-exporting items from `alloc` which would
/// otherwise be included in the `std` implicit prelude.
pub mod prelude {
#[cfg(feature = "alloc")]
pub use alloc::{
borrow::ToOwned, boxed::Box, format, string::String, string::ToString, vec, vec::Vec,
};
// Items from `std::prelude` that are missing in this module:
// * dbg
// * eprint
// * eprintln
// * is_x86_feature_detected
// * print
// * println
// * thread_local
}

View File

@ -1,30 +0,0 @@
//! Provides various synchronization alternatives to language primitives.
#[cfg(feature = "alloc")]
pub use arc::{Arc, Weak};
pub mod atomic {
//! Provides various atomic alternatives to language primitives.
//!
//! Certain platforms lack complete atomic support, requiring the use of a fallback
//! such as `portable-atomic`.
//! Using these types will ensure the correct atomic provider is used without the need for
//! feature gates in your own code.
pub use atomic::{
AtomicBool, AtomicI16, AtomicI32, AtomicI64, AtomicI8, AtomicIsize, AtomicPtr, AtomicU16,
AtomicU32, AtomicU64, AtomicU8, AtomicUsize, Ordering,
};
#[cfg(not(feature = "portable-atomic"))]
use core::sync::atomic;
#[cfg(feature = "portable-atomic")]
use portable_atomic as atomic;
}
#[cfg(all(feature = "alloc", feature = "portable-atomic"))]
use portable_atomic_util as arc;
#[cfg(all(feature = "alloc", not(feature = "portable-atomic")))]
use alloc::sync as arc;

View File

@ -0,0 +1,17 @@
//! Provides various atomic alternatives to language primitives.
//!
//! Certain platforms lack complete atomic support, requiring the use of a fallback
//! such as `portable-atomic`.
//! Using these types will ensure the correct atomic provider is used without the need for
//! feature gates in your own code.
pub use atomic::{
AtomicBool, AtomicI16, AtomicI32, AtomicI64, AtomicI8, AtomicIsize, AtomicPtr, AtomicU16,
AtomicU32, AtomicU64, AtomicU8, AtomicUsize, Ordering,
};
#[cfg(not(feature = "portable-atomic"))]
use core::sync::atomic;
#[cfg(feature = "portable-atomic")]
use portable_atomic as atomic;

View File

@ -0,0 +1,66 @@
//! Provides `Barrier` and `BarrierWaitResult`
pub use barrier::{Barrier, BarrierWaitResult};
#[cfg(feature = "std")]
use std::sync as barrier;
#[cfg(not(feature = "std"))]
mod barrier {
use core::fmt;
/// Fallback implementation of `Barrier` from the standard library.
pub struct Barrier {
inner: spin::Barrier,
}
impl Barrier {
/// Creates a new barrier that can block a given number of threads.
///
/// See the standard library for further details.
#[must_use]
pub const fn new(n: usize) -> Self {
Self {
inner: spin::Barrier::new(n),
}
}
/// Blocks the current thread until all threads have rendezvoused here.
///
/// See the standard library for further details.
pub fn wait(&self) -> BarrierWaitResult {
BarrierWaitResult {
inner: self.inner.wait(),
}
}
}
impl fmt::Debug for Barrier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Barrier").finish_non_exhaustive()
}
}
/// Fallback implementation of `BarrierWaitResult` from the standard library.
pub struct BarrierWaitResult {
inner: spin::barrier::BarrierWaitResult,
}
impl BarrierWaitResult {
/// Returns `true` if this thread is the "leader thread" for the call to [`Barrier::wait()`].
///
/// See the standard library for further details.
#[must_use]
pub fn is_leader(&self) -> bool {
self.inner.is_leader()
}
}
impl fmt::Debug for BarrierWaitResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BarrierWaitResult")
.field("is_leader", &self.is_leader())
.finish()
}
}
}

View File

@ -0,0 +1,11 @@
//! Provides `LazyLock`
pub use lazy_lock::LazyLock;
#[cfg(feature = "std")]
use std::sync as lazy_lock;
#[cfg(not(feature = "std"))]
mod lazy_lock {
pub use spin::Lazy as LazyLock;
}

View File

@ -0,0 +1,33 @@
//! Provides various synchronization alternatives to language primitives.
//!
//! Currently missing from this module are the following items:
//! * `Condvar`
//! * `WaitTimeoutResult`
//! * `mpsc`
//!
//! Otherwise, this is a drop-in replacement for `std::sync`.
pub use barrier::{Barrier, BarrierWaitResult};
pub use lazy_lock::LazyLock;
pub use mutex::{Mutex, MutexGuard};
pub use once::{Once, OnceLock, OnceState};
pub use poison::{LockResult, PoisonError, TryLockError, TryLockResult};
pub use rwlock::{RwLock, RwLockReadGuard, RwLockWriteGuard};
#[cfg(feature = "alloc")]
pub use arc::{Arc, Weak};
pub mod atomic;
mod barrier;
mod lazy_lock;
mod mutex;
mod once;
mod poison;
mod rwlock;
#[cfg(all(feature = "alloc", feature = "portable-atomic"))]
use portable_atomic_util as arc;
#[cfg(all(feature = "alloc", not(feature = "portable-atomic")))]
use alloc::sync as arc;

View File

@ -0,0 +1,108 @@
//! Provides `Mutex` and `MutexGuard`
pub use mutex::{Mutex, MutexGuard};
#[cfg(feature = "std")]
use std::sync as mutex;
#[cfg(not(feature = "std"))]
mod mutex {
use crate::sync::{LockResult, TryLockError, TryLockResult};
use core::fmt;
pub use spin::MutexGuard;
/// Fallback implementation of `Mutex` from the standard library.
pub struct Mutex<T: ?Sized> {
inner: spin::Mutex<T>,
}
impl<T> Mutex<T> {
/// Creates a new mutex in an unlocked state ready for use.
///
/// See the standard library for further details.
pub const fn new(t: T) -> Self {
Self {
inner: spin::Mutex::new(t),
}
}
}
impl<T: ?Sized> Mutex<T> {
/// Acquires a mutex, blocking the current thread until it is able to do so.
///
/// See the standard library for further details.
pub fn lock(&self) -> LockResult<MutexGuard<'_, T>> {
Ok(self.inner.lock())
}
/// Attempts to acquire this lock.
///
/// See the standard library for further details.
pub fn try_lock(&self) -> TryLockResult<MutexGuard<'_, T>> {
self.inner.try_lock().ok_or(TryLockError::WouldBlock)
}
/// Determines whether the mutex is poisoned.
///
/// See the standard library for further details.
pub fn is_poisoned(&self) -> bool {
false
}
/// Clear the poisoned state from a mutex.
///
/// See the standard library for further details.
pub fn clear_poison(&self) {
// no-op
}
/// Consumes this mutex, returning the underlying data.
///
/// See the standard library for further details.
pub fn into_inner(self) -> LockResult<T>
where
T: Sized,
{
Ok(self.inner.into_inner())
}
/// Returns a mutable reference to the underlying data.
///
/// See the standard library for further details.
pub fn get_mut(&mut self) -> LockResult<&mut T> {
Ok(self.inner.get_mut())
}
}
impl<T> From<T> for Mutex<T> {
fn from(t: T) -> Self {
Mutex::new(t)
}
}
impl<T: ?Sized + Default> Default for Mutex<T> {
fn default() -> Mutex<T> {
Mutex::new(Default::default())
}
}
impl<T: ?Sized + fmt::Debug> fmt::Debug for Mutex<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_struct("Mutex");
match self.try_lock() {
Ok(guard) => {
d.field("data", &&*guard);
}
Err(TryLockError::Poisoned(err)) => {
d.field("data", &&**err.get_ref());
}
Err(TryLockError::WouldBlock) => {
d.field("data", &format_args!("<locked>"));
}
}
d.field("poisoned", &false);
d.finish_non_exhaustive()
}
}
}

View File

@ -0,0 +1,216 @@
//! Provides `Once`, `OnceState`, `OnceLock`
pub use once::{Once, OnceLock, OnceState};
#[cfg(feature = "std")]
use std::sync as once;
#[cfg(not(feature = "std"))]
mod once {
use core::{
fmt,
panic::{RefUnwindSafe, UnwindSafe},
};
/// Fallback implementation of `OnceLock` from the standard library.
pub struct OnceLock<T> {
inner: spin::Once<T>,
}
impl<T> OnceLock<T> {
/// Creates a new empty cell.
///
/// See the standard library for further details.
#[must_use]
pub const fn new() -> Self {
Self {
inner: spin::Once::new(),
}
}
/// Gets the reference to the underlying value.
///
/// See the standard library for further details.
pub fn get(&self) -> Option<&T> {
self.inner.get()
}
/// Gets the mutable reference to the underlying value.
///
/// See the standard library for further details.
pub fn get_mut(&mut self) -> Option<&mut T> {
self.inner.get_mut()
}
/// Sets the contents of this cell to `value`.
///
/// See the standard library for further details.
pub fn set(&self, value: T) -> Result<(), T> {
let mut value = Some(value);
self.inner.call_once(|| value.take().unwrap());
match value {
Some(value) => Err(value),
None => Ok(()),
}
}
/// Gets the contents of the cell, initializing it with `f` if the cell
/// was empty.
///
/// See the standard library for further details.
pub fn get_or_init<F>(&self, f: F) -> &T
where
F: FnOnce() -> T,
{
self.inner.call_once(f)
}
/// Consumes the `OnceLock`, returning the wrapped value. Returns
/// `None` if the cell was empty.
///
/// See the standard library for further details.
pub fn into_inner(mut self) -> Option<T> {
self.take()
}
/// Takes the value out of this `OnceLock`, moving it back to an uninitialized state.
///
/// See the standard library for further details.
pub fn take(&mut self) -> Option<T> {
if self.inner.is_completed() {
let mut inner = spin::Once::new();
core::mem::swap(&mut self.inner, &mut inner);
inner.try_into_inner()
} else {
None
}
}
}
impl<T: RefUnwindSafe + UnwindSafe> RefUnwindSafe for OnceLock<T> {}
impl<T: UnwindSafe> UnwindSafe for OnceLock<T> {}
impl<T> Default for OnceLock<T> {
fn default() -> OnceLock<T> {
OnceLock::new()
}
}
impl<T: fmt::Debug> fmt::Debug for OnceLock<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_tuple("OnceLock");
match self.get() {
Some(v) => d.field(v),
None => d.field(&format_args!("<uninit>")),
};
d.finish()
}
}
impl<T: Clone> Clone for OnceLock<T> {
fn clone(&self) -> OnceLock<T> {
let cell = Self::new();
if let Some(value) = self.get() {
cell.set(value.clone()).ok().unwrap();
}
cell
}
}
impl<T> From<T> for OnceLock<T> {
fn from(value: T) -> Self {
let cell = Self::new();
cell.set(value).map(move |_| cell).ok().unwrap()
}
}
impl<T: PartialEq> PartialEq for OnceLock<T> {
fn eq(&self, other: &OnceLock<T>) -> bool {
self.get() == other.get()
}
}
impl<T: Eq> Eq for OnceLock<T> {}
/// Fallback implementation of `Once` from the standard library.
pub struct Once {
inner: OnceLock<()>,
}
impl Once {
/// Creates a new `Once` value.
///
/// See the standard library for further details.
pub const fn new() -> Self {
Self {
inner: OnceLock::new(),
}
}
/// Performs an initialization routine once and only once. The given closure
/// will be executed if this is the first time `call_once` has been called,
/// and otherwise the routine will *not* be invoked.
///
/// See the standard library for further details.
pub fn call_once<F: FnOnce()>(&self, f: F) {
self.inner.get_or_init(f);
}
/// Performs the same function as [`call_once()`] except ignores poisoning.
///
/// See the standard library for further details.
pub fn call_once_force<F: FnOnce(&OnceState)>(&self, f: F) {
const STATE: OnceState = OnceState { _private: () };
self.call_once(move || f(&STATE));
}
/// Returns `true` if some [`call_once()`] call has completed
/// successfully. Specifically, `is_completed` will return false in
/// the following situations:
/// * [`call_once()`] was not called at all,
/// * [`call_once()`] was called, but has not yet completed,
/// * the [`Once`] instance is poisoned
///
/// See the standard library for further details.
pub fn is_completed(&self) -> bool {
self.inner.get().is_some()
}
}
impl RefUnwindSafe for Once {}
impl UnwindSafe for Once {}
impl fmt::Debug for Once {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Once").finish_non_exhaustive()
}
}
/// Fallback implementation of `OnceState` from the standard library.
pub struct OnceState {
_private: (),
}
impl OnceState {
/// Returns `true` if the associated [`Once`] was poisoned prior to the
/// invocation of the closure passed to [`Once::call_once_force()`].
///
/// See the standard library for further details.
pub fn is_poisoned(&self) -> bool {
false
}
}
impl fmt::Debug for OnceState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OnceState")
.field("poisoned", &self.is_poisoned())
.finish()
}
}
}

View File

@ -0,0 +1,107 @@
//! Provides `LockResult`, `PoisonError`, `TryLockError`, `TryLockResult`
pub use poison::{LockResult, PoisonError, TryLockError, TryLockResult};
#[cfg(feature = "std")]
use std::sync as poison;
#[cfg(not(feature = "std"))]
mod poison {
use core::{error::Error, fmt};
/// Fallback implementation of `PoisonError` from the standard library.
pub struct PoisonError<T> {
guard: T,
}
impl<T> fmt::Debug for PoisonError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PoisonError").finish_non_exhaustive()
}
}
impl<T> fmt::Display for PoisonError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
"poisoned lock: another task failed inside".fmt(f)
}
}
impl<T> Error for PoisonError<T> {}
impl<T> PoisonError<T> {
/// Creates a `PoisonError`.
///
/// See the standard library for further details.
#[cfg(panic = "unwind")]
pub fn new(guard: T) -> PoisonError<T> {
PoisonError { guard }
}
/// Consumes this error indicating that a lock is poisoned, returning the
/// underlying guard to allow access regardless.
///
/// See the standard library for further details.
pub fn into_inner(self) -> T {
self.guard
}
/// Reaches into this error indicating that a lock is poisoned, returning a
/// reference to the underlying guard to allow access regardless.
///
/// See the standard library for further details.
pub fn get_ref(&self) -> &T {
&self.guard
}
/// Reaches into this error indicating that a lock is poisoned, returning a
/// mutable reference to the underlying guard to allow access regardless.
///
/// See the standard library for further details.
pub fn get_mut(&mut self) -> &mut T {
&mut self.guard
}
}
/// Fallback implementation of `TryLockError` from the standard library.
pub enum TryLockError<T> {
/// The lock could not be acquired because another thread failed while holding
/// the lock.
Poisoned(PoisonError<T>),
/// The lock could not be acquired at this time because the operation would
/// otherwise block.
WouldBlock,
}
impl<T> From<PoisonError<T>> for TryLockError<T> {
fn from(err: PoisonError<T>) -> TryLockError<T> {
TryLockError::Poisoned(err)
}
}
impl<T> fmt::Debug for TryLockError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
TryLockError::Poisoned(..) => "Poisoned(..)".fmt(f),
TryLockError::WouldBlock => "WouldBlock".fmt(f),
}
}
}
impl<T> fmt::Display for TryLockError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
TryLockError::Poisoned(..) => "poisoned lock: another task failed inside",
TryLockError::WouldBlock => "try_lock failed because the operation would block",
}
.fmt(f)
}
}
impl<T> Error for TryLockError<T> {}
/// Fallback implementation of `LockResult` from the standard library.
pub type LockResult<Guard> = Result<Guard, PoisonError<Guard>>;
/// Fallback implementation of `TryLockResult` from the standard library.
pub type TryLockResult<Guard> = Result<Guard, TryLockError<Guard>>;
}

View File

@ -0,0 +1,124 @@
//! TODO: Implement `RwLock`, `RwLockReadGuard`, `RwLockWriteGuard`
pub use rwlock::{RwLock, RwLockReadGuard, RwLockWriteGuard};
#[cfg(feature = "std")]
use std::sync as rwlock;
#[cfg(not(feature = "std"))]
mod rwlock {
use crate::sync::{LockResult, TryLockError, TryLockResult};
use core::fmt;
pub use spin::rwlock::{RwLockReadGuard, RwLockWriteGuard};
/// Fallback implementation of `RwLock` from the standard library.
pub struct RwLock<T: ?Sized> {
inner: spin::RwLock<T>,
}
impl<T> RwLock<T> {
/// Creates a new instance of an `RwLock<T>` which is unlocked.
///
/// See the standard library for further details.
pub const fn new(t: T) -> RwLock<T> {
Self {
inner: spin::RwLock::new(t),
}
}
}
impl<T: ?Sized> RwLock<T> {
/// Locks this `RwLock` with shared read access, blocking the current thread
/// until it can be acquired.
///
/// See the standard library for further details.
pub fn read(&self) -> LockResult<RwLockReadGuard<'_, T>> {
Ok(self.inner.read())
}
/// Attempts to acquire this `RwLock` with shared read access.
///
/// See the standard library for further details.
pub fn try_read(&self) -> TryLockResult<RwLockReadGuard<'_, T>> {
self.inner.try_read().ok_or(TryLockError::WouldBlock)
}
/// Locks this `RwLock` with exclusive write access, blocking the current
/// thread until it can be acquired.
///
/// See the standard library for further details.
pub fn write(&self) -> LockResult<RwLockWriteGuard<'_, T>> {
Ok(self.inner.write())
}
/// Attempts to lock this `RwLock` with exclusive write access.
///
/// See the standard library for further details.
pub fn try_write(&self) -> TryLockResult<RwLockWriteGuard<'_, T>> {
self.inner.try_write().ok_or(TryLockError::WouldBlock)
}
/// Determines whether the lock is poisoned.
///
/// See the standard library for further details.
pub fn is_poisoned(&self) -> bool {
false
}
/// Clear the poisoned state from a lock.
///
/// See the standard library for further details.
pub fn clear_poison(&self) {
// no-op
}
/// Consumes this `RwLock`, returning the underlying data.
///
/// See the standard library for further details.
pub fn into_inner(self) -> LockResult<T>
where
T: Sized,
{
Ok(self.inner.into_inner())
}
/// Returns a mutable reference to the underlying data.
///
/// See the standard library for further details.
pub fn get_mut(&mut self) -> LockResult<&mut T> {
Ok(self.inner.get_mut())
}
}
impl<T: ?Sized + fmt::Debug> fmt::Debug for RwLock<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut d = f.debug_struct("RwLock");
match self.try_read() {
Ok(guard) => {
d.field("data", &&*guard);
}
Err(TryLockError::Poisoned(err)) => {
d.field("data", &&**err.get_ref());
}
Err(TryLockError::WouldBlock) => {
d.field("data", &format_args!("<locked>"));
}
}
d.field("poisoned", &false);
d.finish_non_exhaustive()
}
}
impl<T: Default> Default for RwLock<T> {
fn default() -> RwLock<T> {
RwLock::new(Default::default())
}
}
impl<T> From<T> for RwLock<T> {
fn from(t: T) -> Self {
RwLock::new(t)
}
}
}

View File

@ -58,7 +58,6 @@ std = [
"erased-serde/std",
"downcast-rs/std",
"serde/std",
"spin/std",
"glam?/std",
"smol_str?/std",
"uuid?/std",
@ -76,7 +75,6 @@ critical-section = [
## operations, even on targets without native support.
portable-atomic = [
"bevy_platform_support/portable-atomic",
"spin/portable_atomic",
"bevy_utils/portable-atomic",
]
@ -103,10 +101,6 @@ downcast-rs = { version = "2", default-features = false }
thiserror = { version = "2", default-features = false }
derive_more = { version = "1", default-features = false, features = ["from"] }
serde = { version = "1", default-features = false, features = ["alloc"] }
spin = { version = "0.9.8", default-features = false, features = [
"once",
"rwlock",
] }
assert_type_match = "0.1.1"
smallvec = { version = "1.11", default-features = false, optional = true }
glam = { version = "0.29", default-features = false, features = [

View File

@ -1,6 +1,6 @@
use crate::{serde::Serializable, FromReflect, Reflect, TypeInfo, TypePath, Typed};
use alloc::{boxed::Box, string::String};
use bevy_platform_support::sync::Arc;
use bevy_platform_support::sync::{Arc, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard};
use bevy_ptr::{Ptr, PtrMut};
use bevy_utils::{HashMap, HashSet, TypeIdMap};
use core::{
@ -11,12 +11,6 @@ use core::{
use downcast_rs::{impl_downcast, Downcast};
use serde::Deserialize;
#[cfg(feature = "std")]
use std::sync::{PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard};
#[cfg(not(feature = "std"))]
use spin::rwlock::{RwLock, RwLockReadGuard, RwLockWriteGuard};
/// A registry of [reflected] types.
///
/// This struct is used as the central store for type information.
@ -46,12 +40,12 @@ pub struct TypeRegistryArc {
impl Debug for TypeRegistryArc {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let read_lock = self.internal.read();
#[cfg(feature = "std")]
let read_lock = read_lock.unwrap_or_else(PoisonError::into_inner);
read_lock.type_path_to_id.keys().fmt(f)
self.internal
.read()
.unwrap_or_else(PoisonError::into_inner)
.type_path_to_id
.keys()
.fmt(f)
}
}
@ -433,22 +427,14 @@ impl TypeRegistry {
impl TypeRegistryArc {
/// Takes a read lock on the underlying [`TypeRegistry`].
pub fn read(&self) -> RwLockReadGuard<'_, TypeRegistry> {
let read_lock = self.internal.read();
#[cfg(feature = "std")]
let read_lock = read_lock.unwrap_or_else(PoisonError::into_inner);
read_lock
self.internal.read().unwrap_or_else(PoisonError::into_inner)
}
/// Takes a write lock on the underlying [`TypeRegistry`].
pub fn write(&self) -> RwLockWriteGuard<'_, TypeRegistry> {
let write_lock = self.internal.write();
#[cfg(feature = "std")]
let write_lock = write_lock.unwrap_or_else(PoisonError::into_inner);
write_lock
self.internal
.write()
.unwrap_or_else(PoisonError::into_inner)
}
}

View File

@ -2,18 +2,13 @@
use crate::TypeInfo;
use alloc::boxed::Box;
use bevy_platform_support::sync::{OnceLock, PoisonError, RwLock};
use bevy_utils::{DefaultHasher, FixedHasher, NoOpHash, TypeIdMap};
use core::{
any::{Any, TypeId},
hash::BuildHasher,
};
#[cfg(feature = "std")]
use std::sync::{OnceLock, PoisonError, RwLock};
#[cfg(not(feature = "std"))]
use spin::{Once as OnceLock, RwLock};
/// A type that can be stored in a ([`Non`])[`GenericTypeCell`].
///
/// [`Non`]: NonGenericTypeCell
@ -122,11 +117,7 @@ impl<T: TypedProperty> NonGenericTypeCell<T> {
where
F: FnOnce() -> T::Stored,
{
#[cfg(feature = "std")]
return self.0.get_or_init(f);
#[cfg(not(feature = "std"))]
return self.0.call_once(f);
self.0.get_or_init(f)
}
}
@ -259,12 +250,11 @@ impl<T: TypedProperty> GenericTypeCell<T> {
///
/// This method will then return the correct [`TypedProperty`] reference for the given type `T`.
fn get_by_type_id(&self, type_id: TypeId) -> Option<&T::Stored> {
let read_lock = self.0.read();
#[cfg(feature = "std")]
let read_lock = read_lock.unwrap_or_else(PoisonError::into_inner);
read_lock.get(&type_id).copied()
self.0
.read()
.unwrap_or_else(PoisonError::into_inner)
.get(&type_id)
.copied()
}
/// Returns a reference to the [`TypedProperty`] stored in the cell.
@ -282,12 +272,7 @@ impl<T: TypedProperty> GenericTypeCell<T> {
}
fn insert_by_type_id(&self, type_id: TypeId, value: T::Stored) -> &T::Stored {
let write_lock = self.0.write();
#[cfg(feature = "std")]
let write_lock = write_lock.unwrap_or_else(PoisonError::into_inner);
let mut write_lock = write_lock;
let mut write_lock = self.0.write().unwrap_or_else(PoisonError::into_inner);
write_lock
.entry(type_id)

View File

@ -13,7 +13,6 @@ default = ["std", "async_executor"]
std = [
"futures-lite/std",
"async-task/std",
"spin/std",
"edge-executor?/std",
"bevy_platform_support/std",
]
@ -28,7 +27,6 @@ portable-atomic = [
"bevy_platform_support/portable-atomic",
"edge-executor?/portable-atomic",
"async-task/portable-atomic",
"spin/portable_atomic",
]
[dependencies]
@ -40,11 +38,6 @@ futures-lite = { version = "2.0.1", default-features = false, features = [
"alloc",
] }
async-task = { version = "4.4.0", default-features = false }
spin = { version = "0.9.8", default-features = false, features = [
"spin_mutex",
"rwlock",
"once",
] }
derive_more = { version = "1", default-features = false, features = [
"deref",
"deref_mut",

View File

@ -7,6 +7,9 @@ use crate::Task;
#[cfg(feature = "std")]
use std::thread_local;
#[cfg(not(feature = "std"))]
use bevy_platform_support::sync::{Mutex, PoisonError};
#[cfg(all(
feature = "std",
any(feature = "async_executor", feature = "edge_executor")
@ -63,7 +66,7 @@ static LOCAL_EXECUTOR: LocalExecutor<'static> = const { LocalExecutor::new() };
type ScopeResult<T> = alloc::rc::Rc<RefCell<Option<T>>>;
#[cfg(not(feature = "std"))]
type ScopeResult<T> = Arc<spin::Mutex<Option<T>>>;
type ScopeResult<T> = Arc<Mutex<Option<T>>>;
/// Used to create a [`TaskPool`].
#[derive(Debug, Default, Clone)]
@ -214,7 +217,7 @@ impl TaskPool {
#[cfg(not(feature = "std"))]
{
let mut lock = result.lock();
let mut lock = result.lock().unwrap_or_else(PoisonError::into_inner);
lock.take().unwrap()
}
})
@ -343,7 +346,7 @@ impl<'scope, 'env, T: Send + 'env> Scope<'scope, 'env, T> {
#[cfg(not(feature = "std"))]
{
let mut lock = result.lock();
let mut lock = result.lock().unwrap_or_else(PoisonError::into_inner);
*lock = Some(temp_result);
}
};

View File

@ -1,20 +1,11 @@
use super::TaskPool;
use bevy_platform_support::sync::OnceLock;
use core::ops::Deref;
#[cfg(feature = "std")]
use std::sync::OnceLock;
#[cfg(not(feature = "std"))]
use spin::Once;
macro_rules! taskpool {
($(#[$attr:meta])* ($static:ident, $type:ident)) => {
#[cfg(feature = "std")]
static $static: OnceLock<$type> = OnceLock::new();
#[cfg(not(feature = "std"))]
static $static: Once<$type> = Once::new();
$(#[$attr])*
#[derive(Debug)]
pub struct $type(TaskPool);
@ -22,15 +13,7 @@ macro_rules! taskpool {
impl $type {
#[doc = concat!(" Gets the global [`", stringify!($type), "`] instance, or initializes it with `f`.")]
pub fn get_or_init(f: impl FnOnce() -> TaskPool) -> &'static Self {
#[cfg(feature = "std")]
{
$static.get_or_init(|| Self(f()))
}
#[cfg(not(feature = "std"))]
{
$static.call_once(|| Self(f()))
}
$static.get_or_init(|| Self(f()))
}
#[doc = concat!(" Attempts to get the global [`", stringify!($type), "`] instance, \

View File

@ -42,6 +42,7 @@ std = [
"bevy_reflect?/std",
"serde?/std",
"raw-window-handle/std",
"bevy_platform_support/std",
]
## Uses the `libm` maths library instead of the one provided in `std` and `core`.
@ -58,6 +59,7 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-featu
"smol_str",
], optional = true }
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false }
bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false }
# other
serde = { version = "1.0", features = [
@ -69,9 +71,6 @@ raw-window-handle = { version = "0.6", features = [
], default-features = false }
smol_str = { version = "0.2", default-features = false }
log = { version = "0.4", default-features = false }
spin = { version = "0.9.8", default-features = false, features = [
"spin_mutex",
] }
[target.'cfg(target_os = "android")'.dependencies]
android-activity = "0.6"

View File

@ -19,11 +19,7 @@ extern crate alloc;
use alloc::sync::Arc;
#[cfg(feature = "std")]
use std::sync::Mutex;
#[cfg(not(feature = "std"))]
use spin::mutex::Mutex;
use bevy_platform_support::sync::Mutex;
mod event;
mod monitor;

View File

@ -5,18 +5,13 @@
use alloc::sync::Arc;
use bevy_ecs::prelude::Component;
use bevy_platform_support::sync::Mutex;
use core::{any::Any, marker::PhantomData, ops::Deref};
use raw_window_handle::{
DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, RawDisplayHandle,
RawWindowHandle, WindowHandle,
};
#[cfg(feature = "std")]
use std::sync::Mutex;
#[cfg(not(feature = "std"))]
use spin::mutex::Mutex;
/// A wrapper over a window.
///
/// This allows us to extend the lifetime of the window, so it doesn't get eagerly dropped while a