use concinnity_core::memory::{Ledger, MemTag, Realm};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct PoolReport {
pub(crate) tag: MemTag,
pub(crate) resident_bytes: u64,
pub(crate) byte_budget: Option<u64>,
}
pub(crate) fn publish(ledger: &Ledger, reports: impl IntoIterator<Item = PoolReport>) {
for report in reports {
ledger.set(report.tag, Realm::Device, report.resident_bytes);
ledger.set_budget(report.tag, Realm::Device, report.byte_budget);
}
}
pub(crate) fn textures_report(
streamer: Option<(u64, Option<u64>)>,
transient_pool_bytes: u64,
) -> Option<PoolReport> {
if streamer.is_none() && transient_pool_bytes == 0 {
return None;
}
let (resident, budget) = streamer.unwrap_or((0, None));
Some(PoolReport {
tag: MemTag::Textures,
resident_bytes: resident + transient_pool_bytes,
byte_budget: budget.map(|b| b + transient_pool_bytes),
})
}
#[cfg(test)]
mod tests {
use super::*;
fn report(tag: MemTag, resident_bytes: u64, byte_budget: Option<u64>) -> PoolReport {
PoolReport {
tag,
resident_bytes,
byte_budget,
}
}
#[test]
fn each_pool_lands_under_its_own_tag() {
let ledger = Ledger::new();
publish(
&ledger,
[
report(MemTag::Textures, 4_096, Some(8_192)),
report(MemTag::Meshes, 1_024, None),
],
);
let textures = ledger.usage(MemTag::Textures, Realm::Device);
assert_eq!(textures.bytes, 4_096);
assert_eq!(textures.budget, Some(8_192));
let meshes = ledger.usage(MemTag::Meshes, Realm::Device);
assert_eq!(meshes.bytes, 1_024);
assert_eq!(meshes.budget, None);
}
#[test]
fn republishing_replaces_the_previous_frame() {
let ledger = Ledger::new();
publish(&ledger, [report(MemTag::Chunks, 10_000, Some(16_000))]);
publish(&ledger, [report(MemTag::Chunks, 2_000, Some(16_000))]);
assert_eq!(ledger.usage(MemTag::Chunks, Realm::Device).bytes, 2_000);
assert_eq!(
ledger.usage(MemTag::Chunks, Realm::Device).peak_bytes,
10_000
);
}
#[test]
fn reports_land_in_device_memory_only() {
let ledger = Ledger::new();
publish(&ledger, [report(MemTag::Textures, 4_096, None)]);
assert_eq!(ledger.usage(MemTag::Textures, Realm::Host).bytes, 0);
assert_eq!(ledger.snapshot().realm_bytes(Realm::Device), 4_096);
}
#[test]
fn an_absent_pool_leaves_its_tag_untouched() {
let ledger = Ledger::new();
publish(&ledger, [report(MemTag::Meshes, 512, None)]);
publish(&ledger, []);
assert_eq!(ledger.usage(MemTag::Meshes, Realm::Device).bytes, 512);
}
#[test]
fn the_transient_pool_joins_the_texture_row() {
let r = textures_report(Some((4_000, Some(10_000))), 1_000).expect("reports");
assert_eq!(r.tag, MemTag::Textures);
assert_eq!(r.resident_bytes, 5_000);
assert_eq!(r.byte_budget, Some(11_000));
let headroom = r.byte_budget.unwrap() - r.resident_bytes;
assert_eq!(headroom, 10_000 - 4_000, "streamer headroom is unchanged");
}
#[test]
fn the_transient_pool_reports_without_a_streamer() {
let r = textures_report(None, 2_048).expect("reports");
assert_eq!(r.resident_bytes, 2_048);
assert_eq!(r.byte_budget, None, "no streamer means no cap to report");
let r = textures_report(Some((100, None)), 50).expect("reports");
assert_eq!(r.resident_bytes, 150);
assert_eq!(r.byte_budget, None);
}
#[test]
fn nothing_to_say_says_nothing() {
assert_eq!(textures_report(None, 0), None);
assert!(textures_report(Some((0, Some(8_000))), 0).is_some());
}
}