-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enforce uniqueness in Children #1079
Changes from all commits
71025eb
7fbe5bb
cb70192
eef4c16
54175f9
f4c3dd7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,22 @@ | ||
use bevy_ecs::{Entity, MapEntities}; | ||
use bevy_reflect::{Reflect, ReflectComponent, ReflectMapEntities}; | ||
use bevy_utils::HashMap; | ||
use smallvec::SmallVec; | ||
use std::ops::Deref; | ||
|
||
#[derive(Default, Clone, Debug, Reflect)] | ||
#[derive(Default, Clone, Debug, Reflect, PartialEq, Eq)] | ||
#[reflect(Component, MapEntities)] | ||
pub struct Children(pub(crate) SmallVec<[Entity; 8]>); | ||
pub struct Children { | ||
order: SmallVec<[Entity; 8]>, | ||
uniqueness: HashMap<Entity, usize>, | ||
} | ||
|
||
impl MapEntities for Children { | ||
fn map_entities( | ||
&mut self, | ||
entity_map: &bevy_ecs::EntityMap, | ||
) -> Result<(), bevy_ecs::MapEntitiesError> { | ||
for entity in self.0.iter_mut() { | ||
for entity in self.order.iter_mut() { | ||
*entity = entity_map.get(*entity)?; | ||
} | ||
|
||
|
@@ -21,20 +25,91 @@ impl MapEntities for Children { | |
} | ||
|
||
impl Children { | ||
pub fn with(entity: &[Entity]) -> Self { | ||
Self(SmallVec::from_slice(entity)) | ||
pub fn with(entities: &[Entity]) -> Self { | ||
let mut children = Self::default(); | ||
for entity in entities { | ||
children.push(*entity); | ||
} | ||
children | ||
} | ||
|
||
/// Swaps the child at `a_index` with the child at `b_index` | ||
pub fn swap(&mut self, a_index: usize, b_index: usize) { | ||
self.0.swap(a_index, b_index); | ||
let a_entity = self.order[a_index]; | ||
let b_entity = self.order[b_index]; | ||
self.order.swap(a_index, b_index); | ||
self.uniqueness.insert(a_entity, b_index); | ||
self.uniqueness.insert(b_entity, a_index); | ||
} | ||
|
||
pub(crate) fn push(&mut self, entity: Entity) { | ||
let order = &mut self.order; | ||
let uniqueness = &mut self.uniqueness; | ||
|
||
uniqueness.entry(entity).or_insert_with(|| { | ||
order.push(entity); | ||
order.len() - 1 | ||
}); | ||
|
||
let desired_index = order.len() - 1; | ||
let current_index = uniqueness[&entity]; | ||
if current_index != desired_index { | ||
self.swap(current_index, desired_index); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Example order is [1,2,3,4] and I push(2). Would the result be [1,4,3,2]? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would need to see some tests for this Children component. It is not entirely trivial anymore. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are a couple of new tests in bevy_transform/src/hierarchy/child_builder.rs, but you are correct they should be closer to the actual code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, you are right about the order. My goal is to have the entity be in the same place in the list whether or not it already existed in the set. So if you reinsert an existing entity it will get moved to a new position. I'm not certain that that's the right behavior, I think you can make a strong case for the entity retaining it's old position. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think its good to move the existing entity to the new position, but this should not jump an unrelated entity to another position. In the example I push(2) to the end but 4 jumps from the end to index 1 were the original entity 2 was before to make room for the new entity 2. |
||
} | ||
} | ||
|
||
pub(crate) fn retain<F: FnMut(&Entity) -> bool>(&mut self, mut f: F) { | ||
let order = &mut self.order; | ||
let uniqueness = &mut self.uniqueness; | ||
|
||
let mut offset = 0; | ||
order.retain(|e| { | ||
if f(e) { | ||
*uniqueness.get_mut(e).unwrap() -= offset; | ||
true | ||
} else { | ||
offset += 1; | ||
uniqueness.remove(e); | ||
false | ||
} | ||
}); | ||
} | ||
|
||
pub fn contains(&self, entity: &Entity) -> bool { | ||
self.uniqueness.contains_key(entity) | ||
} | ||
|
||
pub(crate) fn extend<T: IntoIterator<Item = Entity>>(&mut self, iter: T) { | ||
for entity in iter { | ||
self.push(entity); | ||
} | ||
} | ||
|
||
pub(crate) fn insert(&mut self, index: usize, entities: &[Entity]) { | ||
let initial_count = self.order.len(); | ||
self.extend(entities.iter().cloned()); | ||
let actually_inserted = self.order.len() - initial_count; | ||
let mut desired_index = | ||
(index as i32 - (entities.len() as i32 - actually_inserted as i32)).max(0) as usize; | ||
for entity in entities { | ||
let current_index = self.uniqueness[entity]; | ||
if current_index != desired_index { | ||
self.swap(current_index, desired_index); | ||
} | ||
desired_index += 1; | ||
} | ||
} | ||
|
||
pub(crate) fn take(&mut self) -> SmallVec<[Entity; 8]> { | ||
self.uniqueness.clear(); | ||
std::mem::take(&mut self.order) | ||
} | ||
} | ||
|
||
impl Deref for Children { | ||
type Target = [Entity]; | ||
|
||
fn deref(&self) -> &Self::Target { | ||
&self.0[..] | ||
&self.order[..] | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO we should consider pulling in
smolset
to do this for us, rather than worrying about enforcing uniqueness ourselves. I've been poking at this space for storing input maps too.