use core::{
any::{Any, TypeId},
fmt::Debug,
marker::PhantomData,
};
use alloc::{collections::BTreeMap, rc::Rc, vec::Vec};
#[derive(Debug, Clone)]
pub struct Environment {
state: Rc<EnvironmentState>,
}
#[derive(Debug, Clone)]
enum EnvironmentState {
Map(BTreeMap<TypeId, Rc<dyn Any>>),
Overlay {
parent: Rc<Self>,
key: TypeId,
entry: EnvironmentEntry,
},
}
#[derive(Debug, Clone)]
enum EnvironmentEntry {
Present(Rc<dyn Any>),
Removed,
}
impl MetadataKey for Environment {}
impl Default for Environment {
fn default() -> Self {
Self::new()
}
}
use crate::{
View,
components::Metadata,
extract::Extractor,
layout::StretchAxis,
metadata::MetadataKey,
plugin::Plugin,
view::{Hook, ViewConfiguration},
};
#[derive(Debug)]
pub struct Store<K, V> {
key: PhantomData<K>,
value: V,
}
impl<K, V> Store<K, V> {
#[must_use]
pub const fn new(value: V) -> Self {
Self {
key: PhantomData,
value,
}
}
#[must_use]
pub const fn value(&self) -> &V {
&self.value
}
}
impl Environment {
#[must_use]
pub fn identity(&self) -> usize {
Rc::as_ptr(&self.state) as usize
}
fn insert_any(&mut self, key: TypeId, value: Rc<dyn Any>) {
match Rc::get_mut(&mut self.state) {
Some(EnvironmentState::Map(map)) => {
map.insert(key, value);
}
Some(EnvironmentState::Overlay {
key: overlay_key,
entry,
..
}) if *overlay_key == key => {
*entry = EnvironmentEntry::Present(value);
}
_ => self.push_overlay(key, EnvironmentEntry::Present(value)),
}
}
fn lookup_any_in_state(state: &EnvironmentState, key: TypeId) -> Option<&Rc<dyn Any>> {
match state {
EnvironmentState::Map(map) => map.get(&key),
EnvironmentState::Overlay {
parent,
key: overlay_key,
entry,
} => {
if *overlay_key == key {
match entry {
EnvironmentEntry::Present(value) => Some(value),
EnvironmentEntry::Removed => None,
}
} else {
Self::lookup_any_in_state(parent.as_ref(), key)
}
}
}
}
fn lookup_any(&self, key: TypeId) -> Option<&Rc<dyn Any>> {
Self::lookup_any_in_state(self.state.as_ref(), key)
}
fn push_overlay(&mut self, key: TypeId, entry: EnvironmentEntry) {
self.state = Rc::new(EnvironmentState::Overlay {
parent: self.state.clone(),
key,
entry,
});
}
fn extend_from_state(&mut self, state: &EnvironmentState) {
match state {
EnvironmentState::Map(map) => {
for (key, value) in map {
self.insert_any(*key, value.clone());
}
}
EnvironmentState::Overlay { parent, key, entry } => {
self.extend_from_state(parent.as_ref());
self.push_overlay(*key, entry.clone());
}
}
}
fn collect_matches_in_state<'a, T: 'static>(
state: &'a EnvironmentState,
matches: &mut Vec<&'a T>,
) {
match state {
EnvironmentState::Map(map) => {
if let Some(value) = map.get(&TypeId::of::<T>()) {
matches.push(
value
.downcast_ref::<T>()
.expect("failed to downcast value while collecting environment state"),
);
}
}
EnvironmentState::Overlay { parent, key, entry } => {
Self::collect_matches_in_state(parent.as_ref(), matches);
if *key != TypeId::of::<T>() {
return;
}
match entry {
EnvironmentEntry::Present(value) => matches.push(
value
.downcast_ref::<T>()
.expect("failed to downcast value while collecting environment state"),
),
EnvironmentEntry::Removed => matches.clear(),
}
}
}
}
#[must_use]
pub fn new() -> Self {
Self {
state: Rc::new(EnvironmentState::Map(BTreeMap::new())),
}
}
#[must_use]
pub fn store<K: 'static, V: 'static>(mut self, value: V) -> Self {
self.insert(Store {
key: PhantomData::<K>,
value,
});
self
}
#[must_use]
pub fn query<K: 'static, V: 'static>(&self) -> Option<&V> {
self.get::<Store<K, V>>().map(|s| &s.value)
}
pub fn install(&mut self, plugin: impl Plugin) -> &mut Self {
plugin.install(self);
self
}
pub fn insert<T: 'static>(&mut self, value: T) {
let key = TypeId::of::<T>();
let value = Rc::new(value) as Rc<dyn Any>;
self.insert_any(key, value);
}
pub fn insert_hook<T: ViewConfiguration, V: View>(
&mut self,
hook: impl Fn(&Self, T) -> V + 'static,
) {
self.insert(Hook::new(hook));
}
pub fn remove<T: 'static>(&mut self) {
let key = TypeId::of::<T>();
match Rc::get_mut(&mut self.state) {
Some(EnvironmentState::Map(map)) => {
map.remove(&key);
}
Some(EnvironmentState::Overlay {
key: overlay_key,
entry,
..
}) if *overlay_key == key => {
*entry = EnvironmentEntry::Removed;
}
_ => self.push_overlay(key, EnvironmentEntry::Removed),
}
}
pub fn with<T: 'static>(&mut self, value: T) -> &mut Self {
self.insert(value);
self
}
#[must_use]
pub fn extending<T: 'static>(&self, value: T) -> Self {
Self {
state: Rc::new(EnvironmentState::Overlay {
parent: self.state.clone(),
key: TypeId::of::<T>(),
entry: EnvironmentEntry::Present(Rc::new(value) as Rc<dyn Any>),
}),
}
}
#[must_use]
#[allow(clippy::coerce_container_to_any)]
pub fn get<T: 'static>(&self) -> Option<&T> {
self.lookup_any(TypeId::of::<T>())
.map(|v| v.downcast_ref::<T>().expect("failed to downcast value"))
}
#[must_use]
pub fn get_nth<T: 'static>(&self, index: usize) -> Option<&T> {
let mut matches = Vec::new();
Self::collect_matches_in_state(self.state.as_ref(), &mut matches);
matches.into_iter().nth_back(index)
}
#[must_use]
pub fn get_or_insert_with<T: 'static, F: FnOnce() -> T>(&mut self, f: F) -> &T {
if self.lookup_any(TypeId::of::<T>()).is_none() {
self.insert(f());
}
self.get::<T>()
.expect("value missing from environment after insertion")
}
pub fn extract<T: Extractor>(&self) -> Result<T, anyhow::Error> {
T::extract(self)
}
#[must_use]
pub fn layered_on(&self, parent: &Self) -> Self {
let mut layered = parent.clone();
layered.extend_from_state(self.state.as_ref());
layered
}
}
#[derive(Debug, Clone)]
pub struct UseEnv<F> {
handler: F,
}
impl<F> UseEnv<F> {
#[must_use]
pub const fn new(handler: F) -> Self {
Self { handler }
}
}
#[must_use]
pub fn use_env<E, V, F>(f: F) -> UseEnv<impl FnOnce(&Environment) -> V>
where
E: Extractor,
V: View,
F: FnOnce(E) -> V + 'static,
{
UseEnv::new(move |env: &Environment| {
let extracted = E::extract(env).expect("failed to extract value from environment");
f(extracted)
})
}
impl<V, F> View for UseEnv<F>
where
V: View,
F: FnOnce(&Environment) -> V + 'static,
{
fn body(self, env: &Environment) -> impl View {
(self.handler)(env)
}
}
#[derive(Debug, Clone)]
pub struct With<V, T> {
content: V,
value: T,
}
impl<V: View, T: 'static> With<V, T> {
pub const fn new(content: V, value: T) -> Self {
Self { content, value }
}
}
pub const fn with<V: View, T: 'static>(view: V, value: T) -> With<V, T> {
With::new(view, value)
}
impl<V: View, T: 'static> View for With<V, T> {
fn body(self, env: &Environment) -> impl View {
let env = env.extending(self.value);
Metadata::new(self.content, env)
}
fn stretch_axis(&self) -> StretchAxis {
self.content.stretch_axis()
}
}
#[cfg(test)]
mod tests {
use alloc::string::String;
use super::*;
#[test]
fn extending_reuses_parent_state_via_overlay() {
let mut base = Environment::new();
base.insert(7_u32);
let parent_state = base.state.clone();
let extended = base.extending(11_u64);
match extended.state.as_ref() {
EnvironmentState::Overlay { parent, key, entry } => {
assert!(Rc::ptr_eq(parent, &parent_state));
assert_eq!(*key, TypeId::of::<u64>());
match entry {
EnvironmentEntry::Present(value) => {
assert_eq!(value.downcast_ref::<u64>(), Some(&11_u64));
}
EnvironmentEntry::Removed => {
panic!("overlay entry unexpectedly removed");
}
}
}
EnvironmentState::Map(_) => panic!("extending must create overlay state"),
}
}
#[test]
fn get_nth_counts_from_the_nearest_overlay_outwards() {
let env = Environment::new()
.extending(1_i32)
.extending(2_i32)
.extending(3_i32);
assert_eq!(env.get_nth::<i32>(0), Some(&3_i32));
assert_eq!(env.get_nth::<i32>(1), Some(&2_i32));
assert_eq!(env.get_nth::<i32>(2), Some(&1_i32));
assert_eq!(env.get_nth::<i32>(3), None);
}
#[test]
fn get_nth_zero_agrees_with_get() {
let env = Environment::new().extending(1_i32).extending(2_i32);
assert_eq!(env.get_nth::<i32>(0), env.get::<i32>());
}
#[test]
fn deep_overlay_chain_preserves_parent_visibility() {
let mut env = Environment::new();
env.insert(String::from("root"));
let env = env.extending(3_i32).extending(true).extending(9_u8);
assert_eq!(env.get::<String>(), Some(&String::from("root")));
assert_eq!(env.get::<i32>(), Some(&3_i32));
assert_eq!(env.get::<bool>(), Some(&true));
assert_eq!(env.get::<u8>(), Some(&9_u8));
}
}