use super::runtime::{InputHandlerId, SignalId, TimelineId};
#[derive(Debug, Clone)]
pub enum HookSlot {
State(SignalId),
Input(InputHandlerId),
Timeline(TimelineId),
}
pub struct ComponentInstance {
pub(crate) hooks: Vec<HookSlot>,
pub(crate) hook_cursor: usize,
#[allow(dead_code)]
pub(crate) cleanup: Vec<Box<dyn FnOnce()>>,
}
impl ComponentInstance {
pub fn new() -> Self {
Self {
hooks: Vec::new(),
hook_cursor: 0,
cleanup: Vec::new(),
}
}
pub fn reset_cursor(&mut self) {
self.hook_cursor = 0;
}
pub fn cursor(&self) -> usize {
self.hook_cursor
}
pub fn advance_cursor(&mut self) -> usize {
let pos = self.hook_cursor;
self.hook_cursor += 1;
pos
}
pub fn current_hook(&self) -> Option<&HookSlot> {
self.hooks.get(self.hook_cursor)
}
pub fn push_hook(&mut self, slot: HookSlot) {
self.hooks.push(slot);
}
pub fn get_hook(&self, index: usize) -> Option<&HookSlot> {
self.hooks.get(index)
}
pub fn hook_count(&self) -> usize {
self.hooks.len()
}
#[allow(dead_code)]
pub fn verify_hook_count(&self) {
if self.hook_cursor != self.hooks.len() {
panic!(
"Hook count mismatch: expected {} hooks, but {} were called. \
Hooks must be called unconditionally and in the same order every render.",
self.hooks.len(),
self.hook_cursor
);
}
}
}
impl Default for ComponentInstance {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use slotmap::SlotMap;
#[test]
fn test_instance_new() {
let instance = ComponentInstance::new();
assert_eq!(instance.hook_cursor, 0);
assert!(instance.hooks.is_empty());
}
#[test]
fn test_reset_cursor() {
let mut instance = ComponentInstance::new();
instance.hook_cursor = 5;
instance.reset_cursor();
assert_eq!(instance.hook_cursor, 0);
}
#[test]
fn test_advance_cursor() {
let mut instance = ComponentInstance::new();
assert_eq!(instance.advance_cursor(), 0);
assert_eq!(instance.advance_cursor(), 1);
assert_eq!(instance.advance_cursor(), 2);
assert_eq!(instance.cursor(), 3);
}
#[test]
fn test_push_and_get_hook() {
let mut signals: SlotMap<SignalId, ()> = SlotMap::with_key();
let signal_id = signals.insert(());
let mut instance = ComponentInstance::new();
instance.push_hook(HookSlot::State(signal_id));
assert_eq!(instance.hook_count(), 1);
assert!(matches!(instance.get_hook(0), Some(HookSlot::State(_))));
}
#[test]
fn test_current_hook() {
let mut signals: SlotMap<SignalId, ()> = SlotMap::with_key();
let signal_id = signals.insert(());
let mut instance = ComponentInstance::new();
assert!(instance.current_hook().is_none());
instance.push_hook(HookSlot::State(signal_id));
assert!(instance.current_hook().is_some());
instance.advance_cursor();
assert!(instance.current_hook().is_none());
}
#[test]
fn test_hook_slot_clone() {
let mut signals: SlotMap<SignalId, ()> = SlotMap::with_key();
let signal_id = signals.insert(());
let slot = HookSlot::State(signal_id);
let cloned = slot.clone();
if let (HookSlot::State(id1), HookSlot::State(id2)) = (&slot, &cloned) {
assert_eq!(id1, id2);
} else {
panic!("Clone should preserve variant");
}
}
#[test]
fn test_hook_slot_debug() {
let mut signals: SlotMap<SignalId, ()> = SlotMap::with_key();
let signal_id = signals.insert(());
let slot = HookSlot::State(signal_id);
let debug_str = format!("{:?}", slot);
assert!(debug_str.contains("State"));
}
}