core: span allocation with shared free list and geometric growth

Amp-Thread-ID: https://ampcode.com/threads/T-01a0b2dd-db32-712d-99ce-12ce4a86d979
Co-authored-by: Heaust Azure <heaust.azure@gmail.com>
This commit is contained in:
Amp
2026-09-18 05:26:26 +00:00
co-authored by heaust
parent e7546e4b78
commit dda602c4a2
7 changed files with 199 additions and 91 deletions
+67
View File
@@ -30,3 +30,70 @@ fn skip_and_bundle_signals_hold_dirty_work() {
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));
assert_eq!(data.allocate_object("v", 3).unwrap(), (2, false));
assert_eq!(data.allocate_object("v", 3).unwrap(), (5, 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));
assert_eq!(rows_of(&data, "v"), 8);
// A span that does not fit doubles capacity instead of growing to the exact need.
assert_eq!(data.allocate_object("v", 1).unwrap(), (8, true));
assert_eq!(rows_of(&data, "v"), 16);
assert_eq!(data.allocate_object("v", 40).unwrap(), (9, true));
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 explicit_growth_feeds_the_span_allocator_and_relocation_keeps_content() {
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));
let base = (data.rows("a").unwrap().offset - data.base) as usize + data.arena_start;
data.arena[base..base + 16].fill(0xCD);
let grown = data.create_rows("a".into(), 5, 16, "u32".into()).unwrap();
assert_eq!(grown.rows, 5);
assert!(data.bytes("a").unwrap()[..16]
.iter()
.all(|byte| *byte == 0xCD));
assert!(data.bytes("a").unwrap()[16..].iter().all(|byte| *byte == 0));
assert_eq!(data.allocate_object("a", 4).unwrap(), (1, false));
assert_eq!(data.allocate_object("a", 1).unwrap(), (5, true));
}