Skip to content

Commit 7a42034

Browse files
authored
make TypeIdMap iteration order respect its comment (#23864)
# Objective - Fixes #23840 - Make `TypeIdMap` iteration order depends only on insertion/removals ## Solution - Replace the backing `HashMap` by an `IndexMap` ## Testing - CI
1 parent 5927c47 commit 7a42034

9 files changed

Lines changed: 44 additions & 39 deletions

File tree

crates/bevy_animation/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -748,10 +748,10 @@ impl AnimationCurveEvaluators {
748748
.component_property_curve_evaluators
749749
.get_or_insert_with(component_property, func),
750750
EvaluatorId::Type(type_id) => match self.type_id_curve_evaluators.entry(type_id) {
751-
bevy_platform::collections::hash_map::Entry::Occupied(occupied_entry) => {
751+
bevy_utils::TypeIdMapEntry::Occupied(occupied_entry) => {
752752
&mut **occupied_entry.into_mut()
753753
}
754-
bevy_platform::collections::hash_map::Entry::Vacant(vacant_entry) => {
754+
bevy_utils::TypeIdMapEntry::Vacant(vacant_entry) => {
755755
&mut **vacant_entry.insert(func())
756756
}
757757
},
@@ -781,7 +781,7 @@ impl CurrentEvaluators {
781781
(visit)(EvaluatorId::ComponentField(&key))?;
782782
}
783783

784-
for (key, _) in self.type_ids.drain() {
784+
for (key, _) in self.type_ids.drain(..) {
785785
(visit)(EvaluatorId::Type(key))?;
786786
}
787787

crates/bevy_app/src/plugin_group.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ use alloc::{
44
string::{String, ToString},
55
vec::Vec,
66
};
7-
use bevy_platform::collections::hash_map::Entry;
8-
use bevy_utils::TypeIdMap;
7+
use bevy_utils::{TypeIdMap, TypeIdMapEntry as Entry};
98
use core::any::TypeId;
109
use log::{debug, warn};
1110

@@ -368,7 +367,7 @@ impl PluginGroupBuilder {
368367
for plugin_id in order {
369368
self.upsert_plugin_entry_state(
370369
plugin_id,
371-
plugins.remove(&plugin_id).unwrap(),
370+
plugins.shift_remove(&plugin_id).unwrap(),
372371
self.order.len(),
373372
);
374373

@@ -517,7 +516,7 @@ impl PluginGroupBuilder {
517516
#[track_caller]
518517
pub fn finish(mut self, app: &mut App) {
519518
for ty in &self.order {
520-
if let Some(entry) = self.plugins.remove(ty)
519+
if let Some(entry) = self.plugins.shift_remove(ty)
521520
&& entry.enabled
522521
{
523522
debug!("added plugin: {}", entry.plugin.name());

crates/bevy_asset/src/server/info.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use alloc::{
1313
use bevy_ecs::world::World;
1414
use bevy_platform::collections::{hash_map::Entry, HashMap, HashSet};
1515
use bevy_tasks::Task;
16-
use bevy_utils::TypeIdMap;
16+
use bevy_utils::{TypeIdMap, TypeIdMapEntry};
1717
use core::{
1818
any::{type_name, TypeId},
1919
task::Waker,
@@ -222,7 +222,7 @@ impl AssetInfos {
222222
.ok_or(GetOrCreateHandleInternalError::HandleMissingButTypeIdNotSpecified)?;
223223

224224
match handles.entry(type_id) {
225-
Entry::Occupied(entry) => {
225+
TypeIdMapEntry::Occupied(entry) => {
226226
let index = *entry.get();
227227
// if there is a path_to_id entry, info always exists
228228
let info = self
@@ -264,7 +264,7 @@ impl AssetInfos {
264264
}
265265
}
266266
// The entry does not exist, so this is a "fresh" asset load. We must create a new handle
267-
Entry::Vacant(entry) => {
267+
TypeIdMapEntry::Vacant(entry) => {
268268
let should_load = match loading_mode {
269269
HandleLoadingMode::NotLoading => false,
270270
HandleLoadingMode::Request | HandleLoadingMode::Force => true,
@@ -746,7 +746,7 @@ impl AssetInfos {
746746
}
747747

748748
if let Some(map) = path_to_id.get_mut(path) {
749-
map.remove(&type_id);
749+
map.shift_remove(&type_id);
750750

751751
if map.is_empty() {
752752
path_to_id.remove(path);

crates/bevy_ecs/src/component/register.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,12 @@ impl<'w> ComponentsRegistrator<'w> {
133133
.unwrap_or_else(PoisonError::into_inner);
134134
queued.components.keys().next().copied().map(|type_id| {
135135
// SAFETY: the id just came from a valid iterator.
136-
unsafe { queued.components.remove(&type_id).debug_checked_unwrap() }
136+
unsafe {
137+
queued
138+
.components
139+
.shift_remove(&type_id)
140+
.debug_checked_unwrap()
141+
}
137142
})
138143
} {
139144
registrator.register(self);
@@ -189,7 +194,7 @@ impl<'w> ComponentsRegistrator<'w> {
189194
.get_mut()
190195
.unwrap_or_else(PoisonError::into_inner)
191196
.components
192-
.remove(&type_id)
197+
.shift_remove(&type_id)
193198
{
194199
// If we are trying to register something that has already been queued, we respect the queue.
195200
// Just like if we are trying to register something that already is, we respect the first registration.
@@ -338,7 +343,7 @@ impl<'w> ComponentsRegistrator<'w> {
338343
.get_mut()
339344
.unwrap_or_else(PoisonError::into_inner)
340345
.components
341-
.remove(&type_id)
346+
.shift_remove(&type_id)
342347
{
343348
// If we are trying to register something that has already been queued, we respect the queue.
344349
// Just like if we are trying to register something that already is, we respect the first registration.

crates/bevy_reflect/src/type_registry.rs

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -292,22 +292,18 @@ impl TypeRegistry {
292292
type_id: TypeId,
293293
get_registration: impl FnOnce() -> TypeRegistration,
294294
) -> bool {
295-
use bevy_platform::collections::hash_map::Entry;
296-
297-
match self.registrations.entry(type_id) {
298-
Entry::Occupied(_) => false,
299-
Entry::Vacant(entry) => {
300-
let registration = get_registration();
301-
Self::update_registration_indices(
302-
&registration,
303-
&mut self.short_path_to_id,
304-
&mut self.type_path_to_id,
305-
&mut self.ambiguous_names,
306-
);
307-
entry.insert(registration);
308-
true
309-
}
295+
if self.registrations.contains_key(&type_id) {
296+
return false;
310297
}
298+
let registration = get_registration();
299+
Self::update_registration_indices(
300+
&registration,
301+
&mut self.short_path_to_id,
302+
&mut self.type_path_to_id,
303+
&mut self.ambiguous_names,
304+
);
305+
self.registrations.insert(type_id, registration);
306+
true
311307
}
312308

313309
/// Internal method to register additional lookups for a given [`TypeRegistration`].

crates/bevy_reflect/src/utility.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ impl<T: TypedProperty> GenericTypeCell<T> {
278278

279279
write_lock
280280
.entry(type_id)
281-
.insert({
281+
.insert_entry({
282282
// We leak here in order to obtain a `&'static` reference.
283283
// Otherwise, we won't be able to return a reference due to the `RwLock`.
284284
// This should be okay, though, since we expect it to remain statically

crates/bevy_render/src/batching/gpu_preprocessing.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ where
199199
BatchedInstanceBuffers {
200200
current_input_buffer: InstanceInputUniformBuffer::new(),
201201
previous_input_buffer: PreviousInstanceInputUniformBuffer::new(),
202-
phase_instance_buffers: HashMap::default(),
202+
phase_instance_buffers: TypeIdMap::default(),
203203
}
204204
}
205205
}

crates/bevy_utils/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ bevy_platform = { path = "../bevy_platform", version = "0.19.0-dev", default-fea
2626
disqualified = { version = "1.0", default-features = false }
2727
thread_local = { version = "1.0", optional = true }
2828
async-channel = { version = "2.3.0", optional = true }
29+
indexmap = { version = "2", default-features = false }
2930

3031
[dev-dependencies]
3132
static_assertions = "1.1.0"

crates/bevy_utils/src/map.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
use core::{any::TypeId, hash::Hash};
22

33
use bevy_platform::{
4-
collections::{hash_map::Entry, HashMap},
4+
collections::HashMap,
55
hash::{Hashed, NoOpHash, PassHash},
66
};
7+
use indexmap::map::IndexMap;
8+
9+
/// The [`Entry`][indexmap::map::Entry] type for [`TypeIdMap`].
10+
pub use indexmap::map::Entry as TypeIdMapEntry;
711

812
/// A [`HashMap`] pre-configured to use [`Hashed`] keys and [`PassHash`] passthrough hashing.
913
/// Iteration order only depends on the order of insertions and deletions.
@@ -34,14 +38,14 @@ impl<K: Hash + Eq + PartialEq + Clone, V> PreHashMapExt<K, V> for PreHashMap<K,
3438
}
3539
}
3640

37-
/// A specialized hashmap type with Key of [`TypeId`]
41+
/// A specialized map type with Key of [`TypeId`]
3842
/// Iteration order only depends on the order of insertions and deletions.
39-
pub type TypeIdMap<V> = HashMap<TypeId, V, NoOpHash>;
43+
pub type TypeIdMap<V> = IndexMap<TypeId, V, NoOpHash>;
4044

4145
/// Extension trait to make use of [`TypeIdMap`] more ergonomic.
4246
///
4347
/// Each function on this trait is a trivial wrapper for a function
44-
/// on [`HashMap`], replacing a `TypeId` key with a
48+
/// on [`IndexMap`], replacing a `TypeId` key with a
4549
/// generic parameter `T`.
4650
///
4751
/// # Examples
@@ -80,7 +84,7 @@ pub trait TypeIdMapExt<V> {
8084
fn remove_type<T: ?Sized + 'static>(&mut self) -> Option<V>;
8185

8286
/// Gets the type `T`'s entry in the map for in-place manipulation.
83-
fn entry_type<T: ?Sized + 'static>(&mut self) -> Entry<'_, TypeId, V, NoOpHash>;
87+
fn entry_type<T: ?Sized + 'static>(&mut self) -> TypeIdMapEntry<'_, TypeId, V>;
8488
}
8589

8690
impl<V> TypeIdMapExt<V> for TypeIdMap<V> {
@@ -101,11 +105,11 @@ impl<V> TypeIdMapExt<V> for TypeIdMap<V> {
101105

102106
#[inline]
103107
fn remove_type<T: ?Sized + 'static>(&mut self) -> Option<V> {
104-
self.remove(&TypeId::of::<T>())
108+
self.shift_remove(&TypeId::of::<T>())
105109
}
106110

107111
#[inline]
108-
fn entry_type<T: ?Sized + 'static>(&mut self) -> Entry<'_, TypeId, V, NoOpHash> {
112+
fn entry_type<T: ?Sized + 'static>(&mut self) -> TypeIdMapEntry<'_, TypeId, V> {
109113
self.entry(TypeId::of::<T>())
110114
}
111115
}
@@ -152,4 +156,4 @@ mod tests {
152156
);
153157
}
154158
}
155-
}
159+
}

0 commit comments

Comments
 (0)