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:
parent
dd2d84b342
commit
04990fcd27
@ -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"
|
||||
|
@ -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 {
|
||||
|
@ -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 }
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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;
|
17
crates/bevy_platform_support/src/sync/atomic.rs
Normal file
17
crates/bevy_platform_support/src/sync/atomic.rs
Normal 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;
|
66
crates/bevy_platform_support/src/sync/barrier.rs
Normal file
66
crates/bevy_platform_support/src/sync/barrier.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
11
crates/bevy_platform_support/src/sync/lazy_lock.rs
Normal file
11
crates/bevy_platform_support/src/sync/lazy_lock.rs
Normal 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;
|
||||
}
|
33
crates/bevy_platform_support/src/sync/mod.rs
Normal file
33
crates/bevy_platform_support/src/sync/mod.rs
Normal 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;
|
108
crates/bevy_platform_support/src/sync/mutex.rs
Normal file
108
crates/bevy_platform_support/src/sync/mutex.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
216
crates/bevy_platform_support/src/sync/once.rs
Normal file
216
crates/bevy_platform_support/src/sync/once.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
107
crates/bevy_platform_support/src/sync/poison.rs
Normal file
107
crates/bevy_platform_support/src/sync/poison.rs
Normal 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>>;
|
||||
}
|
124
crates/bevy_platform_support/src/sync/rwlock.rs
Normal file
124
crates/bevy_platform_support/src/sync/rwlock.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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 = [
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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",
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
@ -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, \
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user