Core graph structure
This commit is contained in:
parent
4dfe4f8aa4
commit
3b943cb828
45
Cargo.lock
generated
45
Cargo.lock
generated
@ -310,7 +310,7 @@ dependencies = [
|
|||||||
"atty",
|
"atty",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"clap_lex",
|
"clap_lex",
|
||||||
"indexmap",
|
"indexmap 1.9.1",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"strsim",
|
"strsim",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
@ -536,6 +536,12 @@ dependencies = [
|
|||||||
"termcolor",
|
"termcolor",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "event-listener"
|
name = "event-listener"
|
||||||
version = "2.5.3"
|
version = "2.5.3"
|
||||||
@ -563,6 +569,12 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fixedbitset"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flume"
|
name = "flume"
|
||||||
version = "0.10.14"
|
version = "0.10.14"
|
||||||
@ -767,7 +779,7 @@ dependencies = [
|
|||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
"indexmap",
|
"indexmap 1.9.1",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
@ -789,6 +801,12 @@ version = "0.12.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashlink"
|
name = "hashlink"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
@ -974,6 +992,16 @@ dependencies = [
|
|||||||
"hashbrown 0.12.3",
|
"hashbrown 0.12.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "2.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
|
||||||
|
dependencies = [
|
||||||
|
"equivalent",
|
||||||
|
"hashbrown 0.15.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "inotify"
|
name = "inotify"
|
||||||
version = "0.9.6"
|
version = "0.9.6"
|
||||||
@ -1471,6 +1499,16 @@ version = "2.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "petgraph"
|
||||||
|
version = "0.6.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
|
||||||
|
dependencies = [
|
||||||
|
"fixedbitset",
|
||||||
|
"indexmap 2.6.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf"
|
name = "phf"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
@ -1940,7 +1978,7 @@ dependencies = [
|
|||||||
"futures-util",
|
"futures-util",
|
||||||
"hashlink",
|
"hashlink",
|
||||||
"hex",
|
"hex",
|
||||||
"indexmap",
|
"indexmap 1.9.1",
|
||||||
"itoa",
|
"itoa",
|
||||||
"libc",
|
"libc",
|
||||||
"libsqlite3-sys",
|
"libsqlite3-sys",
|
||||||
@ -2550,6 +2588,7 @@ dependencies = [
|
|||||||
"notify-debouncer-mini",
|
"notify-debouncer-mini",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"openssl",
|
"openssl",
|
||||||
|
"petgraph",
|
||||||
"pulldown-cmark",
|
"pulldown-cmark",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
@ -67,6 +67,7 @@ tree-sitter-rust = "0.20"
|
|||||||
unicode-normalization = "0.1.19"
|
unicode-normalization = "0.1.19"
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
uuid = { version = "1.1", features = ["v4" ] }
|
uuid = { version = "1.1", features = ["v4" ] }
|
||||||
|
petgraph = "0.6.5"
|
||||||
|
|
||||||
[target.'cfg(target_os = "ios")'.dependencies]
|
[target.'cfg(target_os = "ios")'.dependencies]
|
||||||
openssl = { version = "0.10", features = ["vendored"] }
|
openssl = { version = "0.10", features = ["vendored"] }
|
||||||
|
461
src/graph/mod.rs
Normal file
461
src/graph/mod.rs
Normal file
@ -0,0 +1,461 @@
|
|||||||
|
use petgraph::{
|
||||||
|
graph::{DiGraph, NodeIndex},
|
||||||
|
visit::EdgeRef,
|
||||||
|
};
|
||||||
|
use std::cell::{Cell, RefCell};
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::{any::Any, collections::VecDeque};
|
||||||
|
|
||||||
|
type NodeGraph = DiGraph<ErasedNode, (), u32>;
|
||||||
|
|
||||||
|
pub struct Graph<Output> {
|
||||||
|
// we treat this as a StableGraph, since nodes are never removed
|
||||||
|
node_graph: Rc<RefCell<NodeGraph>>,
|
||||||
|
output: Option<NodeIndex<u32>>,
|
||||||
|
output_type: std::marker::PhantomData<Output>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Output: Clone + 'static> Graph<Output> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
node_graph: Rc::new(RefCell::new(DiGraph::new())),
|
||||||
|
output: None,
|
||||||
|
output_type: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_output<R: Rule<Output> + 'static>(&mut self, rule: R) {
|
||||||
|
assert!(self.output.is_none(), "cannot replace graph output");
|
||||||
|
let input = self.add_rule(rule);
|
||||||
|
self.output = Some(input.node_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_node<V: Clone + 'static>(&mut self, node: impl Node<V> + 'static) -> Input<V> {
|
||||||
|
let value = node.value_rc();
|
||||||
|
let erased = ErasedNode::new(node);
|
||||||
|
let idx = self.node_graph.borrow_mut().add_node(erased);
|
||||||
|
Input {
|
||||||
|
node_idx: idx,
|
||||||
|
value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_value<V: Clone + 'static>(&mut self, value: V) -> Input<V> {
|
||||||
|
return self.add_node(ConstNode(value.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_rule<R: Rule<V> + 'static, V: Clone + 'static>(&mut self, rule: R) -> Input<V> {
|
||||||
|
return self.add_node(RuleNode::new(rule));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_input<I, F>(&mut self, mut f: F) -> Input<I::Output>
|
||||||
|
where
|
||||||
|
I: ExternalInput + 'static,
|
||||||
|
I::Output: Clone,
|
||||||
|
F: FnMut(InvalidationSignal) -> I,
|
||||||
|
{
|
||||||
|
let node_idx = Rc::new(Cell::new(None));
|
||||||
|
let signal = InvalidationSignal {
|
||||||
|
node_idx: Rc::clone(&node_idx),
|
||||||
|
graph: Rc::clone(&self.node_graph),
|
||||||
|
};
|
||||||
|
let input = f(signal);
|
||||||
|
let node = ExternalInputNode::<I>::new(input);
|
||||||
|
let input = self.add_node(node);
|
||||||
|
node_idx.set(Some(input.node_idx));
|
||||||
|
input
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn freeze(self) -> Result<FrozenGraph<Output>, GraphFreezeError> {
|
||||||
|
if self.output.is_none() {
|
||||||
|
return Err(GraphFreezeError::NoOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
let graph = self.node_graph.borrow();
|
||||||
|
let indices = graph.node_indices();
|
||||||
|
drop(graph);
|
||||||
|
let mut edges = vec![];
|
||||||
|
for idx in indices {
|
||||||
|
let node = &mut self.node_graph.borrow_mut()[idx];
|
||||||
|
(node.visit_inputs)(&mut node.any, &mut |input_idx| {
|
||||||
|
edges.push((input_idx, idx));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (source, dest) in edges {
|
||||||
|
self.node_graph.borrow_mut().add_edge(source, dest, ());
|
||||||
|
}
|
||||||
|
|
||||||
|
let sorted = petgraph::algo::toposort(&*self.node_graph.borrow(), None);
|
||||||
|
if let Err(_cycle) = sorted {
|
||||||
|
// TODO: actually build a vec describing the cycle path for debugging
|
||||||
|
return Err(GraphFreezeError::Cyclic(vec![]));
|
||||||
|
}
|
||||||
|
|
||||||
|
let frozen = FrozenGraph {
|
||||||
|
node_graph: self.node_graph,
|
||||||
|
output: self.output.unwrap(),
|
||||||
|
output_type: std::marker::PhantomData,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(frozen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum GraphFreezeError {
|
||||||
|
NoOutput,
|
||||||
|
Cyclic(Vec<NodeIndex<u32>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FrozenGraph<Output> {
|
||||||
|
node_graph: Rc<RefCell<NodeGraph>>,
|
||||||
|
output: NodeIndex<u32>,
|
||||||
|
output_type: std::marker::PhantomData<Output>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Output: Clone + 'static> FrozenGraph<Output> {
|
||||||
|
fn update_node(&mut self, idx: NodeIndex<u32>) {
|
||||||
|
let graph = self.node_graph.borrow();
|
||||||
|
let node = &graph[idx];
|
||||||
|
let is_valid = (node.is_valid)(&node.any);
|
||||||
|
drop(graph);
|
||||||
|
if !is_valid {
|
||||||
|
// collect all the edges into a vec so that we can mutably borrow the graph to update the nodes
|
||||||
|
let edge_sources = self
|
||||||
|
.node_graph
|
||||||
|
.borrow()
|
||||||
|
.edges_directed(idx, petgraph::Direction::Incoming)
|
||||||
|
.map(|edge| edge.source())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Update the dependencies of this node.
|
||||||
|
// TODO: iterating/recursing here seems less than efficient
|
||||||
|
// instead, in evaluate, topo sort the graph and update invalid nodes?
|
||||||
|
for source in edge_sources {
|
||||||
|
self.update_node(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
let node = &mut self.node_graph.borrow_mut()[idx];
|
||||||
|
// Update the inputs of in this node's struct.
|
||||||
|
|
||||||
|
// Actually update the node's value.
|
||||||
|
(node.update)(&mut node.any);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn evaluate(&mut self) -> Output {
|
||||||
|
self.update_node(self.output);
|
||||||
|
let graph = self.node_graph.borrow();
|
||||||
|
let node = &graph[self.output].expect_type::<Output>();
|
||||||
|
node.value()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Input<T> {
|
||||||
|
node_idx: NodeIndex<u32>,
|
||||||
|
value: Rc<RefCell<Option<T>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone + 'static> Input<T> {
|
||||||
|
fn value(&self) -> T {
|
||||||
|
self.value
|
||||||
|
.as_ref()
|
||||||
|
.borrow()
|
||||||
|
.clone()
|
||||||
|
.expect("Input must be updated before being deref'd")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: there's a lot happening here, make sure this doesn't create a reference cycle
|
||||||
|
pub struct InvalidationSignal {
|
||||||
|
node_idx: Rc<Cell<Option<NodeIndex<u32>>>>,
|
||||||
|
graph: Rc<RefCell<NodeGraph>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InvalidationSignal {
|
||||||
|
pub fn invalidate(&self) {
|
||||||
|
let mut graph = self.graph.borrow_mut();
|
||||||
|
let mut queue = VecDeque::new();
|
||||||
|
queue.push_back(self.node_idx.get().unwrap());
|
||||||
|
while let Some(idx) = queue.pop_front() {
|
||||||
|
let node = &mut graph[idx];
|
||||||
|
if (node.is_valid)(&node.any) {
|
||||||
|
(node.invalidate)(&mut node.any);
|
||||||
|
let dependents = graph
|
||||||
|
.edges_directed(idx, petgraph::Direction::Outgoing)
|
||||||
|
.map(|edge| edge.target());
|
||||||
|
queue.extend(dependents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: i really want Input to be able to implement Deref somehow
|
||||||
|
|
||||||
|
struct ErasedNode {
|
||||||
|
any: Box<dyn Any>,
|
||||||
|
is_valid: Box<dyn Fn(&Box<dyn Any>) -> bool>,
|
||||||
|
invalidate: Box<dyn Fn(&mut Box<dyn Any>) -> ()>,
|
||||||
|
visit_inputs: Box<dyn Fn(&mut Box<dyn Any>, &mut dyn FnMut(NodeIndex<u32>) -> ()) -> ()>,
|
||||||
|
update: Box<dyn Fn(&mut Box<dyn Any>) -> ()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ErasedNode {
|
||||||
|
fn new<N: Node<V> + 'static, V: 'static>(base: N) -> Self {
|
||||||
|
// i don't love the double boxing, but i'm not sure how else to do this
|
||||||
|
let thing: Box<dyn Node<V>> = Box::new(base);
|
||||||
|
let any: Box<dyn Any> = Box::new(thing);
|
||||||
|
Self {
|
||||||
|
any,
|
||||||
|
is_valid: Box::new(|any| {
|
||||||
|
let x = any.downcast_ref::<Box<dyn Node<V>>>().unwrap();
|
||||||
|
x.is_valid()
|
||||||
|
}),
|
||||||
|
invalidate: Box::new(|any| {
|
||||||
|
let x = any.downcast_mut::<Box<dyn Node<V>>>().unwrap();
|
||||||
|
x.invalidate();
|
||||||
|
}),
|
||||||
|
visit_inputs: Box::new(|any, visitor| {
|
||||||
|
let x = any.downcast_mut::<Box<dyn Node<V>>>().unwrap();
|
||||||
|
x.visit_inputs(visitor);
|
||||||
|
}),
|
||||||
|
update: Box::new(|any| {
|
||||||
|
let x = any.downcast_mut::<Box<dyn Node<V>>>().unwrap();
|
||||||
|
x.update();
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: revisit if these are necessary
|
||||||
|
fn expect_type<'a, V: 'static>(&'a self) -> &'a dyn Node<V> {
|
||||||
|
let res = self
|
||||||
|
.any
|
||||||
|
.downcast_ref::<Box<dyn Node<V>>>()
|
||||||
|
.expect("matching node type");
|
||||||
|
res.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Node<Value> {
|
||||||
|
fn is_valid(&self) -> bool;
|
||||||
|
fn invalidate(&mut self);
|
||||||
|
fn visit_inputs(&mut self, visitor: &mut dyn FnMut(NodeIndex<u32>) -> ());
|
||||||
|
fn update(&mut self);
|
||||||
|
// TODO: are these both necessary?
|
||||||
|
fn value_rc(&self) -> Rc<RefCell<Option<Value>>>;
|
||||||
|
// TODO: it would be nice if this borrowed and didn't require Clone
|
||||||
|
fn value(&self) -> Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ConstNode<V>(V);
|
||||||
|
|
||||||
|
impl<V: Clone + 'static> Node<V> for ConstNode<V> {
|
||||||
|
fn is_valid(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invalidate(&mut self) {}
|
||||||
|
|
||||||
|
fn visit_inputs(&mut self, _visitor: &mut dyn FnMut(NodeIndex<u32>) -> ()) {}
|
||||||
|
|
||||||
|
fn update(&mut self) {}
|
||||||
|
|
||||||
|
fn value_rc(&self) -> Rc<RefCell<Option<V>>> {
|
||||||
|
Rc::new(RefCell::new(Some(self.0.clone())))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value(&self) -> V {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct RuleNode<R, V> {
|
||||||
|
rule: R,
|
||||||
|
value: Rc<RefCell<Option<V>>>,
|
||||||
|
valid: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Rule<V>, V> RuleNode<R, V> {
|
||||||
|
fn new(rule: R) -> Self {
|
||||||
|
Self {
|
||||||
|
rule,
|
||||||
|
value: Rc::new(RefCell::new(None)),
|
||||||
|
valid: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Rule<V> + 'static, V: Clone + 'static> Node<V> for RuleNode<R, V> {
|
||||||
|
fn is_valid(&self) -> bool {
|
||||||
|
self.valid
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invalidate(&mut self) {
|
||||||
|
self.valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_inputs(&mut self, visitor: &mut dyn FnMut(NodeIndex<u32>) -> ()) {
|
||||||
|
struct InputIndexVisitor<'a>(&'a mut dyn FnMut(NodeIndex<u32>) -> ());
|
||||||
|
impl<'a> InputVisitor for InputIndexVisitor<'a> {
|
||||||
|
fn visit<T>(&mut self, input: &mut Input<T>) {
|
||||||
|
self.0(input.node_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.rule.visit_inputs(&mut InputIndexVisitor(visitor));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self) {
|
||||||
|
let new_value = self.rule.evaluate();
|
||||||
|
*self.value.borrow_mut() = Some(new_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value_rc(&self) -> Rc<RefCell<Option<V>>> {
|
||||||
|
Rc::clone(&self.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value(&self) -> V {
|
||||||
|
self.value
|
||||||
|
.as_ref()
|
||||||
|
.borrow()
|
||||||
|
.clone()
|
||||||
|
.expect("RuleNode must be updated before getting value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ExternalInputNode<I: ExternalInput> {
|
||||||
|
input: I,
|
||||||
|
value: Rc<RefCell<Option<I::Output>>>,
|
||||||
|
valid: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I: ExternalInput> ExternalInputNode<I> {
|
||||||
|
fn new(input: I) -> Self {
|
||||||
|
Self {
|
||||||
|
input,
|
||||||
|
value: Rc::new(RefCell::new(None)),
|
||||||
|
valid: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I: ExternalInput> Node<I::Output> for ExternalInputNode<I> {
|
||||||
|
fn is_valid(&self) -> bool {
|
||||||
|
self.valid
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invalidate(&mut self) {
|
||||||
|
self.valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_inputs(&mut self, _visitor: &mut dyn FnMut(NodeIndex<u32>) -> ()) {}
|
||||||
|
|
||||||
|
fn update(&mut self) {
|
||||||
|
self.valid = true;
|
||||||
|
*self.value.borrow_mut() = Some(self.input.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value_rc(&self) -> Rc<RefCell<Option<I::Output>>> {
|
||||||
|
Rc::clone(&self.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn value(&self) -> I::Output {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Rule<Output> {
|
||||||
|
fn visit_inputs(&mut self, visitor: &mut impl InputVisitor);
|
||||||
|
|
||||||
|
fn evaluate(&mut self) -> Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait InputVisitor {
|
||||||
|
fn visit<T>(&mut self, input: &mut Input<T>);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ExternalInput {
|
||||||
|
type Output;
|
||||||
|
|
||||||
|
fn value(&mut self) -> Self::Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn erase_node() {
|
||||||
|
let node = ErasedNode::new(ConstNode(1234 as i32));
|
||||||
|
let unwrapped = node.expect_type::<i32>();
|
||||||
|
assert_eq!(unwrapped.value(), 1234);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ConstantRule(i32);
|
||||||
|
impl Rule<i32> for ConstantRule {
|
||||||
|
fn visit_inputs(&mut self, _visitor: &mut impl InputVisitor) {}
|
||||||
|
fn evaluate(&mut self) -> i32 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rule_output_with_no_inputs() {
|
||||||
|
let mut graph = Graph::new();
|
||||||
|
graph.set_output(ConstantRule(1234));
|
||||||
|
assert_eq!(graph.freeze().unwrap().evaluate(), 1234);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Double(Input<i32>);
|
||||||
|
impl Rule<i32> for Double {
|
||||||
|
fn visit_inputs(&mut self, visitor: &mut impl InputVisitor) {
|
||||||
|
visitor.visit(&mut self.0);
|
||||||
|
}
|
||||||
|
fn evaluate(&mut self) -> i32 {
|
||||||
|
self.0.value() * 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rule_with_input() {
|
||||||
|
let mut graph = Graph::new();
|
||||||
|
let input = graph.add_value(42);
|
||||||
|
graph.set_output(Double(input));
|
||||||
|
assert_eq!(graph.freeze().unwrap().evaluate(), 84);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rule_with_input_rule() {
|
||||||
|
let mut graph = Graph::new();
|
||||||
|
let input = graph.add_value(42);
|
||||||
|
let doubled = graph.add_rule(Double(input));
|
||||||
|
graph.set_output(Double(doubled));
|
||||||
|
assert_eq!(graph.freeze().unwrap().evaluate(), 168);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn external_input() {
|
||||||
|
struct Inc(i32);
|
||||||
|
impl ExternalInput for Inc {
|
||||||
|
type Output = i32;
|
||||||
|
fn value(&mut self) -> i32 {
|
||||||
|
self.0 += 1;
|
||||||
|
return self.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut graph = Graph::new();
|
||||||
|
let mut invalidate = None;
|
||||||
|
let input = graph.add_input(|inv| {
|
||||||
|
invalidate = Some(inv);
|
||||||
|
Inc(0)
|
||||||
|
});
|
||||||
|
graph.set_output(Double(input));
|
||||||
|
let mut frozen = graph.freeze().unwrap();
|
||||||
|
assert_eq!(frozen.evaluate(), 2);
|
||||||
|
invalidate.as_ref().unwrap().invalidate();
|
||||||
|
assert_eq!(frozen.evaluate(), 4);
|
||||||
|
assert_eq!(frozen.evaluate(), 4);
|
||||||
|
invalidate.as_ref().unwrap().invalidate();
|
||||||
|
assert_eq!(frozen.evaluate(), 6);
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
mod activitypub;
|
mod activitypub;
|
||||||
mod generator;
|
mod generator;
|
||||||
|
mod graph;
|
||||||
|
|
||||||
use crate::generator::{HtmlContent, Post};
|
use crate::generator::{HtmlContent, Post};
|
||||||
use axum::{
|
use axum::{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user