|
| 1 | +//! This example demonstrates how to access `Component` data through an `Index`. |
| 2 | +#![allow(clippy::type_complexity)] |
| 3 | + |
| 4 | +use bevy::{ |
| 5 | + ecs::{ |
| 6 | + component::Tick, |
| 7 | + system::{SystemChangeTick, SystemParam}, |
| 8 | + }, |
| 9 | + prelude::*, |
| 10 | + utils::{HashMap, HashSet}, |
| 11 | +}; |
| 12 | +use bevy_internal::ecs::query::ReadOnlyWorldQuery; |
| 13 | +use std::{hash::Hash, marker::PhantomData}; |
| 14 | + |
| 15 | +pub trait Indexer { |
| 16 | + type Input: Component; |
| 17 | + type Index: Hash + Eq + Clone + 'static; |
| 18 | + |
| 19 | + fn index(input: &Self::Input) -> Self::Index; |
| 20 | +} |
| 21 | + |
| 22 | +pub struct SimpleIndexer<T>(PhantomData<T>); |
| 23 | + |
| 24 | +impl<T> Indexer for SimpleIndexer<T> where T: Component + Hash + Eq + Clone { |
| 25 | + type Input = T; |
| 26 | + |
| 27 | + type Index = T; |
| 28 | + |
| 29 | + fn index(input: &Self::Input) -> Self::Index { |
| 30 | + input.clone() |
| 31 | + } |
| 32 | +} |
| 33 | + |
| 34 | +#[derive(Resource)] |
| 35 | +struct IndexBacking<T, F = (), I = SimpleIndexer<T>> { |
| 36 | + forward: HashMap<T, HashSet<Entity>>, |
| 37 | + reverse: HashMap<Entity, T>, |
| 38 | + last_this_run: Option<Tick>, |
| 39 | + _phantom: PhantomData<fn(F, I)>, |
| 40 | +} |
| 41 | + |
| 42 | +impl<T, F, I> Default for IndexBacking<T, F, I> { |
| 43 | + fn default() -> Self { |
| 44 | + Self { |
| 45 | + forward: default(), |
| 46 | + reverse: default(), |
| 47 | + last_this_run: default(), |
| 48 | + _phantom: PhantomData, |
| 49 | + } |
| 50 | + } |
| 51 | +} |
| 52 | + |
| 53 | +impl<T, F> IndexBacking<T, F> { |
| 54 | + fn update(&mut self, entity: Entity, value: Option<T>) -> Option<T> |
| 55 | + where |
| 56 | + T: Hash + Eq + Clone, |
| 57 | + { |
| 58 | + let old = if let Some(ref value) = value { |
| 59 | + self.reverse.insert(entity, value.clone()) |
| 60 | + } else { |
| 61 | + self.reverse.remove(&entity) |
| 62 | + }; |
| 63 | + |
| 64 | + if let Some(ref old) = old { |
| 65 | + if let Some(set) = self.forward.get_mut(old) { |
| 66 | + set.remove(&entity); |
| 67 | + |
| 68 | + if set.is_empty() { |
| 69 | + self.forward.remove(old); |
| 70 | + } |
| 71 | + } |
| 72 | + } |
| 73 | + |
| 74 | + if let Some(value) = value { |
| 75 | + self.forward.entry(value).or_default().insert(entity); |
| 76 | + }; |
| 77 | + |
| 78 | + old |
| 79 | + } |
| 80 | + |
| 81 | + fn get(&self, value: &T) -> Option<impl Iterator<Item = Entity> + '_> |
| 82 | + where |
| 83 | + T: Hash + Eq + Clone, |
| 84 | + { |
| 85 | + Some(self.forward.get(value)?.iter().copied()) |
| 86 | + } |
| 87 | +} |
| 88 | + |
| 89 | +#[derive(SystemParam)] |
| 90 | +pub struct Index<'w, 's, T, F = ()> |
| 91 | +where |
| 92 | + T: Component + Hash + Eq, |
| 93 | + F: ReadOnlyWorldQuery + 'static, |
| 94 | +{ |
| 95 | + changed: Query<'w, 's, (Entity, Ref<'static, T>), (Changed<T>, F)>, |
| 96 | + removed: RemovedComponents<'w, 's, T>, |
| 97 | + index: ResMut<'w, IndexBacking<T, F>>, |
| 98 | + this_run: SystemChangeTick, |
| 99 | +} |
| 100 | + |
| 101 | +impl<'w, 's, T, F> Index<'w, 's, T, F> |
| 102 | +where |
| 103 | + T: Component + Hash + Eq + Clone, |
| 104 | + F: ReadOnlyWorldQuery + 'static, |
| 105 | +{ |
| 106 | + fn update_index_internal(&mut self) { |
| 107 | + let this_run = self.this_run.this_run(); |
| 108 | + |
| 109 | + // Remove old entires |
| 110 | + for entity in self.removed.read() { |
| 111 | + self.index.update(entity, None); |
| 112 | + } |
| 113 | + |
| 114 | + // Update new and existing entries |
| 115 | + for (entity, component) in self.changed.iter() { |
| 116 | + self.index.update(entity, Some(component.clone())); |
| 117 | + } |
| 118 | + |
| 119 | + self.index.last_this_run = Some(this_run); |
| 120 | + } |
| 121 | + |
| 122 | + fn update_index(mut index: Index<T>) { |
| 123 | + index.update_index_internal(); |
| 124 | + } |
| 125 | + |
| 126 | + fn ensure_updated(&mut self) { |
| 127 | + let this_run = self.this_run.this_run(); |
| 128 | + |
| 129 | + if self.index.last_this_run != Some(this_run) { |
| 130 | + self.update_index_internal(); |
| 131 | + } |
| 132 | + } |
| 133 | + |
| 134 | + pub fn get(&mut self, value: &T) -> Option<impl Iterator<Item = Entity> + '_> { |
| 135 | + self.ensure_updated(); |
| 136 | + |
| 137 | + self.index.get(value) |
| 138 | + } |
| 139 | + |
| 140 | + pub fn iter(&mut self) -> impl Iterator<Item = Entity> + '_ { |
| 141 | + self.ensure_updated(); |
| 142 | + |
| 143 | + self.index.reverse.keys().copied() |
| 144 | + } |
| 145 | +} |
| 146 | + |
| 147 | +pub struct IndexPlugin<T>(PhantomData<T>); |
| 148 | + |
| 149 | +impl<T> Default for IndexPlugin<T> { |
| 150 | + fn default() -> Self { |
| 151 | + Self(PhantomData) |
| 152 | + } |
| 153 | +} |
| 154 | + |
| 155 | +impl<T> Plugin for IndexPlugin<T> where T: Component + Hash + Eq + Clone { |
| 156 | + fn build(&self, app: &mut App) { |
| 157 | + app.init_resource::<IndexBacking<T>>() |
| 158 | + .add_systems(Update, Index::<T>::update_index); |
| 159 | + } |
| 160 | +} |
| 161 | + |
| 162 | +// Usage |
| 163 | + |
| 164 | +#[derive(Component, Hash, Clone, PartialEq, Eq, Debug)] |
| 165 | +struct Player(usize); |
| 166 | + |
| 167 | +#[derive(Component)] |
| 168 | +struct Head; |
| 169 | + |
| 170 | +#[derive(Component)] |
| 171 | +struct Body; |
| 172 | + |
| 173 | +fn main() { |
| 174 | + App::new() |
| 175 | + .add_plugins(DefaultPlugins) |
| 176 | + .add_plugins(IndexPlugin::<Player>::default()) |
| 177 | + .add_systems(Startup, |mut commands: Commands| { |
| 178 | + for player in 0..4 { |
| 179 | + commands.spawn((Head, Player(player))); |
| 180 | + commands.spawn((Body, Player(player))); |
| 181 | + } |
| 182 | + }) |
| 183 | + .add_systems(FixedUpdate, get_bodies_for_head) |
| 184 | + .run(); |
| 185 | +} |
| 186 | + |
| 187 | +fn get_bodies_for_head( |
| 188 | + heads: Query<(Entity, &Player), With<Head>>, |
| 189 | + bodies: Query<Entity, With<Body>>, |
| 190 | + mut index: Index<Player> |
| 191 | +) { |
| 192 | + for (head_entity, head_player) in heads.iter() { |
| 193 | + let Some(body_entities) = index.get(head_player) else { |
| 194 | + continue; |
| 195 | + }; |
| 196 | + |
| 197 | + for body_entity in body_entities { |
| 198 | + let Ok(body_entity) = bodies.get(body_entity) else { |
| 199 | + continue; |
| 200 | + }; |
| 201 | + |
| 202 | + info!("{head_player:?}: {head_entity:?} <-> {body_entity:?}"); |
| 203 | + } |
| 204 | + } |
| 205 | +} |
0 commit comments