use crate::build_runtime_graph::graph_parse::CleanedDefinitionGraph;
use crate::proto2::{ChangeValue, ChangeValueWithCounter, DispatchResult, NodeWillExecute, WrappedChangeValue};
pub trait ExecutionState {
fn get_count_node_execution(&self, node: &[u8]) -> Option<u64>;
fn inc_counter_node_execution(&mut self, node: &[u8]) -> u64;
fn get_value(&self, address: &[u8]) -> Option<(u64, ChangeValue)>;
fn set_value(&mut self, address: &[u8], counter: u64, value: ChangeValue);
}
pub fn evaluate_changes_against_node(
state: &impl ExecutionState,
paths_to_satisfy: &Vec<Vec<String>>
) -> Option<Vec<WrappedChangeValue>> {
let mut satisfied_paths = vec![];
for path in paths_to_satisfy {
if let Some(change_value) = state.get_value(path.join(":").as_bytes()) {
satisfied_paths.push(change_value.clone());
}
}
if satisfied_paths.len() != paths_to_satisfy.len() { return None }
Some(satisfied_paths.into_iter().map(|(counter, v)| WrappedChangeValue {
monotonic_counter: counter,
change_value: Some(v),
}).collect())
}
pub fn dispatch_and_mutate_state(
clean_definition_graph: &CleanedDefinitionGraph,
state: &mut impl ExecutionState,
change_value_with_counter: &ChangeValueWithCounter
) -> DispatchResult {
let g = clean_definition_graph;
for filled_value in &change_value_with_counter.filled_values {
let filled_value_address = &filled_value.clone().path.unwrap().address;
if let Some((prev_counter, _prev_change_value)) = state.get_value(filled_value_address.join(":").as_bytes()) {
if prev_counter >= change_value_with_counter.monotonic_counter {
continue
}
}
state.set_value(
filled_value_address.join(":").as_bytes().clone(),
change_value_with_counter.monotonic_counter,
filled_value.clone());
}
let mut node_executions: Vec<NodeWillExecute> = vec![];
for filled_value in &change_value_with_counter.filled_values {
let filled_value_address = &filled_value.clone().path.unwrap().address;
if let Some(matched_node_names) = g.dispatch_table.get(filled_value_address.join(":").as_str()) {
for node_that_should_exec in matched_node_names {
if let Some(choice_paths_to_satisfy) = g.query_paths.get(node_that_should_exec) {
for (idx, opt_paths_to_satisfy) in choice_paths_to_satisfy.iter().enumerate() {
if let Some(paths_to_satisfy) = opt_paths_to_satisfy {
if let Some(change_values_used_in_execution) = evaluate_changes_against_node(state, paths_to_satisfy) {
let node_will_execute = NodeWillExecute {
source_node: node_that_should_exec.clone(),
change_values_used_in_execution,
matched_query_index: idx as u64
};
node_executions.push(node_will_execute);
}
} else {
if state.get_count_node_execution(node_that_should_exec.as_bytes()).unwrap_or(0) > 0 {
continue;
}
node_executions.push(NodeWillExecute {
source_node: node_that_should_exec.clone(),
change_values_used_in_execution: vec![],
matched_query_index: idx as u64
});
}
state.inc_counter_node_execution(node_that_should_exec.as_bytes());
}
}
}
}
}
DispatchResult {
operations: node_executions,
}
}
#[cfg(test)]
mod tests {
use crate::proto2::{File, item, Item, ItemCore, OutputType, Path, PromptGraphNodeEcho, Query};
use crate::graph_definition::DefinitionGraph;
use std::collections::HashMap;
use super::*;
#[derive(Debug)]
pub struct TestState {
value: HashMap<Vec<u8>, (u64, ChangeValue)>,
node_executions: HashMap<Vec<u8>, u64>
}
impl TestState {
fn new() -> Self {
Self {
value: HashMap::new(),
node_executions: HashMap::new()
}
}
}
impl ExecutionState for TestState {
fn inc_counter_node_execution(&mut self, node: &[u8]) -> u64 {
let v = self.node_executions.entry(node.to_vec()).or_insert(0);
*v += 1;
*v
}
fn get_count_node_execution(&self, node: &[u8]) -> Option<u64> {
self.node_executions.get(node).map(|x| *x)
}
fn get_value(&self, address: &[u8]) -> Option<(u64, ChangeValue)> {
self.value.get(address).cloned()
}
fn set_value(&mut self, address: &[u8], counter: u64, value: ChangeValue) {
self.value.insert(address.to_vec(), (counter, value));
}
}
fn get_file_empty_query() -> File {
File {
id: "test".to_string(),
nodes: vec![Item{
core: Some(ItemCore {
name: "EmptyNode".to_string(),
queries: vec![Query{ query: None}],
output: Some(OutputType {
output: "{}".to_string(),
}),
output_tables: vec![],
}),
item: Some(item::Item::NodeEcho(PromptGraphNodeEcho {
}))}],
}
}
fn get_file() -> File {
File {
id: "test".to_string(),
nodes: vec![Item{
core: Some(ItemCore {
name: "".to_string(),
queries: vec![Query {
query: None,
}],
output: Some(OutputType {
output: "{} ".to_string(),
}),
output_tables: vec![]
}),
item: Some(item::Item::NodeEcho(PromptGraphNodeEcho {
}))}],
}
}
#[test]
fn test_dispatch_with_file_and_change() {
let mut state = TestState::new();
let file = get_file();
let d = DefinitionGraph::from_file(file);
let g = CleanedDefinitionGraph::new(&d);
let c = ChangeValueWithCounter {
filled_values: vec![],
parent_monotonic_counters: vec![],
monotonic_counter: 0,
branch: 0,
source_node: "".to_string(),
};
let result = dispatch_and_mutate_state(&g, &mut state, &c);
assert_eq!(result.operations.len(), 0);
}
#[test]
fn test_we_dispatch_nodes_that_have_no_query_once() {
let mut state = TestState::new();
let file = get_file_empty_query();
let d = DefinitionGraph::from_file(file);
let g = CleanedDefinitionGraph::new(&d);
let c = ChangeValueWithCounter {
filled_values: vec![ChangeValue {
path: Some(Path {
address: vec![],
}),
value: None,
branch: 0,
}],
parent_monotonic_counters: vec![],
monotonic_counter: 0,
branch: 0,
source_node: "EmptyNode".to_string(),
};
let result = dispatch_and_mutate_state(&g, &mut state, &c);
assert_eq!(result.operations.len(), 1);
assert_eq!(result.operations[0], NodeWillExecute {
source_node: "EmptyNode".to_string(),
change_values_used_in_execution: vec![],
matched_query_index: 0
});
let result = dispatch_and_mutate_state(&g, &mut state, &c);
assert_eq!(result.operations.len(), 0);
}
}