Skip to content

Commit c39cda1

Browse files
committed
WIP
1 parent e67cfdf commit c39cda1

File tree

2 files changed

+216
-0
lines changed

2 files changed

+216
-0
lines changed

Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1344,6 +1344,17 @@ description = "Creates a hierarchy of parents and children entities"
13441344
category = "ECS (Entity Component System)"
13451345
wasm = false
13461346

1347+
[[example]]
1348+
name = "indexing"
1349+
path = "examples/ecs/indexing.rs"
1350+
doc-scrape-examples = true
1351+
1352+
[package.metadata.example.indexing]
1353+
name = "Indexing"
1354+
description = "Get access to entities via component values"
1355+
category = "ECS (Entity Component System)"
1356+
wasm = false
1357+
13471358
[[example]]
13481359
name = "iter_combinations"
13491360
path = "examples/ecs/iter_combinations.rs"

examples/ecs/indexing.rs

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
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

Comments
 (0)