use alloc::collections::BTreeMap;
use amplify::confinement::SmallOrdMap;
use indexmap::IndexMap;
use sonicapi::{Api, StateAtom, StateName};
use strict_encoding::TypeName;
use strict_types::{StrictVal, TypeSystem};
use ultrasonic::{AuthToken, CellAddr, Memory, Operation, Opid, StateCell, StateData, StateValue};
use crate::LIB_NAME_SONIC;
#[derive(Clone, PartialEq, Eq, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_SONIC)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
pub struct Transition {
pub opid: Opid,
pub destroyed: SmallOrdMap<CellAddr, StateCell>,
}
impl Transition {
fn new(opid: Opid) -> Self { Self { opid, destroyed: none!() } }
}
#[derive(Clone, Debug, Default)]
pub struct EffectiveState {
pub raw: RawState,
pub main: AdaptedState,
pub aux: BTreeMap<TypeName, AdaptedState>,
}
impl EffectiveState {
#[inline]
pub fn addr(&self, auth: AuthToken) -> CellAddr { self.raw.addr(auth) }
pub fn read(&self, name: impl Into<StateName>) -> &StrictVal {
let name = name.into();
self.main
.computed
.get(&name)
.unwrap_or_else(|| panic!("Computed state {name} is not known"))
}
#[must_use]
pub(crate) fn apply<'a>(
&mut self,
op: Operation,
default_api: &Api,
custom_apis: impl IntoIterator<Item = &'a Api>,
sys: &TypeSystem,
) -> Transition {
self.main.apply(&op, default_api, sys);
for api in custom_apis {
let Some(name) = api.name() else {
continue;
};
let state = self.aux.entry(name.clone()).or_default();
state.apply(&op, api, sys);
}
self.raw.apply(op)
}
}
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
pub struct RawState {
pub auth: IndexMap<AuthToken, CellAddr>,
pub immutable: BTreeMap<CellAddr, StateData>,
pub owned: BTreeMap<CellAddr, StateCell>,
}
impl Memory for RawState {
fn read_once(&self, addr: CellAddr) -> Option<StateCell> { self.owned.get(&addr).copied() }
fn immutable(&self, addr: CellAddr) -> Option<StateValue> { self.immutable.get(&addr).map(|data| data.value) }
}
impl RawState {
pub fn addr(&self, auth: AuthToken) -> CellAddr { *self.auth.get(&auth).expect("undefined token oof authority") }
#[must_use]
pub fn apply(&mut self, op: Operation) -> Transition {
let opid = op.opid();
let mut transition = Transition::new(opid);
for input in op.destroying {
let res = self.owned.remove(&input.addr).expect("unknown input");
self.auth.shift_remove(&res.auth);
let res = transition
.destroyed
.insert(input.addr, res)
.expect("transaction too large");
debug_assert!(res.is_none());
}
for (no, cell) in op.destructible.into_iter().enumerate() {
let addr = CellAddr::new(opid, no as u16);
self.auth.insert(cell.auth, addr);
self.owned.insert(addr, cell);
}
self.immutable.extend(
op.immutable
.into_iter()
.enumerate()
.map(|(no, data)| (CellAddr::new(opid, no as u16), data)),
);
transition
}
}
#[derive(Clone, Eq, PartialEq, Debug, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(rename_all = "camelCase"))]
pub struct AdaptedState {
pub immutable: BTreeMap<StateName, BTreeMap<CellAddr, StateAtom>>,
pub owned: BTreeMap<StateName, BTreeMap<CellAddr, StrictVal>>,
pub computed: BTreeMap<StateName, StrictVal>,
}
impl AdaptedState {
pub fn immutable(&self, name: &StateName) -> Option<&BTreeMap<CellAddr, StateAtom>> { self.immutable.get(name) }
pub fn owned(&self, name: &StateName) -> Option<&BTreeMap<CellAddr, StrictVal>> { self.owned.get(name) }
pub(super) fn compute(&mut self, api: &Api) {
let empty = bmap![];
self.computed = bmap! {};
for reader in api.readers() {
let val = api.read(reader, |name| match self.immutable(&name) {
None => empty.values(),
Some(src) => src.values(),
});
self.computed.insert(reader.clone(), val);
}
}
pub(self) fn apply(&mut self, op: &Operation, api: &Api, sys: &TypeSystem) {
let opid = op.opid();
for (no, state) in op.immutable.iter().enumerate() {
if let Some((name, atom)) = api.convert_immutable(state, sys) {
self.immutable
.entry(name)
.or_default()
.insert(CellAddr::new(opid, no as u16), atom);
}
}
for input in &op.destroying {
for map in self.owned.values_mut() {
map.remove(&input.addr);
}
}
for (no, state) in op.destructible.iter().enumerate() {
if let Some((name, atom)) = api.convert_destructible(state.data, sys) {
self.owned
.entry(name)
.or_default()
.insert(CellAddr::new(opid, no as u16), atom);
}
}
}
}