mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
Clean up pointer use in BundleSpawner/BundleInserter (#12269)
# Objective Following #10756, we're now using raw pointers in BundleInserter and BundleSpawner. This is primarily to get around the need to split the borrow on the World, but it leaves a lot to be desired in terms of safety guarantees. There's no type level guarantee the code can't dereference a null pointer, and it's restoring them to borrows fairly liberally within the associated functions. ## Solution * Replace the pointers with `NonNull` and a new `bevy_ptr::ConstNonNull` that only allows conversion back to read-only borrows * Remove the closure to avoid potentially aliasing through the closure by restructuring the match expression. * Move all conversions back into borrows as far up as possible to ensure that the borrow checker is at least locally followed.
This commit is contained in:
parent
d56e16754c
commit
4cd53cc7e1
2 changed files with 323 additions and 220 deletions
|
@ -15,11 +15,12 @@ use crate::{
|
|||
prelude::World,
|
||||
query::DebugCheckedUnwrap,
|
||||
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
|
||||
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld},
|
||||
world::unsafe_world_cell::UnsafeWorldCell,
|
||||
};
|
||||
use bevy_ptr::OwningPtr;
|
||||
use bevy_ptr::{ConstNonNull, OwningPtr};
|
||||
use bevy_utils::all_tuples;
|
||||
use std::any::TypeId;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
/// The `Bundle` trait enables insertion and removal of [`Component`]s from an entity.
|
||||
///
|
||||
|
@ -510,10 +511,10 @@ impl BundleInfo {
|
|||
// SAFETY: We have exclusive world access so our pointers can't be invalidated externally
|
||||
pub(crate) struct BundleInserter<'w> {
|
||||
world: UnsafeWorldCell<'w>,
|
||||
bundle_info: *const BundleInfo,
|
||||
add_bundle: *const AddBundle,
|
||||
table: *mut Table,
|
||||
archetype: *mut Archetype,
|
||||
bundle_info: ConstNonNull<BundleInfo>,
|
||||
add_bundle: ConstNonNull<AddBundle>,
|
||||
table: NonNull<Table>,
|
||||
archetype: NonNull<Archetype>,
|
||||
result: InsertBundleResult,
|
||||
change_tick: Tick,
|
||||
}
|
||||
|
@ -521,11 +522,11 @@ pub(crate) struct BundleInserter<'w> {
|
|||
pub(crate) enum InsertBundleResult {
|
||||
SameArchetype,
|
||||
NewArchetypeSameTable {
|
||||
new_archetype: *mut Archetype,
|
||||
new_archetype: NonNull<Archetype>,
|
||||
},
|
||||
NewArchetypeNewTable {
|
||||
new_archetype: *mut Archetype,
|
||||
new_table: *mut Table,
|
||||
new_archetype: NonNull<Archetype>,
|
||||
new_table: NonNull<Table>,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -546,7 +547,7 @@ impl<'w> BundleInserter<'w> {
|
|||
/// Creates a new [`BundleInserter`].
|
||||
///
|
||||
/// # Safety
|
||||
/// Caller must ensure that `bundle_id` exists in `world.bundles`
|
||||
/// - Caller must ensure that `bundle_id` exists in `world.bundles`.
|
||||
#[inline]
|
||||
pub(crate) unsafe fn new_with_id(
|
||||
world: &'w mut World,
|
||||
|
@ -575,10 +576,10 @@ impl<'w> BundleInserter<'w> {
|
|||
let table_id = archetype.table_id();
|
||||
let table = &mut world.storages.tables[table_id];
|
||||
Self {
|
||||
add_bundle,
|
||||
archetype,
|
||||
bundle_info,
|
||||
table,
|
||||
add_bundle: add_bundle.into(),
|
||||
archetype: archetype.into(),
|
||||
bundle_info: bundle_info.into(),
|
||||
table: table.into(),
|
||||
result: InsertBundleResult::SameArchetype,
|
||||
change_tick,
|
||||
world: world.as_unsafe_world_cell(),
|
||||
|
@ -598,24 +599,26 @@ impl<'w> BundleInserter<'w> {
|
|||
if table_id == new_table_id {
|
||||
let table = &mut world.storages.tables[table_id];
|
||||
Self {
|
||||
add_bundle,
|
||||
archetype,
|
||||
bundle_info,
|
||||
table,
|
||||
result: InsertBundleResult::NewArchetypeSameTable { new_archetype },
|
||||
add_bundle: add_bundle.into(),
|
||||
archetype: archetype.into(),
|
||||
bundle_info: bundle_info.into(),
|
||||
table: table.into(),
|
||||
result: InsertBundleResult::NewArchetypeSameTable {
|
||||
new_archetype: new_archetype.into(),
|
||||
},
|
||||
change_tick,
|
||||
world: world.as_unsafe_world_cell(),
|
||||
}
|
||||
} else {
|
||||
let (table, new_table) = world.storages.tables.get_2_mut(table_id, new_table_id);
|
||||
Self {
|
||||
add_bundle,
|
||||
archetype,
|
||||
bundle_info,
|
||||
table,
|
||||
add_bundle: add_bundle.into(),
|
||||
archetype: archetype.into(),
|
||||
bundle_info: bundle_info.into(),
|
||||
table: table.into(),
|
||||
result: InsertBundleResult::NewArchetypeNewTable {
|
||||
new_archetype,
|
||||
new_table,
|
||||
new_archetype: new_archetype.into(),
|
||||
new_table: new_table.into(),
|
||||
},
|
||||
change_tick,
|
||||
world: world.as_unsafe_world_cell(),
|
||||
|
@ -623,6 +626,7 @@ impl<'w> BundleInserter<'w> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
/// `entity` must currently exist in the source archetype for this inserter. `location`
|
||||
/// must be `entity`'s location in the archetype. `T` must match this [`BundleInfo`]'s type
|
||||
|
@ -633,13 +637,158 @@ impl<'w> BundleInserter<'w> {
|
|||
location: EntityLocation,
|
||||
bundle: T,
|
||||
) -> EntityLocation {
|
||||
// SAFETY: We do not make any structural changes to the archetype graph through self.world so these pointers always remain valid
|
||||
let trigger_hooks = |archetype: &Archetype, mut world: DeferredWorld| {
|
||||
let bundle_info = &*self.bundle_info;
|
||||
let add_bundle = &*self.add_bundle;
|
||||
let bundle_info = self.bundle_info.as_ref();
|
||||
let add_bundle = self.add_bundle.as_ref();
|
||||
let table = self.table.as_mut();
|
||||
let archetype = self.archetype.as_mut();
|
||||
|
||||
if archetype.has_on_add() {
|
||||
world.trigger_on_add(
|
||||
let (new_archetype, new_location) = match &mut self.result {
|
||||
InsertBundleResult::SameArchetype => {
|
||||
// SAFETY: Mutable references do not alias and will be dropped after this block
|
||||
let sparse_sets = {
|
||||
let world = self.world.world_mut();
|
||||
&mut world.storages.sparse_sets
|
||||
};
|
||||
|
||||
bundle_info.write_components(
|
||||
table,
|
||||
sparse_sets,
|
||||
add_bundle,
|
||||
entity,
|
||||
location.table_row,
|
||||
self.change_tick,
|
||||
bundle,
|
||||
);
|
||||
|
||||
(archetype, location)
|
||||
}
|
||||
InsertBundleResult::NewArchetypeSameTable { new_archetype } => {
|
||||
let new_archetype = new_archetype.as_mut();
|
||||
|
||||
// SAFETY: Mutable references do not alias and will be dropped after this block
|
||||
let (sparse_sets, entities) = {
|
||||
let world = self.world.world_mut();
|
||||
(&mut world.storages.sparse_sets, &mut world.entities)
|
||||
};
|
||||
|
||||
let result = archetype.swap_remove(location.archetype_row);
|
||||
if let Some(swapped_entity) = result.swapped_entity {
|
||||
let swapped_location =
|
||||
// SAFETY: If the swap was successful, swapped_entity must be valid.
|
||||
unsafe { entities.get(swapped_entity).debug_checked_unwrap() };
|
||||
entities.set(
|
||||
swapped_entity.index(),
|
||||
EntityLocation {
|
||||
archetype_id: swapped_location.archetype_id,
|
||||
archetype_row: location.archetype_row,
|
||||
table_id: swapped_location.table_id,
|
||||
table_row: swapped_location.table_row,
|
||||
},
|
||||
);
|
||||
}
|
||||
let new_location = new_archetype.allocate(entity, result.table_row);
|
||||
entities.set(entity.index(), new_location);
|
||||
bundle_info.write_components(
|
||||
table,
|
||||
sparse_sets,
|
||||
add_bundle,
|
||||
entity,
|
||||
result.table_row,
|
||||
self.change_tick,
|
||||
bundle,
|
||||
);
|
||||
|
||||
(new_archetype, new_location)
|
||||
}
|
||||
InsertBundleResult::NewArchetypeNewTable {
|
||||
new_archetype,
|
||||
new_table,
|
||||
} => {
|
||||
let new_table = new_table.as_mut();
|
||||
let new_archetype = new_archetype.as_mut();
|
||||
|
||||
// SAFETY: Mutable references do not alias and will be dropped after this block
|
||||
let (archetypes_ptr, sparse_sets, entities) = {
|
||||
let world = self.world.world_mut();
|
||||
let archetype_ptr: *mut Archetype = world.archetypes.archetypes.as_mut_ptr();
|
||||
(
|
||||
archetype_ptr,
|
||||
&mut world.storages.sparse_sets,
|
||||
&mut world.entities,
|
||||
)
|
||||
};
|
||||
let result = archetype.swap_remove(location.archetype_row);
|
||||
if let Some(swapped_entity) = result.swapped_entity {
|
||||
let swapped_location =
|
||||
// SAFETY: If the swap was successful, swapped_entity must be valid.
|
||||
unsafe { entities.get(swapped_entity).debug_checked_unwrap() };
|
||||
entities.set(
|
||||
swapped_entity.index(),
|
||||
EntityLocation {
|
||||
archetype_id: swapped_location.archetype_id,
|
||||
archetype_row: location.archetype_row,
|
||||
table_id: swapped_location.table_id,
|
||||
table_row: swapped_location.table_row,
|
||||
},
|
||||
);
|
||||
}
|
||||
// PERF: store "non bundle" components in edge, then just move those to avoid
|
||||
// redundant copies
|
||||
let move_result = table.move_to_superset_unchecked(result.table_row, new_table);
|
||||
let new_location = new_archetype.allocate(entity, move_result.new_row);
|
||||
entities.set(entity.index(), new_location);
|
||||
|
||||
// if an entity was moved into this entity's table spot, update its table row
|
||||
if let Some(swapped_entity) = move_result.swapped_entity {
|
||||
let swapped_location =
|
||||
// SAFETY: If the swap was successful, swapped_entity must be valid.
|
||||
unsafe { entities.get(swapped_entity).debug_checked_unwrap() };
|
||||
|
||||
entities.set(
|
||||
swapped_entity.index(),
|
||||
EntityLocation {
|
||||
archetype_id: swapped_location.archetype_id,
|
||||
archetype_row: swapped_location.archetype_row,
|
||||
table_id: swapped_location.table_id,
|
||||
table_row: result.table_row,
|
||||
},
|
||||
);
|
||||
|
||||
if archetype.id() == swapped_location.archetype_id {
|
||||
archetype
|
||||
.set_entity_table_row(swapped_location.archetype_row, result.table_row);
|
||||
} else if new_archetype.id() == swapped_location.archetype_id {
|
||||
new_archetype
|
||||
.set_entity_table_row(swapped_location.archetype_row, result.table_row);
|
||||
} else {
|
||||
// SAFETY: the only two borrowed archetypes are above and we just did collision checks
|
||||
(*archetypes_ptr.add(swapped_location.archetype_id.index()))
|
||||
.set_entity_table_row(swapped_location.archetype_row, result.table_row);
|
||||
}
|
||||
}
|
||||
|
||||
bundle_info.write_components(
|
||||
new_table,
|
||||
sparse_sets,
|
||||
add_bundle,
|
||||
entity,
|
||||
move_result.new_row,
|
||||
self.change_tick,
|
||||
bundle,
|
||||
);
|
||||
|
||||
(new_archetype, new_location)
|
||||
}
|
||||
};
|
||||
|
||||
// SAFETY: We have no outstanding mutable references to world as they were dropped
|
||||
let mut deferred_world = unsafe { self.world.into_deferred() };
|
||||
|
||||
if new_archetype.has_on_add() {
|
||||
// SAFETY: All components in the bundle are guaranteed to exist in the World
|
||||
// as they must be initialized before creating the BundleInfo.
|
||||
unsafe {
|
||||
deferred_world.trigger_on_add(
|
||||
entity,
|
||||
bundle_info
|
||||
.iter_components()
|
||||
|
@ -648,176 +797,14 @@ impl<'w> BundleInserter<'w> {
|
|||
.map(|(id, _)| id),
|
||||
);
|
||||
}
|
||||
if archetype.has_on_insert() {
|
||||
world.trigger_on_insert(entity, bundle_info.iter_components());
|
||||
}
|
||||
};
|
||||
match &mut self.result {
|
||||
InsertBundleResult::SameArchetype => {
|
||||
{
|
||||
// SAFETY: Mutable references do not alias and will be dropped after this block
|
||||
let sparse_sets = {
|
||||
let world = self.world.world_mut();
|
||||
&mut world.storages.sparse_sets
|
||||
};
|
||||
let table = &mut *self.table;
|
||||
let bundle_info = &*self.bundle_info;
|
||||
let add_bundle = &*self.add_bundle;
|
||||
|
||||
bundle_info.write_components(
|
||||
table,
|
||||
sparse_sets,
|
||||
add_bundle,
|
||||
entity,
|
||||
location.table_row,
|
||||
self.change_tick,
|
||||
bundle,
|
||||
);
|
||||
}
|
||||
// SAFETY: We have no outstanding mutable references to world as they were dropped
|
||||
unsafe {
|
||||
let archetype = &*self.archetype;
|
||||
trigger_hooks(archetype, self.world.into_deferred());
|
||||
}
|
||||
location
|
||||
}
|
||||
InsertBundleResult::NewArchetypeSameTable { new_archetype } => {
|
||||
let new_location = {
|
||||
// SAFETY: Mutable references do not alias and will be dropped after this block
|
||||
let (sparse_sets, entities) = {
|
||||
let world = self.world.world_mut();
|
||||
(&mut world.storages.sparse_sets, &mut world.entities)
|
||||
};
|
||||
let table = &mut *self.table;
|
||||
let archetype = &mut *self.archetype;
|
||||
let new_archetype = &mut **new_archetype;
|
||||
|
||||
let result = archetype.swap_remove(location.archetype_row);
|
||||
if let Some(swapped_entity) = result.swapped_entity {
|
||||
let swapped_location =
|
||||
// SAFETY: If the swap was successful, swapped_entity must be valid.
|
||||
unsafe { entities.get(swapped_entity).debug_checked_unwrap() };
|
||||
entities.set(
|
||||
swapped_entity.index(),
|
||||
EntityLocation {
|
||||
archetype_id: swapped_location.archetype_id,
|
||||
archetype_row: location.archetype_row,
|
||||
table_id: swapped_location.table_id,
|
||||
table_row: swapped_location.table_row,
|
||||
},
|
||||
);
|
||||
}
|
||||
let new_location = new_archetype.allocate(entity, result.table_row);
|
||||
entities.set(entity.index(), new_location);
|
||||
|
||||
let bundle_info = &*self.bundle_info;
|
||||
let add_bundle = &*self.add_bundle;
|
||||
bundle_info.write_components(
|
||||
table,
|
||||
sparse_sets,
|
||||
add_bundle,
|
||||
entity,
|
||||
result.table_row,
|
||||
self.change_tick,
|
||||
bundle,
|
||||
);
|
||||
new_location
|
||||
};
|
||||
|
||||
// SAFETY: We have no outstanding mutable references to world as they were dropped
|
||||
unsafe { trigger_hooks(&**new_archetype, self.world.into_deferred()) };
|
||||
|
||||
new_location
|
||||
}
|
||||
InsertBundleResult::NewArchetypeNewTable {
|
||||
new_archetype,
|
||||
new_table,
|
||||
} => {
|
||||
let new_location = {
|
||||
// SAFETY: Mutable references do not alias and will be dropped after this block
|
||||
let (archetypes_ptr, sparse_sets, entities) = {
|
||||
let world = self.world.world_mut();
|
||||
let archetype_ptr: *mut Archetype =
|
||||
world.archetypes.archetypes.as_mut_ptr();
|
||||
(
|
||||
archetype_ptr,
|
||||
&mut world.storages.sparse_sets,
|
||||
&mut world.entities,
|
||||
)
|
||||
};
|
||||
let table = &mut *self.table;
|
||||
let new_table = &mut **new_table;
|
||||
let archetype = &mut *self.archetype;
|
||||
let new_archetype = &mut **new_archetype;
|
||||
let result = archetype.swap_remove(location.archetype_row);
|
||||
if let Some(swapped_entity) = result.swapped_entity {
|
||||
let swapped_location =
|
||||
// SAFETY: If the swap was successful, swapped_entity must be valid.
|
||||
unsafe { entities.get(swapped_entity).debug_checked_unwrap() };
|
||||
entities.set(
|
||||
swapped_entity.index(),
|
||||
EntityLocation {
|
||||
archetype_id: swapped_location.archetype_id,
|
||||
archetype_row: location.archetype_row,
|
||||
table_id: swapped_location.table_id,
|
||||
table_row: swapped_location.table_row,
|
||||
},
|
||||
);
|
||||
}
|
||||
// PERF: store "non bundle" components in edge, then just move those to avoid
|
||||
// redundant copies
|
||||
let move_result = table.move_to_superset_unchecked(result.table_row, new_table);
|
||||
let new_location = new_archetype.allocate(entity, move_result.new_row);
|
||||
entities.set(entity.index(), new_location);
|
||||
|
||||
// if an entity was moved into this entity's table spot, update its table row
|
||||
if let Some(swapped_entity) = move_result.swapped_entity {
|
||||
let swapped_location =
|
||||
// SAFETY: If the swap was successful, swapped_entity must be valid.
|
||||
unsafe { entities.get(swapped_entity).debug_checked_unwrap() };
|
||||
let swapped_archetype = if archetype.id() == swapped_location.archetype_id {
|
||||
archetype
|
||||
} else if new_archetype.id() == swapped_location.archetype_id {
|
||||
new_archetype
|
||||
} else {
|
||||
// SAFETY: the only two borrowed archetypes are above and we just did collision checks
|
||||
&mut *archetypes_ptr.add(swapped_location.archetype_id.index())
|
||||
};
|
||||
|
||||
entities.set(
|
||||
swapped_entity.index(),
|
||||
EntityLocation {
|
||||
archetype_id: swapped_location.archetype_id,
|
||||
archetype_row: swapped_location.archetype_row,
|
||||
table_id: swapped_location.table_id,
|
||||
table_row: result.table_row,
|
||||
},
|
||||
);
|
||||
swapped_archetype
|
||||
.set_entity_table_row(swapped_location.archetype_row, result.table_row);
|
||||
}
|
||||
|
||||
let bundle_info = &*self.bundle_info;
|
||||
let add_bundle = &*self.add_bundle;
|
||||
bundle_info.write_components(
|
||||
new_table,
|
||||
sparse_sets,
|
||||
add_bundle,
|
||||
entity,
|
||||
move_result.new_row,
|
||||
self.change_tick,
|
||||
bundle,
|
||||
);
|
||||
|
||||
new_location
|
||||
};
|
||||
|
||||
// SAFETY: We have no outstanding mutable references to world as they were dropped
|
||||
unsafe { trigger_hooks(&**new_archetype, self.world.into_deferred()) };
|
||||
|
||||
new_location
|
||||
}
|
||||
}
|
||||
if new_archetype.has_on_insert() {
|
||||
// SAFETY: All components in the bundle are guaranteed to exist in the World
|
||||
// as they must be initialized before creating the BundleInfo.
|
||||
unsafe { deferred_world.trigger_on_insert(entity, bundle_info.iter_components()) }
|
||||
}
|
||||
|
||||
new_location
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -830,9 +817,9 @@ impl<'w> BundleInserter<'w> {
|
|||
// SAFETY: We have exclusive world access so our pointers can't be invalidated externally
|
||||
pub(crate) struct BundleSpawner<'w> {
|
||||
world: UnsafeWorldCell<'w>,
|
||||
bundle_info: *const BundleInfo,
|
||||
table: *mut Table,
|
||||
archetype: *mut Archetype,
|
||||
bundle_info: ConstNonNull<BundleInfo>,
|
||||
table: NonNull<Table>,
|
||||
archetype: NonNull<Archetype>,
|
||||
change_tick: Tick,
|
||||
}
|
||||
|
||||
|
@ -866,9 +853,9 @@ impl<'w> BundleSpawner<'w> {
|
|||
let archetype = &mut world.archetypes[new_archetype_id];
|
||||
let table = &mut world.storages.tables[archetype.table_id()];
|
||||
Self {
|
||||
bundle_info,
|
||||
table,
|
||||
archetype,
|
||||
bundle_info: bundle_info.into(),
|
||||
table: table.into(),
|
||||
archetype: archetype.into(),
|
||||
change_tick,
|
||||
world: world.as_unsafe_world_cell(),
|
||||
}
|
||||
|
@ -877,7 +864,7 @@ impl<'w> BundleSpawner<'w> {
|
|||
#[inline]
|
||||
pub fn reserve_storage(&mut self, additional: usize) {
|
||||
// SAFETY: There are no outstanding world references
|
||||
let (archetype, table) = unsafe { (&mut *self.archetype, &mut *self.table) };
|
||||
let (archetype, table) = unsafe { (self.archetype.as_mut(), self.table.as_mut()) };
|
||||
archetype.reserve(additional);
|
||||
table.reserve(additional);
|
||||
}
|
||||
|
@ -890,6 +877,10 @@ impl<'w> BundleSpawner<'w> {
|
|||
entity: Entity,
|
||||
bundle: T,
|
||||
) -> EntityLocation {
|
||||
let table = self.table.as_mut();
|
||||
let archetype = self.archetype.as_mut();
|
||||
let bundle_info = self.bundle_info.as_ref();
|
||||
|
||||
// SAFETY: We do not make any structural changes to the archetype graph through self.world so this pointer always remain valid
|
||||
let location = {
|
||||
// SAFETY: Mutable references do not alias and will be dropped after this block
|
||||
|
@ -897,11 +888,8 @@ impl<'w> BundleSpawner<'w> {
|
|||
let world = self.world.world_mut();
|
||||
(&mut world.storages.sparse_sets, &mut world.entities)
|
||||
};
|
||||
let table = &mut *self.table;
|
||||
let archetype = &mut *self.archetype;
|
||||
let table_row = table.allocate(entity);
|
||||
let location = archetype.allocate(entity, table_row);
|
||||
let bundle_info = &*self.bundle_info;
|
||||
bundle_info.write_components(
|
||||
table,
|
||||
sparse_sets,
|
||||
|
@ -916,16 +904,16 @@ impl<'w> BundleSpawner<'w> {
|
|||
};
|
||||
|
||||
// SAFETY: We have no outstanding mutable references to world as they were dropped
|
||||
unsafe {
|
||||
let archetype = &*self.archetype;
|
||||
let bundle_info = &*self.bundle_info;
|
||||
let mut world = self.world.into_deferred();
|
||||
if archetype.has_on_add() {
|
||||
world.trigger_on_add(entity, bundle_info.iter_components());
|
||||
}
|
||||
if archetype.has_on_insert() {
|
||||
world.trigger_on_insert(entity, bundle_info.iter_components());
|
||||
}
|
||||
let mut deferred_world = unsafe { self.world.into_deferred() };
|
||||
if archetype.has_on_add() {
|
||||
// SAFETY: All components in the bundle are guaranteed to exist in the World
|
||||
// as they must be initialized before creating the BundleInfo.
|
||||
unsafe { deferred_world.trigger_on_add(entity, bundle_info.iter_components()) };
|
||||
}
|
||||
if archetype.has_on_insert() {
|
||||
// SAFETY: All components in the bundle are guaranteed to exist in the World
|
||||
// as they must be initialized before creating the BundleInfo.
|
||||
unsafe { deferred_world.trigger_on_insert(entity, bundle_info.iter_components()) };
|
||||
}
|
||||
|
||||
location
|
||||
|
|
|
@ -26,6 +26,119 @@ mod sealed {
|
|||
impl Sealed for super::Unaligned {}
|
||||
}
|
||||
|
||||
/// A newtype around [`NonNull`] that only allows conversion to read-only borrows or pointers.
|
||||
///
|
||||
/// This type can be thought of as the `*const T` to [`NonNull<T>`]'s `*mut T`.
|
||||
#[repr(transparent)]
|
||||
pub struct ConstNonNull<T: ?Sized>(NonNull<T>);
|
||||
|
||||
impl<T: ?Sized> ConstNonNull<T> {
|
||||
/// Creates a new `ConstNonNull` if `ptr` is non-null.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_ptr::ConstNonNull;
|
||||
///
|
||||
/// let x = 0u32;
|
||||
/// let ptr = ConstNonNull::<u32>::new(&x as *const _).expect("ptr is null!");
|
||||
///
|
||||
/// if let Some(ptr) = ConstNonNull::<u32>::new(std::ptr::null()) {
|
||||
/// unreachable!();
|
||||
/// }
|
||||
/// ```
|
||||
pub fn new(ptr: *const T) -> Option<Self> {
|
||||
NonNull::new(ptr.cast_mut()).map(Self)
|
||||
}
|
||||
|
||||
/// Creates a new `ConstNonNull`.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `ptr` must be non-null.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_ptr::ConstNonNull;
|
||||
///
|
||||
/// let x = 0u32;
|
||||
/// let ptr = unsafe { ConstNonNull::new_unchecked(&x as *const _) };
|
||||
/// ```
|
||||
///
|
||||
/// *Incorrect* usage of this function:
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use bevy_ptr::ConstNonNull;
|
||||
///
|
||||
/// // NEVER DO THAT!!! This is undefined behavior. ⚠️
|
||||
/// let ptr = unsafe { ConstNonNull::<u32>::new_unchecked(std::ptr::null()) };
|
||||
/// ```
|
||||
pub const unsafe fn new_unchecked(ptr: *const T) -> Self {
|
||||
// SAFETY: This function's safety invariants are identical to `NonNull::new_unchecked`
|
||||
// The caller must satisfy all of them.
|
||||
unsafe { Self(NonNull::new_unchecked(ptr.cast_mut())) }
|
||||
}
|
||||
|
||||
/// Returns a shared reference to the value.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// When calling this method, you have to ensure that all of the following is true:
|
||||
///
|
||||
/// * The pointer must be properly aligned.
|
||||
///
|
||||
/// * It must be "dereferenceable" in the sense defined in [the module documentation].
|
||||
///
|
||||
/// * The pointer must point to an initialized instance of `T`.
|
||||
///
|
||||
/// * You must enforce Rust's aliasing rules, since the returned lifetime `'a` is
|
||||
/// arbitrarily chosen and does not necessarily reflect the actual lifetime of the data.
|
||||
/// In particular, while this reference exists, the memory the pointer points to must
|
||||
/// not get mutated (except inside `UnsafeCell`).
|
||||
///
|
||||
/// This applies even if the result of this method is unused!
|
||||
/// (The part about being initialized is not yet fully decided, but until
|
||||
/// it is, the only safe approach is to ensure that they are indeed initialized.)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_ptr::ConstNonNull;
|
||||
///
|
||||
/// let mut x = 0u32;
|
||||
/// let ptr = ConstNonNull::new(&mut x as *mut _).expect("ptr is null!");
|
||||
///
|
||||
/// let ref_x = unsafe { ptr.as_ref() };
|
||||
/// println!("{ref_x}");
|
||||
/// ```
|
||||
///
|
||||
/// [the module documentation]: core::ptr#safety
|
||||
#[inline]
|
||||
pub unsafe fn as_ref<'a>(&self) -> &'a T {
|
||||
// SAFETY: This function's safety invariants are identical to `NonNull::as_ref`
|
||||
// The caller must satisfy all of them.
|
||||
unsafe { self.0.as_ref() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ?Sized> From<NonNull<T>> for ConstNonNull<T> {
|
||||
fn from(value: NonNull<T>) -> ConstNonNull<T> {
|
||||
ConstNonNull(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> From<&'a T> for ConstNonNull<T> {
|
||||
fn from(value: &'a T) -> ConstNonNull<T> {
|
||||
ConstNonNull(NonNull::from(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> From<&'a mut T> for ConstNonNull<T> {
|
||||
fn from(value: &'a mut T) -> ConstNonNull<T> {
|
||||
ConstNonNull(NonNull::from(value))
|
||||
}
|
||||
}
|
||||
/// Type-erased borrow of some unknown type chosen when constructing this type.
|
||||
///
|
||||
/// This type tries to act "borrow-like" which means that:
|
||||
|
@ -280,6 +393,7 @@ impl<'a> OwningPtr<'a> {
|
|||
f(unsafe { PtrMut::from(&mut *temp).promote() })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A: IsAligned> OwningPtr<'a, A> {
|
||||
/// Creates a new instance from a raw pointer.
|
||||
///
|
||||
|
@ -346,6 +460,7 @@ impl<'a, A: IsAligned> OwningPtr<'a, A> {
|
|||
unsafe { PtrMut::new(self.0) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> OwningPtr<'a, Unaligned> {
|
||||
/// Consumes the [`OwningPtr`] to obtain ownership of the underlying data of type `T`.
|
||||
///
|
||||
|
|
Loading…
Reference in a new issue