Skip to content

Commit d3f3d66

Browse files
committed
NonUniqueResource
1 parent 506bdc5 commit d3f3d66

File tree

8 files changed

+472
-4
lines changed

8 files changed

+472
-4
lines changed

crates/bevy_ecs/src/component.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Types for declaring and storing [`Component`]s.
22
3+
use crate::storage::non_unique_resource::NonUniqueResourceEntry;
34
use crate::{
45
self as bevy_ecs,
56
change_detection::MAX_CHANGE_AGE,
@@ -415,6 +416,18 @@ impl ComponentDescriptor {
415416
}
416417
}
417418

419+
fn new_non_unique_resource<T: Any>() -> Self {
420+
Self {
421+
name: Cow::Borrowed(std::any::type_name::<NonUniqueResourceEntry<T>>()),
422+
storage_type: StorageType::Table,
423+
is_send_and_sync: true,
424+
type_id: Some(TypeId::of::<NonUniqueResourceEntry<T>>()),
425+
layout: Layout::new::<NonUniqueResourceEntry<T>>(),
426+
drop: needs_drop::<NonUniqueResourceEntry<T>>()
427+
.then_some(Self::drop_ptr::<NonUniqueResourceEntry<T>>),
428+
}
429+
}
430+
418431
/// Returns a value indicating the storage strategy for the current component.
419432
#[inline]
420433
pub fn storage_type(&self) -> StorageType {
@@ -647,6 +660,16 @@ impl Components {
647660
}
648661
}
649662

663+
#[inline]
664+
pub(crate) fn init_non_unique_resource<T: Any>(&mut self) -> ComponentId {
665+
// SAFETY: The [`ComponentDescriptor`] matches the [`TypeId`]
666+
unsafe {
667+
self.get_or_insert_resource_with(TypeId::of::<NonUniqueResourceEntry<T>>(), || {
668+
ComponentDescriptor::new_non_unique_resource::<T>()
669+
})
670+
}
671+
}
672+
650673
/// # Safety
651674
///
652675
/// The [`ComponentDescriptor`] must match the [`TypeId`]

