#![allow(clippy::unwrap_used, clippy::disallowed_methods)]
use crate::widget::WidgetId;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LifecyclePhase {
Mount,
Update,
Unmount,
BeforePaint,
AfterPaint,
Focus,
Blur,
Visible,
Hidden,
}
pub type LifecycleCallback = Box<dyn FnMut(LifecycleEvent) + Send>;
#[derive(Debug, Clone)]
pub struct LifecycleEvent {
pub widget_id: WidgetId,
pub phase: LifecyclePhase,
pub timestamp: u64,
}
impl LifecycleEvent {
pub fn new(widget_id: WidgetId, phase: LifecyclePhase, timestamp: u64) -> Self {
Self {
widget_id,
phase,
timestamp,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct HookId(pub u64);
impl HookId {
pub const fn new(id: u64) -> Self {
Self(id)
}
}
#[derive(Debug)]
struct HookRegistration {
#[allow(dead_code)]
id: HookId,
widget_id: WidgetId,
phases: Vec<LifecyclePhase>,
}
pub struct LifecycleManager {
next_id: u64,
hooks: HashMap<HookId, HookRegistration>,
callbacks: HashMap<HookId, LifecycleCallback>,
by_widget: HashMap<WidgetId, Vec<HookId>>,
by_phase: HashMap<LifecyclePhase, Vec<HookId>>,
timestamp: u64,
pending_events: Vec<LifecycleEvent>,
}
impl LifecycleManager {
pub fn new() -> Self {
Self {
next_id: 0,
hooks: HashMap::new(),
callbacks: HashMap::new(),
by_widget: HashMap::new(),
by_phase: HashMap::new(),
timestamp: 0,
pending_events: Vec::new(),
}
}
pub fn register(
&mut self,
widget_id: WidgetId,
phases: Vec<LifecyclePhase>,
callback: LifecycleCallback,
) -> HookId {
let id = HookId::new(self.next_id);
self.next_id += 1;
let registration = HookRegistration {
id,
widget_id,
phases: phases.clone(),
};
self.hooks.insert(id, registration);
self.callbacks.insert(id, callback);
self.by_widget.entry(widget_id).or_default().push(id);
for phase in phases {
self.by_phase.entry(phase).or_default().push(id);
}
id
}
pub fn on_mount(&mut self, widget_id: WidgetId, callback: LifecycleCallback) -> HookId {
self.register(widget_id, vec![LifecyclePhase::Mount], callback)
}
pub fn on_unmount(&mut self, widget_id: WidgetId, callback: LifecycleCallback) -> HookId {
self.register(widget_id, vec![LifecyclePhase::Unmount], callback)
}
pub fn on_update(&mut self, widget_id: WidgetId, callback: LifecycleCallback) -> HookId {
self.register(widget_id, vec![LifecyclePhase::Update], callback)
}
pub fn on_focus(&mut self, widget_id: WidgetId, callback: LifecycleCallback) -> HookId {
self.register(widget_id, vec![LifecyclePhase::Focus], callback)
}
pub fn on_blur(&mut self, widget_id: WidgetId, callback: LifecycleCallback) -> HookId {
self.register(widget_id, vec![LifecyclePhase::Blur], callback)
}
pub fn unregister(&mut self, hook_id: HookId) -> bool {
if let Some(registration) = self.hooks.remove(&hook_id) {
self.callbacks.remove(&hook_id);
if let Some(hooks) = self.by_widget.get_mut(®istration.widget_id) {
hooks.retain(|&id| id != hook_id);
}
for phase in ®istration.phases {
if let Some(hooks) = self.by_phase.get_mut(phase) {
hooks.retain(|&id| id != hook_id);
}
}
true
} else {
false
}
}
pub fn unregister_widget(&mut self, widget_id: WidgetId) {
if let Some(hook_ids) = self.by_widget.remove(&widget_id) {
for hook_id in hook_ids {
if let Some(registration) = self.hooks.remove(&hook_id) {
self.callbacks.remove(&hook_id);
for phase in ®istration.phases {
if let Some(hooks) = self.by_phase.get_mut(phase) {
hooks.retain(|&id| id != hook_id);
}
}
}
}
}
}
pub fn emit(&mut self, widget_id: WidgetId, phase: LifecyclePhase) {
let event = LifecycleEvent::new(widget_id, phase, self.timestamp);
let widget_hooks = self.by_widget.get(&widget_id).cloned().unwrap_or_default();
let phase_hooks = self.by_phase.get(&phase).cloned().unwrap_or_default();
for hook_id in widget_hooks {
if phase_hooks.contains(&hook_id) {
if let Some(callback) = self.callbacks.get_mut(&hook_id) {
callback(event.clone());
}
}
}
}
pub fn queue(&mut self, widget_id: WidgetId, phase: LifecyclePhase) {
let event = LifecycleEvent::new(widget_id, phase, self.timestamp);
self.pending_events.push(event);
}
pub fn flush(&mut self) {
let events: Vec<LifecycleEvent> = self.pending_events.drain(..).collect();
for event in events {
let widget_hooks = self
.by_widget
.get(&event.widget_id)
.cloned()
.unwrap_or_default();
let phase_hooks = self.by_phase.get(&event.phase).cloned().unwrap_or_default();
for hook_id in widget_hooks {
if phase_hooks.contains(&hook_id) {
if let Some(callback) = self.callbacks.get_mut(&hook_id) {
callback(event.clone());
}
}
}
}
}
pub fn pending_count(&self) -> usize {
self.pending_events.len()
}
pub fn tick(&mut self) {
self.timestamp += 1;
}
pub fn timestamp(&self) -> u64 {
self.timestamp
}
pub fn hook_count(&self) -> usize {
self.hooks.len()
}
pub fn has_hooks(&self, widget_id: WidgetId) -> bool {
self.by_widget
.get(&widget_id)
.is_some_and(|h| !h.is_empty())
}
pub fn clear(&mut self) {
self.hooks.clear();
self.callbacks.clear();
self.by_widget.clear();
self.by_phase.clear();
self.pending_events.clear();
}
}
impl Default for LifecycleManager {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for LifecycleManager {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LifecycleManager")
.field("next_id", &self.next_id)
.field("hook_count", &self.hooks.len())
.field("timestamp", &self.timestamp)
.field("pending_count", &self.pending_events.len())
.finish()
}
}
pub struct Effect {
effect: Option<Box<dyn FnOnce() -> Option<Box<dyn FnOnce() + Send>> + Send>>,
cleanup: Option<Box<dyn FnOnce() + Send>>,
deps: Vec<u64>,
}
impl Effect {
pub fn new<F>(effect: F) -> Self
where
F: FnOnce() -> Option<Box<dyn FnOnce() + Send>> + Send + 'static,
{
Self {
effect: Some(Box::new(effect)),
cleanup: None,
deps: Vec::new(),
}
}
pub fn with_deps<F>(effect: F, deps: Vec<u64>) -> Self
where
F: FnOnce() -> Option<Box<dyn FnOnce() + Send>> + Send + 'static,
{
Self {
effect: Some(Box::new(effect)),
cleanup: None,
deps,
}
}
pub fn deps_changed(&self, new_deps: &[u64]) -> bool {
if self.deps.len() != new_deps.len() {
return true;
}
self.deps.iter().zip(new_deps).any(|(a, b)| a != b)
}
pub fn run(&mut self, new_deps: Option<&[u64]>) -> bool {
let should_run = match new_deps {
Some(deps) if !self.deps_changed(deps) => false,
_ => true,
};
if !should_run {
return false;
}
if let Some(cleanup) = self.cleanup.take() {
cleanup();
}
if let Some(effect) = self.effect.take() {
self.cleanup = effect();
}
if let Some(deps) = new_deps {
self.deps = deps.to_vec();
}
true
}
pub fn cleanup(&mut self) {
if let Some(cleanup) = self.cleanup.take() {
cleanup();
}
}
pub fn has_cleanup(&self) -> bool {
self.cleanup.is_some()
}
}
impl std::fmt::Debug for Effect {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Effect")
.field("has_effect", &self.effect.is_some())
.field("has_cleanup", &self.cleanup.is_some())
.field("deps", &self.deps)
.finish()
}
}
#[derive(Debug, Default)]
pub struct EffectManager {
effects: HashMap<WidgetId, Vec<Effect>>,
}
impl EffectManager {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, widget_id: WidgetId, effect: Effect) {
self.effects.entry(widget_id).or_default().push(effect);
}
pub fn run_effects(&mut self, widget_id: WidgetId, deps: Option<&[u64]>) {
if let Some(effects) = self.effects.get_mut(&widget_id) {
for effect in effects {
effect.run(deps);
}
}
}
pub fn cleanup_widget(&mut self, widget_id: WidgetId) {
if let Some(effects) = self.effects.get_mut(&widget_id) {
for effect in effects {
effect.cleanup();
}
}
self.effects.remove(&widget_id);
}
pub fn widget_count(&self) -> usize {
self.effects.len()
}
pub fn effect_count(&self) -> usize {
self.effects.values().map(std::vec::Vec::len).sum()
}
pub fn clear(&mut self) {
for effects in self.effects.values_mut() {
for effect in effects {
effect.cleanup();
}
}
self.effects.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
#[test]
fn test_lifecycle_phase_equality() {
assert_eq!(LifecyclePhase::Mount, LifecyclePhase::Mount);
assert_ne!(LifecyclePhase::Mount, LifecyclePhase::Unmount);
}
#[test]
fn test_lifecycle_event_new() {
let event = LifecycleEvent::new(WidgetId::new(1), LifecyclePhase::Mount, 42);
assert_eq!(event.widget_id, WidgetId::new(1));
assert_eq!(event.phase, LifecyclePhase::Mount);
assert_eq!(event.timestamp, 42);
}
#[test]
fn test_hook_id() {
let id1 = HookId::new(1);
let id2 = HookId::new(1);
let id3 = HookId::new(2);
assert_eq!(id1, id2);
assert_ne!(id1, id3);
}
#[test]
fn test_manager_new() {
let manager = LifecycleManager::new();
assert_eq!(manager.hook_count(), 0);
assert_eq!(manager.timestamp(), 0);
}
#[test]
fn test_manager_register() {
let mut manager = LifecycleManager::new();
let widget_id = WidgetId::new(1);
let hook_id = manager.register(widget_id, vec![LifecyclePhase::Mount], Box::new(|_| {}));
assert_eq!(manager.hook_count(), 1);
assert!(manager.has_hooks(widget_id));
assert!(!manager.has_hooks(WidgetId::new(999)));
assert_eq!(hook_id.0, 0);
}
#[test]
fn test_manager_on_mount() {
let mut manager = LifecycleManager::new();
let widget_id = WidgetId::new(1);
let _hook_id = manager.on_mount(widget_id, Box::new(|_| {}));
assert_eq!(manager.hook_count(), 1);
}
#[test]
fn test_manager_on_unmount() {
let mut manager = LifecycleManager::new();
let widget_id = WidgetId::new(1);
let _hook_id = manager.on_unmount(widget_id, Box::new(|_| {}));
assert_eq!(manager.hook_count(), 1);
}
#[test]
fn test_manager_emit() {
let counter = Arc::new(AtomicUsize::new(0));
let counter_clone = counter.clone();
let mut manager = LifecycleManager::new();
let widget_id = WidgetId::new(1);
manager.on_mount(
widget_id,
Box::new(move |_| {
counter_clone.fetch_add(1, Ordering::SeqCst);
}),
);
manager.emit(widget_id, LifecyclePhase::Mount);
assert_eq!(counter.load(Ordering::SeqCst), 1);
manager.emit(widget_id, LifecyclePhase::Mount);
assert_eq!(counter.load(Ordering::SeqCst), 2);
}
#[test]
fn test_manager_emit_wrong_phase() {
let counter = Arc::new(AtomicUsize::new(0));
let counter_clone = counter.clone();
let mut manager = LifecycleManager::new();
let widget_id = WidgetId::new(1);
manager.on_mount(
widget_id,
Box::new(move |_| {
counter_clone.fetch_add(1, Ordering::SeqCst);
}),
);
manager.emit(widget_id, LifecyclePhase::Unmount);
assert_eq!(counter.load(Ordering::SeqCst), 0);
}
#[test]
fn test_manager_queue_and_flush() {
let counter = Arc::new(AtomicUsize::new(0));
let counter_clone = counter.clone();
let mut manager = LifecycleManager::new();
let widget_id = WidgetId::new(1);
manager.on_mount(
widget_id,
Box::new(move |_| {
counter_clone.fetch_add(1, Ordering::SeqCst);
}),
);
manager.queue(widget_id, LifecyclePhase::Mount);
manager.queue(widget_id, LifecyclePhase::Mount);
assert_eq!(manager.pending_count(), 2);
assert_eq!(counter.load(Ordering::SeqCst), 0);
manager.flush();
assert_eq!(manager.pending_count(), 0);
assert_eq!(counter.load(Ordering::SeqCst), 2);
}
#[test]
fn test_manager_unregister() {
let mut manager = LifecycleManager::new();
let widget_id = WidgetId::new(1);
let hook_id = manager.on_mount(widget_id, Box::new(|_| {}));
assert_eq!(manager.hook_count(), 1);
let removed = manager.unregister(hook_id);
assert!(removed);
assert_eq!(manager.hook_count(), 0);
assert!(!manager.has_hooks(widget_id));
}
#[test]
fn test_manager_unregister_widget() {
let mut manager = LifecycleManager::new();
let widget_id = WidgetId::new(1);
manager.on_mount(widget_id, Box::new(|_| {}));
manager.on_unmount(widget_id, Box::new(|_| {}));
manager.on_update(widget_id, Box::new(|_| {}));
assert_eq!(manager.hook_count(), 3);
manager.unregister_widget(widget_id);
assert_eq!(manager.hook_count(), 0);
}
#[test]
fn test_manager_tick() {
let mut manager = LifecycleManager::new();
assert_eq!(manager.timestamp(), 0);
manager.tick();
assert_eq!(manager.timestamp(), 1);
manager.tick();
manager.tick();
assert_eq!(manager.timestamp(), 3);
}
#[test]
fn test_manager_clear() {
let mut manager = LifecycleManager::new();
let widget_id = WidgetId::new(1);
manager.on_mount(widget_id, Box::new(|_| {}));
manager.queue(widget_id, LifecyclePhase::Mount);
manager.clear();
assert_eq!(manager.hook_count(), 0);
assert_eq!(manager.pending_count(), 0);
}
#[test]
fn test_manager_multiple_widgets() {
let counter1 = Arc::new(AtomicUsize::new(0));
let counter2 = Arc::new(AtomicUsize::new(0));
let c1 = counter1.clone();
let c2 = counter2.clone();
let mut manager = LifecycleManager::new();
manager.on_mount(
WidgetId::new(1),
Box::new(move |_| {
c1.fetch_add(1, Ordering::SeqCst);
}),
);
manager.on_mount(
WidgetId::new(2),
Box::new(move |_| {
c2.fetch_add(1, Ordering::SeqCst);
}),
);
manager.emit(WidgetId::new(1), LifecyclePhase::Mount);
assert_eq!(counter1.load(Ordering::SeqCst), 1);
assert_eq!(counter2.load(Ordering::SeqCst), 0);
manager.emit(WidgetId::new(2), LifecyclePhase::Mount);
assert_eq!(counter1.load(Ordering::SeqCst), 1);
assert_eq!(counter2.load(Ordering::SeqCst), 1);
}
#[test]
fn test_effect_new() {
let effect = Effect::new(|| None);
assert!(!effect.has_cleanup());
}
#[test]
fn test_effect_with_deps() {
let effect = Effect::with_deps(|| None, vec![1, 2, 3]);
assert_eq!(effect.deps, vec![1, 2, 3]);
}
#[test]
fn test_effect_deps_changed() {
let effect = Effect::with_deps(|| None, vec![1, 2, 3]);
assert!(!effect.deps_changed(&[1, 2, 3]));
assert!(effect.deps_changed(&[1, 2, 4]));
assert!(effect.deps_changed(&[1, 2]));
assert!(effect.deps_changed(&[1, 2, 3, 4]));
}
#[test]
fn test_effect_run() {
let counter = Arc::new(AtomicUsize::new(0));
let c = counter.clone();
let mut effect = Effect::new(move || {
c.fetch_add(1, Ordering::SeqCst);
None
});
effect.run(None);
assert_eq!(counter.load(Ordering::SeqCst), 1);
effect.run(None);
assert_eq!(counter.load(Ordering::SeqCst), 1);
}
#[test]
fn test_effect_cleanup() {
let cleanup_counter = Arc::new(AtomicUsize::new(0));
let cc = cleanup_counter.clone();
let mut effect = Effect::new(move || {
let cc = cc.clone();
Some(Box::new(move || {
cc.fetch_add(1, Ordering::SeqCst);
}) as Box<dyn FnOnce() + Send>)
});
effect.run(None);
assert!(effect.has_cleanup());
assert_eq!(cleanup_counter.load(Ordering::SeqCst), 0);
effect.cleanup();
assert!(!effect.has_cleanup());
assert_eq!(cleanup_counter.load(Ordering::SeqCst), 1);
}
#[test]
fn test_effect_manager_new() {
let manager = EffectManager::new();
assert_eq!(manager.widget_count(), 0);
assert_eq!(manager.effect_count(), 0);
}
#[test]
fn test_effect_manager_add() {
let mut manager = EffectManager::new();
let widget_id = WidgetId::new(1);
manager.add(widget_id, Effect::new(|| None));
manager.add(widget_id, Effect::new(|| None));
assert_eq!(manager.widget_count(), 1);
assert_eq!(manager.effect_count(), 2);
}
#[test]
fn test_effect_manager_run_effects() {
let counter = Arc::new(AtomicUsize::new(0));
let c = counter.clone();
let mut manager = EffectManager::new();
let widget_id = WidgetId::new(1);
manager.add(
widget_id,
Effect::new(move || {
c.fetch_add(1, Ordering::SeqCst);
None
}),
);
manager.run_effects(widget_id, None);
assert_eq!(counter.load(Ordering::SeqCst), 1);
}
#[test]
fn test_effect_manager_cleanup_widget() {
let cleanup_counter = Arc::new(AtomicUsize::new(0));
let cc = cleanup_counter.clone();
let mut manager = EffectManager::new();
let widget_id = WidgetId::new(1);
manager.add(
widget_id,
Effect::new(move || {
let cc = cc.clone();
Some(Box::new(move || {
cc.fetch_add(1, Ordering::SeqCst);
}) as Box<dyn FnOnce() + Send>)
}),
);
manager.run_effects(widget_id, None);
assert_eq!(manager.effect_count(), 1);
manager.cleanup_widget(widget_id);
assert_eq!(manager.effect_count(), 0);
assert_eq!(cleanup_counter.load(Ordering::SeqCst), 1);
}
#[test]
fn test_effect_manager_clear() {
let mut manager = EffectManager::new();
manager.add(WidgetId::new(1), Effect::new(|| None));
manager.add(WidgetId::new(2), Effect::new(|| None));
manager.clear();
assert_eq!(manager.widget_count(), 0);
assert_eq!(manager.effect_count(), 0);
}
}