async_ecs/join/mod.rs
1mod impls;
2mod iter;
3mod maybe;
4mod parallel;
5
6pub use iter::JoinIter;
7pub use maybe::MaybeJoin;
8pub use parallel::JoinParIter;
9
10use hibitset::BitSetLike;
11
12use crate::entity::Index;
13
14/// The purpose of the `Join` trait is to provide a way
15/// to access multiple storages at the same time with
16/// the merged bit set.
17///
18/// Joining component storages means that you'll only get values where
19/// for a given entity every storage has an associated component.
20///
21/// ## Example
22///
23/// ```
24/// # use async_ecs::*;
25/// #
26/// # #[derive(Debug, PartialEq)]
27/// # struct Pos;
28/// # impl Component for Pos { type Storage = VecStorage<Self>; }
29/// #
30/// # #[derive(Debug, PartialEq)]
31/// # struct Vel;
32/// # impl Component for Vel { type Storage = VecStorage<Self>; }
33/// #
34/// let mut world = World::default();
35///
36/// world.register_component::<Pos>();
37/// world.register_component::<Vel>();
38///
39/// {
40/// let pos = world.component::<Pos>();
41/// let vel = world.component::<Vel>();
42///
43/// // There are no entities yet, so no pair will be returned.
44/// let joined: Vec<_> = (&pos, &vel).join().collect();
45/// assert_eq!(joined, vec![]);
46/// }
47///
48/// world.create_entity().with(Pos).build();
49///
50/// {
51/// let pos = world.component::<Pos>();
52/// let vel = world.component::<Vel>();
53///
54/// // Although there is an entity, it only has `Pos`.
55/// let joined: Vec<_> = (&pos, &vel).join().collect();
56/// assert_eq!(joined, vec![]);
57/// }
58///
59/// let ent = world.create_entity().with(Pos).with(Vel).build();
60///
61/// {
62/// let pos = world.component::<Pos>();
63/// let vel = world.component::<Vel>();
64///
65/// // Now there is one entity that has both a `Vel` and a `Pos`.
66/// let joined: Vec<_> = (&pos, &vel).join().collect();
67/// assert_eq!(joined, vec![(&Pos, &Vel)]);
68///
69/// // If we want to get the entity the components are associated to,
70/// // we need to join over `Entities`:
71///
72/// let entities = world.entities();
73/// // note: `Entities` is the fetched resource; we get back
74/// // `Read<Entities>`.
75/// // `Read<Entities>` can also be referred to by `Entities` which
76/// // is a shorthand type definition to the former type.
77///
78/// let joined: Vec<_> = (&entities, &pos, &vel).join().collect();
79/// assert_eq!(joined, vec![(ent, &Pos, &Vel)]);
80/// }
81/// ```
82///
83/// ## Iterating over a single storage
84///
85/// `Join` can also be used to iterate over a single
86/// storage, just by writing `(&storage).join()`.
87pub trait Join {
88 /// Type of joined components.
89 type Type;
90
91 /// Type of joined storages.
92 type Value;
93
94 /// Type of joined bit mask.
95 type Mask: BitSetLike;
96
97 /// Create a joined iterator over the contents.
98 fn join(self) -> JoinIter<Self>
99 where
100 Self: Sized,
101 {
102 JoinIter::new(self)
103 }
104
105 /// Returns a `Join`-able structure that yields all indices, returning
106 /// `None` for all missing elements and `Some(T)` for found elements.
107 ///
108 /// WARNING: Do not have a join of only `MaybeJoin`s. Otherwise the join
109 /// will iterate over every single index of the bitset. If you want a
110 /// join with all `MaybeJoin`s, add an `Entities` to the join as well
111 /// to bound the join to all entities that are alive.
112 ///
113 /// ```
114 /// # use async_ecs::*;
115 /// #
116 /// # #[derive(Debug, PartialEq)]
117 /// # struct Pos { x: i32, y: i32 }
118 /// # impl Component for Pos { type Storage = VecStorage<Self>; }
119 /// #
120 /// # #[derive(Debug, PartialEq)]
121 /// # struct Vel { x: i32, y: i32 }
122 /// # impl Component for Vel { type Storage = VecStorage<Self>; }
123 /// #
124 /// struct ExampleSystem;
125 ///
126 /// impl<'a> System<'a> for ExampleSystem {
127 /// type SystemData = (WriteStorage<'a, Pos>, ReadStorage<'a, Vel>);
128 ///
129 /// fn run(&mut self, (mut positions, velocities): Self::SystemData) {
130 /// for (mut position, maybe_velocity) in (&mut positions, velocities.maybe()).join() {
131 /// if let Some(velocity) = maybe_velocity {
132 /// position.x += velocity.x;
133 /// position.y += velocity.y;
134 /// }
135 /// }
136 /// }
137 /// }
138 ///
139 /// #[tokio::main]
140 /// async fn main() {
141 /// let mut world = World::default();
142 /// let mut dispatcher = Dispatcher::setup_builder(&mut world)
143 /// .with(ExampleSystem, "example_system", &[])
144 /// .unwrap()
145 /// .build();
146 ///
147 /// let e1 = world
148 /// .create_entity()
149 /// .with(Pos { x: 0, y: 0 })
150 /// .with(Vel { x: 5, y: 2 })
151 /// .build();
152 ///
153 /// let e2 = world.create_entity().with(Pos { x: 0, y: 0 }).build();
154 ///
155 /// dispatcher.dispatch(&mut world).await;
156 ///
157 /// let positions = world.component::<Pos>();
158 /// assert_eq!(positions.get(e1), Some(&Pos { x: 5, y: 2 }));
159 /// assert_eq!(positions.get(e2), Some(&Pos { x: 0, y: 0 }));
160 /// }
161 /// ```
162 fn maybe(self) -> MaybeJoin<Self>
163 where
164 Self: Sized,
165 {
166 MaybeJoin(self)
167 }
168
169 /// Open this join by returning the mask and the storages.
170 ///
171 /// # Safety
172 ///
173 /// This is unsafe because implementations of this trait can permit
174 /// the `Value` to be mutated independently of the `Mask`.
175 /// If the `Mask` does not correctly report the status of the `Value`
176 /// then illegal memory access can occur.
177 unsafe fn open(self) -> (Self::Mask, Self::Value);
178
179 /// Get a joined component value by a given index.
180 ///
181 /// # Safety
182 ///
183 /// * A call to `get` must be preceded by a check if `id` is part of
184 /// `Self::Mask`
185 /// * The implementation of this method may use unsafe code, but has no
186 /// invariants to meet
187 unsafe fn get(value: &mut Self::Value, index: Index) -> Self::Type;
188
189 /// If this `Join` typically returns all indices in the mask, then iterating
190 /// over only it or combined with other joins that are also dangerous
191 /// will cause the `JoinIter`/`ParJoin` to go through all indices which
192 /// is usually not what is wanted and will kill performance.
193 #[inline]
194 fn is_unconstrained() -> bool {
195 false
196 }
197}
198
199// SAFETY: This is safe as long as `T` implements `ParJoin` safely. `MaybeJoin`
200// relies on `T as Join` for all storage access and safely wraps the inner
201// `Join` API, so it should also be able to implement `ParJoin`.
202pub trait ParJoin: Join {
203 fn par_join(self) -> JoinParIter<Self>
204 where
205 Self: Sized,
206 {
207 if <Self as Join>::is_unconstrained() {
208 log::warn!(
209 "`ParJoin` possibly iterating through all indices, you might've made a join with all `MaybeJoin`s, which is unbounded in length."
210 );
211 }
212
213 JoinParIter::new(self)
214 }
215}