use std::{
any::{Any, TypeId},
cell::{Ref, RefCell, RefMut},
};
pub enum Context<'a> {
Ref(&'a dyn Any),
Mut(&'a mut dyn Any),
Owned(Box<dyn Any>),
}
impl<'a> Context<'a> {
pub fn owned<T: Any>(context: T) -> Self {
Context::Owned(Box::new(context))
}
pub fn from_ref<T: Any>(context: &'a T) -> Self {
Context::Ref(context)
}
pub fn from_mut<T: Any>(context: &'a mut T) -> Self {
Context::Mut(context)
}
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
match self {
Context::Ref(context) => context.downcast_ref(),
Context::Mut(context) => context.downcast_ref(),
Context::Owned(context) => context.downcast_ref(),
}
}
pub fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> {
match self {
Context::Ref(_) => None,
Context::Mut(context) => context.downcast_mut(),
Context::Owned(context) => context.downcast_mut(),
}
}
pub fn borrow(&'_ mut self) -> Context<'_> {
match self {
Context::Ref(context) => Context::Ref(*context),
Context::Mut(context) => Context::Mut(*context),
Context::Owned(context) => Context::Mut(&mut **context),
}
}
fn type_id(&self) -> TypeId {
match self {
Context::Ref(context) => (*context).type_id(),
Context::Mut(context) => (**context).type_id(),
Context::Owned(context) => (**context).type_id(),
}
}
}
struct ContextEntry<'a> {
type_id: TypeId,
context: RefCell<Context<'a>>,
}
impl<'a> ContextEntry<'a> {
fn new(context: Context<'a>) -> Self {
Self {
type_id: context.type_id(),
context: RefCell::new(context),
}
}
}
pub(crate) enum ContextLookup<R> {
Found(R),
AlreadyBorrowed,
NotFound,
}
pub struct ContextStack<'a> {
stack: Vec<ContextEntry<'a>>,
}
impl<'a> ContextStack<'a> {
pub(crate) fn root(root_context: &'a mut dyn Any) -> Self {
ContextStack {
stack: vec![ContextEntry::new(Context::Mut(root_context))],
}
}
pub(crate) fn with_context<'b, F>(&'b mut self, context: Option<Context<'b>>, f: F)
where
F: FnOnce(&mut ContextStack),
{
if let Some(context) = context {
let shorter_lived_self =
unsafe { std::mem::transmute::<&mut Self, &mut ContextStack<'b>>(self) };
shorter_lived_self.stack.push(ContextEntry::new(context));
f(shorter_lived_self);
shorter_lived_self.stack.pop();
} else {
f(self);
};
}
pub(crate) fn get_context<T: Any>(&'_ self) -> ContextLookup<Ref<'_, T>> {
let expected_type_id = TypeId::of::<T>();
for entry in self.stack.iter().rev() {
if entry.type_id != expected_type_id {
continue;
}
let Ok(context) = entry.context.try_borrow() else {
return ContextLookup::AlreadyBorrowed;
};
if let Ok(res) = Ref::filter_map(context, |context| context.downcast_ref::<T>()) {
return ContextLookup::Found(res);
}
}
ContextLookup::NotFound
}
pub(crate) fn get_context_mut<T: Any>(&'_ self) -> ContextLookup<RefMut<'_, T>> {
let expected_type_id = TypeId::of::<T>();
for entry in self.stack.iter().rev() {
if entry.type_id != expected_type_id {
continue;
}
let Ok(context) = entry.context.try_borrow_mut() else {
return ContextLookup::AlreadyBorrowed;
};
if let Ok(res) = RefMut::filter_map(context, |context| context.downcast_mut::<T>()) {
return ContextLookup::Found(res);
}
}
ContextLookup::NotFound
}
}
pub struct SystemContext {
should_exit: bool,
auto_quit_on_ctrl_c: bool,
pub(crate) input: crate::input::InputRuntime,
}
impl SystemContext {
pub(crate) fn new() -> Self {
Self {
should_exit: false,
auto_quit_on_ctrl_c: true,
input: crate::input::InputRuntime::default(),
}
}
pub(crate) fn should_exit(&self) -> bool {
self.should_exit
}
pub fn exit(&mut self) {
self.should_exit = true;
}
pub fn set_auto_quit_on_ctrl_c(&mut self, enabled: bool) {
self.auto_quit_on_ctrl_c = enabled;
}
pub(crate) fn auto_quit_on_ctrl_c(&self) -> bool {
self.auto_quit_on_ctrl_c
}
}