use super::RenderData; #[test] fn dirty_signal_is_consumed_once() { let mut data = RenderData::new(64).unwrap(); assert!(data.rows("signals").is_some()); assert!(data.begin_render()); assert!(!data.begin_render()); data.mark_dirty(); assert!(data.begin_render()); assert!(!data.render_pending()); } #[test] fn skip_and_bundle_signals_hold_dirty_work() { let mut data = RenderData::new(64).unwrap(); assert!(data.begin_render()); data.write_signal(4, 1.0); data.mark_dirty(); assert!(!data.begin_render()); data.write_signal(4, 0.0); assert!(data.begin_render()); data.write_signal(6, 1.0); data.mark_dirty(); assert!(!data.begin_render()); data.loadout_ready(); assert!(data.begin_render()); } fn rows_of(data: &RenderData, name: &str) -> u32 { data.rows(name).unwrap().rows } #[test] fn spans_reuse_coalesced_gaps_before_growing() { let mut data = RenderData::new(4096).unwrap(); data.create_rows("v".into(), 8, 16, "f32".into()).unwrap(); assert_eq!(data.allocate_object("v", 2).unwrap(), (0, false, false)); assert_eq!(data.allocate_object("v", 3).unwrap(), (2, false, false)); assert_eq!(data.allocate_object("v", 3).unwrap(), (5, false, false)); assert_eq!(rows_of(&data, "v"), 8); // Freeing the two middle spans leaves one coalesced hole of 6 rows. data.delete_object("v", 2).unwrap(); data.delete_object("v", 5).unwrap(); assert_eq!(data.delete_object("v", 5), Err("OBJECT_UNKNOWN")); assert_eq!(data.allocate_object("v", 6).unwrap(), (2, false, false)); assert_eq!(rows_of(&data, "v"), 8); // A span that does not fit doubles capacity instead of growing to the exact need; the only // array in the arena extends in place, so nothing is staged. assert_eq!(data.allocate_object("v", 1).unwrap(), (8, true, false)); assert_eq!(rows_of(&data, "v"), 16); assert_eq!(data.allocate_object("v", 40).unwrap(), (9, true, false)); assert_eq!(rows_of(&data, "v"), 56); assert_eq!(data.allocate_object("v", 0), Err("ROWS")); } #[test] fn deleting_a_span_zeroes_every_row_and_keeps_neighbours() { let mut data = RenderData::new(4096).unwrap(); data.create_rows("v".into(), 4, 16, "u32".into()).unwrap(); let (first, ..) = data.allocate_object("v", 2).unwrap(); let (second, ..) = data.allocate_object("v", 1).unwrap(); let base = (data.rows("v").unwrap().offset - data.base) as usize + data.arena_start; data.arena[base..base + 48].fill(0xAB); data.delete_object("v", first).unwrap(); assert!(data.bytes("v").unwrap()[..32].iter().all(|byte| *byte == 0)); assert!(data.bytes("v").unwrap()[32..48] .iter() .all(|byte| *byte == 0xAB)); assert_eq!(data.delete_rows("v"), Err("ROWS_ACTIVE")); data.delete_object("v", second).unwrap(); data.delete_rows("v").unwrap(); } #[test] fn relocation_is_staged_until_the_writer_commits() { let mut data = RenderData::new(4096).unwrap(); data.create_rows("a".into(), 1, 16, "u32".into()).unwrap(); data.create_rows("b".into(), 1, 16, "u32".into()).unwrap(); assert_eq!(data.allocate_object("a", 1).unwrap(), (0, false, false)); let old = data.rows("a").unwrap().clone(); let base = (old.offset - data.base) as usize + data.arena_start; data.arena[base..base + 16].fill(0xCD); // "b" sits right behind "a", so growing "a" must move it: the worker stages a zeroed region // and keeps rendering from (and the arena owning) the old bytes. let (staged, moved) = data.create_rows("a".into(), 5, 16, "u32".into()).unwrap(); assert!(moved); assert_eq!(staged.rows, 5); assert_ne!(staged.offset, old.offset); assert_eq!(data.rows("a").unwrap().offset, old.offset); assert_eq!(data.staged("a").unwrap().offset, staged.offset); assert_eq!( data.create_rows("a".into(), 6, 16, "u32".into()), Err("ROWS_PENDING") ); assert_eq!(data.delete_rows("a"), Err("ROWS_PENDING")); assert!(!data.free.take(old.offset - data.base, old.bytes)); // The writer keeps writing the old rows until it copies them over and commits. data.arena[base + 4..base + 8].fill(0xEF); let target = (staged.offset - data.base) as usize + data.arena_start; data.arena.copy_within(base..base + 16, target); data.commit(&["a".into()]).unwrap(); assert_eq!(data.rows("a").unwrap().offset, staged.offset); assert_eq!( &data.bytes("a").unwrap()[..8], &[0xCD, 0xCD, 0xCD, 0xCD, 0xEF, 0xEF, 0xEF, 0xEF] ); assert!(data.bytes("a").unwrap()[16..].iter().all(|byte| *byte == 0)); assert!(data.free.take(old.offset - data.base, old.bytes)); assert_eq!(data.commit(&["a".into()]), Err("ROWS_PENDING")); // Newly staged rows already feed the span allocator; the writer owns the staged region. data.create_rows("c".into(), 1, 16, "u32".into()).unwrap(); assert_eq!(data.allocate_object("a", 4).unwrap(), (1, false, false)); let (id, grew, moved) = data.allocate_object("a", 1).unwrap(); assert_eq!((id, grew, moved), (5, true, true)); assert_eq!(data.staged("a").unwrap().rows, 10); assert_eq!(data.rows("a").unwrap().rows, 5); data.commit(&["a".into()]).unwrap(); assert_eq!(rows_of(&data, "a"), 10); } #[test] fn discarding_a_staged_relocation_restores_capacity() { let mut data = RenderData::new(4096).unwrap(); data.create_rows("a".into(), 2, 16, "u32".into()).unwrap(); data.create_rows("b".into(), 1, 16, "u32".into()).unwrap(); assert_eq!(data.allocate_object("a", 2).unwrap(), (0, false, false)); let (staged, moved) = data.create_rows("a".into(), 8, 16, "u32".into()).unwrap(); assert!(moved); data.discard_pending(); assert_eq!(rows_of(&data, "a"), 2); assert!(data.free.take(staged.offset - data.base, staged.bytes)); // Rows 2..8 must not be handed out: they only existed in the discarded region. assert_eq!(data.allocate_object("a", 1).unwrap(), (2, true, false)); assert_eq!(rows_of(&data, "a"), 4); }