use super::env::Env;
use super::key::CapabilityKey;
use crate::concurrency::fiber_ref::with_fiber_id;
use crate::runtime::FiberId;
use std::cell::RefCell;
use std::collections::HashMap;
thread_local! {
static FIBER_STACKS: RefCell<HashMap<u64, Vec<Env>>> = RefCell::new(HashMap::new());
}
#[inline]
fn fiber_key() -> u64 {
crate::concurrency::fiber_ref::current_fiber_id().as_u64()
}
fn push_env(env: Env) {
FIBER_STACKS.with(|stacks| {
stacks
.borrow_mut()
.entry(fiber_key())
.or_default()
.push(env);
});
}
fn pop_env() {
FIBER_STACKS.with(|stacks| {
let mut stacks = stacks.borrow_mut();
if let Some(layer) = stacks.get_mut(&fiber_key()) {
layer.pop();
if layer.is_empty() {
stacks.remove(&fiber_key());
}
}
});
}
pub fn active_env() -> Option<Env> {
FIBER_STACKS.with(|stacks| {
stacks
.borrow()
.get(&fiber_key())
.and_then(|v| v.last())
.cloned()
})
}
pub fn with_override<K, R, F>(base: &Env, value: K::Value, f: F) -> R
where
K: CapabilityKey,
K::Value: Clone + Send + Sync + 'static,
F: FnOnce(&Env) -> R,
{
let mut overlay = base.clone();
overlay.insert::<K>(value);
push_env(overlay.clone());
let out = f(&overlay);
pop_env();
out
}
pub fn with_fiber_and_override<K, R, F>(fiber: FiberId, base: &Env, value: K::Value, f: F) -> R
where
K: CapabilityKey,
K::Value: Clone + Send + Sync + 'static,
F: FnOnce(&Env) -> R,
{
with_fiber_id(fiber, || with_override::<K, R, F>(base, value, f))
}
#[cfg(test)]
#[allow(dead_code, clippy::new_ret_no_self)]
mod tests {
use super::super::needs::Needs;
use super::*;
#[::id_effect::capability(u32)]
struct Counter;
#[test]
fn nested_override_restores_parent() {
let mut base = Env::new();
base.insert::<CounterKey>(1u32);
with_override::<CounterKey, _, _>(&base, 9u32, |env| {
assert_eq!(*Needs::<CounterKey>::need(env), 9);
with_override::<CounterKey, _, _>(env, 3u32, |inner| {
assert_eq!(*Needs::<CounterKey>::need(inner), 3);
});
assert_eq!(*Needs::<CounterKey>::need(env), 9);
});
assert_eq!(*base.get::<CounterKey>(), 1);
}
#[test]
fn active_env_returns_none_without_overlay() {
assert!(active_env().is_none());
}
#[test]
fn active_env_sees_pushed_overlay() {
let mut base = Env::new();
base.insert::<CounterKey>(1u32);
with_override::<CounterKey, _, _>(&base, 5u32, |_env| {
let active = active_env().expect("overlay");
assert_eq!(*active.get::<CounterKey>(), 5);
});
assert!(active_env().is_none());
}
#[test]
fn with_fiber_and_override_scopes_to_fiber() {
let mut base = Env::new();
base.insert::<CounterKey>(0u32);
let fid = FiberId::new(99);
let got = with_fiber_and_override::<CounterKey, _, _>(fid, &base, 7u32, |env| {
*Needs::<CounterKey>::need(env)
});
assert_eq!(got, 7);
}
}