Skip to content

Commit 568d7b0

Browse files
committed
NonUniqueResource
1 parent 506bdc5 commit 568d7b0

File tree

8 files changed

+469
-4
lines changed

8 files changed

+469
-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: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
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+
NonUniqueResourceSystem::<_, _, _, _, true>::new(*self, |(), ptr| unsafe {
173+
ptr.assert_unique()
174+
.deref_mut::<NonUniqueResourceEntry<T>>()
175+
.value
176+
.take()
177+
})
178+
}
179+
180+
/// Read the value if it is set, panic otherwise.
181+
pub fn read_system(&self) -> impl System<In = (), Out = T> {
182+
// Slightly inefficient: we store index twice in the resulting system.
183+
let index = self.index.index();
184+
self.read_opt_system().map(move |opt| match opt {
185+
Some(v) => v,
186+
None => panic!(
187+
"Non-unique resource {}.{} is not set",
188+
any::type_name::<T>(),
189+
index
190+
),
191+
})
192+
}
193+
194+
/// Read the value if it is set, return `None` otherwise.
195+
///
196+
/// Keeps the value in the resource.
197+
pub fn read_opt_clone_system(&self) -> impl System<In = (), Out = Option<T>>
198+
where
199+
T: Clone,
200+
{
201+
NonUniqueResourceSystem::<_, _, _, _, false>::new(*self, |(), ptr| unsafe {
202+
ptr.deref::<NonUniqueResourceEntry<T>>().value.clone()
203+
})
204+
}
205+
206+
/// Read the value if it is set, panic otherwise.
207+
///
208+
/// Keeps the value in the resource.
209+
pub fn read_clone_system(&self) -> impl System<In = (), Out = T>
210+
where
211+
T: Clone,
212+
{
213+
// Slightly inefficient: we store index twice in the resulting system.
214+
let index = self.index.index();
215+
self.read_opt_clone_system().map(move |opt| match opt {
216+
Some(v) => v,
217+
None => panic!(
218+
"Non-unique resource {}.{} is not set",
219+
any::type_name::<T>(),
220+
index
221+
),
222+
})
223+
}
224+
225+
/// Write the value overwriting the previous one.
226+
pub fn write_opt_system(&self) -> impl System<In = Option<T>, Out = ()> {
227+
NonUniqueResourceSystem::<_, _, _, _, true>::new(*self, |value, ptr| unsafe {
228+
ptr.assert_unique()
229+
.deref_mut::<NonUniqueResourceEntry<T>>()
230+
.value = value
231+
})
232+
}
233+
234+
/// Write the value overwriting the previous one.
235+
pub fn write_system(&self) -> impl System<In = T, Out = ()> {
236+
(|In(value)| Some(value)).pipe(self.write_opt_system())
237+
}
238+
239+
/// Write the given value into the resource.
240+
pub fn write_value_system(&self, value: T) -> impl System<In = (), Out = ()>
241+
where
242+
T: Clone,
243+
{
244+
(move || value.clone()).pipe(self.write_system())
245+
}
246+
247+
/// Unset the resource.
248+
pub fn remove_system(&self) -> impl System<In = (), Out = ()> {
249+
(|| None).pipe(self.write_opt_system())
250+
}
251+
}
252+
253+
#[cfg(test)]
254+
mod tests {
255+
use crate as bevy_ecs;
256+
use crate::schedule::IntoSystemConfigs;
257+
use crate::schedule::Schedule;
258+
use crate::schedule::SystemSet;
259+
use crate::system::Resource;
260+
use crate::system::{In, IntoSystem, ResMut};
261+
use crate::world::World;
262+
263+
#[test]
264+
fn test_write_read() {
265+
let mut world = World::default();
266+
267+
let res = world.new_non_unique_resource::<String>();
268+
269+
#[derive(Resource, Default)]
270+
struct TestState(bool);
271+
272+
world.init_resource::<TestState>();
273+
274+
let a = res.write_value_system("a".to_owned());
275+
276+
let b = res
277+
.read_system()
278+
.pipe(|In(v), mut result: ResMut<TestState>| {
279+
assert_eq!("a", v);
280+
assert!(!result.0);
281+
result.0 = true;
282+
});
283+
284+
#[derive(SystemSet, Clone, Debug, Eq, PartialEq, Hash)]
285+
struct Between;
286+
287+
let mut schedule = Schedule::default();
288+
schedule.add_systems(a.before(Between));
289+
schedule.add_systems(b.after(Between));
290+
291+
schedule.run(&mut world);
292+
293+
assert!(world.get_resource::<TestState>().unwrap().0);
294+
}
295+
296+
#[test]
297+
fn test_write_read_clone() {
298+
let mut world = World::default();
299+
300+
let res = world.new_non_unique_resource::<String>();
301+
302+
#[derive(Resource, Default)]
303+
struct TestState {
304+
b_read: bool,
305+
c_read: bool,
306+
}
307+
308+
world.init_resource::<TestState>();
309+
310+
let a = res.write_value_system("a".to_owned());
311+
312+
let b = res
313+
.read_clone_system()
314+
.pipe(|In(v): In<String>, mut result: ResMut<TestState>| {
315+
assert_eq!("a", v);
316+
assert!(!result.b_read);
317+
result.b_read = true;
318+
});
319+
let c = res
320+
.read_clone_system()
321+
.pipe(|In(v): In<String>, mut result: ResMut<TestState>| {
322+
assert_eq!("a", v);
323+
assert!(!result.c_read);
324+
result.c_read = true;
325+
});
326+
327+
#[derive(SystemSet, Clone, Debug, Eq, PartialEq, Hash)]
328+
struct Between;
329+
330+
let mut schedule = Schedule::default();
331+
schedule.add_systems(a.before(Between));
332+
schedule.add_systems(b.after(Between));
333+
schedule.add_systems(c.after(Between));
334+
335+
schedule.run(&mut world);
336+
337+
assert!(world.get_resource::<TestState>().unwrap().b_read);
338+
assert!(world.get_resource::<TestState>().unwrap().c_read);
339+
}
340+
}

0 commit comments

Comments
 (0)