diff --git a/.gitignore b/.gitignore index 49067bd..bca6aa2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ .pre-commit-config.yaml *.pdf /docs/*.png +/testfiles/gen/* +!/testfiles/gen/.gitkeep diff --git a/crates/eval/src/kind/debug/mod.rs b/crates/eval/src/kind/debug/mod.rs index 55c4fbb..1f46c49 100644 --- a/crates/eval/src/kind/debug/mod.rs +++ b/crates/eval/src/kind/debug/mod.rs @@ -1,31 +1,39 @@ -use std::mem; - use ir::{ + id, instruction::{Filter, Kind}, - GraphIr, Instruction, InstructionRef, + GraphIr, Instruction, Map, }; -use crate::value::Dynamic; +use crate::value::Variant; mod instr; #[derive(Debug, Default)] pub struct Evaluator { ir: GraphIr, + + /// What the output of each individual streamer, and as result its output sockets, is. + /// Grows larger as evaluation continues, + /// as there's no mechanism for purging never-to-be-used-anymore instructions yet. + evaluated: Map, } impl crate::Evaluator for Evaluator { fn feed(&mut self, ir: GraphIr) { // TODO: should add instead of replace, see note in Evaluator trait above this method self.ir = ir; + self.evaluated.clear(); } fn eval_full(&mut self) { + // GraphIr::topological_sort returns InstructionRefs, which are mostly cool + // but we'd like to have them owned, so we can call Self::step without lifetime hassle let queue: Vec = self .ir .topological_sort() .into_iter() .map(Into::into) .collect(); + for instr in queue { self.step(instr); } @@ -35,23 +43,64 @@ impl crate::Evaluator for Evaluator { impl Evaluator { #[allow(clippy::needless_pass_by_value)] fn step(&mut self, instr: Instruction) { - let _ = match instr.kind { - Kind::Read(details) => Some(Dynamic::Image(instr::read::read(details))), + // what inputs does this instr need? fetch them + let inputs: Vec<_> = instr + .input_sources() + .iter() + .map(|source| { + let source_socket = source + .as_ref() + .expect("all inputs to be connected when an instruction is ran"); + self.evaluated + .get(source_socket) + .expect("toposort to yield later instrs only after previous ones") + }) + .collect(); + + // then actually do whatever the instruction should do + // NOTE: makes heavy use of index slicing, + // on the basis that ir::instruction::Kind::socket_count is correct + // TODO: make this a more flexible dispatch-ish arch + let output = match instr.kind { + Kind::Read(details) => Some(Variant::Image(instr::read::read(details))), Kind::Write(details) => { - instr::write::write(details, todo!()); + #[allow(irrefutable_let_patterns)] // will necessarily change + let Variant::Image(input) = inputs[0] else { + panic!("cannot only write images, but received: `{:?}`", inputs[0]); + }; + instr::write::write(details, input); None } Kind::Math(_) => todo!(), Kind::Blend(_) => todo!(), Kind::Noise(_) => todo!(), Kind::Filter(filter_instruction) => match filter_instruction { - Filter::Invert => Some(Dynamic::Image(instr::filters::invert::invert( - match todo!() { - Some(Dynamic::Image(img)) => img, - _ => panic!("invalid value type for invert"), - }, - ))), + Filter::Invert => { + #[allow(irrefutable_let_patterns)] + let Variant::Image(input) = inputs[0] else { + panic!( + "cannot only filter invert images, but received: `{:?}`", + inputs[0] + ); + }; + + Some(Variant::Image(instr::filters::invert::invert( + input.clone(), + ))) + } }, }; + + if let Some(output) = output { + // TODO: very inaccurate, actually respect individual instructions. + // should be implied by a different arch + // TODO: all of those should not be public, offer some methods to get this on + // `Instruction` instead (can infer short-term based on Kind::socket_count) + let socket = id::Output(id::Socket { + belongs_to: instr.id, + idx: id::SocketIdx(0), + }); + self.evaluated.insert(socket, output); + } } } diff --git a/crates/eval/src/value/mod.rs b/crates/eval/src/value/mod.rs index f3595d0..b1d85d5 100644 --- a/crates/eval/src/value/mod.rs +++ b/crates/eval/src/value/mod.rs @@ -1,5 +1,12 @@ use image::DynamicImage; -pub enum Dynamic { +/// Any runtime value that an instruction can input or output. +/// +/// The name is taken from [Godot's `Variant` type], +/// which is very similar to this one. +/// +/// [Godot's `Variant` type]: https://docs.godotengine.org/en/stable/classes/class_variant.html +#[derive(Clone, Debug)] +pub enum Variant { Image(DynamicImage), } diff --git a/crates/ir/src/id.rs b/crates/ir/src/id.rs index 8f456a8..4834a4f 100644 --- a/crates/ir/src/id.rs +++ b/crates/ir/src/id.rs @@ -49,7 +49,7 @@ impl Input { /// /// In contrast to [`Input`]s, [`Output`]s may be used or unused. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -pub struct Output(pub(super) Socket); +pub struct Output(pub Socket); // TODO: Restrict publicness to super impl Output { #[must_use] @@ -75,7 +75,7 @@ pub struct Socket { /// This really only serves for denoting where a socket is, /// when it's already clear which instruction is referred to. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] -pub struct SocketIdx(pub u16); +pub struct SocketIdx(pub u16); // TODO: Restrict publicness to super impl fmt::Debug for SocketIdx { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/crates/ir/src/lib.rs b/crates/ir/src/lib.rs index 68aca01..702244c 100644 --- a/crates/ir/src/lib.rs +++ b/crates/ir/src/lib.rs @@ -27,6 +27,8 @@ pub fn from_ron(source: &str) -> ron::error::SpannedResult { /// The toplevel representation of a whole pipeline. /// +/// # DAGs +/// /// Pipelines may not be fully linear. They may branch out and recombine later on. /// As such, the representation for them which is currently used is a /// [**D**irected **A**cyclic **G**raph](https://en.wikipedia.org/wiki/Directed_acyclic_graph). @@ -56,6 +58,27 @@ pub fn from_ron(source: &str) -> ron::error::SpannedResult { /// So the vertices of the DAG are the **sockets** /// (which are either [`id::Input`] or [`id::Output`] depending on the direction), /// and each **socket** in turn belongs to an **instruction**. +/// +/// # Usage +/// +/// - If you want to build one from scratch, +/// add a few helper methods like +/// constructing an empty one, +/// adding instructions and +/// adding edges +/// - If you want to construct one from an existing repr, +/// maybe you want to use [`semi_human::GraphIr`]. +/// +/// # Storing additional data +/// +/// Chances are the graph IR seems somewhat fit to put metadata in it. +/// However, most likely you're interacting in context of some other system, +/// and also want to manage and index that data on your own. +/// +/// As such, consider using _secondary_ maps instead. +/// That is, store in a data structure _you_ own a mapping +/// from [`id`]s +/// to whatever data you need. #[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)] pub struct GraphIr { /// "Backbone" storage of all **instruction** IDs to diff --git a/crates/ir/src/value/mod.rs b/crates/ir/src/value/mod.rs deleted file mode 100644 index 238b9c2..0000000 --- a/crates/ir/src/value/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub enum DynamicValue { - Image(DynamicImage), -} diff --git a/testfiles/bare.ron b/testfiles/bare.ron index f2a9621..de78944 100644 --- a/testfiles/bare.ron +++ b/testfiles/bare.ron @@ -1,12 +1,12 @@ ( instructions: { 0: Read(( - source: File("testfiles/juan.jpg"), - format: Jpeg, + source: File("testfiles/rails.png"), + format: Png, )), 1: Write(( - target: File("testfiles/out.png"), - format: Png, + target: File("testfiles/gen/out.jpg"), + format: Jpeg, )), }, edges: { diff --git a/testfiles/gen/.gitkeep b/testfiles/gen/.gitkeep new file mode 100644 index 0000000..9daccef --- /dev/null +++ b/testfiles/gen/.gitkeep @@ -0,0 +1 @@ +the testfile scripts will place generated images and media here. thank you for your understanding. diff --git a/testfiles/invert.ron b/testfiles/invert.ron index 3f1f48c..ce8e68d 100644 --- a/testfiles/invert.ron +++ b/testfiles/invert.ron @@ -1,12 +1,12 @@ ( instructions: { 0: Read(( - source: File("testfiles/juan.jpg"), - format: Jpeg, + source: File("testfiles/rails.png"), + format: Png, )), 1: Filter(Invert), 2: Write(( - target: File("testfiles/inverted.png"), + target: File("testfiles/gen/inverted.png"), format: Png, )), },