Panic on dropping NonSend in non-origin thread. (#6534)
# Objective Fixes #3310. Fixes #6282. Fixes #6278. Fixes #3666. ## Solution Split out `!Send` resources into `NonSendResources`. Add a `origin_thread_id` to all `!Send` Resources, check it on dropping `NonSendResourceData`, if there's a mismatch, panic. Moved all of the checks that `MainThreadValidator` would do into `NonSendResources` instead. All `!Send` resources now individually track which thread they were inserted from. This is validated against for every access, mutation, and drop that could be done against the value. A regression test using an altered version of the example from #3310 has been added. This is a stopgap solution for the current status quo. A full solution may involve fully removing `!Send` resources/components from `World`, which will likely require a much more thorough design on how to handle the existing in-engine and ecosystem use cases. This PR also introduces another breaking change: ```rust use bevy_ecs::prelude::*; #[derive(Resource)] struct Resource(u32); fn main() { let mut world = World::new(); world.insert_resource(Resource(1)); world.insert_non_send_resource(Resource(2)); let res = world.get_resource_mut::<Resource>().unwrap(); assert_eq!(res.0, 2); } ``` This code will run correctly on 0.9.1 but not with this PR, since NonSend resources and normal resources have become actual distinct concepts storage wise. ## Changelog Changed: Fix soundness bug with `World: Send`. Dropping a `World` that contains a `!Send` resource on the wrong thread will now panic. ## Migration Guide Normal resources and `NonSend` resources no longer share the same backing storage. If `R: Resource`, then `NonSend<R>` and `Res<R>` will return different instances from each other. If you are using both `Res<T>` and `NonSend<T>` (or their mutable variants), to fetch the same resources, it's strongly advised to use `Res<T>`.
This commit is contained in:
parent
1b9c156479
commit
aaf384ae58
@ -1267,6 +1267,15 @@ mod tests {
|
|||||||
assert_eq!(*world.non_send_resource_mut::<i64>(), 456);
|
assert_eq!(*world.non_send_resource_mut::<i64>(), 456);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_send_resource_points_to_distinct_data() {
|
||||||
|
let mut world = World::default();
|
||||||
|
world.insert_resource(A(123));
|
||||||
|
world.insert_non_send_resource(A(456));
|
||||||
|
assert_eq!(*world.resource::<A>(), A(123));
|
||||||
|
assert_eq!(*world.non_send_resource::<A>(), A(456));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn non_send_resource_panic() {
|
fn non_send_resource_panic() {
|
||||||
@ -1406,31 +1415,18 @@ mod tests {
|
|||||||
assert_eq!(world.resource::<A>().0, 1);
|
assert_eq!(world.resource::<A>().0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn non_send_resource_scope() {
|
|
||||||
let mut world = World::default();
|
|
||||||
world.insert_non_send_resource(NonSendA::default());
|
|
||||||
world.resource_scope(|world: &mut World, mut value: Mut<NonSendA>| {
|
|
||||||
value.0 += 1;
|
|
||||||
assert!(!world.contains_resource::<NonSendA>());
|
|
||||||
});
|
|
||||||
assert_eq!(world.non_send_resource::<NonSendA>().0, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(
|
#[should_panic(
|
||||||
expected = "attempted to access NonSend resource bevy_ecs::tests::NonSendA off of the main thread"
|
expected = "Attempted to access or drop non-send resource bevy_ecs::tests::NonSendA from thread"
|
||||||
)]
|
)]
|
||||||
fn non_send_resource_scope_from_different_thread() {
|
fn non_send_resource_drop_from_different_thread() {
|
||||||
let mut world = World::default();
|
let mut world = World::default();
|
||||||
world.insert_non_send_resource(NonSendA::default());
|
world.insert_non_send_resource(NonSendA::default());
|
||||||
|
|
||||||
let thread = std::thread::spawn(move || {
|
let thread = std::thread::spawn(move || {
|
||||||
// Accessing the non-send resource on a different thread
|
// Dropping the non-send resource on a different thread
|
||||||
// Should result in a panic
|
// Should result in a panic
|
||||||
world.resource_scope(|_: &mut World, mut value: Mut<NonSendA>| {
|
drop(world);
|
||||||
value.0 += 1;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Err(err) = thread.join() {
|
if let Err(err) = thread.join() {
|
||||||
@ -1438,6 +1434,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_send_resource_drop_from_same_thread() {
|
||||||
|
let mut world = World::default();
|
||||||
|
world.insert_non_send_resource(NonSendA::default());
|
||||||
|
drop(world);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn insert_overwrite_drop() {
|
fn insert_overwrite_drop() {
|
||||||
let (dropck1, dropped1) = DropCk::new_pair();
|
let (dropck1, dropped1) = DropCk::new_pair();
|
||||||
|
@ -335,6 +335,7 @@ mod tests {
|
|||||||
fn read_only() {
|
fn read_only() {
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
world.insert_resource(R);
|
world.insert_resource(R);
|
||||||
|
world.insert_non_send_resource(R);
|
||||||
world.spawn(A);
|
world.spawn(A);
|
||||||
world.init_resource::<Events<E>>();
|
world.init_resource::<Events<E>>();
|
||||||
|
|
||||||
@ -394,6 +395,7 @@ mod tests {
|
|||||||
fn nonsend() {
|
fn nonsend() {
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
world.insert_resource(R);
|
world.insert_resource(R);
|
||||||
|
world.insert_non_send_resource(R);
|
||||||
|
|
||||||
let mut test_stage = SystemStage::parallel();
|
let mut test_stage = SystemStage::parallel();
|
||||||
test_stage
|
test_stage
|
||||||
@ -497,6 +499,7 @@ mod tests {
|
|||||||
fn ignore_all_ambiguities() {
|
fn ignore_all_ambiguities() {
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
world.insert_resource(R);
|
world.insert_resource(R);
|
||||||
|
world.insert_non_send_resource(R);
|
||||||
|
|
||||||
let mut test_stage = SystemStage::parallel();
|
let mut test_stage = SystemStage::parallel();
|
||||||
test_stage
|
test_stage
|
||||||
@ -513,6 +516,7 @@ mod tests {
|
|||||||
fn ambiguous_with_label() {
|
fn ambiguous_with_label() {
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
world.insert_resource(R);
|
world.insert_resource(R);
|
||||||
|
world.insert_non_send_resource(R);
|
||||||
|
|
||||||
#[derive(SystemLabel)]
|
#[derive(SystemLabel)]
|
||||||
struct IgnoreMe;
|
struct IgnoreMe;
|
||||||
|
@ -14,5 +14,6 @@ pub use table::*;
|
|||||||
pub struct Storages {
|
pub struct Storages {
|
||||||
pub sparse_sets: SparseSets,
|
pub sparse_sets: SparseSets,
|
||||||
pub tables: Tables,
|
pub tables: Tables,
|
||||||
pub resources: Resources,
|
pub resources: Resources<true>,
|
||||||
|
pub non_send_resources: Resources<false>,
|
||||||
}
|
}
|
||||||
|
@ -2,19 +2,61 @@ use crate::archetype::ArchetypeComponentId;
|
|||||||
use crate::component::{ComponentId, ComponentTicks, Components, TickCells};
|
use crate::component::{ComponentId, ComponentTicks, Components, TickCells};
|
||||||
use crate::storage::{Column, SparseSet, TableRow};
|
use crate::storage::{Column, SparseSet, TableRow};
|
||||||
use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref};
|
use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref};
|
||||||
|
use std::{mem::ManuallyDrop, thread::ThreadId};
|
||||||
|
|
||||||
/// The type-erased backing storage and metadata for a single resource within a [`World`].
|
/// The type-erased backing storage and metadata for a single resource within a [`World`].
|
||||||
///
|
///
|
||||||
|
/// If `SEND` is false, values of this type will panic if dropped from a different thread.
|
||||||
|
///
|
||||||
/// [`World`]: crate::world::World
|
/// [`World`]: crate::world::World
|
||||||
pub struct ResourceData {
|
pub struct ResourceData<const SEND: bool> {
|
||||||
column: Column,
|
column: ManuallyDrop<Column>,
|
||||||
|
type_name: String,
|
||||||
id: ArchetypeComponentId,
|
id: ArchetypeComponentId,
|
||||||
|
origin_thread_id: Option<ThreadId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResourceData {
|
impl<const SEND: bool> Drop for ResourceData<SEND> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.is_present() {
|
||||||
|
// If this thread is already panicking, panicking again will cause
|
||||||
|
// the entire process to abort. In this case we choose to avoid
|
||||||
|
// dropping or checking this altogether and just leak the column.
|
||||||
|
if std::thread::panicking() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.validate_access();
|
||||||
|
}
|
||||||
|
// SAFETY: Drop is only called once upon dropping the ResourceData
|
||||||
|
// and is inaccessible after this as the parent ResourceData has
|
||||||
|
// been dropped. The validate_access call above will check that the
|
||||||
|
// data is dropped on the thread it was inserted from.
|
||||||
|
unsafe {
|
||||||
|
ManuallyDrop::drop(&mut self.column);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const SEND: bool> ResourceData<SEND> {
|
||||||
/// The only row in the underlying column.
|
/// The only row in the underlying column.
|
||||||
const ROW: TableRow = TableRow::new(0);
|
const ROW: TableRow = TableRow::new(0);
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn validate_access(&self) {
|
||||||
|
if SEND {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if self.origin_thread_id != Some(std::thread::current().id()) {
|
||||||
|
// Panic in tests, as testing for aborting is nearly impossible
|
||||||
|
panic!(
|
||||||
|
"Attempted to access or drop non-send resource {} from thread {:?} on a thread {:?}. This is not allowed. Aborting.",
|
||||||
|
self.type_name,
|
||||||
|
self.origin_thread_id,
|
||||||
|
std::thread::current().id()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns true if the resource is populated.
|
/// Returns true if the resource is populated.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_present(&self) -> bool {
|
pub fn is_present(&self) -> bool {
|
||||||
@ -28,9 +70,16 @@ impl ResourceData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a read-only pointer to the underlying resource, if available.
|
/// Gets a read-only pointer to the underlying resource, if available.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// If `SEND` is false, this will panic if a value is present and is not accessed from the
|
||||||
|
/// original thread it was inserted from.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_data(&self) -> Option<Ptr<'_>> {
|
pub fn get_data(&self) -> Option<Ptr<'_>> {
|
||||||
self.column.get_data(Self::ROW)
|
self.column.get_data(Self::ROW).map(|res| {
|
||||||
|
self.validate_access();
|
||||||
|
res
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a read-only reference to the change ticks of the underlying resource, if available.
|
/// Gets a read-only reference to the change ticks of the underlying resource, if available.
|
||||||
@ -39,26 +88,35 @@ impl ResourceData {
|
|||||||
self.column.get_ticks(Self::ROW)
|
self.column.get_ticks(Self::ROW)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # Panics
|
||||||
|
/// If `SEND` is false, this will panic if a value is present and is not accessed from the
|
||||||
|
/// original thread it was inserted in.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn get_with_ticks(&self) -> Option<(Ptr<'_>, TickCells<'_>)> {
|
pub(crate) fn get_with_ticks(&self) -> Option<(Ptr<'_>, TickCells<'_>)> {
|
||||||
self.column.get(Self::ROW)
|
self.column.get(Self::ROW).map(|res| {
|
||||||
|
self.validate_access();
|
||||||
|
res
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts a value into the resource. If a value is already present
|
/// Inserts a value into the resource. If a value is already present
|
||||||
/// it will be replaced.
|
/// it will be replaced.
|
||||||
///
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// If `SEND` is false, this will panic if a value is present and is not replaced from
|
||||||
|
/// the original thread it was inserted in.
|
||||||
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// `value` must be valid for the underlying type for the resource.
|
/// - `value` must be valid for the underlying type for the resource.
|
||||||
///
|
|
||||||
/// The underlying type must be [`Send`] or be inserted from the main thread.
|
|
||||||
/// This can be validated with [`World::validate_non_send_access_untyped`].
|
|
||||||
///
|
|
||||||
/// [`World::validate_non_send_access_untyped`]: crate::world::World::validate_non_send_access_untyped
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) unsafe fn insert(&mut self, value: OwningPtr<'_>, change_tick: u32) {
|
pub(crate) unsafe fn insert(&mut self, value: OwningPtr<'_>, change_tick: u32) {
|
||||||
if self.is_present() {
|
if self.is_present() {
|
||||||
|
self.validate_access();
|
||||||
self.column.replace(Self::ROW, value, change_tick);
|
self.column.replace(Self::ROW, value, change_tick);
|
||||||
} else {
|
} else {
|
||||||
|
if !SEND {
|
||||||
|
self.origin_thread_id = Some(std::thread::current().id());
|
||||||
|
}
|
||||||
self.column.push(value, ComponentTicks::new(change_tick));
|
self.column.push(value, ComponentTicks::new(change_tick));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,13 +124,12 @@ impl ResourceData {
|
|||||||
/// Inserts a value into the resource with a pre-existing change tick. If a
|
/// Inserts a value into the resource with a pre-existing change tick. If a
|
||||||
/// value is already present it will be replaced.
|
/// value is already present it will be replaced.
|
||||||
///
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// If `SEND` is false, this will panic if a value is present and is not replaced from
|
||||||
|
/// the original thread it was inserted in.
|
||||||
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// `value` must be valid for the underlying type for the resource.
|
/// - `value` must be valid for the underlying type for the resource.
|
||||||
///
|
|
||||||
/// The underlying type must be [`Send`] or be inserted from the main thread.
|
|
||||||
/// This can be validated with [`World::validate_non_send_access_untyped`].
|
|
||||||
///
|
|
||||||
/// [`World::validate_non_send_access_untyped`]: crate::world::World::validate_non_send_access_untyped
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) unsafe fn insert_with_ticks(
|
pub(crate) unsafe fn insert_with_ticks(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -80,6 +137,7 @@ impl ResourceData {
|
|||||||
change_ticks: ComponentTicks,
|
change_ticks: ComponentTicks,
|
||||||
) {
|
) {
|
||||||
if self.is_present() {
|
if self.is_present() {
|
||||||
|
self.validate_access();
|
||||||
self.column.replace_untracked(Self::ROW, value);
|
self.column.replace_untracked(Self::ROW, value);
|
||||||
*self.column.get_added_ticks_unchecked(Self::ROW).deref_mut() = change_ticks.added;
|
*self.column.get_added_ticks_unchecked(Self::ROW).deref_mut() = change_ticks.added;
|
||||||
*self
|
*self
|
||||||
@ -87,35 +145,41 @@ impl ResourceData {
|
|||||||
.get_changed_ticks_unchecked(Self::ROW)
|
.get_changed_ticks_unchecked(Self::ROW)
|
||||||
.deref_mut() = change_ticks.changed;
|
.deref_mut() = change_ticks.changed;
|
||||||
} else {
|
} else {
|
||||||
|
if !SEND {
|
||||||
|
self.origin_thread_id = Some(std::thread::current().id());
|
||||||
|
}
|
||||||
self.column.push(value, change_ticks);
|
self.column.push(value, change_ticks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes a value from the resource, if present.
|
/// Removes a value from the resource, if present.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Panics
|
||||||
/// The underlying type must be [`Send`] or be removed from the main thread.
|
/// If `SEND` is false, this will panic if a value is present and is not removed from the
|
||||||
/// This can be validated with [`World::validate_non_send_access_untyped`].
|
/// original thread it was inserted from.
|
||||||
///
|
|
||||||
/// The removed value must be used or dropped.
|
|
||||||
///
|
|
||||||
/// [`World::validate_non_send_access_untyped`]: crate::world::World::validate_non_send_access_untyped
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use = "The returned pointer to the removed component should be used or dropped"]
|
#[must_use = "The returned pointer to the removed component should be used or dropped"]
|
||||||
pub(crate) unsafe fn remove(&mut self) -> Option<(OwningPtr<'_>, ComponentTicks)> {
|
pub(crate) fn remove(&mut self) -> Option<(OwningPtr<'_>, ComponentTicks)> {
|
||||||
self.column.swap_remove_and_forget(Self::ROW)
|
if SEND {
|
||||||
|
self.column.swap_remove_and_forget(Self::ROW)
|
||||||
|
} else {
|
||||||
|
self.is_present()
|
||||||
|
.then(|| self.validate_access())
|
||||||
|
.and_then(|_| self.column.swap_remove_and_forget(Self::ROW))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes a value from the resource, if present, and drops it.
|
/// Removes a value from the resource, if present, and drops it.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Panics
|
||||||
/// The underlying type must be [`Send`] or be removed from the main thread.
|
/// If `SEND` is false, this will panic if a value is present and is not
|
||||||
/// This can be validated with [`World::validate_non_send_access_untyped`].
|
/// accessed from the original thread it was inserted in.
|
||||||
///
|
|
||||||
/// [`World::validate_non_send_access_untyped`]: crate::world::World::validate_non_send_access_untyped
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) unsafe fn remove_and_drop(&mut self) {
|
pub(crate) fn remove_and_drop(&mut self) {
|
||||||
self.column.clear();
|
if self.is_present() {
|
||||||
|
self.validate_access();
|
||||||
|
self.column.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,11 +188,11 @@ impl ResourceData {
|
|||||||
/// [`Resource`]: crate::system::Resource
|
/// [`Resource`]: crate::system::Resource
|
||||||
/// [`World`]: crate::world::World
|
/// [`World`]: crate::world::World
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Resources {
|
pub struct Resources<const SEND: bool> {
|
||||||
resources: SparseSet<ComponentId, ResourceData>,
|
resources: SparseSet<ComponentId, ResourceData<SEND>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Resources {
|
impl<const SEND: bool> Resources<SEND> {
|
||||||
/// The total number of resources stored in the [`World`]
|
/// The total number of resources stored in the [`World`]
|
||||||
///
|
///
|
||||||
/// [`World`]: crate::world::World
|
/// [`World`]: crate::world::World
|
||||||
@ -138,7 +202,7 @@ impl Resources {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Iterate over all resources that have been initialized, i.e. given a [`ComponentId`]
|
/// Iterate over all resources that have been initialized, i.e. given a [`ComponentId`]
|
||||||
pub fn iter(&self) -> impl Iterator<Item = (ComponentId, &ResourceData)> {
|
pub fn iter(&self) -> impl Iterator<Item = (ComponentId, &ResourceData<SEND>)> {
|
||||||
self.resources.iter().map(|(id, data)| (*id, data))
|
self.resources.iter().map(|(id, data)| (*id, data))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,13 +217,13 @@ impl Resources {
|
|||||||
|
|
||||||
/// Gets read-only access to a resource, if it exists.
|
/// Gets read-only access to a resource, if it exists.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get(&self, component_id: ComponentId) -> Option<&ResourceData> {
|
pub fn get(&self, component_id: ComponentId) -> Option<&ResourceData<SEND>> {
|
||||||
self.resources.get(component_id)
|
self.resources.get(component_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets mutable access to a resource, if it exists.
|
/// Gets mutable access to a resource, if it exists.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn get_mut(&mut self, component_id: ComponentId) -> Option<&mut ResourceData> {
|
pub(crate) fn get_mut(&mut self, component_id: ComponentId) -> Option<&mut ResourceData<SEND>> {
|
||||||
self.resources.get_mut(component_id)
|
self.resources.get_mut(component_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,17 +231,23 @@ impl Resources {
|
|||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
/// Will panic if `component_id` is not valid for the provided `components`
|
/// Will panic if `component_id` is not valid for the provided `components`
|
||||||
|
/// If `SEND` is false, this will panic if `component_id`'s `ComponentInfo` is not registered as being `Send` + `Sync`.
|
||||||
pub(crate) fn initialize_with(
|
pub(crate) fn initialize_with(
|
||||||
&mut self,
|
&mut self,
|
||||||
component_id: ComponentId,
|
component_id: ComponentId,
|
||||||
components: &Components,
|
components: &Components,
|
||||||
f: impl FnOnce() -> ArchetypeComponentId,
|
f: impl FnOnce() -> ArchetypeComponentId,
|
||||||
) -> &mut ResourceData {
|
) -> &mut ResourceData<SEND> {
|
||||||
self.resources.get_or_insert_with(component_id, || {
|
self.resources.get_or_insert_with(component_id, || {
|
||||||
let component_info = components.get_info(component_id).unwrap();
|
let component_info = components.get_info(component_id).unwrap();
|
||||||
|
if SEND {
|
||||||
|
assert!(component_info.is_send_and_sync());
|
||||||
|
}
|
||||||
ResourceData {
|
ResourceData {
|
||||||
column: Column::with_capacity(component_info, 1),
|
column: ManuallyDrop::new(Column::with_capacity(component_info, 1)),
|
||||||
|
type_name: String::from(component_info.name()),
|
||||||
id: f(),
|
id: f(),
|
||||||
|
origin_thread_id: None,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1038,7 +1038,7 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> {
|
|||||||
.add_unfiltered_read(component_id);
|
.add_unfiltered_read(component_id);
|
||||||
|
|
||||||
let archetype_component_id = world
|
let archetype_component_id = world
|
||||||
.get_resource_archetype_component_id(component_id)
|
.get_non_send_archetype_component_id(component_id)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
system_meta
|
system_meta
|
||||||
.archetype_component_access
|
.archetype_component_access
|
||||||
@ -1054,9 +1054,8 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> {
|
|||||||
world: &'w World,
|
world: &'w World,
|
||||||
change_tick: u32,
|
change_tick: u32,
|
||||||
) -> Self::Item<'w, 's> {
|
) -> Self::Item<'w, 's> {
|
||||||
world.validate_non_send_access::<T>();
|
|
||||||
let (ptr, ticks) = world
|
let (ptr, ticks) = world
|
||||||
.get_resource_with_ticks(component_id)
|
.get_non_send_with_ticks(component_id)
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
panic!(
|
panic!(
|
||||||
"Non-send resource requested by {} does not exist: {}",
|
"Non-send resource requested by {} does not exist: {}",
|
||||||
@ -1090,9 +1089,8 @@ unsafe impl<T: 'static> SystemParam for Option<NonSend<'_, T>> {
|
|||||||
world: &'w World,
|
world: &'w World,
|
||||||
change_tick: u32,
|
change_tick: u32,
|
||||||
) -> Self::Item<'w, 's> {
|
) -> Self::Item<'w, 's> {
|
||||||
world.validate_non_send_access::<T>();
|
|
||||||
world
|
world
|
||||||
.get_resource_with_ticks(component_id)
|
.get_non_send_with_ticks(component_id)
|
||||||
.map(|(ptr, ticks)| NonSend {
|
.map(|(ptr, ticks)| NonSend {
|
||||||
value: ptr.deref(),
|
value: ptr.deref(),
|
||||||
ticks: ticks.read(),
|
ticks: ticks.read(),
|
||||||
@ -1130,7 +1128,7 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> {
|
|||||||
.add_unfiltered_write(component_id);
|
.add_unfiltered_write(component_id);
|
||||||
|
|
||||||
let archetype_component_id = world
|
let archetype_component_id = world
|
||||||
.get_resource_archetype_component_id(component_id)
|
.get_non_send_archetype_component_id(component_id)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
system_meta
|
system_meta
|
||||||
.archetype_component_access
|
.archetype_component_access
|
||||||
@ -1146,9 +1144,8 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> {
|
|||||||
world: &'w World,
|
world: &'w World,
|
||||||
change_tick: u32,
|
change_tick: u32,
|
||||||
) -> Self::Item<'w, 's> {
|
) -> Self::Item<'w, 's> {
|
||||||
world.validate_non_send_access::<T>();
|
|
||||||
let (ptr, ticks) = world
|
let (ptr, ticks) = world
|
||||||
.get_resource_with_ticks(component_id)
|
.get_non_send_with_ticks(component_id)
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
panic!(
|
panic!(
|
||||||
"Non-send resource requested by {} does not exist: {}",
|
"Non-send resource requested by {} does not exist: {}",
|
||||||
@ -1179,9 +1176,8 @@ unsafe impl<'a, T: 'static> SystemParam for Option<NonSendMut<'a, T>> {
|
|||||||
world: &'w World,
|
world: &'w World,
|
||||||
change_tick: u32,
|
change_tick: u32,
|
||||||
) -> Self::Item<'w, 's> {
|
) -> Self::Item<'w, 's> {
|
||||||
world.validate_non_send_access::<T>();
|
|
||||||
world
|
world
|
||||||
.get_resource_with_ticks(component_id)
|
.get_non_send_with_ticks(component_id)
|
||||||
.map(|(ptr, ticks)| NonSendMut {
|
.map(|(ptr, ticks)| NonSendMut {
|
||||||
value: ptr.assert_unique().deref_mut(),
|
value: ptr.assert_unique().deref_mut(),
|
||||||
ticks: Ticks::from_tick_cells(ticks, system_meta.last_change_tick, change_tick),
|
ticks: Ticks::from_tick_cells(ticks, system_meta.last_change_tick, change_tick),
|
||||||
|
@ -16,6 +16,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation},
|
entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation},
|
||||||
event::{Event, Events},
|
event::{Event, Events},
|
||||||
|
ptr::UnsafeCellDeref,
|
||||||
query::{QueryState, ReadOnlyWorldQuery, WorldQuery},
|
query::{QueryState, ReadOnlyWorldQuery, WorldQuery},
|
||||||
storage::{ResourceData, SparseSet, Storages},
|
storage::{ResourceData, SparseSet, Storages},
|
||||||
system::Resource,
|
system::Resource,
|
||||||
@ -60,7 +61,6 @@ pub struct World {
|
|||||||
pub(crate) removed_components: SparseSet<ComponentId, Vec<Entity>>,
|
pub(crate) removed_components: SparseSet<ComponentId, Vec<Entity>>,
|
||||||
/// Access cache used by [WorldCell].
|
/// Access cache used by [WorldCell].
|
||||||
pub(crate) archetype_component_access: ArchetypeComponentAccess,
|
pub(crate) archetype_component_access: ArchetypeComponentAccess,
|
||||||
main_thread_validator: MainThreadValidator,
|
|
||||||
pub(crate) change_tick: AtomicU32,
|
pub(crate) change_tick: AtomicU32,
|
||||||
pub(crate) last_change_tick: u32,
|
pub(crate) last_change_tick: u32,
|
||||||
}
|
}
|
||||||
@ -76,7 +76,6 @@ impl Default for World {
|
|||||||
bundles: Default::default(),
|
bundles: Default::default(),
|
||||||
removed_components: Default::default(),
|
removed_components: Default::default(),
|
||||||
archetype_component_access: Default::default(),
|
archetype_component_access: Default::default(),
|
||||||
main_thread_validator: Default::default(),
|
|
||||||
// Default value is `1`, and `last_change_tick`s default to `0`, such that changes
|
// Default value is `1`, and `last_change_tick`s default to `0`, such that changes
|
||||||
// are detected on first system runs and for direct world queries.
|
// are detected on first system runs and for direct world queries.
|
||||||
change_tick: AtomicU32::new(1),
|
change_tick: AtomicU32::new(1),
|
||||||
@ -803,7 +802,7 @@ impl World {
|
|||||||
/// Panics if called from a thread other than the main thread.
|
/// Panics if called from a thread other than the main thread.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn init_non_send_resource<R: 'static + FromWorld>(&mut self) {
|
pub fn init_non_send_resource<R: 'static + FromWorld>(&mut self) {
|
||||||
if !self.contains_resource::<R>() {
|
if !self.contains_non_send::<R>() {
|
||||||
let resource = R::from_world(self);
|
let resource = R::from_world(self);
|
||||||
self.insert_non_send_resource(resource);
|
self.insert_non_send_resource(resource);
|
||||||
}
|
}
|
||||||
@ -816,16 +815,15 @@ impl World {
|
|||||||
/// Systems with `NonSend` resources are always scheduled on the main thread.
|
/// Systems with `NonSend` resources are always scheduled on the main thread.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
/// If a value is already present, this function will panic if called
|
||||||
/// Panics if called from a thread other than the main thread.
|
/// from a different thread than where the original value was inserted from.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn insert_non_send_resource<R: 'static>(&mut self, value: R) {
|
pub fn insert_non_send_resource<R: 'static>(&mut self, value: R) {
|
||||||
self.validate_non_send_access::<R>();
|
|
||||||
let component_id = self.components.init_non_send::<R>();
|
let component_id = self.components.init_non_send::<R>();
|
||||||
OwningPtr::make(value, |ptr| {
|
OwningPtr::make(value, |ptr| {
|
||||||
// SAFETY: component_id was just initialized and corresponds to resource of type R
|
// SAFETY: component_id was just initialized and corresponds to resource of type R
|
||||||
unsafe {
|
unsafe {
|
||||||
self.insert_resource_by_id(component_id, ptr);
|
self.insert_non_send_by_id(component_id, ptr);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -833,34 +831,38 @@ impl World {
|
|||||||
/// Removes the resource of a given type and returns it, if it exists. Otherwise returns [None].
|
/// Removes the resource of a given type and returns it, if it exists. Otherwise returns [None].
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn remove_resource<R: Resource>(&mut self) -> Option<R> {
|
pub fn remove_resource<R: Resource>(&mut self) -> Option<R> {
|
||||||
// SAFETY: R is Send + Sync
|
let component_id = self.components.get_resource_id(TypeId::of::<R>())?;
|
||||||
unsafe { self.remove_resource_unchecked() }
|
let (ptr, _) = self.storages.resources.get_mut(component_id)?.remove()?;
|
||||||
|
// SAFETY: `component_id` was gotten via looking up the `R` type
|
||||||
|
unsafe { Some(ptr.read::<R>()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Removes a `!Send` resource from the world and returns it, if present.
|
||||||
|
///
|
||||||
|
/// `NonSend` resources cannot be sent across threads,
|
||||||
|
/// and do not need the `Send + Sync` bounds.
|
||||||
|
/// Systems with `NonSend` resources are always scheduled on the main thread.
|
||||||
|
///
|
||||||
|
/// Returns `None` if a value was not previously present.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// If a value is present, this function will panic if called from a different
|
||||||
|
/// thread than where the value was inserted from.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn remove_non_send_resource<R: 'static>(&mut self) -> Option<R> {
|
pub fn remove_non_send_resource<R: 'static>(&mut self) -> Option<R> {
|
||||||
self.validate_non_send_access::<R>();
|
|
||||||
// SAFETY: we are on main thread
|
|
||||||
unsafe { self.remove_resource_unchecked() }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
/// # Safety
|
|
||||||
/// Only remove `NonSend` resources from the main thread
|
|
||||||
/// as they cannot be sent across threads
|
|
||||||
#[allow(unused_unsafe)]
|
|
||||||
pub unsafe fn remove_resource_unchecked<R: 'static>(&mut self) -> Option<R> {
|
|
||||||
let component_id = self.components.get_resource_id(TypeId::of::<R>())?;
|
let component_id = self.components.get_resource_id(TypeId::of::<R>())?;
|
||||||
// SAFETY: the resource is of type R and the value is returned back to the caller.
|
let (ptr, _) = self
|
||||||
unsafe {
|
.storages
|
||||||
let (ptr, _) = self.storages.resources.get_mut(component_id)?.remove()?;
|
.non_send_resources
|
||||||
Some(ptr.read::<R>())
|
.get_mut(component_id)?
|
||||||
}
|
.remove()?;
|
||||||
|
// SAFETY: `component_id` was gotten via looking up the `R` type
|
||||||
|
unsafe { Some(ptr.read::<R>()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if a resource of type `R` exists. Otherwise returns `false`.
|
/// Returns `true` if a resource of type `R` exists. Otherwise returns `false`.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn contains_resource<R: 'static>(&self) -> bool {
|
pub fn contains_resource<R: Resource>(&self) -> bool {
|
||||||
self.components
|
self.components
|
||||||
.get_resource_id(TypeId::of::<R>())
|
.get_resource_id(TypeId::of::<R>())
|
||||||
.and_then(|component_id| self.storages.resources.get(component_id))
|
.and_then(|component_id| self.storages.resources.get(component_id))
|
||||||
@ -868,6 +870,16 @@ impl World {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if a resource of type `R` exists. Otherwise returns `false`.
|
||||||
|
#[inline]
|
||||||
|
pub fn contains_non_send<R: 'static>(&self) -> bool {
|
||||||
|
self.components
|
||||||
|
.get_resource_id(TypeId::of::<R>())
|
||||||
|
.and_then(|component_id| self.storages.non_send_resources.get(component_id))
|
||||||
|
.map(|info| info.is_present())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_resource_added<R: Resource>(&self) -> bool {
|
pub fn is_resource_added<R: Resource>(&self) -> bool {
|
||||||
self.components
|
self.components
|
||||||
.get_resource_id(TypeId::of::<R>())
|
.get_resource_id(TypeId::of::<R>())
|
||||||
@ -979,6 +991,8 @@ impl World {
|
|||||||
///
|
///
|
||||||
/// Panics if the resource does not exist.
|
/// Panics if the resource does not exist.
|
||||||
/// Use [`get_non_send_resource`](World::get_non_send_resource) instead if you want to handle this case.
|
/// Use [`get_non_send_resource`](World::get_non_send_resource) instead if you want to handle this case.
|
||||||
|
///
|
||||||
|
/// This function will panic if it isn't called from the same thread that the resource was inserted from.
|
||||||
#[inline]
|
#[inline]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn non_send_resource<R: 'static>(&self) -> &R {
|
pub fn non_send_resource<R: 'static>(&self) -> &R {
|
||||||
@ -999,6 +1013,8 @@ impl World {
|
|||||||
///
|
///
|
||||||
/// Panics if the resource does not exist.
|
/// Panics if the resource does not exist.
|
||||||
/// Use [`get_non_send_resource_mut`](World::get_non_send_resource_mut) instead if you want to handle this case.
|
/// Use [`get_non_send_resource_mut`](World::get_non_send_resource_mut) instead if you want to handle this case.
|
||||||
|
///
|
||||||
|
/// This function will panic if it isn't called from the same thread that the resource was inserted from.
|
||||||
#[inline]
|
#[inline]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn non_send_resource_mut<R: 'static>(&mut self) -> Mut<'_, R> {
|
pub fn non_send_resource_mut<R: 'static>(&mut self) -> Mut<'_, R> {
|
||||||
@ -1014,7 +1030,10 @@ impl World {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a reference to the non-send resource of the given type, if it exists.
|
/// Gets a reference to the non-send resource of the given type, if it exists.
|
||||||
/// Otherwise returns [None]
|
/// Otherwise returns [None].
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// This function will panic if it isn't called from the same thread that the resource was inserted from.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_non_send_resource<R: 'static>(&self) -> Option<&R> {
|
pub fn get_non_send_resource<R: 'static>(&self) -> Option<&R> {
|
||||||
let component_id = self.components.get_resource_id(TypeId::of::<R>())?;
|
let component_id = self.components.get_resource_id(TypeId::of::<R>())?;
|
||||||
@ -1024,6 +1043,9 @@ impl World {
|
|||||||
|
|
||||||
/// Gets a mutable reference to the non-send resource of the given type, if it exists.
|
/// Gets a mutable reference to the non-send resource of the given type, if it exists.
|
||||||
/// Otherwise returns [None]
|
/// Otherwise returns [None]
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// This function will panic if it isn't called from the same thread that the resource was inserted from.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_non_send_resource_mut<R: 'static>(&mut self) -> Option<Mut<'_, R>> {
|
pub fn get_non_send_resource_mut<R: 'static>(&mut self) -> Option<Mut<'_, R>> {
|
||||||
// SAFETY: unique world access
|
// SAFETY: unique world access
|
||||||
@ -1033,6 +1055,9 @@ impl World {
|
|||||||
/// Gets a mutable reference to the non-send resource of the given type, if it exists.
|
/// Gets a mutable reference to the non-send resource of the given type, if it exists.
|
||||||
/// Otherwise returns [None]
|
/// Otherwise returns [None]
|
||||||
///
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// This function will panic if it isn't called from the same thread that the resource was inserted from.
|
||||||
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// This will allow aliased mutable access to the given non-send resource type. The caller must
|
/// This will allow aliased mutable access to the given non-send resource type. The caller must
|
||||||
/// ensure that there is either only one mutable access or multiple immutable accesses at a time.
|
/// ensure that there is either only one mutable access or multiple immutable accesses at a time.
|
||||||
@ -1051,6 +1076,21 @@ impl World {
|
|||||||
self.storages.resources.get(component_id)?.get_with_ticks()
|
self.storages.resources.get(component_id)?.get_with_ticks()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shorthand helper function for getting the data and change ticks for a resource.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// This function will panic if it isn't called from the same thread that the resource was inserted from.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn get_non_send_with_ticks(
|
||||||
|
&self,
|
||||||
|
component_id: ComponentId,
|
||||||
|
) -> Option<(Ptr<'_>, TickCells<'_>)> {
|
||||||
|
self.storages
|
||||||
|
.non_send_resources
|
||||||
|
.get(component_id)?
|
||||||
|
.get_with_ticks()
|
||||||
|
}
|
||||||
|
|
||||||
// Shorthand helper function for getting the [`ArchetypeComponentId`] for a resource.
|
// Shorthand helper function for getting the [`ArchetypeComponentId`] for a resource.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn get_resource_archetype_component_id(
|
pub(crate) fn get_resource_archetype_component_id(
|
||||||
@ -1061,6 +1101,16 @@ impl World {
|
|||||||
Some(resource.id())
|
Some(resource.id())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shorthand helper function for getting the [`ArchetypeComponentId`] for a resource.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn get_non_send_archetype_component_id(
|
||||||
|
&self,
|
||||||
|
component_id: ComponentId,
|
||||||
|
) -> Option<ArchetypeComponentId> {
|
||||||
|
let resource = self.storages.non_send_resources.get(component_id)?;
|
||||||
|
Some(resource.id())
|
||||||
|
}
|
||||||
|
|
||||||
/// For a given batch of ([Entity], [Bundle]) pairs, either spawns each [Entity] with the given
|
/// For a given batch of ([Entity], [Bundle]) pairs, either spawns each [Entity] with the given
|
||||||
/// bundle (if the entity does not exist), or inserts the [Bundle] (if the entity already exists).
|
/// bundle (if the entity does not exist), or inserts the [Bundle] (if the entity already exists).
|
||||||
/// This is faster than doing equivalent operations one-by-one.
|
/// This is faster than doing equivalent operations one-by-one.
|
||||||
@ -1207,13 +1257,7 @@ impl World {
|
|||||||
/// });
|
/// });
|
||||||
/// assert_eq!(world.get_resource::<A>().unwrap().0, 2);
|
/// assert_eq!(world.get_resource::<A>().unwrap().0, 2);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn resource_scope<
|
pub fn resource_scope<R: Resource, U>(&mut self, f: impl FnOnce(&mut World, Mut<R>) -> U) -> U {
|
||||||
R: 'static, /* The resource doesn't need to be Send nor Sync. */
|
|
||||||
U,
|
|
||||||
>(
|
|
||||||
&mut self,
|
|
||||||
f: impl FnOnce(&mut World, Mut<R>) -> U,
|
|
||||||
) -> U {
|
|
||||||
let last_change_tick = self.last_change_tick();
|
let last_change_tick = self.last_change_tick();
|
||||||
let change_tick = self.change_tick();
|
let change_tick = self.change_tick();
|
||||||
|
|
||||||
@ -1222,17 +1266,11 @@ impl World {
|
|||||||
.get_resource_id(TypeId::of::<R>())
|
.get_resource_id(TypeId::of::<R>())
|
||||||
.unwrap_or_else(|| panic!("resource does not exist: {}", std::any::type_name::<R>()));
|
.unwrap_or_else(|| panic!("resource does not exist: {}", std::any::type_name::<R>()));
|
||||||
// If the resource isn't send and sync, validate that we are on the main thread, so that we can access it.
|
// If the resource isn't send and sync, validate that we are on the main thread, so that we can access it.
|
||||||
let component_info = self.components().get_info(component_id).unwrap();
|
|
||||||
if !component_info.is_send_and_sync() {
|
|
||||||
self.validate_non_send_access::<R>();
|
|
||||||
}
|
|
||||||
|
|
||||||
let (ptr, mut ticks) = self
|
let (ptr, mut ticks) = self
|
||||||
.storages
|
.storages
|
||||||
.resources
|
.resources
|
||||||
.get_mut(component_id)
|
.get_mut(component_id)
|
||||||
// SAFETY: The type R is Send and Sync or we've already validated that we're on the main thread.
|
.and_then(|info| info.remove())
|
||||||
.and_then(|info| unsafe { info.remove() })
|
|
||||||
.unwrap_or_else(|| panic!("resource does not exist: {}", std::any::type_name::<R>()));
|
.unwrap_or_else(|| panic!("resource does not exist: {}", std::any::type_name::<R>()));
|
||||||
// Read the value onto the stack to avoid potential mut aliasing.
|
// Read the value onto the stack to avoid potential mut aliasing.
|
||||||
// SAFETY: pointer is of type R
|
// SAFETY: pointer is of type R
|
||||||
@ -1331,8 +1369,13 @@ impl World {
|
|||||||
&self,
|
&self,
|
||||||
component_id: ComponentId,
|
component_id: ComponentId,
|
||||||
) -> Option<&R> {
|
) -> Option<&R> {
|
||||||
self.validate_non_send_access::<R>();
|
Some(
|
||||||
self.get_resource_with_id(component_id)
|
self.storages
|
||||||
|
.non_send_resources
|
||||||
|
.get(component_id)?
|
||||||
|
.get_data()?
|
||||||
|
.deref::<R>(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
@ -1343,8 +1386,20 @@ impl World {
|
|||||||
&self,
|
&self,
|
||||||
component_id: ComponentId,
|
component_id: ComponentId,
|
||||||
) -> Option<Mut<'_, R>> {
|
) -> Option<Mut<'_, R>> {
|
||||||
self.validate_non_send_access::<R>();
|
let (ptr, ticks) = self
|
||||||
self.get_resource_unchecked_mut_with_id(component_id)
|
.storages
|
||||||
|
.non_send_resources
|
||||||
|
.get(component_id)?
|
||||||
|
.get_with_ticks()?;
|
||||||
|
Some(Mut {
|
||||||
|
value: ptr.assert_unique().deref_mut(),
|
||||||
|
ticks: Ticks {
|
||||||
|
added: ticks.added.deref_mut(),
|
||||||
|
changed: ticks.changed.deref_mut(),
|
||||||
|
last_change_tick: self.last_change_tick(),
|
||||||
|
change_tick: self.read_change_tick(),
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts a new resource with the given `value`. Will replace the value if it already existed.
|
/// Inserts a new resource with the given `value`. Will replace the value if it already existed.
|
||||||
@ -1353,8 +1408,7 @@ impl World {
|
|||||||
/// use this in cases where the actual types are not known at compile time.**
|
/// use this in cases where the actual types are not known at compile time.**
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// The value referenced by `value` must be valid for the given [`ComponentId`] of this world
|
/// The value referenced by `value` must be valid for the given [`ComponentId`] of this world.
|
||||||
/// `component_id` must exist in this [`World`]
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub unsafe fn insert_resource_by_id(
|
pub unsafe fn insert_resource_by_id(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -1363,18 +1417,43 @@ impl World {
|
|||||||
) {
|
) {
|
||||||
let change_tick = self.change_tick();
|
let change_tick = self.change_tick();
|
||||||
|
|
||||||
// SAFETY: component_id is valid, ensured by caller
|
// SAFETY: value is valid for component_id, ensured by caller
|
||||||
self.initialize_resource_internal(component_id)
|
self.initialize_resource_internal(component_id)
|
||||||
.insert(value, change_tick);
|
.insert(value, change_tick);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inserts a new `!Send` resource with the given `value`. Will replace the value if it already
|
||||||
|
/// existed.
|
||||||
|
///
|
||||||
|
/// **You should prefer to use the typed API [`World::insert_non_send_resource`] where possible and only
|
||||||
|
/// use this in cases where the actual types are not known at compile time.**
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// If a value is already present, this function will panic if not called from the same
|
||||||
|
/// thread that the original value was inserted from.
|
||||||
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// `component_id` must be valid for this world
|
/// The value referenced by `value` must be valid for the given [`ComponentId`] of this world.
|
||||||
#[inline]
|
#[inline]
|
||||||
unsafe fn initialize_resource_internal(
|
pub unsafe fn insert_non_send_by_id(
|
||||||
&mut self,
|
&mut self,
|
||||||
component_id: ComponentId,
|
component_id: ComponentId,
|
||||||
) -> &mut ResourceData {
|
value: OwningPtr<'_>,
|
||||||
|
) {
|
||||||
|
let change_tick = self.change_tick();
|
||||||
|
|
||||||
|
// SAFETY: value is valid for component_id, ensured by caller
|
||||||
|
self.initialize_non_send_internal(component_id)
|
||||||
|
.insert(value, change_tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Panics
|
||||||
|
/// Panics if `component_id` is not registered as a `Send` component type in this `World`
|
||||||
|
#[inline]
|
||||||
|
fn initialize_resource_internal(
|
||||||
|
&mut self,
|
||||||
|
component_id: ComponentId,
|
||||||
|
) -> &mut ResourceData<true> {
|
||||||
let archetype_component_count = &mut self.archetypes.archetype_component_count;
|
let archetype_component_count = &mut self.archetypes.archetype_component_count;
|
||||||
self.storages
|
self.storages
|
||||||
.resources
|
.resources
|
||||||
@ -1385,36 +1464,35 @@ impl World {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # Panics
|
||||||
|
/// panics if `component_id` is not registered in this world
|
||||||
|
#[inline]
|
||||||
|
fn initialize_non_send_internal(
|
||||||
|
&mut self,
|
||||||
|
component_id: ComponentId,
|
||||||
|
) -> &mut ResourceData<false> {
|
||||||
|
let archetype_component_count = &mut self.archetypes.archetype_component_count;
|
||||||
|
self.storages
|
||||||
|
.non_send_resources
|
||||||
|
.initialize_with(component_id, &self.components, || {
|
||||||
|
let id = ArchetypeComponentId::new(*archetype_component_count);
|
||||||
|
*archetype_component_count += 1;
|
||||||
|
id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn initialize_resource<R: Resource>(&mut self) -> ComponentId {
|
pub(crate) fn initialize_resource<R: Resource>(&mut self) -> ComponentId {
|
||||||
let component_id = self.components.init_resource::<R>();
|
let component_id = self.components.init_resource::<R>();
|
||||||
// SAFETY: resource initialized above
|
self.initialize_resource_internal(component_id);
|
||||||
unsafe { self.initialize_resource_internal(component_id) };
|
|
||||||
component_id
|
component_id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn initialize_non_send_resource<R: 'static>(&mut self) -> ComponentId {
|
pub(crate) fn initialize_non_send_resource<R: 'static>(&mut self) -> ComponentId {
|
||||||
let component_id = self.components.init_non_send::<R>();
|
let component_id = self.components.init_non_send::<R>();
|
||||||
// SAFETY: resource initialized above
|
self.initialize_non_send_internal(component_id);
|
||||||
unsafe { self.initialize_resource_internal(component_id) };
|
|
||||||
component_id
|
component_id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn validate_non_send_access<T: 'static>(&self) {
|
|
||||||
assert!(
|
|
||||||
self.main_thread_validator.is_main_thread(),
|
|
||||||
"attempted to access NonSend resource {} off of the main thread",
|
|
||||||
std::any::type_name::<T>(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn validate_non_send_access_untyped(&self, name: &str) {
|
|
||||||
assert!(
|
|
||||||
self.main_thread_validator.is_main_thread(),
|
|
||||||
"attempted to access NonSend resource {} off of the main thread",
|
|
||||||
name
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Empties queued entities and adds them to the empty [Archetype](crate::archetype::Archetype).
|
/// Empties queued entities and adds them to the empty [Archetype](crate::archetype::Archetype).
|
||||||
/// This should be called before doing operations that might operate on queued entities,
|
/// This should be called before doing operations that might operate on queued entities,
|
||||||
/// such as inserting a [Component].
|
/// such as inserting a [Component].
|
||||||
@ -1464,9 +1542,16 @@ impl World {
|
|||||||
// Iterate over all component change ticks, clamping their age to max age
|
// Iterate over all component change ticks, clamping their age to max age
|
||||||
// PERF: parallelize
|
// PERF: parallelize
|
||||||
let change_tick = self.change_tick();
|
let change_tick = self.change_tick();
|
||||||
self.storages.tables.check_change_ticks(change_tick);
|
let Storages {
|
||||||
self.storages.sparse_sets.check_change_ticks(change_tick);
|
ref mut tables,
|
||||||
self.storages.resources.check_change_ticks(change_tick);
|
ref mut sparse_sets,
|
||||||
|
ref mut resources,
|
||||||
|
ref mut non_send_resources,
|
||||||
|
} = self.storages;
|
||||||
|
tables.check_change_ticks(change_tick);
|
||||||
|
sparse_sets.check_change_ticks(change_tick);
|
||||||
|
resources.check_change_ticks(change_tick);
|
||||||
|
non_send_resources.check_change_ticks(change_tick);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_entities(&mut self) {
|
pub fn clear_entities(&mut self) {
|
||||||
@ -1486,10 +1571,6 @@ impl World {
|
|||||||
/// use this in cases where the actual types are not known at compile time.**
|
/// use this in cases where the actual types are not known at compile time.**
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_resource_by_id(&self, component_id: ComponentId) -> Option<Ptr<'_>> {
|
pub fn get_resource_by_id(&self, component_id: ComponentId) -> Option<Ptr<'_>> {
|
||||||
let info = self.components.get_info(component_id)?;
|
|
||||||
if !info.is_send_and_sync() {
|
|
||||||
self.validate_non_send_access_untyped(info.name());
|
|
||||||
}
|
|
||||||
self.storages.resources.get(component_id)?.get_data()
|
self.storages.resources.get(component_id)?.get_data()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1501,13 +1582,7 @@ impl World {
|
|||||||
/// use this in cases where the actual types are not known at compile time.**
|
/// use this in cases where the actual types are not known at compile time.**
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_resource_mut_by_id(&mut self, component_id: ComponentId) -> Option<MutUntyped<'_>> {
|
pub fn get_resource_mut_by_id(&mut self, component_id: ComponentId) -> Option<MutUntyped<'_>> {
|
||||||
let info = self.components.get_info(component_id)?;
|
|
||||||
if !info.is_send_and_sync() {
|
|
||||||
self.validate_non_send_access_untyped(info.name());
|
|
||||||
}
|
|
||||||
|
|
||||||
let change_tick = self.change_tick();
|
let change_tick = self.change_tick();
|
||||||
|
|
||||||
let (ptr, ticks) = self.get_resource_with_ticks(component_id)?;
|
let (ptr, ticks) = self.get_resource_with_ticks(component_id)?;
|
||||||
|
|
||||||
// SAFETY: This function has exclusive access to the world so nothing aliases `ticks`.
|
// SAFETY: This function has exclusive access to the world so nothing aliases `ticks`.
|
||||||
@ -1522,22 +1597,73 @@ impl World {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists.
|
||||||
|
/// The returned pointer must not be used to modify the resource, and must not be
|
||||||
|
/// dereferenced after the immutable borrow of the [`World`] ends.
|
||||||
|
///
|
||||||
|
/// **You should prefer to use the typed API [`World::get_resource`] where possible and only
|
||||||
|
/// use this in cases where the actual types are not known at compile time.**
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// This function will panic if it isn't called from the same thread that the resource was inserted from.
|
||||||
|
#[inline]
|
||||||
|
pub fn get_non_send_by_id(&self, component_id: ComponentId) -> Option<Ptr<'_>> {
|
||||||
|
self.storages
|
||||||
|
.non_send_resources
|
||||||
|
.get(component_id)?
|
||||||
|
.get_data()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists.
|
||||||
|
/// The returned pointer may be used to modify the resource, as long as the mutable borrow
|
||||||
|
/// of the [`World`] is still valid.
|
||||||
|
///
|
||||||
|
/// **You should prefer to use the typed API [`World::get_resource_mut`] where possible and only
|
||||||
|
/// use this in cases where the actual types are not known at compile time.**
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// This function will panic if it isn't called from the same thread that the resource was inserted from.
|
||||||
|
#[inline]
|
||||||
|
pub fn get_non_send_mut_by_id(&mut self, component_id: ComponentId) -> Option<MutUntyped<'_>> {
|
||||||
|
let change_tick = self.change_tick();
|
||||||
|
let (ptr, ticks) = self.get_non_send_with_ticks(component_id)?;
|
||||||
|
|
||||||
|
// SAFETY: This function has exclusive access to the world so nothing aliases `ticks`.
|
||||||
|
// - index is in-bounds because the column is initialized and non-empty
|
||||||
|
// - no other reference to the ticks of the same row can exist at the same time
|
||||||
|
let ticks = unsafe { Ticks::from_tick_cells(ticks, self.last_change_tick(), change_tick) };
|
||||||
|
|
||||||
|
Some(MutUntyped {
|
||||||
|
// SAFETY: This function has exclusive access to the world so nothing aliases `ptr`.
|
||||||
|
value: unsafe { ptr.assert_unique() },
|
||||||
|
ticks,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Removes the resource of a given type, if it exists. Otherwise returns [None].
|
/// Removes the resource of a given type, if it exists. Otherwise returns [None].
|
||||||
///
|
///
|
||||||
/// **You should prefer to use the typed API [`World::remove_resource`] where possible and only
|
/// **You should prefer to use the typed API [`World::remove_resource`] where possible and only
|
||||||
/// use this in cases where the actual types are not known at compile time.**
|
/// use this in cases where the actual types are not known at compile time.**
|
||||||
pub fn remove_resource_by_id(&mut self, component_id: ComponentId) -> Option<()> {
|
pub fn remove_resource_by_id(&mut self, component_id: ComponentId) -> Option<()> {
|
||||||
let info = self.components.get_info(component_id)?;
|
self.storages
|
||||||
if !info.is_send_and_sync() {
|
.resources
|
||||||
self.validate_non_send_access_untyped(info.name());
|
.get_mut(component_id)?
|
||||||
}
|
.remove_and_drop();
|
||||||
// SAFETY: The underlying type is Send and Sync or we've already validated we're on the main thread
|
Some(())
|
||||||
unsafe {
|
}
|
||||||
self.storages
|
|
||||||
.resources
|
/// Removes the resource of a given type, if it exists. Otherwise returns [None].
|
||||||
.get_mut(component_id)?
|
///
|
||||||
.remove_and_drop();
|
/// **You should prefer to use the typed API [`World::remove_resource`] where possible and only
|
||||||
}
|
/// use this in cases where the actual types are not known at compile time.**
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// This function will panic if it isn't called from the same thread that the resource was inserted from.
|
||||||
|
pub fn remove_non_send_by_id(&mut self, component_id: ComponentId) -> Option<()> {
|
||||||
|
self.storages
|
||||||
|
.non_send_resources
|
||||||
|
.get_mut(component_id)?
|
||||||
|
.remove_and_drop();
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1546,6 +1672,9 @@ impl World {
|
|||||||
///
|
///
|
||||||
/// **You should prefer to use the typed API [`World::get_mut`] where possible and only
|
/// **You should prefer to use the typed API [`World::get_mut`] where possible and only
|
||||||
/// use this in cases where the actual types are not known at compile time.**
|
/// use this in cases where the actual types are not known at compile time.**
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// This function will panic if it isn't called from the same thread that the resource was inserted from.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn get_by_id(&self, entity: Entity, component_id: ComponentId) -> Option<Ptr<'_>> {
|
pub fn get_by_id(&self, entity: Entity, component_id: ComponentId) -> Option<Ptr<'_>> {
|
||||||
let info = self.components().get_info(component_id)?;
|
let info = self.components().get_info(component_id)?;
|
||||||
@ -1622,24 +1751,6 @@ impl<T: Default> FromWorld for T {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MainThreadValidator {
|
|
||||||
main_thread: std::thread::ThreadId,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MainThreadValidator {
|
|
||||||
fn is_main_thread(&self) -> bool {
|
|
||||||
self.main_thread == std::thread::current().id()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for MainThreadValidator {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
main_thread: std::thread::current().id(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::World;
|
use super::World;
|
||||||
|
@ -256,7 +256,7 @@ impl<'w> WorldCell<'w> {
|
|||||||
let component_id = self.world.components.get_resource_id(TypeId::of::<T>())?;
|
let component_id = self.world.components.get_resource_id(TypeId::of::<T>())?;
|
||||||
let archetype_component_id = self
|
let archetype_component_id = self
|
||||||
.world
|
.world
|
||||||
.get_resource_archetype_component_id(component_id)?;
|
.get_non_send_archetype_component_id(component_id)?;
|
||||||
WorldBorrow::try_new(
|
WorldBorrow::try_new(
|
||||||
// SAFETY: ComponentId matches TypeId
|
// SAFETY: ComponentId matches TypeId
|
||||||
|| unsafe { self.world.get_non_send_with_id(component_id) },
|
|| unsafe { self.world.get_non_send_with_id(component_id) },
|
||||||
@ -289,7 +289,7 @@ impl<'w> WorldCell<'w> {
|
|||||||
let component_id = self.world.components.get_resource_id(TypeId::of::<T>())?;
|
let component_id = self.world.components.get_resource_id(TypeId::of::<T>())?;
|
||||||
let archetype_component_id = self
|
let archetype_component_id = self
|
||||||
.world
|
.world
|
||||||
.get_resource_archetype_component_id(component_id)?;
|
.get_non_send_archetype_component_id(component_id)?;
|
||||||
WorldBorrowMut::try_new(
|
WorldBorrowMut::try_new(
|
||||||
// SAFETY: ComponentId matches TypeId and access is checked by WorldBorrowMut
|
// SAFETY: ComponentId matches TypeId and access is checked by WorldBorrowMut
|
||||||
|| unsafe { self.world.get_non_send_unchecked_mut_with_id(component_id) },
|
|| unsafe { self.world.get_non_send_unchecked_mut_with_id(component_id) },
|
||||||
|
@ -29,7 +29,7 @@ impl Plugin for WindowRenderPlugin {
|
|||||||
render_app
|
render_app
|
||||||
.init_resource::<ExtractedWindows>()
|
.init_resource::<ExtractedWindows>()
|
||||||
.init_resource::<WindowSurfaces>()
|
.init_resource::<WindowSurfaces>()
|
||||||
.init_resource::<NonSendMarker>()
|
.init_non_send_resource::<NonSendMarker>()
|
||||||
.add_system_to_stage(RenderStage::Extract, extract_windows)
|
.add_system_to_stage(RenderStage::Extract, extract_windows)
|
||||||
.add_system_to_stage(
|
.add_system_to_stage(
|
||||||
RenderStage::Prepare,
|
RenderStage::Prepare,
|
||||||
|
Loading…
Reference in New Issue
Block a user