use alloc::collections::BTreeMap;
use alloc::collections::btree_map::Entry;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::any::Any;
use core::fmt;
use crate::core::error::{Error, Result};
use crate::core::record::{Channel, Recorder};
use crate::core::sync::{LockRank, Mutex};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct HostKind(pub &'static str);
impl HostKind {
pub const CAPTURE: HostKind = HostKind("capture");
#[must_use]
pub const fn new(name: &'static str) -> HostKind {
HostKind(name)
}
#[must_use]
pub const fn as_str(self) -> &'static str {
self.0
}
}
impl fmt::Display for HostKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.0)
}
}
pub struct HostObjects {
entries: Mutex<BTreeMap<(HostKind, String), Arc<dyn Any + Send + Sync>>>,
policy: Mutex<InputPolicy>,
}
#[derive(Debug, Clone)]
pub enum InputPolicy {
Open,
Sealed(Arc<Recorder>),
}
impl Default for HostObjects {
fn default() -> HostObjects {
HostObjects::new()
}
}
impl fmt::Debug for HostObjects {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let sealed = self.is_sealed();
match self.entries.try_lock() {
Some(table) => f
.debug_struct("HostObjects")
.field(
"objects",
&table
.keys()
.map(|(kind, name)| format!("{kind}:{name}"))
.collect::<Vec<_>>(),
)
.field("sealed", &sealed)
.finish(),
None => f
.debug_struct("HostObjects")
.field("objects", &"<in use>")
.field("sealed", &sealed)
.finish(),
}
}
}
impl HostObjects {
#[must_use]
pub fn new() -> HostObjects {
HostObjects {
entries: Mutex::with_rank(LockRank::LEAF, BTreeMap::new()),
policy: Mutex::with_rank(LockRank::LEAF, InputPolicy::Open),
}
}
pub fn open<T, F>(&self, kind: HostKind, name: &str, make: F) -> Result<Arc<T>>
where
T: Any + Send + Sync,
F: FnOnce() -> T,
{
self.check_policy(kind, name)?;
if let Some(found) = self.get::<T>(kind, name)? {
return Ok(found);
}
let fresh = Arc::new(make());
let mut table = self.entries.lock();
match table.entry((kind, name.to_string())) {
Entry::Occupied(slot) => downcast(kind, name, Arc::clone(slot.get())),
Entry::Vacant(slot) => {
slot.insert(Arc::clone(&fresh) as Arc<dyn Any + Send + Sync>);
Ok(fresh)
}
}
}
pub fn get<T: Any + Send + Sync>(&self, kind: HostKind, name: &str) -> Result<Option<Arc<T>>> {
let found = {
let table = self.entries.lock();
table.get(&(kind, name.to_string())).map(Arc::clone)
};
found.map(|any| downcast(kind, name, any)).transpose()
}
pub fn insert<T: Any + Send + Sync>(&self, kind: HostKind, name: &str, object: Arc<T>) {
let _ = self.try_insert(kind, name, object);
}
pub fn try_insert<T: Any + Send + Sync>(
&self,
kind: HostKind,
name: &str,
object: Arc<T>,
) -> Result<()> {
self.check_policy(kind, name)?;
self.entries.lock().insert(
(kind, name.to_string()),
object as Arc<dyn Any + Send + Sync>,
);
Ok(())
}
pub fn seal(&self, recorder: Arc<Recorder>) -> Result<()> {
let open: Vec<(HostKind, String)> = self.entries.lock().keys().cloned().collect();
for (kind, name) in &open {
if !recorder.knows(&Channel::new(*kind, name)) {
return Err(unrecorded(*kind, name));
}
}
recorder.seal();
*self.policy.lock() = InputPolicy::Sealed(recorder);
Ok(())
}
pub fn unseal(&self) {
*self.policy.lock() = InputPolicy::Open;
}
#[must_use]
pub fn is_sealed(&self) -> bool {
matches!(&*self.policy.lock(), InputPolicy::Sealed(_))
}
fn check_policy(&self, kind: HostKind, name: &str) -> Result<()> {
let recorder = match &*self.policy.lock() {
InputPolicy::Open => return Ok(()),
InputPolicy::Sealed(recorder) => Arc::clone(recorder),
};
if recorder.knows(&Channel::new(kind, name)) {
return Ok(());
}
Err(unrecorded(kind, name))
}
pub fn close(&self, kind: HostKind, name: &str) -> bool {
self.entries
.lock()
.remove(&(kind, name.to_string()))
.is_some()
}
#[must_use]
pub fn names(&self, kind: HostKind) -> Vec<String> {
self.entries
.lock()
.keys()
.filter(|(k, _)| *k == kind)
.map(|(_, name)| name.clone())
.collect()
}
#[must_use]
pub fn len(&self) -> usize {
self.entries.lock().len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.lock().is_empty()
}
}
fn unrecorded(kind: HostKind, name: &str) -> Error {
Error::Config {
at: format!("{kind}:{name}"),
message: String::from(
"this host object would carry non-deterministic input into a machine whose \
recorder has no channel for it, so the run could not be replayed \
(CLAUDE.md, determinism). Register the channel with the recorder before \
building the machine, or do not seal the host-object table",
),
}
}
fn downcast<T: Any + Send + Sync>(
kind: HostKind,
name: &str,
any: Arc<dyn Any + Send + Sync>,
) -> Result<Arc<T>> {
any.downcast::<T>().map_err(|_| Error::Config {
at: format!("{kind}:{name}"),
message: String::from("a host object of another type is already open under this name"),
})
}
pub struct Captured<T> {
seen: Mutex<Vec<Arc<T>>>,
}
impl<T> Default for Captured<T> {
fn default() -> Captured<T> {
Captured::new()
}
}
impl<T> fmt::Debug for Captured<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.seen.try_lock() {
Some(seen) => f
.debug_struct("Captured")
.field("len", &seen.len())
.finish(),
None => f
.debug_struct("Captured")
.field("len", &"<in use>")
.finish(),
}
}
}
impl<T> Captured<T> {
#[must_use]
pub fn new() -> Captured<T> {
Captured {
seen: Mutex::with_rank(LockRank::LEAF, Vec::new()),
}
}
pub fn push(&self, object: &Arc<T>) {
self.seen.lock().push(Arc::clone(object));
}
#[must_use]
pub fn last(&self) -> Option<Arc<T>> {
self.seen.lock().last().map(Arc::clone)
}
#[must_use]
pub fn take(&self) -> Option<Arc<T>> {
let mut seen = self.seen.lock();
let last = seen.pop();
seen.clear();
last
}
#[must_use]
pub fn all(&self) -> Vec<Arc<T>> {
self.seen.lock().clone()
}
#[must_use]
pub fn len(&self) -> usize {
self.seen.lock().len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.seen.lock().is_empty()
}
pub fn clear(&self) {
self.seen.lock().clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
const A: HostKind = HostKind::new("test.a");
const B: HostKind = HostKind::new("test.b");
#[derive(Debug, PartialEq, Eq)]
struct Thing(u32);
#[derive(Debug)]
struct Other;
#[test]
fn one_name_is_one_object_and_two_tables_share_nothing() {
let left = HostObjects::new();
let right = HostObjects::new();
let a = left.open(A, "console", || Thing(1)).unwrap();
let b = left.open(A, "console", || Thing(2)).unwrap();
assert!(Arc::ptr_eq(&a, &b), "the same name is the same object");
assert_eq!(*a, Thing(1), "and the second value was discarded");
let elsewhere = right.open(A, "console", || Thing(3)).unwrap();
assert!(!Arc::ptr_eq(&a, &elsewhere));
assert_eq!(*elsewhere, Thing(3));
}
#[test]
fn kinds_do_not_collide_and_names_are_ordered() {
let hosts = HostObjects::new();
let a = hosts.open(A, "x", || Thing(1)).unwrap();
let b = hosts.open(B, "x", || Thing(2)).unwrap();
assert!(
!Arc::ptr_eq(&a, &b),
"one name under two kinds is two things"
);
hosts.open(A, "zulu", || Thing(3)).unwrap();
hosts.open(A, "alpha", || Thing(4)).unwrap();
assert_eq!(hosts.names(A), ["alpha", "x", "zulu"]);
assert_eq!(hosts.names(B), ["x"]);
assert_eq!(hosts.len(), 4);
assert!(!hosts.is_empty());
}
#[test]
fn a_type_collision_is_reported_rather_than_papered_over() {
let hosts = HostObjects::new();
hosts.open(A, "x", || Thing(1)).unwrap();
let e = hosts.open(A, "x", || Other).unwrap_err().to_string();
assert!(e.contains("another type"), "{e}");
}
#[test]
fn closing_a_name_leaves_the_arc_alone() {
let hosts = HostObjects::new();
let held = hosts.open(A, "x", || Thing(7)).unwrap();
assert!(hosts.close(A, "x"));
assert!(!hosts.close(A, "x"));
assert!(hosts.get::<Thing>(A, "x").unwrap().is_none());
assert_eq!(*held, Thing(7), "the holder is unaffected");
let again = hosts.open(A, "x", || Thing(8)).unwrap();
assert!(!Arc::ptr_eq(&held, &again));
}
#[test]
fn a_host_may_supply_the_object_itself() {
let hosts = HostObjects::new();
let mine = Arc::new(Thing(42));
hosts.insert(A, "x", Arc::clone(&mine));
let theirs = hosts.open(A, "x", || Thing(0)).unwrap();
assert!(Arc::ptr_eq(&mine, &theirs));
}
#[test]
fn a_capture_table_remembers_in_order_and_takes_the_last() {
let seen: Captured<Thing> = Captured::new();
assert!(seen.is_empty());
assert!(seen.take().is_none());
let first = Arc::new(Thing(1));
let second = Arc::new(Thing(2));
seen.push(&first);
seen.push(&second);
assert_eq!(seen.len(), 2);
assert_eq!(seen.all().len(), 2);
assert_eq!(*seen.last().unwrap(), Thing(2));
let took = seen.take().unwrap();
assert!(Arc::ptr_eq(&took, &second));
assert!(seen.is_empty(), "and the earlier one is forgotten");
}
}