Use TypeIdMap whenever possible (#11684)

Use `TypeIdMap<T>` instead of `HashMap<TypeId, T>`

- ~~`TypeIdMap` was in `bevy_ecs`. I've kept it there because of
#11478~~
- ~~I haven't swapped `bevy_reflect` over because it doesn't depend on
`bevy_ecs`, but I'd also be happy with moving `TypeIdMap` to
`bevy_utils` and then adding a dependency to that~~
- ~~this is a slight change in the public API of
`DrawFunctionsInternal`, does this need to go in the changelog?~~

## Changelog
- moved `TypeIdMap` to `bevy_utils`
- changed `DrawFunctionsInternal::indices` to `TypeIdMap`

## Migration Guide

- `TypeIdMap` now lives in `bevy_utils`
- `DrawFunctionsInternal::indices` now uses a `TypeIdMap`.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
SpecificProtagonist 2024-02-04 00:47:04 +01:00 committed by GitHub
parent 4e9590a5ce
commit 21aa5fe2b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 79 additions and 81 deletions

View File

@ -1,5 +1,5 @@
use crate::{App, AppError, Plugin}; use crate::{App, AppError, Plugin};
use bevy_utils::{tracing::debug, tracing::warn, HashMap}; use bevy_utils::{tracing::debug, tracing::warn, TypeIdMap};
use std::any::TypeId; use std::any::TypeId;
/// Combines multiple [`Plugin`]s into a single unit. /// Combines multiple [`Plugin`]s into a single unit.
@ -33,7 +33,7 @@ impl PluginGroup for PluginGroupBuilder {
/// can be disabled, enabled or reordered. /// can be disabled, enabled or reordered.
pub struct PluginGroupBuilder { pub struct PluginGroupBuilder {
group_name: String, group_name: String,
plugins: HashMap<TypeId, PluginEntry>, plugins: TypeIdMap<PluginEntry>,
order: Vec<TypeId>, order: Vec<TypeId>,
} }

View File

@ -6,7 +6,7 @@ use crate::{
}; };
use bevy_ecs::world::World; use bevy_ecs::world::World;
use bevy_log::warn; use bevy_log::warn;
use bevy_utils::{Entry, HashMap, HashSet}; use bevy_utils::{Entry, HashMap, HashSet, TypeIdMap};
use crossbeam_channel::Sender; use crossbeam_channel::Sender;
use std::{ use std::{
any::TypeId, any::TypeId,
@ -61,7 +61,7 @@ impl AssetInfo {
#[derive(Default)] #[derive(Default)]
pub(crate) struct AssetInfos { pub(crate) struct AssetInfos {
path_to_id: HashMap<AssetPath<'static>, HashMap<TypeId, UntypedAssetId>>, path_to_id: HashMap<AssetPath<'static>, TypeIdMap<UntypedAssetId>>,
infos: HashMap<UntypedAssetId, AssetInfo>, infos: HashMap<UntypedAssetId, AssetInfo>,
/// If set to `true`, this informs [`AssetInfos`] to track data relevant to watching for changes (such as `load_dependants`) /// If set to `true`, this informs [`AssetInfos`] to track data relevant to watching for changes (such as `load_dependants`)
/// This should only be set at startup. /// This should only be set at startup.
@ -72,10 +72,10 @@ pub(crate) struct AssetInfos {
/// Tracks living labeled assets for a given source asset. /// Tracks living labeled assets for a given source asset.
/// This should only be set when watching for changes to avoid unnecessary work. /// This should only be set when watching for changes to avoid unnecessary work.
pub(crate) living_labeled_assets: HashMap<AssetPath<'static>, HashSet<String>>, pub(crate) living_labeled_assets: HashMap<AssetPath<'static>, HashSet<String>>,
pub(crate) handle_providers: HashMap<TypeId, AssetHandleProvider>, pub(crate) handle_providers: TypeIdMap<AssetHandleProvider>,
pub(crate) dependency_loaded_event_sender: HashMap<TypeId, fn(&mut World, UntypedAssetId)>, pub(crate) dependency_loaded_event_sender: TypeIdMap<fn(&mut World, UntypedAssetId)>,
pub(crate) dependency_failed_event_sender: pub(crate) dependency_failed_event_sender:
HashMap<TypeId, fn(&mut World, UntypedAssetId, AssetPath<'static>, AssetLoadError)>, TypeIdMap<fn(&mut World, UntypedAssetId, AssetPath<'static>, AssetLoadError)>,
} }
impl std::fmt::Debug for AssetInfos { impl std::fmt::Debug for AssetInfos {
@ -112,7 +112,7 @@ impl AssetInfos {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn create_handle_internal( fn create_handle_internal(
infos: &mut HashMap<UntypedAssetId, AssetInfo>, infos: &mut HashMap<UntypedAssetId, AssetInfo>,
handle_providers: &HashMap<TypeId, AssetHandleProvider>, handle_providers: &TypeIdMap<AssetHandleProvider>,
living_labeled_assets: &mut HashMap<AssetPath<'static>, HashSet<String>>, living_labeled_assets: &mut HashMap<AssetPath<'static>, HashSet<String>>,
watching_for_changes: bool, watching_for_changes: bool,
type_id: TypeId, type_id: TypeId,
@ -205,7 +205,7 @@ impl AssetInfos {
.ok_or(GetOrCreateHandleInternalError::HandleMissingButTypeIdNotSpecified)?; .ok_or(GetOrCreateHandleInternalError::HandleMissingButTypeIdNotSpecified)?;
match handles.entry(type_id) { match handles.entry(type_id) {
Entry::Occupied(entry) => { bevy_utils::hashbrown::hash_map::Entry::Occupied(entry) => {
let id = *entry.get(); let id = *entry.get();
// if there is a path_to_id entry, info always exists // if there is a path_to_id entry, info always exists
let info = self.infos.get_mut(&id).unwrap(); let info = self.infos.get_mut(&id).unwrap();
@ -246,7 +246,7 @@ impl AssetInfos {
} }
} }
// The entry does not exist, so this is a "fresh" asset load. We must create a new handle // The entry does not exist, so this is a "fresh" asset load. We must create a new handle
Entry::Vacant(entry) => { bevy_utils::hashbrown::hash_map::Entry::Vacant(entry) => {
let should_load = match loading_mode { let should_load = match loading_mode {
HandleLoadingMode::NotLoading => false, HandleLoadingMode::NotLoading => false,
HandleLoadingMode::Request | HandleLoadingMode::Force => true, HandleLoadingMode::Request | HandleLoadingMode::Force => true,
@ -640,7 +640,7 @@ impl AssetInfos {
fn process_handle_drop_internal( fn process_handle_drop_internal(
infos: &mut HashMap<UntypedAssetId, AssetInfo>, infos: &mut HashMap<UntypedAssetId, AssetInfo>,
path_to_id: &mut HashMap<AssetPath<'static>, HashMap<TypeId, UntypedAssetId>>, path_to_id: &mut HashMap<AssetPath<'static>, TypeIdMap<UntypedAssetId>>,
loader_dependants: &mut HashMap<AssetPath<'static>, HashSet<AssetPath<'static>>>, loader_dependants: &mut HashMap<AssetPath<'static>, HashSet<AssetPath<'static>>>,
living_labeled_assets: &mut HashMap<AssetPath<'static>, HashSet<String>>, living_labeled_assets: &mut HashMap<AssetPath<'static>, HashSet<String>>,
watching_for_changes: bool, watching_for_changes: bool,

View File

@ -19,7 +19,7 @@ use crate::{
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_log::{error, info, warn}; use bevy_log::{error, info, warn};
use bevy_tasks::IoTaskPool; use bevy_tasks::IoTaskPool;
use bevy_utils::{CowArc, HashMap, HashSet}; use bevy_utils::{CowArc, HashMap, HashSet, TypeIdMap};
use crossbeam_channel::{Receiver, Sender}; use crossbeam_channel::{Receiver, Sender};
use futures_lite::StreamExt; use futures_lite::StreamExt;
use info::*; use info::*;
@ -1238,7 +1238,7 @@ pub fn handle_internal_asset_events(world: &mut World) {
#[derive(Default)] #[derive(Default)]
pub(crate) struct AssetLoaders { pub(crate) struct AssetLoaders {
type_id_to_loader: HashMap<TypeId, MaybeAssetLoader>, type_id_to_loader: TypeIdMap<MaybeAssetLoader>,
extension_to_type_id: HashMap<String, TypeId>, extension_to_type_id: HashMap<String, TypeId>,
type_name_to_type_id: HashMap<&'static str, TypeId>, type_name_to_type_id: HashMap<&'static str, TypeId>,
preregistered_loaders: HashMap<&'static str, TypeId>, preregistered_loaders: HashMap<&'static str, TypeId>,

View File

@ -3,7 +3,7 @@
//! This module contains the [`Bundle`] trait and some other helper types. //! This module contains the [`Bundle`] trait and some other helper types.
pub use bevy_ecs_macros::Bundle; pub use bevy_ecs_macros::Bundle;
use bevy_utils::{HashMap, HashSet}; use bevy_utils::{HashMap, HashSet, TypeIdMap};
use crate::{ use crate::{
archetype::{ archetype::{
@ -14,7 +14,6 @@ use crate::{
entity::{Entities, Entity, EntityLocation}, entity::{Entities, Entity, EntityLocation},
query::DebugCheckedUnwrap, query::DebugCheckedUnwrap,
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
TypeIdMap,
}; };
use bevy_ptr::OwningPtr; use bevy_ptr::OwningPtr;
use bevy_utils::all_tuples; use bevy_utils::all_tuples;

View File

@ -6,12 +6,12 @@ use crate::{
storage::{SparseSetIndex, Storages}, storage::{SparseSetIndex, Storages},
system::{Local, Resource, SystemParam}, system::{Local, Resource, SystemParam},
world::{FromWorld, World}, world::{FromWorld, World},
TypeIdMap,
}; };
pub use bevy_ecs_macros::Component; pub use bevy_ecs_macros::Component;
use bevy_ptr::{OwningPtr, UnsafeCellDeref}; use bevy_ptr::{OwningPtr, UnsafeCellDeref};
#[cfg(feature = "bevy_reflect")] #[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect; use bevy_reflect::Reflect;
use bevy_utils::TypeIdMap;
use std::cell::UnsafeCell; use std::cell::UnsafeCell;
use std::{ use std::{
alloc::Layout, alloc::Layout,

View File

@ -21,8 +21,6 @@ pub mod storage;
pub mod system; pub mod system;
pub mod world; pub mod world;
use std::any::TypeId;
pub use bevy_ptr as ptr; pub use bevy_ptr as ptr;
/// Most commonly used re-exported types. /// Most commonly used re-exported types.
@ -56,34 +54,6 @@ pub mod prelude {
pub use bevy_utils::all_tuples; pub use bevy_utils::all_tuples;
/// A specialized hashmap type with Key of [`TypeId`]
type TypeIdMap<V> =
std::collections::HashMap<TypeId, V, std::hash::BuildHasherDefault<NoOpTypeIdHasher>>;
#[doc(hidden)]
#[derive(Default)]
struct NoOpTypeIdHasher(u64);
// TypeId already contains a high-quality hash, so skip re-hashing that hash.
impl std::hash::Hasher for NoOpTypeIdHasher {
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, bytes: &[u8]) {
// This will never be called: TypeId always just calls write_u64 once!
// This is a known trick and unlikely to change, but isn't officially guaranteed.
// Don't break applications (slower fallback, just check in test):
self.0 = bytes.iter().fold(self.0, |hash, b| {
hash.rotate_left(8).wrapping_add(*b as u64)
});
}
fn write_u64(&mut self, i: u64) {
self.0 = i;
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate as bevy_ecs; use crate as bevy_ecs;
@ -1755,23 +1725,6 @@ mod tests {
); );
} }
#[test]
fn fast_typeid_hash() {
struct Hasher;
impl std::hash::Hasher for Hasher {
fn finish(&self) -> u64 {
0
}
fn write(&mut self, _: &[u8]) {
panic!("Hashing of std::any::TypeId changed");
}
fn write_u64(&mut self, _: u64) {}
}
std::hash::Hash::hash(&TypeId::of::<()>(), &mut Hasher);
}
#[derive(Component)] #[derive(Component)]
struct ComponentA(u32); struct ComponentA(u32);

View File

@ -9,6 +9,7 @@ use crate::{
use bevy_utils::{ use bevy_utils::{
thiserror::Error, thiserror::Error,
tracing::{error, info, warn}, tracing::{error, info, warn},
TypeIdMap,
}; };
#[cfg(test)] #[cfg(test)]
@ -617,7 +618,7 @@ struct ScheduleState {
/// changes to system behavior that should be applied the next time /// changes to system behavior that should be applied the next time
/// [`ScheduleState::skipped_systems()`] is called /// [`ScheduleState::skipped_systems()`] is called
behavior_updates: HashMap<TypeId, Option<SystemBehavior>>, behavior_updates: TypeIdMap<Option<SystemBehavior>>,
/// This field contains the first steppable system in the schedule. /// This field contains the first steppable system in the schedule.
first: Option<usize>, first: Option<usize>,

View File

@ -6,7 +6,7 @@ pub use bevy_gizmos_macros::GizmoConfigGroup;
use bevy_ecs::{component::Component, system::Resource}; use bevy_ecs::{component::Component, system::Resource};
use bevy_reflect::{Reflect, TypePath}; use bevy_reflect::{Reflect, TypePath};
use bevy_render::view::RenderLayers; use bevy_render::view::RenderLayers;
use bevy_utils::HashMap; use bevy_utils::TypeIdMap;
use core::panic; use core::panic;
use std::{ use std::{
any::TypeId, any::TypeId,
@ -30,7 +30,7 @@ pub struct DefaultGizmoConfigGroup;
#[derive(Resource, Default)] #[derive(Resource, Default)]
pub struct GizmoConfigStore { pub struct GizmoConfigStore {
// INVARIANT: must map TypeId::of::<T>() to correct type T // INVARIANT: must map TypeId::of::<T>() to correct type T
store: HashMap<TypeId, (GizmoConfig, Box<dyn Reflect>)>, store: TypeIdMap<(GizmoConfig, Box<dyn Reflect>)>,
} }
impl GizmoConfigStore { impl GizmoConfigStore {

View File

@ -77,7 +77,7 @@ use bevy_render::{
renderer::RenderDevice, renderer::RenderDevice,
Extract, ExtractSchedule, Render, RenderApp, RenderSet, Extract, ExtractSchedule, Render, RenderApp, RenderSet,
}; };
use bevy_utils::HashMap; use bevy_utils::TypeIdMap;
use config::{ use config::{
DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore, GizmoMeshConfig, DefaultGizmoConfigGroup, GizmoConfig, GizmoConfigGroup, GizmoConfigStore, GizmoMeshConfig,
}; };
@ -206,8 +206,8 @@ impl AppGizmoBuilder for App {
#[derive(Resource, Default)] #[derive(Resource, Default)]
struct LineGizmoHandles { struct LineGizmoHandles {
list: HashMap<TypeId, Handle<LineGizmo>>, list: TypeIdMap<Handle<LineGizmo>>,
strip: HashMap<TypeId, Handle<LineGizmo>>, strip: TypeIdMap<Handle<LineGizmo>>,
} }
fn update_gizmo_meshes<T: GizmoConfigGroup>( fn update_gizmo_meshes<T: GizmoConfigGroup>(

View File

@ -1,6 +1,6 @@
use crate::{serde::Serializable, Reflect, TypeInfo, TypePath, Typed}; use crate::{serde::Serializable, Reflect, TypeInfo, TypePath, Typed};
use bevy_ptr::{Ptr, PtrMut}; use bevy_ptr::{Ptr, PtrMut};
use bevy_utils::{HashMap, HashSet}; use bevy_utils::{HashMap, HashSet, TypeIdMap};
use downcast_rs::{impl_downcast, Downcast}; use downcast_rs::{impl_downcast, Downcast};
use serde::Deserialize; use serde::Deserialize;
use std::{ use std::{
@ -22,7 +22,7 @@ use std::{
/// [Registering]: TypeRegistry::register /// [Registering]: TypeRegistry::register
/// [crate-level documentation]: crate /// [crate-level documentation]: crate
pub struct TypeRegistry { pub struct TypeRegistry {
registrations: HashMap<TypeId, TypeRegistration>, registrations: TypeIdMap<TypeRegistration>,
short_path_to_id: HashMap<&'static str, TypeId>, short_path_to_id: HashMap<&'static str, TypeId>,
type_path_to_id: HashMap<&'static str, TypeId>, type_path_to_id: HashMap<&'static str, TypeId>,
ambiguous_names: HashSet<&'static str>, ambiguous_names: HashSet<&'static str>,
@ -318,7 +318,7 @@ impl TypeRegistryArc {
/// ///
/// [crate-level documentation]: crate /// [crate-level documentation]: crate
pub struct TypeRegistration { pub struct TypeRegistration {
data: HashMap<TypeId, Box<dyn TypeData>>, data: TypeIdMap<Box<dyn TypeData>>,
type_info: &'static TypeInfo, type_info: &'static TypeInfo,
} }
@ -373,7 +373,7 @@ impl TypeRegistration {
/// Creates type registration information for `T`. /// Creates type registration information for `T`.
pub fn of<T: Reflect + Typed + TypePath>() -> Self { pub fn of<T: Reflect + Typed + TypePath>() -> Self {
Self { Self {
data: HashMap::default(), data: Default::default(),
type_info: T::type_info(), type_info: T::type_info(),
} }
} }
@ -381,7 +381,7 @@ impl TypeRegistration {
impl Clone for TypeRegistration { impl Clone for TypeRegistration {
fn clone(&self) -> Self { fn clone(&self) -> Self {
let mut data = HashMap::default(); let mut data = TypeIdMap::default();
for (id, type_data) in &self.data { for (id, type_data) in &self.data {
data.insert(*id, (*type_data).clone_type_data()); data.insert(*id, (*type_data).clone_type_data());
} }

View File

@ -6,7 +6,7 @@ use bevy_ecs::{
system::{ReadOnlySystemParam, Resource, SystemParam, SystemParamItem, SystemState}, system::{ReadOnlySystemParam, Resource, SystemParam, SystemParamItem, SystemState},
world::World, world::World,
}; };
use bevy_utils::{all_tuples, HashMap}; use bevy_utils::{all_tuples, TypeIdMap};
use std::{ use std::{
any::TypeId, any::TypeId,
fmt::Debug, fmt::Debug,
@ -47,7 +47,7 @@ pub struct DrawFunctionId(u32);
/// For retrieval, the [`Draw`] functions are mapped to their respective [`TypeId`]s. /// For retrieval, the [`Draw`] functions are mapped to their respective [`TypeId`]s.
pub struct DrawFunctionsInternal<P: PhaseItem> { pub struct DrawFunctionsInternal<P: PhaseItem> {
pub draw_functions: Vec<Box<dyn Draw<P>>>, pub draw_functions: Vec<Box<dyn Draw<P>>>,
pub indices: HashMap<TypeId, DrawFunctionId>, pub indices: TypeIdMap<DrawFunctionId>,
} }
impl<P: PhaseItem> DrawFunctionsInternal<P> { impl<P: PhaseItem> DrawFunctionsInternal<P> {
@ -111,7 +111,7 @@ impl<P: PhaseItem> Default for DrawFunctions<P> {
Self { Self {
internal: RwLock::new(DrawFunctionsInternal { internal: RwLock::new(DrawFunctionsInternal {
draw_functions: Vec::new(), draw_functions: Vec::new(),
indices: HashMap::default(), indices: Default::default(),
}), }),
} }
} }

View File

@ -5,8 +5,7 @@ use bevy_ecs::{
world::World, world::World,
}; };
use bevy_reflect::{Reflect, TypePath, TypeRegistryArc}; use bevy_reflect::{Reflect, TypePath, TypeRegistryArc};
use bevy_utils::{EntityHashMap, HashMap}; use bevy_utils::{EntityHashMap, TypeIdMap};
use std::any::TypeId;
#[cfg(feature = "serialize")] #[cfg(feature = "serialize")]
use crate::serde::SceneSerializer; use crate::serde::SceneSerializer;
@ -97,7 +96,7 @@ impl DynamicScene {
// of which entities in the scene use that component. // of which entities in the scene use that component.
// This is so we can update the scene-internal references to references // This is so we can update the scene-internal references to references
// of the actual entities in the world. // of the actual entities in the world.
let mut scene_mappings: HashMap<TypeId, Vec<Entity>> = HashMap::default(); let mut scene_mappings: TypeIdMap<Vec<Entity>> = Default::default();
for scene_entity in &self.entities { for scene_entity in &self.entities {
// Fetch the entity with the given entity id from the `entity_map` // Fetch the entity with the given entity id from the `entity_map`
@ -132,7 +131,7 @@ impl DynamicScene {
if registration.data::<ReflectMapEntities>().is_some() { if registration.data::<ReflectMapEntities>().is_some() {
scene_mappings scene_mappings
.entry(registration.type_id()) .entry(registration.type_id())
.or_insert(Vec::new()) .or_default()
.push(entity); .push(entity);
} }

View File

@ -43,6 +43,7 @@ pub mod nonmax {
use hashbrown::hash_map::RawEntryMut; use hashbrown::hash_map::RawEntryMut;
use std::{ use std::{
any::TypeId,
fmt::Debug, fmt::Debug,
future::Future, future::Future,
hash::{BuildHasher, BuildHasherDefault, Hash, Hasher}, hash::{BuildHasher, BuildHasherDefault, Hash, Hasher},
@ -326,6 +327,34 @@ pub type EntityHashMap<K, V> = hashbrown::HashMap<K, V, EntityHash>;
/// A [`HashSet`] pre-configured to use [`EntityHash`] hashing. /// A [`HashSet`] pre-configured to use [`EntityHash`] hashing.
pub type EntityHashSet<T> = hashbrown::HashSet<T, EntityHash>; pub type EntityHashSet<T> = hashbrown::HashSet<T, EntityHash>;
/// A specialized hashmap type with Key of [`TypeId`]
pub type TypeIdMap<V> =
hashbrown::HashMap<TypeId, V, std::hash::BuildHasherDefault<NoOpTypeIdHasher>>;
#[doc(hidden)]
#[derive(Default)]
pub struct NoOpTypeIdHasher(u64);
// TypeId already contains a high-quality hash, so skip re-hashing that hash.
impl std::hash::Hasher for NoOpTypeIdHasher {
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, bytes: &[u8]) {
// This will never be called: TypeId always just calls write_u64 once!
// This is a known trick and unlikely to change, but isn't officially guaranteed.
// Don't break applications (slower fallback, just check in test):
self.0 = bytes.iter().fold(self.0, |hash, b| {
hash.rotate_left(8).wrapping_add(*b as u64)
});
}
fn write_u64(&mut self, i: u64) {
self.0 = i;
}
}
/// A type which calls a function when dropped. /// A type which calls a function when dropped.
/// This can be used to ensure that cleanup code is run even in case of a panic. /// This can be used to ensure that cleanup code is run even in case of a panic.
/// ///
@ -423,4 +452,21 @@ mod tests {
// Check that the HashMaps are Clone if the key/values are Clone // Check that the HashMaps are Clone if the key/values are Clone
assert_impl_all!(EntityHashMap::<u64, usize>: Clone); assert_impl_all!(EntityHashMap::<u64, usize>: Clone);
assert_impl_all!(PreHashMap::<u64, usize>: Clone); assert_impl_all!(PreHashMap::<u64, usize>: Clone);
#[test]
fn fast_typeid_hash() {
struct Hasher;
impl std::hash::Hasher for Hasher {
fn finish(&self) -> u64 {
0
}
fn write(&mut self, _: &[u8]) {
panic!("Hashing of std::any::TypeId changed");
}
fn write_u64(&mut self, _: u64) {}
}
std::hash::Hash::hash(&TypeId::of::<()>(), &mut Hasher);
}
} }