use alloc::boxed::Box;
use alloc::string::String;
use alloc::collections::BTreeMap;
use core::any::{Any, TypeId};
use crate::runner::{Events, RunnerEvent};
use crate::source::SequenceRef;
use crate::vocab::{Condition, Effect, EffectCtx, QueryCtx};
#[derive(Default)]
pub struct TypeMap {
entries: BTreeMap<TypeId, Box<dyn Any>>,
}
impl TypeMap {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn insert<T: Any>(&mut self, value: T) -> Option<T> {
self.entries
.insert(TypeId::of::<T>(), Box::new(value))
.and_then(|old| old.downcast::<T>().ok())
.map(|boxed| *boxed)
}
#[must_use]
pub fn get<T: Any>(&self) -> Option<&T> {
self.entries
.get(&TypeId::of::<T>())
.and_then(|v| v.downcast_ref::<T>())
}
pub fn get_mut<T: Any>(&mut self) -> Option<&mut T> {
self.entries
.get_mut(&TypeId::of::<T>())
.and_then(|v| v.downcast_mut::<T>())
}
pub fn remove<T: Any>(&mut self) -> Option<T> {
self.entries
.remove(&TypeId::of::<T>())
.and_then(|old| old.downcast::<T>().ok())
.map(|boxed| *boxed)
}
}
impl core::fmt::Debug for TypeMap {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("TypeMap")
.field("entries", &self.entries.len())
.finish()
}
}
#[derive(Debug, Default)]
pub struct ChainFlags {
flags: BTreeMap<String, bool>,
}
impl ChainFlags {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn flag(&self, name: &str) -> bool {
self.flags.get(name).copied().unwrap_or(false)
}
pub fn set_flag(&mut self, name: impl Into<String>, value: bool) {
self.flags.insert(name.into(), value);
}
}
#[derive(Default)]
pub(crate) struct ChainState {
pub(crate) flags: ChainFlags,
pub(crate) instigator: Option<Box<dyn Any>>,
}
pub struct Context<'a> {
services: &'a mut TypeMap,
chain: &'a mut ChainState,
events: &'a mut Events,
location: (SequenceRef, usize),
}
impl<'a> Context<'a> {
pub(crate) fn new(
services: &'a mut TypeMap,
chain: &'a mut ChainState,
events: &'a mut Events,
location: (SequenceRef, usize),
) -> Self {
Self {
services,
chain,
events,
location,
}
}
#[must_use]
pub fn location(&self) -> (SequenceRef, usize) {
self.location
}
pub fn note(&mut self, message: impl Into<String>) {
let (sequence, index) = self.location;
self.events.record(RunnerEvent::Note {
sequence,
index,
message: message.into(),
});
}
#[must_use]
pub fn services(&self) -> &TypeMap {
self.services
}
pub fn services_mut(&mut self) -> &mut TypeMap {
self.services
}
#[must_use]
pub fn service<T: Any>(&self) -> Option<&T> {
self.services.get::<T>()
}
pub fn service_mut<T: Any>(&mut self) -> Option<&mut T> {
self.services.get_mut::<T>()
}
#[must_use]
pub fn instigator(&self) -> Option<&dyn Any> {
self.chain.instigator.as_deref()
}
#[must_use]
pub fn instigator_as<T: Any>(&self) -> Option<&T> {
self.instigator().and_then(|any| any.downcast_ref::<T>())
}
#[must_use]
pub fn flags(&self) -> &ChainFlags {
&self.chain.flags
}
pub fn flags_mut(&mut self) -> &mut ChainFlags {
&mut self.chain.flags
}
#[must_use]
pub fn flag(&self, name: &str) -> bool {
self.chain.flags.flag(name)
}
pub fn set_flag(&mut self, name: impl Into<String>, value: bool) {
self.chain.flags.set_flag(name, value);
}
#[must_use]
pub fn eval(&self, condition: &dyn Condition) -> bool {
condition.evaluate(&QueryCtx {
target: self.chain.instigator.as_deref(),
chain: Some(&self.chain.flags),
caps: self.services,
})
}
pub fn enact<'e>(&mut self, effects: impl IntoIterator<Item = &'e dyn Effect>) {
let ChainState {
flags, instigator, ..
} = &mut *self.chain;
for effect in effects {
effect.apply(&mut EffectCtx {
target: instigator.as_deref(),
chain: Some(flags),
caps: self.services,
});
}
}
}
#[cfg(test)]
mod tests {
use alloc::boxed::Box;
use alloc::string::String;
use super::*;
const HERE: (SequenceRef, usize) = (SequenceRef::from_raw(0), 0);
#[test]
fn typemap_stores_one_value_per_type() {
let mut map = TypeMap::new();
assert!(map.insert(7_u32).is_none());
assert_eq!(map.insert(9_u32), Some(7)); assert_eq!(map.get::<u32>(), Some(&9));
*map.get_mut::<u32>().unwrap() += 1;
assert_eq!(map.remove::<u32>(), Some(10));
assert!(map.get::<u32>().is_none());
}
#[test]
fn chain_flags_unset_reads_false() {
let flags = ChainFlags::new();
assert!(!flags.flag("accepted"));
}
#[test]
fn chain_flags_set_and_read() {
let mut flags = ChainFlags::new();
flags.set_flag("accepted", true);
assert!(flags.flag("accepted"));
flags.set_flag("accepted", false);
assert!(!flags.flag("accepted"));
}
#[test]
fn context_exposes_instigator_by_downcast() {
let mut services = TypeMap::new();
let mut state = ChainState {
instigator: Some(Box::new(42_i32)),
..ChainState::default()
};
let mut events = Events::default();
let ctx = Context::new(&mut services, &mut state, &mut events, HERE);
assert_eq!(ctx.instigator_as::<i32>(), Some(&42));
assert!(ctx.instigator_as::<String>().is_none());
}
#[test]
fn context_exposes_typed_service_helpers() {
let mut services = TypeMap::new();
services.insert(7_u32);
let mut state = ChainState::default();
let mut events = Events::default();
let mut ctx = Context::new(&mut services, &mut state, &mut events, HERE);
assert_eq!(ctx.service::<u32>(), Some(&7));
*ctx.service_mut::<u32>().unwrap() = 8;
assert_eq!(ctx.service::<u32>(), Some(&8));
}
#[test]
fn query_and_effect_contexts_expose_targets_and_services() {
let target = 42_i32;
let mut caps = TypeMap::new();
caps.insert(7_u32);
let query = QueryCtx {
target: Some(&target),
chain: None,
caps: &caps,
};
assert_eq!(query.target_as::<i32>(), Some(&42));
assert_eq!(query.service::<u32>(), Some(&7));
let mut effect = EffectCtx {
target: Some(&target),
chain: None,
caps: &mut caps,
};
assert_eq!(effect.target_as::<i32>(), Some(&42));
assert_eq!(effect.service::<u32>(), Some(&7));
*effect.service_mut::<u32>().unwrap() = 8;
assert_eq!(effect.service::<u32>(), Some(&8));
}
}