iocraft/context.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
use std::{
any::Any,
cell::{Ref, RefCell, RefMut},
mem,
};
/// The system context, which is always available to all components.
pub struct SystemContext {
should_exit: bool,
}
impl SystemContext {
pub(crate) fn new() -> Self {
Self { should_exit: false }
}
/// If called from a component that is being dynamically rendered, this will cause the render
/// loop to exit and return to the caller after the current render pass.
pub fn exit(&mut self) {
self.should_exit = true;
}
pub(crate) fn should_exit(&self) -> bool {
self.should_exit
}
}
/// A context that can be passed to components.
pub enum Context<'a> {
/// Provides the context via a mutable reference. Children will be able to get mutable or
/// immutable references to the context.
Mut(&'a mut dyn Any),
/// Provides the context via an immutable reference. Children will not be able to get a mutable
/// reference to the context.
Ref(&'a dyn Any),
/// Provides the context via an owned value. Children will be able to get mutable or immutable
/// references to the context.
Owned(Box<dyn Any>),
}
impl<'a> Context<'a> {
/// Creates a new context from an owned value. Children will be able to get mutable or
/// immutable references to the context.
pub fn owned<T: Any>(context: T) -> Self {
Context::Owned(Box::new(context))
}
/// Creates a new context from a mutable reference. Children will be able to get mutable or
/// immutable references to the context.
pub fn from_mut<T: Any>(context: &'a mut T) -> Self {
Context::Mut(context)
}
/// Creates a new context from an immutable reference. Children will not be able to get a
/// mutable reference to the context.
pub fn from_ref<T: Any>(context: &'a T) -> Self {
Context::Ref(context)
}
#[doc(hidden)]
pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
match self {
Context::Mut(context) => context.downcast_ref::<T>(),
Context::Ref(context) => context.downcast_ref::<T>(),
Context::Owned(context) => context.downcast_ref::<T>(),
}
}
#[doc(hidden)]
pub fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> {
match self {
Context::Mut(context) => context.downcast_mut::<T>(),
Context::Ref(_) => None,
Context::Owned(context) => context.downcast_mut::<T>(),
}
}
#[doc(hidden)]
pub fn borrow(&mut self) -> Context {
match self {
Context::Mut(context) => Context::Mut(*context),
Context::Ref(context) => Context::Ref(*context),
Context::Owned(context) => Context::Mut(&mut **context),
}
}
}
#[doc(hidden)]
pub struct ContextStack<'a> {
contexts: Vec<RefCell<Context<'a>>>,
}
impl<'a> ContextStack<'a> {
pub(crate) fn root(root_context: &'a mut dyn Any) -> Self {
Self {
contexts: vec![RefCell::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 {
// SAFETY: Mutable references to this struct are invariant over 'a, so in order to
// append a shorter-lived context, we need to transmute 'a to the shorter lifetime.
//
// This is only safe because we don't allow any other changes to the stack, and we
// revert the stack right after the call.
let shorter_lived_self =
unsafe { mem::transmute::<&mut Self, &mut ContextStack<'b>>(self) };
shorter_lived_self.contexts.push(RefCell::new(context));
f(shorter_lived_self);
shorter_lived_self.contexts.pop();
} else {
f(self);
}
}
pub fn get_context<T: Any>(&self) -> Option<Ref<T>> {
for context in self.contexts.iter().rev() {
if let Ok(context) = context.try_borrow() {
if let Ok(ret) = Ref::filter_map(context, |context| context.downcast_ref::<T>()) {
return Some(ret);
}
}
}
None
}
pub fn get_context_mut<T: Any>(&self) -> Option<RefMut<T>> {
for context in self.contexts.iter().rev() {
if let Ok(context) = context.try_borrow_mut() {
if let Ok(ret) = RefMut::filter_map(context, |context| context.downcast_mut::<T>())
{
return Some(ret);
}
}
}
None
}
}