crates/bevy_ecs/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub mod change_detection;
1010
pub mod component;
1111
pub mod entity;
1212
pub mod event;
13+
pub mod non_unique_resource;
1314
pub mod query;
1415
#[cfg(feature = "bevy_reflect")]
1516
pub mod reflect;
Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
//! Non-unique resource.
2+
//!
3+
//! See [`NonUniqueResourceRef`] for details.
4+
5+
use crate::archetype::ArchetypeComponentId;
6+
use crate::component::{ComponentId, Tick};
7+
use crate::prelude::World;
8+
use crate::query::Access;
9+
use crate::storage::non_unique_resource::NonUniqueResourceEntry;
10+
use crate::storage::TableRow;
11+
use crate::system::{check_system_change_tick, System, SystemMeta};
12+
use crate::system::{In, IntoSystem};
13+
use crate::world::unsafe_world_cell::UnsafeWorldCell;
14+
use bevy_ptr::Ptr;
15+
use std::any;
16+
use std::any::TypeId;
17+
use std::borrow::Cow;
18+
use std::marker::PhantomData;
19+
20+
/// Non-unique resource (multiple instances of the same type are stored in the world).
21+
///
22+
/// Resource is allocated with [`World::new_non_unique_resource()`].
23+
pub struct NonUniqueResourceRef<T: Sync + Send + 'static> {
24+
/// Unique per `NonUniqueResourceRef<T>` instance.
25+
component_id: ComponentId,
26+
/// Index in table.
27+
index: TableRow,
28+
/// Allow concurrent access to different instances of `NonUniqueResourceRef<T>`.
29+
archetype_component_id: ArchetypeComponentId,
30+
/// We secretly store a `T` here.
31+
_phantom: PhantomData<T>,
32+
}
33+
34+
impl<T: Sync + Send + 'static> Clone for NonUniqueResourceRef<T> {
35+
fn clone(&self) -> Self {
36+
*self
37+
}
38+
}
39+
40+
impl<T: Sync + Send + 'static> Copy for NonUniqueResourceRef<T> {}
41+
42+
struct NonUniqueResourceSystem<T: Sync + Send + 'static, In, Out, F, const WRITE: bool> {
43+
non_unique_resource_ref: NonUniqueResourceRef<T>,
44+
system_meta: SystemMeta,
45+
function: F,
46+
_phantom: PhantomData<(In, Out)>,
47+
}
48+
49+
impl<T, In, Out, F, const WRITE: bool> NonUniqueResourceSystem<T, In, Out, F, WRITE>
50+
where
51+
T: Sync + Send + 'static,
52+
In: Send + Sync + 'static,
53+
Out: Send + Sync + 'static,
54+
F: Fn(In, Ptr) -> Out + Sync + Send + 'static,
55+
{
56+
pub fn new(
57+
non_unique_resource_ref: NonUniqueResourceRef<T>,
58+
function: F,
59+
) -> NonUniqueResourceSystem<T, In, Out, F, WRITE> {
60+
NonUniqueResourceSystem {
61+
non_unique_resource_ref,
62+
system_meta: SystemMeta::new::<Self>(),
63+
function,
64+
_phantom: PhantomData,
65+
}
66+
}
67+
}
68+
69+
impl<T, In, Out, F, const WRITE: bool> System for NonUniqueResourceSystem<T, In, Out, F, WRITE>
70+
where
71+
T: Sync + Send + 'static,
72+
In: Send + Sync + 'static,
73+
Out: Send + Sync + 'static,
74+
F: Fn(In, Ptr) -> Out + Sync + Send + 'static,
75+
{
76+
type In = In;
77+
type Out = Out;
78+
79+
fn name(&self) -> Cow<'static, str> {
80+
self.system_meta.name.clone()
81+
}
82+
83+
fn type_id(&self) -> TypeId {
84+
TypeId::of::<Self>()
85+
}
86+
87+
fn component_access(&self) -> &Access<ComponentId> {
88+
self.system_meta.component_access_set.combined_access()
89+
}
90+
91+
fn archetype_component_access(&self) -> &Access<ArchetypeComponentId> {
92+
&self.system_meta.archetype_component_access
93+
}
94+
95+
fn is_send(&self) -> bool {
96+
true
97+
}
98+
99+
fn is_exclusive(&self) -> bool {
100+
WRITE
101+
}
102+
103+
unsafe fn run_unsafe(&mut self, input: Self::In, world: UnsafeWorldCell) -> Self::Out {
104+
let ptr = world.get_non_unique_resource_by_id(
105+
self.non_unique_resource_ref.component_id,
106+
self.non_unique_resource_ref.index,
107+
);
108+
(self.function)(input, ptr)
109+
}
110+
111+
fn apply_deferred(&mut self, _world: &mut World) {}
112+
113+
fn initialize(&mut self, _world: &mut World) {}
114+
115+
fn update_archetype_component_access(&mut self, _world: UnsafeWorldCell) {
116+
// TODO: is it correct?
117+
// TODO: panic somewhere if the same system (this and combined with this)
118+
// accesses the same resource incompatibly.
119+
if WRITE {
120+
self.system_meta
121+
.component_access_set
122+
.add_unfiltered_write(self.non_unique_resource_ref.component_id);
123+
124+
self.system_meta
125+
.archetype_component_access
126+
.add_write(self.non_unique_resource_ref.archetype_component_id);
127+
} else {
128+
self.system_meta
129+
.component_access_set
130+
.add_unfiltered_read(self.non_unique_resource_ref.component_id);
131+
132+
self.system_meta
133+
.archetype_component_access
134+
.add_read(self.non_unique_resource_ref.archetype_component_id);
135+
}
136+
}
137+
138+
fn check_change_tick(&mut self, change_tick: Tick) {
139+
check_system_change_tick(
140+
&mut self.system_meta.last_run,
141+
change_tick,
142+
self.system_meta.name.as_ref(),
143+
);
144+
}
145+
146+
fn get_last_run(&self) -> Tick {
147+
self.system_meta.last_run
148+
}
149+
150+
fn set_last_run(&mut self, last_run: Tick) {
151+
self.system_meta.last_run = last_run;
152+
}
153+
}
154+
155+
impl<T: Sync + Send + 'static> NonUniqueResourceRef<T> {
156+
// Technically this function is unsafe because argument must match.
157+
pub(crate) fn new(
158+
component_id: ComponentId,
159+
index: TableRow,
160+
archetype_component_id: ArchetypeComponentId,
161+
) -> Self {
162+
NonUniqueResourceRef {
163+
component_id,
164+
index,
165+
archetype_component_id,
166+
_phantom: PhantomData,
167+
}
168+
}
169+
170+
/// Read the value if it is set, return `None` otherwise.
171+
pub fn read_opt_system(&self) -> impl System<In = (), Out = Option<T>> {
172+
// SAFETY: `NonUniqueResourceSystem` guarantees that the pointer is correct.
173+
NonUniqueResourceSystem::<_, _, _, _, true>::new(*self, |(), ptr| unsafe {
174+
ptr.assert_unique()
175+
.deref_mut::<NonUniqueResourceEntry<T>>()
176+
.value
177+
.take()
178+
})
179+
}
180+
181+
/// Read the value if it is set, panic otherwise.
182+
pub fn read_system(&self) -> impl System<In = (), Out = T> {
183+
// Slightly inefficient: we store index twice in the resulting system.
184+
let index = self.index.index();
185+
self.read_opt_system().map(move |opt| match opt {
186+
Some(v) => v,
187+
None => panic!(
188+
"Non-unique resource {}.{} is not set",
189+
any::type_name::<T>(),
190+
index
191+
),
192+
})
193+
}
194+
195+
/// Read the value if it is set, return `None` otherwise.
196+
///
197+
/// Keeps the value in the resource.
198+
pub fn read_opt_clone_system(&self) -> impl System<In = (), Out = Option<T>>
199+
where
200+
T: Clone,
201+
{
202+
// SAFETY: `NonUniqueResourceSystem` guarantees that the pointer is correct.
203+
NonUniqueResourceSystem::<_, _, _, _, false>::new(*self, |(), ptr| unsafe {
204+
ptr.deref::<NonUniqueResourceEntry<T>>().value.clone()
205+
})
206+
}
207+
208+
/// Read the value if it is set, panic otherwise.
209+
///
210+
/// Keeps the value in the resource.
211+
pub fn read_clone_system(&self) -> impl System<In = (), Out = T>
212+
where
213+
T: Clone,
214+
{
215+
// Slightly inefficient: we store index twice in the resulting system.
216+
let index = self.index.index();
217+
self.read_opt_clone_system().map(move |opt| match opt {
218+
Some(v) => v,
219+
None => panic!(
220+
"Non-unique resource {}.{} is not set",
221+
any::type_name::<T>(),
222+
index
223+
),
224+
})
225+
}
226+
227+
/// Write the value overwriting the previous one.
228+
pub fn write_opt_system(&self) -> impl System<In = Option<T>, Out = ()> {
229+
// SAFETY: `NonUniqueResourceSystem` guarantees that the pointer is correct.
230+
NonUniqueResourceSystem::<_, _, _, _, true>::new(*self, |value, ptr| unsafe {
231+
ptr.assert_unique()
232+
.deref_mut::<NonUniqueResourceEntry<T>>()
233+
.value = value;
234+
})
235+
}
236+
237+
/// Write the value overwriting the previous one.
238+
pub fn write_system(&self) -> impl System<In = T, Out = ()> {
239+
(|In(value)| Some(value)).pipe(self.write_opt_system())
240+
}
241+
242+
/// Write the given value into the resource.
243+
pub fn write_value_system(&self, value: T) -> impl System<In = (), Out = ()>
244+
where
245+
T: Clone,
246+
{
247+
(move || value.clone()).pipe(self.write_system())
248+
}
249+
250+
/// Unset the resource.
251+
pub fn remove_system(&self) -> impl System<In = (), Out = ()> {
252+
(|| None).pipe(self.write_opt_system())
253+
}
254+
}
255+
256+
#[cfg(test)]
257+
mod tests {
258+
use crate as bevy_ecs;
259+
use crate::schedule::IntoSystemConfigs;
260+
use crate::schedule::Schedule;
261+
use crate::schedule::SystemSet;
262+
use crate::system::Resource;
263+
use crate::system::{In, IntoSystem, ResMut};
264+
use crate::world::World;
265+
266+
#[test]
267+
fn test_write_read() {
268+
let mut world = World::default();
269+
270+
let res = world.new_non_unique_resource::<String>();
271+
272+
#[derive(Resource, Default)]
273+
struct TestState(bool);
274+
275+
world.init_resource::<TestState>();
276+
277+
let a = res.write_value_system("a".to_owned());
278+
279+
let b = res
280+
.read_system()
281+
.pipe(|In(v), mut result: ResMut<TestState>| {
282+
assert_eq!("a", v);
283+
assert!(!result.0);
284+
result.0 = true;
285+
});
286+
287+
#[derive(SystemSet, Clone, Debug, Eq, PartialEq, Hash)]
288+
struct Between;
289+
290+
let mut schedule = Schedule::default();
291+
schedule.add_systems(a.before(Between));
292+
schedule.add_systems(b.after(Between));
293+
294+
schedule.run(&mut world);
295+
296+
assert!(world.get_resource::<TestState>().unwrap().0);
297+
}
298+
299+
#[test]
300+
fn test_write_read_clone() {
301+
let mut world = World::default();
302+
303+
let res = world.new_non_unique_resource::<String>();
304+
305+
#[derive(Resource, Default)]
306+
struct TestState {
307+
b_read: bool,
308+
c_read: bool,
309+
}
310+
311+
world.init_resource::<TestState>();
312+
313+
let a = res.write_value_system("a".to_owned());
314+
315+
let b = res
316+
.read_clone_system()
317+
.pipe(|In(v): In<String>, mut result: ResMut<TestState>| {
318+
assert_eq!("a", v);
319+
assert!(!result.b_read);
320+
result.b_read = true;
321+
});
322+
let c = res
323+
.read_clone_system()
324+
.pipe(|In(v): In<String>, mut result: ResMut<TestState>| {
325+
assert_eq!("a", v);
326+
assert!(!result.c_read);
327+
result.c_read = true;
328+
});
329+
330+
#[derive(SystemSet, Clone, Debug, Eq, PartialEq, Hash)]
331+
struct Between;
332+
333+
let mut schedule = Schedule::default();
334+
schedule.add_systems(a.before(Between));
335+
schedule.add_systems(b.after(Between));
336+
schedule.add_systems(c.after(Between));
337+
338+
schedule.run(&mut world);
339+
340+
assert!(world.get_resource::<TestState>().unwrap().b_read);
341+
assert!(world.get_resource::<TestState>().unwrap().c_read);
342+
}
343+
}

0 commit comments

Comments
 (0)