Skip to content

Commit 4544790

Browse files
committed
Refine Dynamic System Queries
Uses separate mutable and immutable query arrays and presents a ( hopefully ) safer/sounder API by avoiding the creation of false &mut's.
1 parent 9109199 commit 4544790

File tree

3 files changed

+130
-117
lines changed

3 files changed

+130
-117
lines changed

crates/bevy_ecs/hecs/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ pub use bundle::{Bundle, DynamicBundle, MissingComponent};
8181
pub use entities::{Entity, EntityReserver, Location, NoSuchEntity};
8282
pub use entity_builder::{BuiltEntity, EntityBuilder};
8383
pub use query::{
84-
Access, Added, BatchedIter, Changed, DynamicComponentAccess, DynamicComponentInfo,
85-
DynamicComponentQuery, Mut, Mutated, Or, Query, QueryBorrow, QueryIter, ReadOnlyFetch, With,
84+
Access, Added, BatchedIter, Changed, DynamicComponentInfo, DynamicComponentQuery,
85+
DynamicQueryResult, Mut, Mutated, Or, Query, QueryBorrow, QueryIter, ReadOnlyFetch, With,
8686
Without,
8787
};
8888
pub use query_one::QueryOne;

crates/bevy_ecs/hecs/src/query.rs

Lines changed: 99 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
use core::{
1818
marker::PhantomData,
1919
ops::{Deref, DerefMut},
20-
ptr::{slice_from_raw_parts_mut, NonNull},
20+
ptr::{slice_from_raw_parts, slice_from_raw_parts_mut, NonNull},
2121
};
2222

2323
use crate::{archetype::Archetype, Component, ComponentId, Entity, MissingComponent};
@@ -91,121 +91,115 @@ pub struct DynamicComponentInfo {
9191
pub size: usize,
9292
}
9393

94-
/// The requested access to a dynamic component
95-
#[derive(Debug, Copy, Clone)]
96-
pub struct DynamicComponentAccess {
97-
/// The information related to the component type
98-
pub info: DynamicComponentInfo,
99-
/// the access required for that component
100-
pub access: Access,
101-
}
102-
10394
const COMPONENT_QUERY_SIZE: usize = 16;
10495

