1use alloc::vec::Vec;
15
16use crate::ecs::{
17 ColumnTicks, ComponentSlot, ComponentStorage, Entity, EventStore, Events, FrameContext,
18 PayloadLocator, PayloadStore, Resources, Tick, access_check,
19};
20use crate::gfx::profile::FrameProfile;
21use crate::result::CnResult;
22
23#[cfg(debug_assertions)]
26fn note_read<C: ComponentSlot>() {
27 access_check::touch(access_check::Touch::ComponentRead {
28 id: C::DISCRIMINANT,
29 type_name: core::any::type_name::<C>(),
30 });
31}
32
33#[cfg(debug_assertions)]
34fn note_write<C: ComponentSlot>() {
35 access_check::touch(access_check::Touch::ComponentWrite {
36 id: C::DISCRIMINANT,
37 type_name: core::any::type_name::<C>(),
38 });
39}
40
41#[cfg(debug_assertions)]
42fn note_structural(op: &'static str) {
43 access_check::touch(access_check::Touch::Structural { op });
44}
45
46#[cfg(debug_assertions)]
47fn note_resource<T: 'static>(write: bool) {
48 access_check::touch(access_check::Touch::Resource {
49 type_id: core::any::TypeId::of::<T>(),
50 type_name: core::any::type_name::<T>(),
51 write,
52 });
53}
54
55pub struct PipelineContext<'a> {
58 pub components: &'a mut ComponentStorage,
61 pub blob: &'a mut dyn PayloadStore,
65 pub profile: &'a mut FrameProfile,
69 pub resources: &'a mut Resources,
74 pub frame: FrameContext<'a>,
78}
79
80impl<'a> PipelineContext<'a> {
81 pub fn query<C: ComponentSlot>(&self) -> core::slice::Iter<'_, C> {
83 #[cfg(debug_assertions)]
84 note_read::<C>();
85 C::slot(self.components).iter()
86 }
87
88 pub fn query_with_entity<C: ComponentSlot>(&self) -> impl Iterator<Item = (Entity, &C)> {
90 #[cfg(debug_assertions)]
91 note_read::<C>();
92 C::slot(self.components).iter_with_entities()
93 }
94
95 pub fn query_mut<C: ComponentSlot>(&mut self) -> core::slice::IterMut<'_, C> {
97 #[cfg(debug_assertions)]
98 note_write::<C>();
99 self.components.values_mut::<C>().iter_mut()
100 }
101
102 pub fn query_mut_with_entity<C: ComponentSlot>(
107 &mut self,
108 ) -> impl Iterator<Item = (Entity, &mut C)> {
109 #[cfg(debug_assertions)]
110 note_write::<C>();
111 self.components.values_mut_with_entities::<C>()
112 }
113
114 pub fn changed_tick<C: ComponentSlot>(&self) -> Tick {
119 #[cfg(debug_assertions)]
120 note_read::<C>();
121 self.components.changed_tick::<C>()
122 }
123
124 pub fn column_ticks<C: ComponentSlot>(&self) -> ColumnTicks {
130 #[cfg(debug_assertions)]
131 note_read::<C>();
132 self.components.column_ticks::<C>()
133 }
134
135 pub fn changed_rows<C: ComponentSlot>(
140 &self,
141 since: Tick,
142 ) -> impl Iterator<Item = (Entity, &C)> {
143 #[cfg(debug_assertions)]
144 note_read::<C>();
145 self.components.changed_rows::<C>(since)
146 }
147
148 pub fn query_slice_mut<C: ComponentSlot>(&mut self) -> &mut [C] {
152 #[cfg(debug_assertions)]
153 note_write::<C>();
154 self.components.values_mut::<C>()
155 }
156
157 pub fn drain<C: ComponentSlot>(&mut self) -> Vec<C> {
160 #[cfg(debug_assertions)]
161 note_structural("drain");
162 self.components.drain::<C>()
163 }
164
165 pub fn push<C: ComponentSlot>(&mut self, c: C) {
169 #[cfg(debug_assertions)]
170 note_structural("push");
171 self.components.push_typed(c);
172 }
173
174 pub fn insert<C: ComponentSlot>(&mut self, entity: Entity, c: C) {
179 #[cfg(debug_assertions)]
180 note_structural("insert");
181 self.components.insert_typed(entity, c);
182 }
183
184 pub fn remove<C: ComponentSlot>(&mut self, entity: Entity) -> Option<C> {
188 #[cfg(debug_assertions)]
189 note_structural("remove");
190 self.components.remove_typed::<C>(entity)
191 }
192
193 pub fn despawn(&mut self, entity: Entity) {
199 #[cfg(debug_assertions)]
200 note_structural("despawn");
201 self.components.despawn(entity);
202 }
203
204 pub fn is_alive(&self, entity: Entity) -> bool {
208 self.components.is_alive(entity)
209 }
210
211 pub fn get<C: ComponentSlot>(&self, entity: Entity) -> Option<&C> {
215 #[cfg(debug_assertions)]
216 note_read::<C>();
217 self.components.get::<C>(entity)
218 }
219
220 pub fn entities_with_tag(&self, tag: u8) -> &[Entity] {
223 #[cfg(debug_assertions)]
224 access_check::touch(access_check::Touch::ComponentRead {
225 id: tag,
226 type_name: "<by tag>",
227 });
228 self.components.entities_with_tag(tag)
229 }
230
231 pub fn join2<A: ComponentSlot, B: ComponentSlot>(
235 &self,
236 ) -> impl Iterator<Item = (Entity, &A, &B)> {
237 #[cfg(debug_assertions)]
238 {
239 note_read::<A>();
240 note_read::<B>();
241 }
242 self.components.join2::<A, B>()
243 }
244
245 pub fn get_mut<C: ComponentSlot>(&mut self, entity: Entity) -> Option<&mut C> {
249 #[cfg(debug_assertions)]
250 note_write::<C>();
251 self.components.get_mut::<C>(entity)
252 }
253
254 pub fn resource<T: core::any::Any>(&self) -> Option<&T> {
256 #[cfg(debug_assertions)]
257 note_resource::<T>(false);
258 self.resources.get::<T>()
259 }
260
261 pub fn resource_mut<T: core::any::Any>(&mut self) -> Option<&mut T> {
263 #[cfg(debug_assertions)]
264 note_resource::<T>(true);
265 self.resources.get_mut::<T>()
266 }
267
268 pub fn insert_resource<T: core::any::Any + Send>(&mut self, value: T) -> Option<T> {
271 #[cfg(debug_assertions)]
272 note_resource::<T>(true);
273 self.resources.insert(value)
274 }
275
276 pub fn remove_resource<T: core::any::Any>(&mut self) -> Option<T> {
278 #[cfg(debug_assertions)]
279 note_resource::<T>(true);
280 self.resources.remove::<T>()
281 }
282
283 pub fn take_resource<T: core::any::Any + Send + Default>(&mut self) -> Option<T> {
287 #[cfg(debug_assertions)]
288 note_resource::<T>(true);
289 self.resources.take::<T>()
290 }
291
292 pub fn events<E: 'static>(&self) -> Option<&Events<E>> {
295 #[cfg(debug_assertions)]
296 note_resource::<E>(false);
297 self.resources.get::<EventStore>()?.get::<E>()
298 }
299
300 pub fn events_mut<E: Send + 'static>(&mut self) -> &mut Events<E> {
304 #[cfg(debug_assertions)]
305 note_resource::<E>(true);
306 if !self.resources.contains::<EventStore>() {
307 self.resources.insert(EventStore::new());
308 }
309 self.resources
310 .get_mut::<EventStore>()
311 .expect("EventStore was just inserted")
312 .get_mut_or_create::<E>()
313 }
314
315 pub fn read_payload(&mut self, locator: &PayloadLocator) -> Result<&[u8], CnResult> {
321 #[cfg(debug_assertions)]
322 access_check::touch(access_check::Touch::Blob { op: "read_payload" });
323 self.blob.read(locator)
324 }
325
326 pub fn release_blob(&mut self, blob_index: u32) {
331 #[cfg(debug_assertions)]
332 access_check::touch(access_check::Touch::Blob { op: "release_blob" });
333 self.blob.release(blob_index);
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340 use crate::ecs::{AssetKind, BlobAssetDef, ComponentAsset, ComponentTag, EventCursor};
341 use alloc::vec;
342
343 struct EmptyStore;
347
348 impl PayloadStore for EmptyStore {
349 fn read(&mut self, _locator: &PayloadLocator) -> Result<&[u8], CnResult> {
350 Err(CnResult::FileIo)
351 }
352 fn release(&mut self, _blob_index: u32) {}
353 fn disk_backed(&self) -> bool {
354 false
355 }
356 }
357
358 fn parts() -> (
361 ComponentStorage,
362 EmptyStore,
363 FrameProfile,
364 Resources,
365 concinnity_memory::Arena,
366 ) {
367 (
368 ComponentStorage::default(),
369 EmptyStore,
370 FrameProfile::default(),
371 Resources::new(),
372 concinnity_memory::Arena::with_capacity(64 * 1024),
373 )
374 }
375
376 #[test]
377 fn resources_round_trip_through_context() {
378 let (mut c, mut b, mut p, mut r, scratch) = parts();
379 let mut ctx = PipelineContext {
380 components: &mut c,
381 blob: &mut b,
382 profile: &mut p,
383 resources: &mut r,
384 frame: FrameContext::new(&scratch),
385 };
386 assert!(ctx.resource::<u32>().is_none());
387 assert_eq!(ctx.insert_resource(7u32), None);
388 assert_eq!(ctx.resource::<u32>(), Some(&7));
389 *ctx.resource_mut::<u32>().unwrap() = 9;
390 assert_eq!(ctx.resource::<u32>(), Some(&9));
391 assert_eq!(ctx.insert_resource(1u32), Some(9));
393 }
394
395 #[test]
396 fn events_round_trip_through_context() {
397 let (mut c, mut b, mut p, mut r, scratch) = parts();
398 let mut ctx = PipelineContext {
399 components: &mut c,
400 blob: &mut b,
401 profile: &mut p,
402 resources: &mut r,
403 frame: FrameContext::new(&scratch),
404 };
405 assert!(ctx.events::<u32>().is_none());
406 ctx.events_mut::<u32>().send(1);
407 ctx.events_mut::<u32>().send(2);
408
409 let mut cursor = EventCursor::default();
410 let seen: Vec<u32> = ctx
411 .events::<u32>()
412 .unwrap()
413 .read(&mut cursor)
414 .copied()
415 .collect();
416 assert_eq!(seen, vec![1, 2]);
417 assert_eq!(ctx.events::<u32>().unwrap().read(&mut cursor).count(), 0);
419 }
420
421 #[test]
425 fn baked_records_load_through_from_baked() {
426 use crate::components::PointLight;
427 let light = PointLight {
428 intensity: 3.5,
429 range: 12.0,
430 ..Default::default()
431 };
432 let baked = BlobAssetDef {
433 name: None,
434 kind: AssetKind::Component,
435 discriminant: ComponentTag::PointLight as u8,
436 args_bytes: postcard::to_allocvec(&light).unwrap(),
437 payload: None,
438 };
439 let from_baked = ComponentAsset::from_baked(&baked).unwrap();
440 let ComponentAsset::PointLight(b) = &from_baked else {
441 panic!("expected PointLight");
442 };
443 assert_eq!(b.intensity, 3.5);
444 assert_eq!(b.range, 12.0);
445 let mut bad = baked;
447 bad.discriminant = 255;
448 assert_eq!(
449 ComponentAsset::from_baked(&bad).unwrap_err(),
450 CnResult::AssetInvalidType
451 );
452 }
453
454 #[test]
455 fn storage_push_dispatches_into_the_typed_column() {
456 let mut storage = ComponentStorage::default();
457 storage.push(crate::components::Transform::default().into());
458 let census = storage.component_census();
459 assert_eq!(census, vec![(ComponentTag::Transform as u8, 1)]);
461 }
462
463 #[test]
464 fn context_component_ops_cover_the_entity_lifecycle() {
465 use crate::components::{GlobalTransform, Transform};
466 let (mut c, mut b, mut p, mut r, scratch) = parts();
467 let mut ctx = PipelineContext {
468 components: &mut c,
469 blob: &mut b,
470 profile: &mut p,
471 resources: &mut r,
472 frame: FrameContext::new(&scratch),
473 };
474
475 ctx.push(Transform::default());
476 let e = ctx.components.push_typed(Transform::default());
477 assert!(ctx.is_alive(e));
478 assert_eq!(ctx.query::<Transform>().count(), 2);
479 assert_eq!(ctx.query_with_entity::<Transform>().count(), 2);
480
481 for t in ctx.query_mut::<Transform>() {
483 t.position[0] = 1.0;
484 }
485 ctx.query_slice_mut::<Transform>()[0].position[1] = 2.0;
486 ctx.get_mut::<Transform>(e).unwrap().position[2] = 3.0;
487 assert_eq!(ctx.get::<Transform>(e).unwrap().position, [1.0, 0.0, 3.0]);
488
489 ctx.insert(e, GlobalTransform::default());
491 assert_eq!(ctx.join2::<Transform, GlobalTransform>().count(), 1);
492 assert!(ctx.remove::<GlobalTransform>(e).is_some());
493 assert!(ctx.remove::<GlobalTransform>(e).is_none());
494
495 ctx.despawn(e);
497 assert!(!ctx.is_alive(e));
498 assert_eq!(ctx.query::<Transform>().count(), 1);
499 assert!(ctx.get::<Transform>(e).is_none());
500
501 let drained = ctx.drain::<Transform>();
503 assert_eq!(drained.len(), 1);
504 assert_eq!(ctx.query::<Transform>().count(), 0);
505 }
506
507 #[test]
508 fn read_payload_and_release_forward_to_the_store() {
509 let (mut c, mut b, mut p, mut r, scratch) = parts();
510 let mut ctx = PipelineContext {
511 components: &mut c,
512 blob: &mut b,
513 profile: &mut p,
514 resources: &mut r,
515 frame: FrameContext::new(&scratch),
516 };
517 let loc = PayloadLocator {
518 blob_index: 0,
519 offset: 0,
520 len: 4,
521 };
522 assert_eq!(ctx.read_payload(&loc).unwrap_err(), CnResult::FileIo);
524 ctx.release_blob(0);
526 }
527}