use std::collections::HashMap;
pub type SlotId = u64;
pub struct Registry<H> {
live: HashMap<SlotId, H>,
}
impl<H> Registry<H> {
pub fn new() -> Self {
Self {
live: HashMap::new(),
}
}
pub fn insert(&mut self, slot_id: SlotId, handle: H) -> Option<H> {
self.live.insert(slot_id, handle)
}
pub fn remove(&mut self, slot_id: SlotId) -> Option<H> {
self.live.remove(&slot_id)
}
pub fn remove_matching(&mut self, mut is_match: impl FnMut(&H) -> bool) -> Option<(SlotId, H)> {
let slot_id = self
.live
.iter()
.find(|(_, handle)| is_match(handle))
.map(|(slot_id, _)| *slot_id)?;
self.live.remove(&slot_id).map(|handle| (slot_id, handle))
}
pub fn contains(&self, slot_id: SlotId) -> bool {
self.live.contains_key(&slot_id)
}
pub fn get(&self, slot_id: SlotId) -> Option<&H> {
self.live.get(&slot_id)
}
pub fn get_mut(&mut self, slot_id: SlotId) -> Option<&mut H> {
self.live.get_mut(&slot_id)
}
pub fn live_count(&self) -> usize {
self.live.len()
}
}
impl<H> Default for Registry<H> {
fn default() -> Self {
Self::new()
}
}
#[cfg(target_os = "android")]
pub mod android {
use jni::objects::JObject;
use jni::refs::Global;
pub struct AndroidHandle {
pub view: Global<JObject<'static>>,
pub extra: Vec<Global<JObject<'static>>>,
}
impl AndroidHandle {
pub fn new(view: Global<JObject<'static>>) -> Self {
Self {
view,
extra: Vec::new(),
}
}
pub fn with_extra(
view: Global<JObject<'static>>,
extra: Vec<Global<JObject<'static>>>,
) -> Self {
Self { view, extra }
}
}
}
#[cfg(target_os = "ios")]
pub mod apple {
use objc2::MainThreadMarker;
use objc2::rc::Retained;
use objc2_ui_kit::UIView;
pub struct AppleHandle {
view: Retained<UIView>,
}
impl AppleHandle {
pub fn new(view: Retained<UIView>, _mtm: MainThreadMarker) -> Self {
Self { view }
}
pub fn view(&self, _mtm: MainThreadMarker) -> &Retained<UIView> {
&self.view
}
}
}
#[cfg(target_os = "macos")]
pub mod appkit {
use objc2::MainThreadMarker;
use objc2::rc::Retained;
use objc2_app_kit::NSView;
pub struct AppKitHandle {
view: Retained<NSView>,
}
impl AppKitHandle {
pub fn new(view: Retained<NSView>, _mtm: MainThreadMarker) -> Self {
Self { view }
}
pub fn view(&self, _mtm: MainThreadMarker) -> &Retained<NSView> {
&self.view
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
use std::rc::Rc;
struct CountingHandle {
identity: u32,
count: Rc<RefCell<i32>>,
}
impl CountingHandle {
fn new(identity: u32, count: &Rc<RefCell<i32>>) -> Self {
*count.borrow_mut() += 1;
Self {
identity,
count: Rc::clone(count),
}
}
}
impl Drop for CountingHandle {
fn drop(&mut self) {
*self.count.borrow_mut() -= 1;
}
}
#[test]
fn create_dispose_cycles_return_to_zero_live_refs() {
let count = Rc::new(RefCell::new(0));
let mut registry: Registry<CountingHandle> = Registry::new();
for slot in 0..100u64 {
registry.insert(slot, CountingHandle::new(slot as u32, &count));
assert_eq!(registry.live_count(), 1);
let removed = registry.remove(slot);
assert!(removed.is_some(), "dispose finds the handle it created");
drop(removed);
assert_eq!(registry.live_count(), 0);
}
assert_eq!(
*count.borrow(),
0,
"every one of the 100 creates was paired with a dispose"
);
}
#[test]
fn insert_replaces_and_releases_the_old_handle_for_a_reused_slot() {
let count = Rc::new(RefCell::new(0));
let mut registry: Registry<CountingHandle> = Registry::new();
let slot = 7u64;
registry.insert(slot, CountingHandle::new(100, &count));
assert_eq!(*count.borrow(), 1);
let replaced = registry.insert(slot, CountingHandle::new(200, &count));
assert!(replaced.is_some());
drop(replaced);
assert_eq!(
*count.borrow(),
1,
"replacing drops the old handle immediately, leaving only the new one"
);
assert_eq!(registry.live_count(), 1);
}
#[test]
fn a_late_dispose_for_a_replaced_handle_is_a_no_op_via_identity_match() {
let count = Rc::new(RefCell::new(0));
let mut registry: Registry<CountingHandle> = Registry::new();
let slot = 42u64;
const OLD_IDENTITY: u32 = 1;
const NEW_IDENTITY: u32 = 2;
registry.insert(slot, CountingHandle::new(OLD_IDENTITY, &count));
let replaced = registry.insert(slot, CountingHandle::new(NEW_IDENTITY, &count));
drop(replaced);
assert_eq!(*count.borrow(), 1);
let found = registry.remove_matching(|h| h.identity == OLD_IDENTITY);
assert!(
found.is_none(),
"a late dispose for an already-replaced handle is a no-op"
);
assert_eq!(*count.borrow(), 1, "the current (new) handle is untouched");
assert!(registry.contains(slot));
let found = registry.remove_matching(|h| h.identity == NEW_IDENTITY);
assert!(found.is_some());
drop(found); assert_eq!(*count.borrow(), 0);
assert!(!registry.contains(slot));
}
#[test]
fn duplicate_dispose_is_a_silent_no_op() {
let count = Rc::new(RefCell::new(0));
let mut registry: Registry<CountingHandle> = Registry::new();
let slot = 3u64;
registry.insert(slot, CountingHandle::new(9, &count));
assert!(registry.remove(slot).is_some());
assert_eq!(*count.borrow(), 0);
assert!(registry.remove(slot).is_none());
assert_eq!(*count.borrow(), 0);
}
#[test]
fn dispose_for_an_unknown_slot_is_a_silent_no_op() {
let mut registry: Registry<CountingHandle> = Registry::new();
assert!(registry.remove(999).is_none());
assert_eq!(registry.live_count(), 0);
}
#[test]
fn remove_matching_against_an_empty_registry_finds_nothing() {
let mut registry: Registry<CountingHandle> = Registry::new();
assert!(registry.remove_matching(|_| true).is_none());
}
}