windows-reactor 0.100.0

Declarative Windows UI library for Rust
Documentation
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ScopeId {
    index: u32,
    generation: u32,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ScopeState {
    Reserved,
    Published,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ScopeError {
    InvalidTransition(ScopeState, ScopeState),
    Stale(ScopeId),
}

struct ScopeSlot<T> {
    generation: u32,
    entry: Option<ScopeEntry<T>>,
}

struct ScopeEntry<T> {
    state: ScopeState,
    value: T,
}

pub struct ScopeArena<T> {
    slots: Vec<ScopeSlot<T>>,
    free: Vec<u32>,
    live: usize,
}

impl<T> Default for ScopeArena<T> {
    fn default() -> Self {
        Self::new()
    }
}

impl<T> ScopeArena<T> {
    pub fn new() -> Self {
        Self {
            slots: Vec::new(),
            free: Vec::new(),
            live: 0,
        }
    }

    #[cfg(test)]
    pub fn reserve(&mut self, value: T) -> ScopeId {
        self.reserve_with(|_| value)
    }

    pub fn reserve_with(&mut self, create: impl FnOnce(ScopeId) -> T) -> ScopeId {
        let id = if let Some(index) = self.free.pop() {
            let slot = &mut self.slots[index as usize];
            debug_assert!(slot.entry.is_none());
            ScopeId {
                index,
                generation: slot.generation,
            }
        } else {
            let index = u32::try_from(self.slots.len()).unwrap();
            self.slots.push(ScopeSlot {
                generation: 0,
                entry: None,
            });
            ScopeId {
                index,
                generation: 0,
            }
        };
        self.slots[id.index as usize].entry = Some(ScopeEntry {
            state: ScopeState::Reserved,
            value: create(id),
        });
        self.live += 1;
        id
    }

    pub fn publish(&mut self, id: ScopeId) -> Result<(), ScopeError> {
        self.transition(id, ScopeState::Reserved, ScopeState::Published)
    }

    pub fn state(&self, id: ScopeId) -> Result<ScopeState, ScopeError> {
        Ok(self.entry(id)?.state)
    }

    pub fn get(&self, id: ScopeId) -> Result<&T, ScopeError> {
        Ok(&self.entry(id)?.value)
    }

    pub fn get_mut(&mut self, id: ScopeId) -> Result<&mut T, ScopeError> {
        Ok(&mut self.entry_mut(id)?.value)
    }

    pub fn remove(&mut self, id: ScopeId) -> Result<T, ScopeError> {
        let slot = self.slot_mut(id)?;
        let entry = slot.entry.take().ok_or(ScopeError::Stale(id))?;
        if let Some(generation) = slot.generation.checked_add(1) {
            slot.generation = generation;
            self.free.push(id.index);
        }
        self.live -= 1;
        Ok(entry.value)
    }

    #[cfg(test)]
    pub fn is_empty(&self) -> bool {
        self.live == 0
    }

    fn transition(
        &mut self,
        id: ScopeId,
        from: ScopeState,
        to: ScopeState,
    ) -> Result<(), ScopeError> {
        let entry = self.entry_mut(id)?;
        if entry.state != from {
            return Err(ScopeError::InvalidTransition(entry.state, to));
        }
        entry.state = to;
        Ok(())
    }

    fn entry(&self, id: ScopeId) -> Result<&ScopeEntry<T>, ScopeError> {
        self.slot(id)?.entry.as_ref().ok_or(ScopeError::Stale(id))
    }

    fn entry_mut(&mut self, id: ScopeId) -> Result<&mut ScopeEntry<T>, ScopeError> {
        self.slot_mut(id)?
            .entry
            .as_mut()
            .ok_or(ScopeError::Stale(id))
    }

    fn slot(&self, id: ScopeId) -> Result<&ScopeSlot<T>, ScopeError> {
        let slot = self
            .slots
            .get(id.index as usize)
            .ok_or(ScopeError::Stale(id))?;
        if slot.generation == id.generation {
            Ok(slot)
        } else {
            Err(ScopeError::Stale(id))
        }
    }

    fn slot_mut(&mut self, id: ScopeId) -> Result<&mut ScopeSlot<T>, ScopeError> {
        let slot = self
            .slots
            .get_mut(id.index as usize)
            .ok_or(ScopeError::Stale(id))?;
        if slot.generation == id.generation {
            Ok(slot)
        } else {
            Err(ScopeError::Stale(id))
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::cell::Cell;
    use std::rc::Rc;

    struct NonCloneState {
        drops: Rc<Cell<u32>>,
    }

    impl Drop for NonCloneState {
        fn drop(&mut self) {
            self.drops.set(self.drops.get() + 1);
        }
    }

    #[test]
    fn enforces_scope_lifecycle() {
        let mut arena = ScopeArena::new();
        let scope = arena.reserve("scope");

        assert_eq!(arena.state(scope), Ok(ScopeState::Reserved));
        arena.publish(scope).unwrap();
        assert_eq!(arena.state(scope), Ok(ScopeState::Published));
        assert_eq!(arena.remove(scope), Ok("scope"));
        assert!(arena.is_empty());
    }

    #[test]
    fn failed_reservation_can_be_removed_without_publication() {
        let drops = Rc::new(Cell::new(0));
        let mut arena = ScopeArena::new();
        let scope = arena.reserve(NonCloneState {
            drops: Rc::clone(&drops),
        });

        drop(arena.remove(scope).unwrap());

        assert_eq!(drops.get(), 1);
        assert_eq!(arena.state(scope), Err(ScopeError::Stale(scope)));
    }

    #[test]
    fn reused_slot_rejects_old_generation() {
        let mut arena = ScopeArena::new();
        let first = arena.reserve("first");
        arena.remove(first).unwrap();

        let second = arena.reserve("second");

        assert_ne!(first, second);
        assert_eq!(arena.get(first), Err(ScopeError::Stale(first)));
        assert_eq!(arena.get(second), Ok(&"second"));
    }
}