10596
/// A dynamically constructable component query
106-
#[derive(Debug, Clone)]
107-
pub struct DynamicComponentQuery([Option<DynamicComponentAccess>; COMPONENT_QUERY_SIZE]);
108-
109-
impl Default for DynamicComponentQuery {
110-
fn default() -> Self {
111-
DynamicComponentQuery([None; COMPONENT_QUERY_SIZE])
112-
}
113-
}
114-
115-
impl std::ops::Deref for DynamicComponentQuery {
116-
type Target = [Option<DynamicComponentAccess>; COMPONENT_QUERY_SIZE];
117-
118-
fn deref(&self) -> &Self::Target {
119-
&self.0
120-
}
121-
}
122-
123-
impl std::ops::DerefMut for DynamicComponentQuery {
124-
fn deref_mut(&mut self) -> &mut Self::Target {
125-
&mut self.0
126-
}
97+
#[derive(Debug, Default, Clone)]
98+
pub struct DynamicComponentQuery {
99+
/// The list of immutable components to query for
100+
pub immutable: [Option<DynamicComponentInfo>; COMPONENT_QUERY_SIZE],
101+
/// This list of mutable components to query for
102+
pub mutable: [Option<DynamicComponentInfo>; COMPONENT_QUERY_SIZE],
127103
}
128104

129105
impl Query for DynamicComponentQuery {
130106
type Fetch = DynamicFetch;
131107
}
132108

133-
/// A [`Fetch`] implementation for dynamic components
134-
#[derive(Debug)]
135-
pub struct DynamicFetch {
136-
datas: [Option<NonNull<[u8]>>; COMPONENT_QUERY_SIZE],
109+
/// The result of a dynamic component query, containing references to the component's bytes
110+
#[derive(Default, Debug)]
111+
pub struct DynamicQueryResult<'a> {
112+
/// the list of immutable components returned
113+
pub immutable: [Option<&'a [u8]>; COMPONENT_QUERY_SIZE],
114+
/// the list of mutable components returned
115+
pub mutable: [Option<&'a mut [u8]>; COMPONENT_QUERY_SIZE],
137116
}
138117

139-
impl Default for DynamicFetch {
140-
fn default() -> Self {
141-
DynamicFetch {
142-
datas: [None; COMPONENT_QUERY_SIZE],
143-
}
144-
}
118+
/// A [`Fetch`] implementation for dynamic components
119+
#[derive(Debug, Default, Clone)]
120+
pub struct DynamicFetch {
121+
immutable: [Option<NonNull<u8>>; COMPONENT_QUERY_SIZE],
122+
mutable: [Option<NonNull<u8>>; COMPONENT_QUERY_SIZE],
145123
}
146124

147125
impl<'a> Fetch<'a> for DynamicFetch {
148-
type Item = [Option<&'a mut [u8]>; COMPONENT_QUERY_SIZE];
126+
type Item = DynamicQueryResult<'a>;
149127
type State = DynamicComponentQuery;
150128

151-
fn access(archetype: &Archetype, state: &Self::State) -> Option<Access> {
129+
fn access(archetype: &Archetype, query: &Self::State) -> Option<Access> {
152130
let mut access = None;
153131

154-
for component_access in state.iter().filter_map(|x| x.as_ref()) {
155-
if archetype.has_component(component_access.info.id) {
156-
access = access.map_or(Some(component_access.access), |access| {
157-
if access < component_access.access {
158-
Some(component_access.access)
159-
} else {
160-
Some(access)
161-
}
162-
});
132+
for component in query.immutable.iter().filter_map(|&x| x) {
133+
if archetype.has_component(component.id) {
134+
access = Some(Access::Read);
135+
}
136+
}
137+
138+
for component in query.mutable.iter().filter_map(|&x| x) {
139+
if archetype.has_component(component.id) {
140+
access = Some(Access::Write);
163141
}
164142
}
165143

166144
access
167145
}
168146

169-
fn borrow(archetype: &Archetype, state: &Self::State) {
170-
for component_access in state.iter().filter_map(|&x| x) {
171-
archetype.borrow_component(component_access.info.id);
147+
fn borrow(archetype: &Archetype, query: &Self::State) {
148+
for component in query.immutable.iter().filter_map(|&x| x) {
149+
archetype.borrow_component(component.id);
150+
}
151+
152+
for component in query.mutable.iter().filter_map(|&x| x) {
153+
archetype.borrow_component(component.id);
172154
}
173155
}
174156

175-
fn release(archetype: &Archetype, state: &Self::State) {
176-
for component_access in state.iter().filter_map(|&x| x) {
177-
archetype.release_component(component_access.info.id);
157+
fn release(archetype: &Archetype, query: &Self::State) {
158+
for component in query.immutable.iter().filter_map(|&x| x) {
159+
archetype.release_component(component.id);
160+
}
161+
162+
for component in query.mutable.iter().filter_map(|&x| x) {
163+
archetype.release_component(component.id);
178164
}
179165
}
180166

181-
unsafe fn get(archetype: &'a Archetype, offset: usize, state: &Self::State) -> Option<Self> {
182-
let mut fetch = Self {
183-
datas: [None; COMPONENT_QUERY_SIZE],
184-
};
167+
unsafe fn get(archetype: &'a Archetype, offset: usize, query: &Self::State) -> Option<Self> {
168+
let mut fetch = Self::default();
185169

186170
let mut matches_any = false;
187-
for (component_index, component_access) in state
171+
for (component_index, component) in query
172+
.immutable
188173
.iter()
189174
.enumerate()
190175
.filter_map(|(i, &x)| x.map(|y| (i, y)))
191176
{
192-
let ptr = archetype.get_dynamic(
193-
component_access.info.id,
194-
component_access.info.size,
195-
// FIXME: Is this right for the index?
196-
0,
197-
);
177+
// FIXME: is 0 right for the index?
178+
let ptr = archetype.get_dynamic(component.id, component.size, 0 /* index */);
198179

199180
if ptr.is_some() {
200-
matches_any = true
181+
matches_any = true;
201182
}
202183

203-
fetch.datas[component_index] = ptr.map(|x| {
204-
NonNull::new_unchecked(slice_from_raw_parts_mut(
205-
x.as_ptr().add(offset),
206-
component_access.info.size,
207-
))
208-
});
184+
fetch.immutable[component_index] =
185+
ptr.map(|x| NonNull::new_unchecked(x.as_ptr().add(offset)));
186+
}
187+
188+
for (component_index, component) in query
189+
.mutable
190+
.iter()
191+
.enumerate()
192+
.filter_map(|(i, &x)| x.map(|y| (i, y)))
193+
{
194+
// FIXME: is 0 right for the index?
195+
let ptr = archetype.get_dynamic(component.id, component.size, 0 /* index */);
196+
197+
if ptr.is_some() {
198+
matches_any = true;
199+
}
200+
201+
fetch.mutable[component_index] =
202+
ptr.map(|x| NonNull::new_unchecked(x.as_ptr().add(offset)));
209203
}
210204

211205
if matches_any {
@@ -215,28 +209,42 @@ impl<'a> Fetch<'a> for DynamicFetch {
215209
}
216210
}
217211

218-
unsafe fn next(&mut self, state: &Self::State) -> Self::Item {
219-
const INIT: Option<&mut [u8]> = None;
220-
let mut components = [INIT; COMPONENT_QUERY_SIZE];
212+
unsafe fn next(&mut self, query: &Self::State) -> Self::Item {
213+
let mut query_result = DynamicQueryResult::default();
221214

222-
for (component_index, component_access) in state
215+
for (component_index, component) in query
216+
.immutable
223217
.iter()
224218
.enumerate()
225219
.filter_map(|(i, &x)| x.map(|y| (i, y)))
226220
{
227-
if let Some(nonnull) = &mut self.datas[component_index] {
228-
components[component_index] = {
229-
let x = nonnull.as_ptr();
230-
*nonnull = NonNull::new_unchecked(slice_from_raw_parts_mut(
231-
(x as *mut u8).add(component_access.info.size),
232-
component_access.info.size,
233-
));
234-
Some(&mut *x)
235-
};
236-
}
221+
let ptr = self.immutable[component_index].expect("Expected pointer to component data");
222+
223+
// Increment the pointer for the next iteration
224+
self.immutable[component_index] =
225+
Some(NonNull::new_unchecked(ptr.as_ptr().add(component.size)));
226+
227+
query_result.immutable[component_index] =
228+
Some(&*slice_from_raw_parts(ptr.as_ptr(), component.size));
229+
}
230+
231+
for (component_index, component) in query
232+
.mutable
233+
.iter()
234+
.enumerate()
235+
.filter_map(|(i, &x)| x.map(|y| (i, y)))
236+
{
237+
let ptr = self.mutable[component_index].expect("Expected pointer to component data");
238+
239+
// Increment the pointer for the next iteration
240+
self.mutable[component_index] =
241+
Some(NonNull::new_unchecked(ptr.as_ptr().add(component.size)));
242+
243+
query_result.mutable[component_index] =
244+
Some(&mut *slice_from_raw_parts_mut(ptr.as_ptr(), component.size));
237245
}
238246

239-
components
247+
query_result
240248
}
241249

242250
unsafe fn should_skip(&self, _state: &Self::State) -> bool {

examples/ecs/dynamic_systems.rs

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ use std::time::Duration;
1010

1111
use bevy::prelude::*;
1212
use bevy_app::ScheduleRunnerPlugin;
13-
use bevy_ecs::{
14-
Access, ComponentId, DynamicComponentAccess, DynamicComponentInfo, DynamicComponentQuery,
15-
};
13+
use bevy_ecs::{ComponentId, DynamicComponentInfo, DynamicComponentQuery};
1614

1715
// Define our componens
1816

@@ -39,7 +37,7 @@ fn spawn_scene(world: &mut World, _resources: &mut Resources) {
3937
},
4038
Vel {
4139
x: 0.0,
42-
y: 1.0,
40+
y: -1.0,
4341
}
4442
),
4543
(
@@ -49,9 +47,19 @@ fn spawn_scene(world: &mut World, _resources: &mut Resources) {
4947
},
5048
Vel {
5149
x: 0.0,
52-
y: -1.0,
50+
y: 1.0,
51+
}
52+
),
53+
(
54+
Pos {
55+
x: 1.0,
56+
y: 1.0
57+
},
58+
Vel {
59+
x: -0.5,
60+
y: 0.5,
5361
}
54-
)
62+
),
5563
]);
5664
}
5765

@@ -71,22 +79,14 @@ fn main() {
7179
// storage of any data as an array of bytes.
7280
let mut query = DynamicComponentQuery::default();
7381

74-
// Set the first element of the query to be write access to our `Pos` component
75-
query[0] = Some(DynamicComponentAccess {
76-
info: DynamicComponentInfo {
77-
id: ComponentId::RustTypeId(std::any::TypeId::of::<Pos>()),
78-
size: std::mem::size_of::<Pos>(),
79-
},
80-
access: Access::Write,
82+
query.immutable[0] = Some(DynamicComponentInfo {
83+
id: ComponentId::RustTypeId(std::any::TypeId::of::<Vel>()),
84+
size: std::mem::size_of::<Vel>(),
8185
});
8286

83-
// Set the second element of the query to be read access to our `Vel` component
84-
query[1] = Some(DynamicComponentAccess {
85-
info: DynamicComponentInfo {
86-
id: ComponentId::RustTypeId(std::any::TypeId::of::<Vel>()),
87-
size: std::mem::size_of::<Vel>(),
88-
},
89-
access: Access::Read,
87+
query.mutable[0] = Some(DynamicComponentInfo {
88+
id: ComponentId::RustTypeId(std::any::TypeId::of::<Pos>()),
89+
size: std::mem::size_of::<Pos>(),
9090
});
9191

9292
// Create our dynamic system by specifying the name, the query we created above, and a closure
@@ -103,17 +103,22 @@ fn main() {
103103

104104
// Here we take the mutable reference to the bytes of our position and velocity
105105
// components
106-
let pos_bytes = components[0].take().unwrap();
107-
let vel_bytes = components[1].take().unwrap();
106+
let pos_bytes = components.mutable[0].take().unwrap();
107+
let vel_bytes = components.immutable[0].take().unwrap();
108108

109-
unsafe fn from_slice<T>(s: &mut [u8]) -> &mut T {
109+
unsafe fn from_slice_mut<T>(s: &mut [u8]) -> &mut T {
110110
debug_assert_eq!(std::mem::size_of::<T>(), s.len());
111111
&mut *(s.as_mut_ptr() as *mut T)
112112
}
113113

114+
unsafe fn from_slice<T>(s: &[u8]) -> &T {
115+
debug_assert_eq!(std::mem::size_of::<T>(), s.len());
116+
&*(s.as_ptr() as *mut T)
117+
}
118+
114119
// Instead of interacting with the raw bytes of our components, we first cast them to
115120
// their Rust structs
116-
let mut pos: &mut Pos = unsafe { from_slice(pos_bytes) };
121+
let mut pos: &mut Pos = unsafe { from_slice_mut(pos_bytes) };
117122
let vel: &Vel = unsafe { from_slice(vel_bytes) };
118123

119124
// Now we can operate on our components

0 commit comments

Comments
 (0)