use std::any::Any;
pub(crate) struct ManagedState {
state: Vec<Tracked>,
}
enum Tracked {
Begin { id: u64, state: Box<dyn Any + Send + Sync> },
End,
}
#[doc(hidden)]
pub struct ManagedStateTracker<'a> {
tracker: &'a mut ManagedState,
index: usize,
}
impl ManagedState {
pub fn tracker(&mut self) -> ManagedStateTracker {
ManagedStateTracker {
tracker: self,
index: 0,
}
}
}
impl Default for ManagedState {
fn default() -> Self {
Self { state: Vec::new() }
}
}
impl Tracked {
unsafe fn unchecked_mut_ref<'a, T: Any + Send + Sync>(&mut self) -> &'a mut T {
match self {
Tracked::Begin { state, .. } => {
let state = state
.downcast_mut::<T>()
.expect("widgets with the same id must always be of the same type");
(state as *mut T).as_mut().unwrap()
}
_ => unreachable!(),
}
}
}
impl<'a> ManagedStateTracker<'a> {
pub(crate) fn begin<'i, T, F>(&mut self, id: u64, default: F) -> &'i mut T
where
T: Any + Send + Sync,
F: FnOnce() -> T,
{
let search_start = self.index;
let mut level = 0;
while self.index < self.tracker.state.len() {
match &self.tracker.state[self.index] {
Tracked::End if level > 0 => level -= 1,
Tracked::End => {
self.index = search_start;
break;
}
&Tracked::Begin { id: tid, state: _ } if level == 0 && tid == id => {
self.tracker.state.splice(search_start..self.index, None);
unsafe {
let i = search_start;
self.index = search_start + 1;
return self.tracker.state[i].unchecked_mut_ref();
}
}
&Tracked::Begin { .. } => level += 1,
}
self.index += 1;
}
let i = self.index;
let state = Box::new(default()) as Box<dyn Any + Send + Sync>;
self.tracker.state.insert(i, Tracked::Begin { id, state });
self.tracker.state.insert(i + 1, Tracked::End);
self.index += 1;
unsafe { self.tracker.state[i].unchecked_mut_ref() }
}
pub(crate) fn end(&mut self) {
let search_start = self.index;
let mut level = 0;
while self.index < self.tracker.state.len() {
match &self.tracker.state[self.index] {
Tracked::Begin { .. } => {
self.index += 1;
level += 1;
}
Tracked::End if level > 0 => {
self.index += 1;
level -= 1;
}
Tracked::End => {
self.tracker.state.splice(search_start..self.index, None);
self.index = search_start + 1;
return;
}
}
}
unreachable!("did not find `End` at the end.");
}
}
impl<'a> Drop for ManagedStateTracker<'a> {
fn drop(&mut self) {
while self.index < self.tracker.state.len() {
self.tracker.state.pop();
}
}
}