Skip to content

Commit 4990dcf

Browse files
committed
NonUniqueResource
1 parent 506bdc5 commit 4990dcf

File tree

8 files changed

+473
-4
lines changed

8 files changed

+473
-4
lines changed

crates/bevy_ecs/src/component.rs

+23
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

+1
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;
+344
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
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+
&mut self
121+
.system_meta
122+
.component_access_set
123+
.add_unfiltered_write(self.non_unique_resource_ref.component_id);
124+
125+
&mut self
126+
.system_meta
127+
.archetype_component_access
128+
.add_write(self.non_unique_resource_ref.archetype_component_id);
129+
} else {
130+
&mut self
131+
.system_meta
132+
.component_access_set
133+
.add_unfiltered_read(self.non_unique_resource_ref.component_id);
134+
135+
&mut self
136+
.system_meta
137+
.archetype_component_access
138+
.add_read(self.non_unique_resource_ref.archetype_component_id);
139+
}
140+
}
141+
142+
fn check_change_tick(&mut self, change_tick: Tick) {
143+
check_system_change_tick(
144+
&mut self.system_meta.last_run,
145+
change_tick,
146+
self.system_meta.name.as_ref(),
147+
);
148+
}
149+
150+
fn get_last_run(&self) -> Tick {
151+
self.system_meta.last_run
152+
}
153+
154+
fn set_last_run(&mut self, last_run: Tick) {
155+
self.system_meta.last_run = last_run;
156+
}
157+
}
158+
159+
impl<T: Sync + Send + 'static> NonUniqueResourceRef<T> {
160+
// Technically this function is unsafe because argument must match.
161+
pub(crate) fn new(
162+
component_id: ComponentId,
163+
index: TableRow,
164+
archetype_component_id: ArchetypeComponentId,
165+
) -> Self {
166+
NonUniqueResourceRef {
167+
component_id,
168+
index,
169+
archetype_component_id,
170+
_phantom: PhantomData,
171+
}
172+
}
173+
174+
/// Read the value if it is set, return `None` otherwise.
175+
pub fn read_opt_system(&self) -> impl System<In = (), Out = Option<T>> {
176+
NonUniqueResourceSystem::<_, _, _, _, true>::new(*self, |(), ptr| unsafe {
177+
ptr.assert_unique()
178+
.deref_mut::<NonUniqueResourceEntry<T>>()
179+
.value
180+
.take()
181+
})
182+
}
183+
184+
/// Read the value if it is set, panic otherwise.
185+
pub fn read_system(&self) -> impl System<In = (), Out = T> {
186+
// Slightly inefficient: we store index twice in the resulting system.
187+
let index = self.index.index();
188+
self.read_opt_system().map(move |opt| match opt {
189+
Some(v) => v,
190+
None => panic!(
191+
"Non-unique resource {}.{} is not set",
192+
any::type_name::<T>(),
193+
index
194+
),
195+
})
196+
}
197+
198+
/// Read the value if it is set, return `None` otherwise.
199+
///
200+
/// Keeps the value in the resource.
201+
pub fn read_opt_clone_system(&self) -> impl System<In = (), Out = Option<T>>
202+
where
203+
T: Clone,
204+
{
205+
NonUniqueResourceSystem::<_, _, _, _, false>::new(*self, |(), ptr| unsafe {
206+
ptr.deref::<NonUniqueResourceEntry<T>>().value.clone()
207+
})
208+
}
209+
210+
/// Read the value if it is set, panic otherwise.
211+
///
212+
/// Keeps the value in the resource.
213+
pub fn read_clone_system(&self) -> impl System<In = (), Out = T>
214+
where
215+
T: Clone,
216+
{
217+
// Slightly inefficient: we store index twice in the resulting system.
218+
let index = self.index.index();
219+
self.read_opt_clone_system().map(move |opt| match opt {
220+
Some(v) => v,
221+
None => panic!(
222+
"Non-unique resource {}.{} is not set",
223+
any::type_name::<T>(),
224+
index
225+
),
226+
})
227+
}
228+
229+
/// Write the value overwriting the previous one.
230+
pub fn write_opt_system(&self) -> impl System<In = Option<T>, Out = ()> {
231+
NonUniqueResourceSystem::<_, _, _, _, true>::new(*self, |value, ptr| unsafe {
232+
ptr.assert_unique()
233+
.deref_mut::<NonUniqueResourceEntry<T>>()
234+
.value = value
235+
})
236+
}
237+
238+
/// Write the value overwriting the previous one.
239+
pub fn write_system(&self) -> impl System<In = T, Out = ()> {
240+
(|In(value)| Some(value)).pipe(self.write_opt_system())
241+
}
242+
243+
/// Write the given value into the resource.
244+
pub fn write_value_system(&self, value: T) -> impl System<In = (), Out = ()>
245+
where
246+
T: Clone,
247+
{
248+
(move || value.clone()).pipe(self.write_system())
249+
}
250+
251+
/// Unset the resource.
252+
pub fn remove_system(&self) -> impl System<In = (), Out = ()> {
253+
(|| None).pipe(self.write_opt_system())
254+
}
255+
}
256+
257+
#[cfg(test)]
258+
mod tests {
259+
use crate as bevy_ecs;
260+
use crate::schedule::IntoSystemConfigs;
261+
use crate::schedule::Schedule;
262+
use crate::schedule::SystemSet;
263+
use crate::system::Resource;
264+
use crate::system::{In, IntoSystem, ResMut};
265+
use crate::world::World;
266+
267+
#[test]
268+
fn test_write_read() {
269+
let mut world = World::default();
270+
271+
let res = world.new_non_unique_resource::<String>();
272+
273+
#[derive(Resource, Default)]
274+
struct TestState(bool);
275+
276+
world.init_resource::<TestState>();
277+
278+
let a = res.write_value_system("a".to_owned());
279+
280+
let b = res
281+
.read_system()
282+
.pipe(|In(v), mut result: ResMut<TestState>| {
283+
assert_eq!("a", v);
284+
assert!(!result.0);
285+
result.0 = true;
286+
});
287+
288+
#[derive(SystemSet, Clone, Debug, Eq, PartialEq, Hash)]
289+
struct Between;
290+
291+
let mut schedule = Schedule::default();
292+
schedule.add_systems(a.before(Between));
293+
schedule.add_systems(b.after(Between));
294+
295+
schedule.run(&mut world);
296+
297+
assert!(world.get_resource::<TestState>().unwrap().0);
298+
}
299+
300+
#[test]
301+
fn test_write_read_clone() {
302+
let mut world = World::default();
303+
304+
let res = world.new_non_unique_resource::<String>();
305+
306+
#[derive(Resource, Default)]
307+
struct TestState {
308+
b_read: bool,
309+
c_read: bool,
310+
}
311+
312+
world.init_resource::<TestState>();
313+
314+
let a = res.write_value_system("a".to_owned());
315+
316+
let b = res
317+
.read_clone_system()
318+
.pipe(|In(v): In<String>, mut result: ResMut<TestState>| {
319+
assert_eq!("a", v);
320+
assert!(!result.b_read);
321+
result.b_read = true;
322+
});
323+
let c = res
324+
.read_clone_system()
325+
.pipe(|In(v): In<String>, mut result: ResMut<TestState>| {
326+
assert_eq!("a", v);
327+
assert!(!result.c_read);
328+
result.c_read = true;
329+
});
330+
331+
#[derive(SystemSet, Clone, Debug, Eq, PartialEq, Hash)]
332+
struct Between;
333+
334+
let mut schedule = Schedule::default();
335+
schedule.add_systems(a.before(Between));
336+
schedule.add_systems(b.after(Between));
337+
schedule.add_systems(c.after(Between));
338+
339+
schedule.run(&mut world);
340+
341+
assert!(world.get_resource::<TestState>().unwrap().b_read);
342+
assert!(world.get_resource::<TestState>().unwrap().c_read);
343+
}
344+
}

0 commit comments

Comments
 (0)