use super::instance::ComponentInstance;
use crate::input::Key;
use crate::timeline::PlayingTimeline;
use slotmap::{new_key_type, SlotMap};
use std::any::Any;
use std::cell::{Cell, RefCell};
use std::rc::Rc;
new_key_type! {
pub struct SignalId;
pub struct ComponentId;
pub struct InputHandlerId;
pub struct TimelineId;
}
type InputHandler = Box<dyn Fn(&Key)>;
type InputHandlerMap = SlotMap<InputHandlerId, InputHandler>;
#[derive(Clone)]
pub struct RuntimeHandle(pub(crate) Rc<RefCell<RuntimeInner>>);
impl RuntimeHandle {
pub fn new() -> Self {
Self(Rc::new(RefCell::new(RuntimeInner::new())))
}
pub fn create_signal<T: 'static>(&self, value: T) -> SignalId {
self.0.borrow_mut().signals.insert(Box::new(value))
}
pub fn get_signal<T: Clone + 'static>(&self, id: SignalId) -> T {
let inner = self.0.borrow();
let boxed = inner
.signals
.get(id)
.expect("Signal not found - invalid SignalId");
boxed
.downcast_ref::<T>()
.expect("Signal type mismatch")
.clone()
}
pub fn set_signal<T: 'static>(&self, id: SignalId, value: T) {
{
let mut inner = self.0.borrow_mut();
let boxed = inner
.signals
.get_mut(id)
.expect("Signal not found - invalid SignalId");
*boxed.downcast_mut::<T>().expect("Signal type mismatch") = value;
}
self.mark_dirty();
}
pub fn mark_dirty(&self) {
self.0.borrow().needs_render.set(true);
}
pub fn needs_render(&self) -> bool {
self.0.borrow().needs_render.get()
}
pub fn clear_dirty(&self) {
self.0.borrow().needs_render.set(false);
}
pub fn create_instance(&self) -> ComponentId {
self.0
.borrow_mut()
.instances
.insert(ComponentInstance::new())
}
pub fn set_current_instance(&self, id: Option<ComponentId>) {
self.0.borrow_mut().current_instance = id;
}
pub fn current_instance(&self) -> Option<ComponentId> {
self.0.borrow().current_instance
}
pub fn reset_hook_cursor(&self, id: ComponentId) {
if let Some(instance) = self.0.borrow_mut().instances.get_mut(id) {
instance.reset_cursor();
}
}
pub fn register_input_handler<F>(&self, handler: F) -> InputHandlerId
where
F: Fn(&Key) + 'static,
{
self.0.borrow_mut().input_handlers.insert(Box::new(handler))
}
pub fn has_input_handler(&self, id: InputHandlerId) -> bool {
self.0.borrow().input_handlers.contains_key(id)
}
pub fn dispatch_input(&self, key: &Key) {
let handlers: Vec<*const InputHandler> = self
.0
.borrow()
.input_handlers
.values()
.map(|h| h as *const _)
.collect();
for handler_ptr in handlers {
let handler = unsafe { &*handler_ptr };
handler(key);
}
}
pub fn create_timeline(&self, timeline: PlayingTimeline) -> TimelineId {
self.0.borrow_mut().timelines.insert(timeline)
}
pub fn has_timeline(&self, id: TimelineId) -> bool {
self.0.borrow().timelines.contains_key(id)
}
pub fn with_timeline<R, F: FnOnce(&PlayingTimeline) -> R>(
&self,
id: TimelineId,
f: F,
) -> Option<R> {
self.0.borrow().timelines.get(id).map(f)
}
pub fn with_timeline_mut<R, F: FnOnce(&mut PlayingTimeline) -> R>(
&self,
id: TimelineId,
f: F,
) -> Option<R> {
self.0.borrow_mut().timelines.get_mut(id).map(f)
}
pub fn with_instance<R, F: FnOnce(&ComponentInstance) -> R>(
&self,
id: ComponentId,
f: F,
) -> Option<R> {
self.0.borrow().instances.get(id).map(f)
}
pub fn with_instance_mut<R, F: FnOnce(&mut ComponentInstance) -> R>(
&self,
id: ComponentId,
f: F,
) -> Option<R> {
self.0.borrow_mut().instances.get_mut(id).map(f)
}
}
impl Default for RuntimeHandle {
fn default() -> Self {
Self::new()
}
}
pub struct RuntimeInner {
pub(crate) signals: SlotMap<SignalId, Box<dyn Any>>,
pub(crate) instances: SlotMap<ComponentId, ComponentInstance>,
pub(crate) current_instance: Option<ComponentId>,
pub(crate) input_handlers: InputHandlerMap,
pub(crate) timelines: SlotMap<TimelineId, PlayingTimeline>,
pub(crate) needs_render: Cell<bool>,
}
impl RuntimeInner {
pub fn new() -> Self {
Self {
signals: SlotMap::with_key(),
instances: SlotMap::with_key(),
current_instance: None,
input_handlers: SlotMap::with_key(),
timelines: SlotMap::with_key(),
needs_render: Cell::new(false),
}
}
}
impl Default for RuntimeInner {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_runtime_create() {
let rt = RuntimeHandle::new();
assert!(!rt.needs_render());
}
#[test]
fn test_signal_create_and_get() {
let rt = RuntimeHandle::new();
let id = rt.create_signal(42i32);
assert_eq!(rt.get_signal::<i32>(id), 42);
}
#[test]
fn test_signal_set() {
let rt = RuntimeHandle::new();
let id = rt.create_signal(0i32);
rt.set_signal(id, 100);
assert_eq!(rt.get_signal::<i32>(id), 100);
}
#[test]
fn test_signal_set_marks_dirty() {
let rt = RuntimeHandle::new();
let id = rt.create_signal(0i32);
assert!(!rt.needs_render());
rt.set_signal(id, 1);
assert!(rt.needs_render());
}
#[test]
fn test_clear_dirty() {
let rt = RuntimeHandle::new();
rt.mark_dirty();
assert!(rt.needs_render());
rt.clear_dirty();
assert!(!rt.needs_render());
}
#[test]
fn test_signal_string() {
let rt = RuntimeHandle::new();
let id = rt.create_signal(String::from("hello"));
assert_eq!(rt.get_signal::<String>(id), "hello");
rt.set_signal(id, String::from("world"));
assert_eq!(rt.get_signal::<String>(id), "world");
}
#[test]
fn test_multiple_signals() {
let rt = RuntimeHandle::new();
let id1 = rt.create_signal(10i32);
let id2 = rt.create_signal(20i32);
let id3 = rt.create_signal(30i32);
assert_eq!(rt.get_signal::<i32>(id1), 10);
assert_eq!(rt.get_signal::<i32>(id2), 20);
assert_eq!(rt.get_signal::<i32>(id3), 30);
}
#[test]
fn test_instance_create() {
let rt = RuntimeHandle::new();
let id = rt.create_instance();
assert!(rt.with_instance(id, |_| ()).is_some());
}
#[test]
fn test_current_instance() {
let rt = RuntimeHandle::new();
let id = rt.create_instance();
assert!(rt.current_instance().is_none());
rt.set_current_instance(Some(id));
assert_eq!(rt.current_instance(), Some(id));
rt.set_current_instance(None);
assert!(rt.current_instance().is_none());
}
#[test]
fn test_input_handler_registration() {
let rt = RuntimeHandle::new();
let called = Rc::new(Cell::new(false));
let called_clone = called.clone();
let handler_id = rt.register_input_handler(move |_key| {
called_clone.set(true);
});
assert!(rt.has_input_handler(handler_id));
let key = Key::new(crossterm::event::KeyCode::Char('a'));
rt.dispatch_input(&key);
assert!(called.get());
}
#[test]
fn test_multiple_input_handlers() {
let rt = RuntimeHandle::new();
let count = Rc::new(Cell::new(0));
let count1 = count.clone();
let count2 = count.clone();
rt.register_input_handler(move |_| {
count1.set(count1.get() + 1);
});
rt.register_input_handler(move |_| {
count2.set(count2.get() + 1);
});
let key = Key::new(crossterm::event::KeyCode::Char('a'));
rt.dispatch_input(&key);
assert_eq!(count.get(), 2);
}
#[test]
fn test_runtime_clone_shares_state() {
let rt1 = RuntimeHandle::new();
let rt2 = rt1.clone();
let id = rt1.create_signal(0i32);
rt2.set_signal(id, 42);
assert_eq!(rt1.get_signal::<i32>(id), 42);
}
}