use std::sync::Mutex;
use frust_text::TextContext;
struct PendingSlot {
pending: Vec<Vec<u8>>,
generation: u64,
}
static PENDING: Mutex<PendingSlot> = Mutex::new(PendingSlot {
pending: Vec::new(),
generation: 0,
});
pub fn register_app_fonts(data: Vec<u8>) {
let mut slot = PENDING.lock().unwrap_or_else(|e| e.into_inner());
slot.pending.push(data);
slot.generation += 1;
}
#[derive(Debug, Default)]
pub struct FontRegistryWatcher {
last_generation: u64,
}
impl FontRegistryWatcher {
pub fn new() -> Self {
Self { last_generation: 0 }
}
pub fn poll(&mut self) -> Vec<Vec<u8>> {
let mut slot = PENDING.lock().unwrap_or_else(|e| e.into_inner());
if slot.generation == self.last_generation {
return Vec::new();
}
self.last_generation = slot.generation;
std::mem::take(&mut slot.pending)
}
pub fn drain_into(&mut self, cx: &mut TextContext) -> bool {
let payloads = self.poll();
let mut applied = false;
for data in payloads {
if cx.register_fonts(data).is_ok() {
applied = true;
}
}
applied
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex as StdMutex;
use std::thread;
static TEST_LOCK: StdMutex<()> = StdMutex::new(());
const TUFFY: &[u8] = include_bytes!("../../frust-text/tests/fonts/Tuffy-Subset.ttf");
fn reset_slot() {
let mut slot = PENDING.lock().unwrap_or_else(|e| e.into_inner());
slot.pending.clear();
slot.generation = 0;
}
#[test]
fn push_then_poll_returns_payloads_in_order_second_poll_empty() {
let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
reset_slot();
let mut watcher = FontRegistryWatcher::new();
assert_eq!(watcher.poll(), Vec::<Vec<u8>>::new());
register_app_fonts(vec![1, 2, 3]);
register_app_fonts(vec![4, 5, 6]);
let drained = watcher.poll();
assert_eq!(drained, vec![vec![1, 2, 3], vec![4, 5, 6]]);
assert_eq!(watcher.poll(), Vec::<Vec<u8>>::new());
}
#[test]
fn push_from_a_spawned_thread_is_observed_on_the_next_poll() {
let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
reset_slot();
let mut watcher = FontRegistryWatcher::new();
assert_eq!(watcher.poll(), Vec::<Vec<u8>>::new());
let handle = thread::spawn(|| {
register_app_fonts(vec![9, 9, 9]);
});
handle.join().expect("spawned thread must not panic");
assert_eq!(watcher.poll(), vec![vec![9, 9, 9]]);
assert_eq!(watcher.poll(), Vec::<Vec<u8>>::new());
}
#[test]
fn independent_watchers_each_drain_the_pending_payloads_once() {
let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
reset_slot();
let mut a = FontRegistryWatcher::new();
register_app_fonts(vec![7]);
assert_eq!(a.poll(), vec![vec![7]]);
assert_eq!(a.poll(), Vec::<Vec<u8>>::new());
let mut b = FontRegistryWatcher::new();
assert_eq!(b.poll(), Vec::<Vec<u8>>::new());
}
#[test]
fn drain_into_applies_a_registered_font_and_reports_applied() {
let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
reset_slot();
register_app_fonts(TUFFY.to_vec());
let mut watcher = FontRegistryWatcher::new();
let mut cx = TextContext::new();
assert!(
watcher.drain_into(&mut cx),
"a valid font payload must report applied=true"
);
assert!(!watcher.drain_into(&mut cx));
}
#[test]
fn drain_into_skips_invalid_payloads_without_panicking() {
let _guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
reset_slot();
register_app_fonts(Vec::new());
let mut watcher = FontRegistryWatcher::new();
let mut cx = TextContext::new();
assert!(
!watcher.drain_into(&mut cx),
"an invalid payload must not report applied=true"
);
}
}