feat: graph-ir (continueing #6) #10
9 changed files with 105 additions and 26 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -4,3 +4,5 @@
|
|||
.pre-commit-config.yaml
|
||||
*.pdf
|
||||
/docs/*.png
|
||||
/testfiles/gen/*
|
||||
!/testfiles/gen/.gitkeep
|
||||
|
|
|
@ -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<id::Output, Variant>,
|
||||
}
|
||||
|
||||
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<Instruction> = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -27,6 +27,8 @@ pub fn from_ron(source: &str) -> ron::error::SpannedResult<GraphIr> {
|
|||
|
||||
/// 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<GraphIr> {
|
|||
/// 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
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
pub enum DynamicValue {
|
||||
Image(DynamicImage),
|
||||
}
|
|
@ -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: {
|
||||
|
|
1
testfiles/gen/.gitkeep
Normal file
1
testfiles/gen/.gitkeep
Normal file
|
@ -0,0 +1 @@
|
|||
the testfile scripts will place generated images and media here. thank you for your understanding.
|
|
@ -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,
|
||||
)),
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue