use crate::render_types::DrawObject;
use alloc::vec;
use alloc::vec::Vec;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct GeomSig {
base_vertex: i32,
vertex_offset: usize,
index_offset: usize,
index_count: usize,
generation: u32,
}
impl GeomSig {
pub fn of(obj: &DrawObject) -> Self {
Self {
base_vertex: obj.base_vertex,
vertex_offset: obj.vertex_offset,
index_offset: obj.index_offset,
index_count: obj.index_count,
generation: obj.geometry_generation,
}
}
}
pub struct TopologyPlan {
pub reuse: Vec<Option<usize>>,
pub retire: Vec<usize>,
}
pub fn plan_topology_refresh(
old_indices: &[usize],
old_sigs: &[GeomSig],
new_indices: &[usize],
new_sigs: &[GeomSig],
) -> TopologyPlan {
use hashbrown::HashMap;
let mut by_idx: HashMap<usize, (usize, GeomSig)> = HashMap::with_capacity(old_indices.len());
for (k, (&idx, &sig)) in old_indices.iter().zip(old_sigs).enumerate() {
by_idx.insert(idx, (k, sig));
}
let mut used = vec![false; old_indices.len()];
let mut reuse = Vec::with_capacity(new_indices.len());
for (&idx, &sig) in new_indices.iter().zip(new_sigs) {
match by_idx.get(&idx) {
Some(&(k, old_sig)) if old_sig == sig && !used[k] => {
used[k] = true;
reuse.push(Some(k));
}
_ => reuse.push(None),
}
}
let retire = used
.iter()
.enumerate()
.filter(|&(_, &u)| !u)
.map(|(k, _)| k)
.collect();
TopologyPlan { reuse, retire }
}
#[cfg(test)]
mod tests {
use super::*;
fn sig(tag: usize) -> GeomSig {
GeomSig {
base_vertex: tag as i32,
vertex_offset: tag * 100,
index_offset: tag,
index_count: 3,
generation: 0,
}
}
#[test]
fn topology_plan_reuses_an_unchanged_set() {
let old_i = [2usize, 5, 7];
let old_s = [sig(2), sig(5), sig(7)];
let plan = plan_topology_refresh(&old_i, &old_s, &old_i, &old_s);
assert_eq!(plan.reuse, vec![Some(0), Some(1), Some(2)]);
assert!(plan.retire.is_empty());
}
#[test]
fn topology_plan_builds_only_the_added_slot() {
let old_i = [2usize, 5];
let old_s = [sig(2), sig(5)];
let new_i = [2usize, 5, 9];
let new_s = [sig(2), sig(5), sig(9)];
let plan = plan_topology_refresh(&old_i, &old_s, &new_i, &new_s);
assert_eq!(plan.reuse, vec![Some(0), Some(1), None]);
assert!(plan.retire.is_empty());
}
#[test]
fn topology_plan_retires_a_removed_slot() {
let old_i = [2usize, 5, 7];
let old_s = [sig(2), sig(5), sig(7)];
let new_i = [2usize, 7];
let new_s = [sig(2), sig(7)];
let plan = plan_topology_refresh(&old_i, &old_s, &new_i, &new_s);
assert_eq!(plan.reuse, vec![Some(0), Some(2)]);
assert_eq!(plan.retire, vec![1]); }
#[test]
fn topology_plan_rebuilds_a_recycled_slot_whose_geometry_moved() {
let old_i = [5usize];
let old_s = [sig(5)];
let new_i = [5usize];
let new_s = [sig(8)]; let plan = plan_topology_refresh(&old_i, &old_s, &new_i, &new_s);
assert_eq!(plan.reuse, vec![None]);
assert_eq!(plan.retire, vec![0]);
}
#[test]
fn topology_plan_rebuilds_a_slot_rewritten_in_place() {
let old_i = [5usize];
let old_s = [sig(5)];
let new_i = [5usize];
let mut moved = sig(5);
moved.generation = 1;
let plan = plan_topology_refresh(&old_i, &old_s, &new_i, &[moved]);
assert_eq!(plan.reuse, vec![None]);
assert_eq!(plan.retire, vec![0]);
}
#[test]
fn geom_sig_tracks_the_draw_object_generation() {
let mut obj = DrawObject {
vertex_offset: 256,
vertex_count: 8,
index_offset: 12,
index_count: 6,
base_vertex: 0,
geometry_generation: 0,
shader_bucket: 0,
model: [[0.0; 4]; 4],
texture_slot: 0,
normal_map_slot: 0,
material: crate::render_types::MaterialUniforms::DEFAULT,
visible: true,
resident: true,
bb_min: [0.0; 3],
bb_max: [1.0; 3],
cull_distance: 0.0,
lod_alternates: Vec::new(),
};
let before = GeomSig::of(&obj);
assert_eq!(before, GeomSig::of(&obj));
obj.geometry_generation += 1;
assert_ne!(before, GeomSig::of(&obj));
}
#[test]
fn topology_plan_reuses_across_reorder_by_index() {
let old_i = [2usize, 5];
let old_s = [sig(2), sig(5)];
let new_i = [5usize, 2];
let new_s = [sig(5), sig(2)];
let plan = plan_topology_refresh(&old_i, &old_s, &new_i, &new_s);
assert_eq!(plan.reuse, vec![Some(1), Some(0)]);
assert!(plan.retire.is_empty());
}
}