1use alloc::vec::Vec;
15
16use crate::ecs::{
17 ColumnTicks, ComponentSlot, ComponentStorage, Entity, EventStore, Events, FrameContext,
18 PayloadLocator, PayloadStore, Resources, Tick,
19};
20#[cfg(debug_assertions)]
23use crate::ecs::access_check;
24use crate::gfx::profile::FrameProfile;
25use crate::result::CnResult;
26
27#[cfg(debug_assertions)]
30fn note_read<C: ComponentSlot>() {
31 access_check::touch(access_check::Touch::ComponentRead {
32 id: C::DISCRIMINANT,
33 type_name: core::any::type_name::<C>(),
34 });
35}
36
37#[cfg(debug_assertions)]
38fn note_write<C: ComponentSlot>() {
39 access_check::touch(access_check::Touch::ComponentWrite {
40 id: C::DISCRIMINANT,
41 type_name: core::any::type_name::<C>(),
42 });
43}
44
45#[cfg(debug_assertions)]
46fn note_structural(op: &'static str) {
47 access_check::touch(access_check::Touch::Structural { op });
48}
49
50#[cfg(debug_assertions)]
51fn note_resource<T: 'static>(write: bool) {
52 access_check::touch(access_check::Touch::Resource {
53 type_id: core::any::TypeId::of::<T>(),
54 type_name: core::any::type_name::<T>(),
55 write,
56 });
57}
58
59pub struct PipelineContext<'a> {
62 pub components: &'a mut ComponentStorage,
65 pub blob: &'a mut dyn PayloadStore,
69 pub profile: &'a mut FrameProfile,
73 pub resources: &'a mut Resources,
78 pub frame: FrameContext<'a>,
82}
83
84impl<'a> PipelineContext<'a> {
85 pub fn query<C: ComponentSlot>(&self) -> core::slice::Iter<'_, C> {
87 #[cfg(debug_assertions)]
88 note_read::<C>();
89 C::slot(self.components).iter()
90 }
91
92 pub fn query_with_entity<C: ComponentSlot>(&self) -> impl Iterator<Item = (Entity, &C)> {
94 #[cfg(debug_assertions)]
95 note_read::<C>();
96 C::slot(self.components).iter_with_entities()
97 }
98
99 pub fn query_mut<C: ComponentSlot>(&mut self) -> core::slice::IterMut<'_, C> {
101 #[cfg(debug_assertions)]
102 note_write::<C>();
103 self.components.values_mut::<C>().iter_mut()
104 }
105
106 pub fn query_mut_with_entity<C: ComponentSlot>(
111 &mut self,
112 ) -> impl Iterator<Item = (Entity, &mut C)> {
113 #[cfg(debug_assertions)]
114 note_write::<C>();
115 self.components.values_mut_with_entities::<C>()
116 }
117
118 pub fn changed_tick<C: ComponentSlot>(&self) -> Tick {
123 #[cfg(debug_assertions)]
124 note_read::<C>();
125 self.components.changed_tick::<C>()
126 }
127
128 pub fn column_ticks<C: ComponentSlot>(&self) -> ColumnTicks {
134 #[cfg(debug_assertions)]
135 note_read::<C>();
136 self.components.column_ticks::<C>()
137 }
138
139 pub fn changed_rows<C: ComponentSlot>(
144 &self,
145 since: Tick,
146 ) -> impl Iterator<Item = (Entity, &C)> {
147 #[cfg(debug_assertions)]
148 note_read::<C>();
149 self.components.changed_rows::<C>(since)
150 }
151
152 pub fn query_slice_mut<C: ComponentSlot>(&mut self) -> &mut [C] {
156 #[cfg(debug_assertions)]
157 note_write::<C>();
158 self.components.values_mut::<C>()
159 }
160
161 pub fn drain<C: ComponentSlot>(&mut self) -> Vec<C> {
164 #[cfg(debug_assertions)]
165 note_structural("drain");
166 self.components.drain::<C>()
167 }
168
169 pub fn push<C: ComponentSlot>(&mut self, c: C) {
173 #[cfg(debug_assertions)]
174 note_structural("push");
175 self.components.push_typed(c);
176 }
177
178 pub fn insert<C: ComponentSlot>(&mut self, entity: Entity, c: C) {
183 #[cfg(debug_assertions)]
184 note_structural("insert");
185 self.components.insert_typed(entity, c);
186 }
187
188 pub fn remove<C: ComponentSlot>(&mut self, entity: Entity) -> Option<C> {
192 #[cfg(debug_assertions)]
193 note_structural("remove");
194 self.components.remove_typed::<C>(entity)
195 }
196
197 pub fn despawn(&mut self, entity: Entity) {
203 #[cfg(debug_assertions)]
204 note_structural("despawn");
205 self.components.despawn(entity);
206 }
207
208 pub fn is_alive(&self, entity: Entity) -> bool {
212 self.components.is_alive(entity)
213 }
214
215 pub fn get<C: ComponentSlot>(&self, entity: Entity) -> Option<&C> {
219 #[cfg(debug_assertions)]
220 note_read::<C>();
221 self.components.get::<C>(entity)
222 }
223
224 pub fn entities_with_tag(&self, tag: u8) -> &[Entity] {
227 #[cfg(debug_assertions)]
228 access_check::touch(access_check::Touch::ComponentRead {
229 id: tag,
230 type_name: "<by tag>",
231 });
232 self.components.entities_with_tag(tag)
233 }
234
235 pub fn join2<A: ComponentSlot, B: ComponentSlot>(
239 &self,
240 ) -> impl Iterator<Item = (Entity, &A, &B)> {
241 #[cfg(debug_assertions)]
242 {
243 note_read::<A>();
244 note_read::<B>();
245 }
246 self.components.join2::<A, B>()
247 }
248
249 pub fn get_mut<C: ComponentSlot>(&mut self, entity: Entity) -> Option<&mut C> {
253 #[cfg(debug_assertions)]
254 note_write::<C>();
255 self.components.get_mut::<C>(entity)
256 }
257
258 pub fn resource<T: core::any::Any>(&self) -> Option<&T> {
260 #[cfg(debug_assertions)]
261 note_resource::<T>(false);
262 self.resources.get::<T>()
263 }
264
265 pub fn resource_mut<T: core::any::Any>(&mut self) -> Option<&mut T> {
267 #[cfg(debug_assertions)]
268 note_resource::<T>(true);
269 self.resources.get_mut::<T>()
270 }
271
272 pub fn insert_resource<T: core::any::Any + Send>(&mut self, value: T) -> Option<T> {
275 #[cfg(debug_assertions)]
276 note_resource::<T>(true);
277 self.resources.insert(value)
278 }
279
280 pub fn remove_resource<T: core::any::Any>(&mut self) -> Option<T> {
282 #[cfg(debug_assertions)]
283 note_resource::<T>(true);
284 self.resources.remove::<T>()
285 }
286
287 pub fn take_resource<T: core::any::Any + Send + Default>(&mut self) -> Option<T> {
291 #[cfg(debug_assertions)]
292 note_resource::<T>(true);
293 self.resources.take::<T>()
294 }
295
296 pub fn events<E: 'static>(&self) -> Option<&Events<E>> {
299 #[cfg(debug_assertions)]
300 note_resource::<E>(false);
301 self.resources.get::<EventStore>()?.get::<E>()
302 }
303
304 pub fn events_mut<E: Send + 'static>(&mut self) -> &mut Events<E> {
308 #[cfg(debug_assertions)]
309 note_resource::<E>(true);
310 if !self.resources.contains::<EventStore>() {
311 self.resources.insert(EventStore::new());
312 }
313 self.resources
314 .get_mut::<EventStore>()
315 .expect("EventStore was just inserted")
316 .get_mut_or_create::<E>()
317 }
318
319 pub fn read_payload(&mut self, locator: &PayloadLocator) -> Result<&[u8], CnResult> {
325 #[cfg(debug_assertions)]
326 access_check::touch(access_check::Touch::Blob { op: "read_payload" });
327 self.blob.read(locator)
328 }
329
330 pub fn release_blob(&mut self, blob_index: u32) {
335 #[cfg(debug_assertions)]
336 access_check::touch(access_check::Touch::Blob { op: "release_blob" });
337 self.blob.release(blob_index);
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344 use crate::ecs::{AssetKind, BlobAssetDef, ComponentAsset, ComponentTag, EventCursor};
345 use alloc::vec;
346
347 struct EmptyStore;
351
352 impl PayloadStore for EmptyStore {
353 fn read(&mut self, _locator: &PayloadLocator) -> Result<&[u8], CnResult> {
354 Err(CnResult::FileIo)
355 }
356 fn release(&mut self, _blob_index: u32) {}
357 fn disk_backed(&self) -> bool {
358 false
359 }
360 }
361
362 fn parts() -> (
365 ComponentStorage,
366 EmptyStore,
367 FrameProfile,
368 Resources,
369 crate::memory::Arena,
370 ) {
371 (
372 ComponentStorage::default(),
373 EmptyStore,
374 FrameProfile::default(),
375 Resources::new(),
376 crate::memory::Arena::with_capacity(64 * 1024),
377 )
378 }
379
380 #[test]
381 fn resources_round_trip_through_context() {
382 let (mut c, mut b, mut p, mut r, scratch) = parts();
383 let mut ctx = PipelineContext {
384 components: &mut c,
385 blob: &mut b,
386 profile: &mut p,
387 resources: &mut r,
388 frame: FrameContext::new(&scratch),
389 };
390 assert!(ctx.resource::<u32>().is_none());
391 assert_eq!(ctx.insert_resource(7u32), None);
392 assert_eq!(ctx.resource::<u32>(), Some(&7));
393 *ctx.resource_mut::<u32>().unwrap() = 9;
394 assert_eq!(ctx.resource::<u32>(), Some(&9));
395 assert_eq!(ctx.insert_resource(1u32), Some(9));
397 }
398
399 #[test]
400 fn events_round_trip_through_context() {
401 let (mut c, mut b, mut p, mut r, scratch) = parts();
402 let mut ctx = PipelineContext {
403 components: &mut c,
404 blob: &mut b,
405 profile: &mut p,
406 resources: &mut r,
407 frame: FrameContext::new(&scratch),
408 };
409 assert!(ctx.events::<u32>().is_none());
410 ctx.events_mut::<u32>().send(1);
411 ctx.events_mut::<u32>().send(2);
412
413 let mut cursor = EventCursor::default();
414 let seen: Vec<u32> = ctx
415 .events::<u32>()
416 .unwrap()
417 .read(&mut cursor)
418 .copied()
419 .collect();
420 assert_eq!(seen, vec![1, 2]);
421 assert_eq!(ctx.events::<u32>().unwrap().read(&mut cursor).count(), 0);
423 }
424
425 #[test]
429 fn baked_records_load_through_from_baked() {
430 use crate::components::PointLight;
431 let light = PointLight {
432 intensity: 3.5,
433 range: 12.0,
434 ..Default::default()
435 };
436 let baked = BlobAssetDef {
437 name: None,
438 kind: AssetKind::Component,
439 discriminant: ComponentTag::PointLight as u8,
440 args_bytes: postcard::to_allocvec(&light).unwrap(),
441 payload: None,
442 };
443 let from_baked = ComponentAsset::from_baked(&baked).unwrap();
444 let ComponentAsset::PointLight(b) = &from_baked else {
445 panic!("expected PointLight");
446 };
447 assert_eq!(b.intensity, 3.5);
448 assert_eq!(b.range, 12.0);
449 let mut bad = baked;
451 bad.discriminant = 255;
452 assert_eq!(
453 ComponentAsset::from_baked(&bad).unwrap_err(),
454 CnResult::AssetInvalidType
455 );
456 }
457
458 #[test]
459 fn storage_push_dispatches_into_the_typed_column() {
460 let mut storage = ComponentStorage::default();
461 storage.push(crate::components::Transform::default().into());
462 let census = storage.component_census();
463 assert_eq!(census, vec![(ComponentTag::Transform as u8, 1)]);
465 }
466
467 #[test]
468 fn context_component_ops_cover_the_entity_lifecycle() {
469 use crate::components::{GlobalTransform, Transform};
470 let (mut c, mut b, mut p, mut r, scratch) = parts();
471 let mut ctx = PipelineContext {
472 components: &mut c,
473 blob: &mut b,
474 profile: &mut p,
475 resources: &mut r,
476 frame: FrameContext::new(&scratch),
477 };
478
479 ctx.push(Transform::default());
480 let e = ctx.components.push_typed(Transform::default());
481 assert!(ctx.is_alive(e));
482 assert_eq!(ctx.query::<Transform>().count(), 2);
483 assert_eq!(ctx.query_with_entity::<Transform>().count(), 2);
484
485 for t in ctx.query_mut::<Transform>() {
487 t.position[0] = 1.0;
488 }
489 ctx.query_slice_mut::<Transform>()[0].position[1] = 2.0;
490 ctx.get_mut::<Transform>(e).unwrap().position[2] = 3.0;
491 assert_eq!(ctx.get::<Transform>(e).unwrap().position, [1.0, 0.0, 3.0]);
492
493 ctx.insert(e, GlobalTransform::default());
495 assert_eq!(ctx.join2::<Transform, GlobalTransform>().count(), 1);
496 assert!(ctx.remove::<GlobalTransform>(e).is_some());
497 assert!(ctx.remove::<GlobalTransform>(e).is_none());
498
499 ctx.despawn(e);
501 assert!(!ctx.is_alive(e));
502 assert_eq!(ctx.query::<Transform>().count(), 1);
503 assert!(ctx.get::<Transform>(e).is_none());
504
505 let drained = ctx.drain::<Transform>();
507 assert_eq!(drained.len(), 1);
508 assert_eq!(ctx.query::<Transform>().count(), 0);
509 }
510
511 #[test]
512 fn read_payload_and_release_forward_to_the_store() {
513 let (mut c, mut b, mut p, mut r, scratch) = parts();
514 let mut ctx = PipelineContext {
515 components: &mut c,
516 blob: &mut b,
517 profile: &mut p,
518 resources: &mut r,
519 frame: FrameContext::new(&scratch),
520 };
521 let loc = PayloadLocator {
522 blob_index: 0,
523 offset: 0,
524 len: 4,
525 };
526 assert_eq!(ctx.read_payload(&loc).unwrap_err(), CnResult::FileIo);
528 ctx.release_blob(0);
530 }
531}