use super::{Deserialize, Serialize};
use crate::{
ROOT_STATE,
node::{self, Node},
};
use steel::{
SteelErr, SteelVal,
gc::Gc,
parser::ast::ExprKind,
rerrs::ErrorKind,
rvals::{FromSteelVal, IntoSteelVal, SteelHashMap},
steel_vm::engine::Engine,
};
pub trait NodeState: Default + FromSteelVal + IntoSteelVal {
const NAME: &str;
fn register_fns(vm: &mut Engine);
fn register(vm: &mut Engine) {
vm.register_type::<Self>(Self::NAME);
Self::register_fns(vm);
}
}
pub trait WithStateType: Node + Sized {
fn with_state_type<S: NodeState>(self) -> State<Self, S> {
State::<Self, S>::new(self)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct State<N, S> {
pub node: N,
pub state: core::marker::PhantomData<S>,
}
impl<N, S> State<N, S> {
pub fn new(node: N) -> Self
where
N: Node,
S: NodeState,
{
State {
node,
state: core::marker::PhantomData,
}
}
}
fn default_node_state_steel_val<S: NodeState>() -> SteelVal {
S::default()
.into_steelval()
.expect("default `NodeState` to `SteelVal` conversion should never fail")
}
impl<N> WithStateType for N
where
N: Node,
{
fn with_state_type<S: NodeState>(self) -> State<Self, S> {
State::<Self, S>::new(self)
}
}
impl<N, S> Node for State<N, S>
where
N: Node,
S: NodeState,
{
fn n_inputs(&self) -> usize {
self.node.n_inputs()
}
fn n_outputs(&self) -> usize {
self.node.n_outputs()
}
fn branches(&self) -> Vec<node::EvalConf> {
self.node.branches()
}
fn expr(&self, ctx: node::ExprCtx) -> ExprKind {
self.node.expr(ctx)
}
fn push_eval(&self) -> Vec<node::EvalConf> {
self.node.push_eval()
}
fn pull_eval(&self) -> Vec<node::EvalConf> {
self.node.pull_eval()
}
fn inlet(&self) -> bool {
self.node.inlet()
}
fn outlet(&self) -> bool {
self.node.outlet()
}
fn stateful(&self) -> bool {
true
}
fn register(&self, path: &[node::Id], vm: &mut Engine) {
S::register(vm);
let val = default_node_state_steel_val::<S>();
update(vm, path, val).unwrap();
}
}
pub fn update_value(vm: &mut Engine, node_path: &[usize], val: SteelVal) -> Result<(), SteelErr> {
let SteelVal::HashMapV(mut root_state) = vm.extract_value(ROOT_STATE)? else {
return Err(SteelErr::new(
ErrorKind::Generic,
"`ROOT_STATE` was not a hashmap".to_string(),
));
};
fn update_hashmap_value(
graph_state: &mut SteelHashMap,
node_path: &[usize],
val: SteelVal,
) -> Result<(), SteelErr> {
match node_path {
&[] => Err(SteelErr::new(ErrorKind::Generic, "empty node path".into())),
&[node_id] => {
let id = node_id.try_into().expect("node_id out of range");
let key = SteelVal::IntV(id);
*graph_state = Gc::new(graph_state.update(key, val)).into();
Ok(())
}
&[graph_id, ..] => {
let id = graph_id.try_into().expect("node_id out of range");
let key = SteelVal::IntV(id);
let update = |opt: Option<SteelVal>| {
let Some(SteelVal::HashMapV(mut state)) = opt else {
panic!("graph state was not a hashmap");
};
update_hashmap_value(&mut state, &node_path[1..], val)
.expect("failed to update value");
Some(SteelVal::HashMapV(state))
};
*graph_state = Gc::new(graph_state.alter(update, key)).into();
Ok(())
}
}
}
update_hashmap_value(&mut root_state, node_path, val)?;
vm.update_value(ROOT_STATE, SteelVal::HashMapV(root_state));
Ok(())
}
pub fn update<S: IntoSteelVal>(
vm: &mut Engine,
node_path: &[usize],
val: S,
) -> Result<(), SteelErr> {
update_value(vm, node_path, val.into_steelval()?)
}
pub fn extract_value(vm: &Engine, node_path: &[usize]) -> Result<Option<SteelVal>, SteelErr> {
let SteelVal::HashMapV(root_state) = vm.extract_value(ROOT_STATE)? else {
return Err(SteelErr::new(
ErrorKind::Generic,
"`ROOT_STATE` was not a hashmap".to_string(),
));
};
fn extract_hashmap_value(
graph_state: &SteelHashMap,
node_path: &[usize],
) -> Result<Option<SteelVal>, SteelErr> {
match node_path {
&[] => Err(SteelErr::new(ErrorKind::Generic, "empty node path".into())),
&[node_id] => {
let id = node_id.try_into().expect("node_id out of range");
let key = SteelVal::IntV(id);
Ok(graph_state.get(&key).cloned())
}
&[graph_id, ..] => {
let id = graph_id.try_into().expect("node_id out of range");
let key = SteelVal::IntV(id);
let Some(SteelVal::HashMapV(state)) = graph_state.get(&key) else {
return Ok(None);
};
extract_hashmap_value(state, &node_path[1..])
}
}
}
extract_hashmap_value(&root_state, node_path)
}
pub fn extract<S: FromSteelVal>(vm: &Engine, node_path: &[usize]) -> Result<Option<S>, SteelErr> {
let Some(val) = extract_value(vm, node_path)? else {
return Ok(None);
};
S::from_steelval(&val).map(Some)
}