use crate::schema::AssetKind;
use std::cell::{Cell, RefCell};
pub(crate) struct PendingAsset {
pub(crate) asset_id: u32,
pub(crate) kind: AssetKind,
pub(crate) bytes: &'static [u8],
}
thread_local! {
static REGISTERED: RefCell<Vec<(&'static [u8], u32, AssetKind)>> =
const { RefCell::new(Vec::new()) };
static QUEUE: RefCell<Vec<PendingAsset>> = const { RefCell::new(Vec::new()) };
static RELEASES: RefCell<Vec<u32>> = const { RefCell::new(Vec::new()) };
static LAST_ID: Cell<u32> = const { Cell::new(0) };
}
pub fn asset(kind: AssetKind, bytes: &'static [u8]) -> u32 {
if let Some(id) = REGISTERED.with_borrow(|registered| registered_id(registered, bytes)) {
return id;
}
let asset_id = REGISTERED.with_borrow_mut(|registered| {
let asset_id = LAST_ID.get() + 1;
LAST_ID.set(asset_id);
registered.push((bytes, asset_id, kind));
asset_id
});
QUEUE.with_borrow_mut(|queue| {
queue.push(PendingAsset {
asset_id,
kind,
bytes,
});
});
asset_id
}
pub fn register_owned(kind: AssetKind, bytes: Vec<u8>) -> u32 {
if let Some(id) = REGISTERED.with_borrow(|registered| {
registered
.iter()
.find(|(known, _, _)| *known == bytes.as_slice())
.map(|(_, id, _)| *id)
}) {
return id;
}
asset(kind, Box::leak(bytes.into_boxed_slice()))
}
fn registered_id(
registered: &[(&'static [u8], u32, AssetKind)],
bytes: &'static [u8],
) -> Option<u32> {
registered
.iter()
.find(|(known, _, _)| std::ptr::eq(*known, bytes))
.or_else(|| registered.iter().find(|(known, _, _)| *known == bytes))
.map(|(_, id, _)| *id)
}
pub(crate) fn drain(mut emit: impl FnMut(&PendingAsset)) {
let mut taken = QUEUE.with_borrow_mut(std::mem::take);
if taken.is_empty() {
return;
}
for pending in &taken {
emit(pending);
}
taken.clear();
QUEUE.with_borrow_mut(|queue| {
if queue.is_empty() {
*queue = taken;
}
});
}
pub(crate) fn release(asset_id: u32, kind: AssetKind) -> bool {
let removed = REGISTERED.with_borrow_mut(|registered| {
let at = registered
.iter()
.position(|(_, id, known)| *id == asset_id && *known == kind)?;
registered.remove(at);
Some(())
});
if removed.is_none() {
return false;
}
let was_waiting = QUEUE.with_borrow_mut(|queue| {
let before = queue.len();
queue.retain(|pending| pending.asset_id != asset_id);
queue.len() != before
});
if !was_waiting {
RELEASES.with_borrow_mut(|releases| releases.push(asset_id));
}
true
}
pub(crate) fn drain_releases(mut emit: impl FnMut(u32)) {
let mut taken = RELEASES.with_borrow_mut(std::mem::take);
if taken.is_empty() {
return;
}
for asset_id in &taken {
emit(*asset_id);
}
taken.clear();
RELEASES.with_borrow_mut(|releases| {
if releases.is_empty() {
*releases = taken;
}
});
}
#[doc(hidden)]
pub fn reset_assets() {
REGISTERED.with_borrow_mut(Vec::clear);
QUEUE.with_borrow_mut(Vec::clear);
RELEASES.with_borrow_mut(Vec::clear);
LAST_ID.set(0);
}
pub(crate) fn requeue_all() {
let all: Vec<PendingAsset> = REGISTERED.with_borrow(|registered| {
registered
.iter()
.map(|(bytes, asset_id, kind)| PendingAsset {
asset_id: *asset_id,
kind: *kind,
bytes,
})
.collect()
});
RELEASES.with_borrow_mut(Vec::clear);
if all.is_empty() {
return;
}
QUEUE.with_borrow_mut(|queue| {
*queue = all;
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fr23_released_brush_ids_are_sent_once_and_never_reused() {
reset_assets();
let kept = register_owned(AssetKind::Brush, vec![1, 2, 3]);
let sent = register_owned(AssetKind::Brush, vec![4, 5, 6]);
drain(|_| {});
let waiting = register_owned(AssetKind::Brush, vec![7, 8, 9]);
assert!(release(sent, AssetKind::Brush));
assert!(release(waiting, AssetKind::Brush));
assert!(
!release(sent, AssetKind::Brush),
"second release is a no-op"
);
assert!(!release(kept, AssetKind::Png), "wrong kind is not released");
let mut released = Vec::new();
drain_releases(|id| released.push(id));
assert_eq!(released, [sent], "only the registration the Renderer saw");
drain(|pending| panic!("nothing should be waiting, found {}", pending.asset_id));
let again = register_owned(AssetKind::Brush, vec![4, 5, 6]);
assert!(again > waiting, "a released id is not handed out again");
reset_assets();
}
}