#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MeshFaceBudget {
pub max_faces: usize,
pub max_face_indices: usize,
}
impl MeshFaceBudget {
pub const BASELINE: Self = Self {
max_faces: 250_000,
max_face_indices: 1_000_000,
};
pub const UNLIMITED: Self = Self {
max_faces: usize::MAX,
max_face_indices: usize::MAX,
};
pub fn from_system() -> Self {
let (total, available) = probe_host_memory();
Self::from_host_memory(total, available)
}
pub fn from_host_memory(total_bytes: Option<u64>, available_bytes: Option<u64>) -> Self {
let total = total_bytes.filter(|value| *value > 0);
let available = available_bytes.filter(|value| *value > 0);
let budget = match (total, available) {
(Some(total), Some(available)) => (total / 8).min(available / 4),
(None, Some(available)) => available / 4,
(Some(total), None) => total / 8,
(None, None) => return Self::BASELINE,
};
Self::from_budget_bytes(budget)
}
pub fn with_overrides(self, max_faces: Option<usize>, max_face_indices: Option<usize>) -> Self {
Self {
max_faces: max_faces.unwrap_or(self.max_faces),
max_face_indices: max_face_indices.unwrap_or(self.max_face_indices),
}
}
pub fn allows_face(
&self,
stored_faces: usize,
stored_indices: usize,
face_vertices: usize,
) -> bool {
stored_faces < self.max_faces
&& stored_indices
.checked_add(face_vertices)
.is_some_and(|count| count <= self.max_face_indices)
}
fn from_budget_bytes(budget: u64) -> Self {
const BYTES_PER_INDEX: u64 = 48;
const BYTES_PER_FACE: u64 = 96 + 3 * BYTES_PER_INDEX;
if budget < BYTES_PER_FACE {
return Self::BASELINE;
}
let faces = usize::try_from(budget / BYTES_PER_FACE).unwrap_or(usize::MAX);
let indices = usize::try_from(budget / BYTES_PER_INDEX).unwrap_or(usize::MAX);
Self {
max_faces: faces.max(Self::BASELINE.max_faces),
max_face_indices: indices.max(Self::BASELINE.max_face_indices),
}
}
}
fn probe_host_memory() -> (Option<u64>, Option<u64>) {
#[cfg(windows)]
{
return windows_memory();
}
#[cfg(any(target_os = "linux", target_os = "android"))]
{
return linux_memory();
}
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly"
))]
{
return (unix_physical_bytes(), None);
}
#[cfg(not(any(
windows,
target_os = "linux",
target_os = "android",
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly"
)))]
{
(None, None)
}
}
#[cfg(windows)]
fn windows_memory() -> (Option<u64>, Option<u64>) {
#[repr(C)]
struct MemoryStatusEx {
length: u32,
memory_load: u32,
total_phys: u64,
avail_phys: u64,
total_page_file: u64,
avail_page_file: u64,
total_virtual: u64,
avail_virtual: u64,
avail_extended_virtual: u64,
}
#[link(name = "kernel32")]
extern "system" {
fn GlobalMemoryStatusEx(buffer: *mut MemoryStatusEx) -> i32;
}
let mut status = MemoryStatusEx {
length: std::mem::size_of::<MemoryStatusEx>() as u32,
memory_load: 0,
total_phys: 0,
avail_phys: 0,
total_page_file: 0,
avail_page_file: 0,
total_virtual: 0,
avail_virtual: 0,
avail_extended_virtual: 0,
};
let ok = unsafe { GlobalMemoryStatusEx(&mut status) };
if ok == 0 {
return (None, None);
}
(Some(status.total_phys), Some(status.avail_phys))
}
#[cfg(any(test, target_os = "linux", target_os = "android"))]
fn memory_from_meminfo(text: &str) -> (Option<u64>, Option<u64>) {
let total = meminfo_kib(text, "MemTotal:").map(|kib| kib.saturating_mul(1024));
let available = meminfo_kib(text, "MemAvailable:")
.or_else(|| meminfo_kib(text, "MemFree:"))
.map(|kib| kib.saturating_mul(1024));
(total, available)
}
#[cfg(any(test, target_os = "linux", target_os = "android"))]
fn meminfo_kib(text: &str, key: &str) -> Option<u64> {
text.lines().find_map(|line| {
let rest = line.trim_start().strip_prefix(key)?;
rest.split_whitespace().next()?.parse().ok()
})
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn linux_memory() -> (Option<u64>, Option<u64>) {
match std::fs::read_to_string("/proc/meminfo") {
Ok(text) => memory_from_meminfo(&text),
Err(_) => (None, None),
}
}
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly"
))]
fn unix_physical_bytes() -> Option<u64> {
sysctl_u64(c"hw.memsize").or_else(|| sysctl_u64(c"hw.physmem"))
}
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly"
))]
fn sysctl_u64(name: &std::ffi::CStr) -> Option<u64> {
extern "C" {
fn sysctlbyname(
name: *const i8,
oldp: *mut std::ffi::c_void,
oldlenp: *mut usize,
newp: *const std::ffi::c_void,
newlen: usize,
) -> i32;
}
let mut value = 0u64;
let mut len = std::mem::size_of::<u64>();
let rc = unsafe {
sysctlbyname(
name.as_ptr(),
&mut value as *mut u64 as *mut std::ffi::c_void,
&mut len,
std::ptr::null(),
0,
)
};
if rc != 0 || len == 0 || len > std::mem::size_of::<u64>() {
return None;
}
let bytes = value.to_ne_bytes();
let mut wide = [0u8; 8];
wide[..len].copy_from_slice(&bytes[..len]);
Some(u64::from_ne_bytes(wide)).filter(|value| *value > 0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unknown_or_tiny_hosts_keep_the_historical_floor() {
assert_eq!(
MeshFaceBudget::from_host_memory(None, None),
MeshFaceBudget::BASELINE
);
assert_eq!(
MeshFaceBudget::from_host_memory(Some(0), Some(0)),
MeshFaceBudget::BASELINE
);
assert_eq!(
MeshFaceBudget::from_host_memory(Some(64 << 20), Some(64 << 20)),
MeshFaceBudget::BASELINE
);
let pressured = MeshFaceBudget::from_host_memory(Some(16 << 30), Some(32 << 20));
assert_eq!(pressured, MeshFaceBudget::BASELINE);
}
#[test]
fn larger_hosts_scale_both_caps_from_the_tighter_share() {
let wide = MeshFaceBudget::from_host_memory(Some(16 << 30), Some(12 << 30));
assert_eq!(wide.max_faces, (2 << 30) / 240);
assert_eq!(wide.max_face_indices, (2 << 30) / 48);
assert!(wide.max_faces > MeshFaceBudget::BASELINE.max_faces);
assert!(wide.max_face_indices > MeshFaceBudget::BASELINE.max_face_indices);
let total_only = MeshFaceBudget::from_host_memory(Some(8 << 30), None);
assert_eq!(total_only.max_faces, (1 << 30) / 240);
assert_eq!(total_only.max_face_indices, (1 << 30) / 48);
let available_only = MeshFaceBudget::from_host_memory(None, Some(8 << 30));
assert_eq!(available_only.max_faces, (2 << 30) / 240);
assert_eq!(available_only.max_face_indices, (2 << 30) / 48);
}
#[test]
fn overrides_replace_one_side_and_unlimited_disables_both() {
let budget = MeshFaceBudget::BASELINE.with_overrides(Some(9), None);
assert_eq!(budget.max_faces, 9);
assert_eq!(
budget.max_face_indices,
MeshFaceBudget::BASELINE.max_face_indices
);
assert_eq!(
budget.with_overrides(Some(usize::MAX), Some(usize::MAX)),
MeshFaceBudget::UNLIMITED
);
}
#[test]
fn a_face_must_fit_both_caps_without_overflow() {
let budget = MeshFaceBudget {
max_faces: 2,
max_face_indices: 7,
};
assert!(budget.allows_face(0, 0, 3));
assert!(budget.allows_face(1, 3, 3));
assert!(!budget.allows_face(2, 6, 3));
assert!(!budget.allows_face(1, 6, 3));
assert!(MeshFaceBudget::UNLIMITED.allows_face(usize::MAX - 1, usize::MAX - 3, 3));
assert!(!MeshFaceBudget::UNLIMITED.allows_face(usize::MAX - 1, usize::MAX - 2, 3));
}
#[test]
fn meminfo_prefers_available_and_falls_back_to_free() {
let text = "\
MemTotal: 16384 kB
MemFree: 1024 kB
MemAvailable: 8192 kB
";
assert_eq!(
memory_from_meminfo(text),
(Some(16_384 * 1024), Some(8_192 * 1024))
);
let free_only = "MemTotal: 10 kB\nMemFree: 4 kB\n";
assert_eq!(
memory_from_meminfo(free_only),
(Some(10 * 1024), Some(4 * 1024))
);
assert_eq!(memory_from_meminfo("nonsense"), (None, None));
}
#[test]
fn system_budget_is_at_least_the_floor() {
let budget = MeshFaceBudget::from_system();
assert!(budget.max_faces >= MeshFaceBudget::BASELINE.max_faces);
assert!(budget.max_face_indices >= MeshFaceBudget::BASELINE.max_face_indices);
#[cfg(any(windows, target_os = "linux", target_os = "macos"))]
{
let (total, available) = probe_host_memory();
assert!(
total.unwrap_or(0).max(available.unwrap_or(0)) > 0,
"host memory probe returned {total:?} {available:?}"
);
}
}
}