use std::collections::VecDeque;
use std::sync::{Condvar, Mutex, PoisonError};
use std::time::Duration;
const FLOOR: u64 = 256 << 20;
pub fn footprint(width: usize, height: usize) -> u64 {
(width as u64)
.saturating_mul(height as u64)
.saturating_mul(16 * 4)
}
pub fn initial(max_buffer: u64) -> u64 {
(max_buffer / 2).saturating_mul(3).max(1 << 30)
}
struct State {
used: u64,
running: u32,
exclusive: bool,
limit: u64,
ceiling: u64,
queue: VecDeque<u64>,
next_ticket: u64,
}
pub struct MemoryBudget {
state: Mutex<State>,
room: Condvar,
}
pub struct Permit<'a> {
budget: &'a MemoryBudget,
bytes: u64,
alone: bool,
}
impl MemoryBudget {
pub fn new(ceiling: u64) -> MemoryBudget {
MemoryBudget {
state: Mutex::new(State {
used: 0,
running: 0,
exclusive: false,
limit: ceiling,
ceiling,
queue: VecDeque::new(),
next_ticket: 0,
}),
room: Condvar::new(),
}
}
pub fn admit(&self, bytes: u64, alone: bool, stop: impl Fn() -> bool) -> Option<Permit<'_>> {
let mut st = self.state.lock().unwrap_or_else(PoisonError::into_inner);
let ticket = st.next_ticket;
st.next_ticket = st.next_ticket.wrapping_add(1);
st.queue.push_back(ticket);
while st.queue.front() != Some(&ticket)
|| st.exclusive
|| (st.running > 0 && (alone || st.used.saturating_add(bytes) > st.limit))
{
if stop() {
st.queue.retain(|t| *t != ticket);
self.room.notify_all();
return None;
}
st = self
.room
.wait_timeout(st, Duration::from_millis(100))
.unwrap_or_else(PoisonError::into_inner)
.0;
}
st.queue.pop_front();
self.room.notify_all();
st.used = st.used.saturating_add(bytes);
st.running += 1;
st.exclusive = alone;
Some(Permit {
budget: self,
bytes,
alone,
})
}
pub fn out_of_memory(&self) {
let mut st = self.state.lock().unwrap_or_else(PoisonError::into_inner);
st.limit = (st.limit / 2).max(FLOOR);
}
pub fn succeeded(&self) {
let mut st = self.state.lock().unwrap_or_else(PoisonError::into_inner);
st.limit = st.limit.saturating_add(st.ceiling / 16).min(st.ceiling);
}
}
impl Drop for Permit<'_> {
fn drop(&mut self) {
let mut st = self
.budget
.state
.lock()
.unwrap_or_else(PoisonError::into_inner);
st.used = st.used.saturating_sub(self.bytes);
st.running = st.running.saturating_sub(1);
if self.alone {
st.exclusive = false;
}
self.budget.room.notify_all();
}
}