use std::{collections::BTreeMap, mem};
use log::debug;
use smallvec::SmallVec;
use crate::{Error, Region, Regions, Result};
#[derive(Debug, Default)]
pub struct Layout {
start_to_region: BTreeMap<usize, Region>,
start_to_hole: BTreeMap<usize, usize>,
hole_to_starts: BTreeMap<usize, SmallVec<[usize; 1]>>,
start_to_reserved: BTreeMap<usize, usize>,
pending_holes: BTreeMap<usize, usize>,
}
impl From<&Regions> for Layout {
fn from(regions: &Regions) -> Self {
let start_to_region: BTreeMap<usize, Region> = regions
.index_to_region()
.iter()
.flatten()
.map(|region| (region.meta().start(), region.clone()))
.collect();
let mut layout = Self {
start_to_hole: BTreeMap::default(),
hole_to_starts: BTreeMap::default(),
start_to_reserved: BTreeMap::default(),
pending_holes: BTreeMap::default(),
start_to_region: BTreeMap::default(),
};
let mut prev_end = 0;
for (&start, region) in &start_to_region {
if prev_end != start {
let size = start - prev_end;
layout.insert_hole(prev_end, size);
}
prev_end = start + region.meta().reserved();
}
layout.start_to_region = start_to_region;
layout
}
}
impl Layout {
fn insert_hole(&mut self, start: usize, size: usize) {
self.start_to_hole.insert(start, size);
self.hole_to_starts.entry(size).or_default().push(start);
}
fn remove_hole(&mut self, start: usize) -> Option<usize> {
let size = self.start_to_hole.remove(&start)?;
if let Some(starts) = self.hole_to_starts.get_mut(&size) {
starts.retain(|s| *s != start);
if starts.is_empty() {
self.hole_to_starts.remove(&size);
}
}
Some(size)
}
pub fn start_to_region(&self) -> &BTreeMap<usize, Region> {
&self.start_to_region
}
pub fn start_to_hole(&self) -> &BTreeMap<usize, usize> {
&self.start_to_hole
}
pub fn len(&self) -> usize {
let mut len = 0;
if let Some((start, reserved)) = self.get_last_reserved() {
len = len.max(start + reserved);
}
if let Some((start, gap)) = self.get_last_hole() {
len = len.max(start + gap);
}
if let Some((&start, &size)) = self.pending_holes.last_key_value() {
len = len.max(start + size);
}
if let Some((start, region)) = self.get_last_region() {
len = len.max(start + region.meta().reserved());
}
len
}
pub fn get_last_region(&self) -> Option<(usize, &Region)> {
self.start_to_region
.last_key_value()
.map(|(start, region)| (*start, region))
}
fn get_last_hole(&self) -> Option<(usize, usize)> {
self.start_to_hole
.last_key_value()
.map(|(start, gap)| (*start, *gap))
}
fn get_last_reserved(&self) -> Option<(usize, usize)> {
self.start_to_reserved
.last_key_value()
.map(|(start, reserved)| (*start, *reserved))
}
pub fn is_last_anything(&self, region: &Region) -> bool {
let Some((last_start, last_region)) = self.get_last_region() else {
return false;
};
last_region.index() == region.index()
&& self
.get_last_hole()
.is_none_or(|(hole_start, _)| last_start > hole_start)
&& self
.get_last_reserved()
.is_none_or(|(reserved_start, _)| last_start > reserved_start)
&& self
.pending_holes
.last_key_value()
.is_none_or(|(&pending_start, _)| last_start > pending_start)
}
pub fn insert_region(&mut self, start: usize, region: &Region) {
assert!(self.start_to_region.insert(start, region.clone()).is_none())
}
pub fn move_region(&mut self, new_start: usize, region: &Region) -> Result<()> {
self.remove_region(region)?;
self.insert_region(new_start, region);
Ok(())
}
pub fn remove_region(&mut self, region: &Region) -> Result<()> {
let region_meta = region.meta();
let start = region_meta.start();
let reserved = region_meta.reserved();
let removed = self.start_to_region.remove(&start);
if removed
.as_ref()
.is_none_or(|region_| region.index() != region_.index())
{
return Err(Error::RegionIndexMismatch);
}
self.pending_holes.insert(start, reserved);
Ok(())
}
pub fn get_hole(&self, start: usize) -> Option<usize> {
self.start_to_hole.get(&start).copied()
}
pub fn find_smallest_adequate_hole(&self, min_size: usize) -> Option<usize> {
self.hole_to_starts
.range(min_size..)
.next()
.and_then(|(_, starts)| starts.first().copied())
}
pub fn remove_or_compress_hole(&mut self, start: usize, compress_by: usize) -> Result<()> {
let Some(size) = self.remove_hole(start) else {
return Ok(());
};
if size == compress_by {
Ok(())
} else if size > compress_by {
let new_start = start + compress_by;
let new_size = size - compress_by;
self.insert_hole(new_start, new_size);
Ok(())
} else {
Err(Error::HoleTooSmall {
hole_size: size,
requested: compress_by,
})
}
}
pub fn reserve(&mut self, start: usize, reserved: usize) {
if self.start_to_reserved.insert(start, reserved).is_some() {
unreachable!();
}
}
pub fn take_reserved(&mut self, start: usize) -> Option<usize> {
self.start_to_reserved.remove(&start)
}
pub fn promote_pending_holes(&mut self, name: &str) {
let count = self.pending_holes.len();
if count > 0 {
debug!("{}: promoted {} pending holes", name, count);
}
for (start, mut size) in mem::take(&mut self.pending_holes) {
let mut final_start = start;
if let Some((&hole_start, &hole_size)) = self.start_to_hole.range(..start).next_back()
&& hole_start + hole_size == start
{
self.remove_hole(hole_start);
final_start = hole_start;
size += hole_size;
}
if let Some(hole_after_size) = self.remove_hole(final_start + size) {
size += hole_after_size;
}
self.insert_hole(final_start, size);
}
}
}