# Objective I thought I'd have a go a trying to fix #2597. Hopefully fixes #2597. ## Solution I reused the memory pointed to by the value parameter, that is already required by `insert` to not be dropped, to contain the extracted value while dropping it.
This commit is contained in:
parent
73f524f61c
commit
8c250919e3
@ -87,7 +87,12 @@ impl BlobVec {
|
||||
|
||||
/// # Safety
|
||||
/// - index must be in bounds
|
||||
/// - memory must be reserved and uninitialized
|
||||
/// - the memory in the `BlobVec` starting at index `index`, of a size matching this `BlobVec`'s
|
||||
/// `item_layout`, must have been previously allocated, but not initialized yet
|
||||
/// - the memory at `*value` must be previously initialized with an item matching this
|
||||
/// `BlobVec`'s `item_layout`
|
||||
/// - the item that was stored in `*value` is left logically uninitialised/moved out of after
|
||||
/// calling this function, and as such should not be used or dropped by the caller.
|
||||
#[inline]
|
||||
pub unsafe fn initialize_unchecked(&mut self, index: usize, value: *mut u8) {
|
||||
debug_assert!(index < self.len());
|
||||
@ -97,12 +102,24 @@ impl BlobVec {
|
||||
|
||||
/// # Safety
|
||||
/// - index must be in-bounds
|
||||
// - memory must be previously initialized
|
||||
/// - the memory in the `BlobVec` starting at index `index`, of a size matching this `BlobVec`'s
|
||||
/// `item_layout`, must have been previously initialized with an item matching this `BlobVec`'s
|
||||
/// item_layout
|
||||
/// - the memory at `*value` must also be previously initialized with an item matching this
|
||||
/// `BlobVec`'s `item_layout`
|
||||
/// - the item that was stored in `*value` is left logically uninitialised/moved out of after
|
||||
/// calling this function, and as such should not be used or dropped by the caller.
|
||||
pub unsafe fn replace_unchecked(&mut self, index: usize, value: *mut u8) {
|
||||
debug_assert!(index < self.len());
|
||||
let ptr = self.get_unchecked(index);
|
||||
// If `drop` panics, then when the collection is dropped during stack unwinding, the
|
||||
// collection's `Drop` impl will call `drop` again for the old value (which is still stored
|
||||
// in the collection), so we get a double drop. To prevent that, we set len to 0 until we're
|
||||
// done.
|
||||
let old_len = std::mem::replace(&mut self.len, 0);
|
||||
(self.drop)(ptr);
|
||||
std::ptr::copy_nonoverlapping(value, ptr, self.item_layout.size());
|
||||
self.len = old_len;
|
||||
}
|
||||
|
||||
/// increases the length by one (and grows the vec if needed) with uninitialized memory and
|
||||
|
||||
@ -123,13 +123,18 @@ impl ComponentSparseSet {
|
||||
self.dense.len() == 0
|
||||
}
|
||||
|
||||
/// Inserts the `entity` key and component `value` pair into this sparse set.
|
||||
/// The caller is responsible for ensuring the value is not dropped. This collection will drop
|
||||
/// the value when needed.
|
||||
/// Inserts the `entity` key and component `value` pair into this sparse
|
||||
/// set. This collection takes ownership of the contents of `value`, and
|
||||
/// will drop the value when needed. Also, it may overwrite the contents of
|
||||
/// the `value` pointer if convenient. The caller is responsible for
|
||||
/// ensuring it does not drop `*value` after calling `insert`.
|
||||
///
|
||||
/// # Safety
|
||||
/// The `value` pointer must point to a valid address that matches the `Layout`
|
||||
/// inside the `ComponentInfo` given when constructing this sparse set.
|
||||
/// * The `value` pointer must point to a valid address that matches the
|
||||
/// `Layout` inside the `ComponentInfo` given when constructing this
|
||||
/// sparse set.
|
||||
/// * The caller is responsible for ensuring it does not drop `*value` after
|
||||
/// calling `insert`.
|
||||
pub unsafe fn insert(&mut self, entity: Entity, value: *mut u8, change_tick: u32) {
|
||||
if let Some(&dense_index) = self.sparse.get(entity) {
|
||||
self.dense.replace_unchecked(dense_index, value);
|
||||
|
||||
@ -1191,3 +1191,136 @@ impl Default for MainThreadValidator {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::World;
|
||||
use bevy_ecs_macros::Component;
|
||||
use std::{
|
||||
panic,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Mutex,
|
||||
},
|
||||
};
|
||||
|
||||
// For bevy_ecs_macros
|
||||
use crate as bevy_ecs;
|
||||
|
||||
type ID = u8;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum DropLogItem {
|
||||
Create(ID),
|
||||
Drop(ID),
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct MayPanicInDrop {
|
||||
drop_log: Arc<Mutex<Vec<DropLogItem>>>,
|
||||
expected_panic_flag: Arc<AtomicBool>,
|
||||
should_panic: bool,
|
||||
id: u8,
|
||||
}
|
||||
|
||||
impl MayPanicInDrop {
|
||||
fn new(
|
||||
drop_log: &Arc<Mutex<Vec<DropLogItem>>>,
|
||||
expected_panic_flag: &Arc<AtomicBool>,
|
||||
should_panic: bool,
|
||||
id: u8,
|
||||
) -> Self {
|
||||
println!("creating component with id {}", id);
|
||||
drop_log.lock().unwrap().push(DropLogItem::Create(id));
|
||||
|
||||
Self {
|
||||
drop_log: Arc::clone(drop_log),
|
||||
expected_panic_flag: Arc::clone(expected_panic_flag),
|
||||
should_panic,
|
||||
id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MayPanicInDrop {
|
||||
fn drop(&mut self) {
|
||||
println!("dropping component with id {}", self.id);
|
||||
|
||||
{
|
||||
let mut drop_log = self.drop_log.lock().unwrap();
|
||||
drop_log.push(DropLogItem::Drop(self.id));
|
||||
// Don't keep the mutex while panicking, or we'll poison it.
|
||||
drop(drop_log);
|
||||
}
|
||||
|
||||
if self.should_panic {
|
||||
self.expected_panic_flag.store(true, Ordering::SeqCst);
|
||||
panic!("testing what happens on panic inside drop");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DropTestHelper {
|
||||
drop_log: Arc<Mutex<Vec<DropLogItem>>>,
|
||||
/// Set to `true` right before we intentionally panic, so that if we get
|
||||
/// a panic, we know if it was intended or not.
|
||||
expected_panic_flag: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl DropTestHelper {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
drop_log: Arc::new(Mutex::new(Vec::<DropLogItem>::new())),
|
||||
expected_panic_flag: Arc::new(AtomicBool::new(false)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_component(&self, should_panic: bool, id: ID) -> MayPanicInDrop {
|
||||
MayPanicInDrop::new(&self.drop_log, &self.expected_panic_flag, should_panic, id)
|
||||
}
|
||||
|
||||
pub fn finish(self, panic_res: std::thread::Result<()>) -> Vec<DropLogItem> {
|
||||
let drop_log = Arc::try_unwrap(self.drop_log)
|
||||
.unwrap()
|
||||
.into_inner()
|
||||
.unwrap();
|
||||
let expected_panic_flag = self.expected_panic_flag.load(Ordering::SeqCst);
|
||||
|
||||
if !expected_panic_flag {
|
||||
match panic_res {
|
||||
Ok(()) => panic!("Expected a panic but it didn't happen"),
|
||||
Err(e) => panic::resume_unwind(e),
|
||||
}
|
||||
}
|
||||
|
||||
drop_log
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn panic_while_overwriting_component() {
|
||||
let helper = DropTestHelper::new();
|
||||
|
||||
let res = panic::catch_unwind(|| {
|
||||
let mut world = World::new();
|
||||
world
|
||||
.spawn()
|
||||
.insert(helper.make_component(true, 0))
|
||||
.insert(helper.make_component(false, 1));
|
||||
|
||||
println!("Done inserting! Dropping world...");
|
||||
});
|
||||
|
||||
let drop_log = helper.finish(res);
|
||||
|
||||
assert_eq!(
|
||||
&*drop_log,
|
||||
[
|
||||
DropLogItem::Create(0),
|
||||
DropLogItem::Create(1),
|
||||
DropLogItem::Drop(0),
|
||||
DropLogItem::Drop(1)
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user