From 29269e2fcde8e31f8aa6488f6e895fb3ed1d7be4 Mon Sep 17 00:00:00 2001 From: MultisampledNight Date: Fri, 19 Jan 2024 00:45:01 +0100 Subject: [PATCH] feat(ir): implement resolve functionality (untested) --- Cargo.lock | 70 --------------- crates/ir/Cargo.toml | 1 - crates/ir/src/id.rs | 24 +++-- crates/ir/src/instruction/mod.rs | 44 ++++++++-- crates/ir/src/instruction/read.rs | 6 +- crates/ir/src/instruction/write.rs | 6 +- crates/ir/src/lib.rs | 135 +++++++++++++++++++++++++++-- crates/ir/src/semi_human.rs | 18 ++-- 8 files changed, 203 insertions(+), 101 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 733ec12..529a1a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,20 +8,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "ahash" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" -dependencies = [ - "cfg-if", - "getrandom", - "once_cell", - "serde", - "version_check", - "zerocopy", -] - [[package]] name = "anstream" version = "0.6.5" @@ -341,17 +327,6 @@ dependencies = [ "spin", ] -[[package]] -name = "getrandom" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - [[package]] name = "gif" version = "0.12.0" @@ -400,7 +375,6 @@ dependencies = [ name = "ir" version = "0.1.0" dependencies = [ - "ahash", "ron", "serde", ] @@ -420,12 +394,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" -[[package]] -name = "libc" -version = "0.2.152" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" - [[package]] name = "lock_api" version = "0.4.11" @@ -476,12 +444,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - [[package]] name = "png" version = "0.17.10" @@ -724,18 +686,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - [[package]] name = "weezl" version = "0.1.7" @@ -874,26 +824,6 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" -[[package]] -name = "zerocopy" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "zune-inflate" version = "0.2.54" diff --git a/crates/ir/Cargo.toml b/crates/ir/Cargo.toml index 1bb10f9..fdd00e8 100644 --- a/crates/ir/Cargo.toml +++ b/crates/ir/Cargo.toml @@ -6,7 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -ahash = { version = "0.8.7", features = ["serde"] } ron = "0.8" serde = { version = "1.0.193", features = ["derive"] } diff --git a/crates/ir/src/id.rs b/crates/ir/src/id.rs index 1991b27..315bc6f 100644 --- a/crates/ir/src/id.rs +++ b/crates/ir/src/id.rs @@ -25,7 +25,7 @@ use crate::Span; /// /// It does **not** contain what kind of instruction this is. /// Refer to [`crate::instruction::Kind`] for this instead. -#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct Instruction(pub(super) Span); impl Instruction { @@ -39,23 +39,37 @@ impl Instruction { /// On an **instruction**, accepts incoming data. /// /// An **instruction** cannot run if any of these are not connected. -#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct Input(pub(super) Socket); +impl Input { + #[must_use] + pub fn socket(&self) -> &Socket { + &self.0 + } +} + /// On an **instruction**, returns outgoing data to be fed to [`Input`]s. /// /// In contrast to [`Input`]s, [`Output`]s may be used or unused. -#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct Output(pub(super) Socket); +impl Output { + #[must_use] + pub fn socket(&self) -> &Socket { + &self.0 + } +} + /// An unspecified socket on a specific **instruction**, /// and where it is on that **instruction**. -#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct Socket { pub belongs_to: Instruction, pub idx: SocketIdx, } /// Where a [`Socket`] is on an **instruction**. -#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct SocketIdx(pub u16); diff --git a/crates/ir/src/instruction/mod.rs b/crates/ir/src/instruction/mod.rs index adb46e6..069fa4f 100644 --- a/crates/ir/src/instruction/mod.rs +++ b/crates/ir/src/instruction/mod.rs @@ -3,8 +3,10 @@ use serde::{Deserialize, Serialize}; pub mod read; pub mod write; -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub enum Kind { + // TODO: `read::Read` and `write::Write` hold real values atm -- they should actually + // point to `Const` instructions instead (which are... yet to be done...) Read(read::Read), Write(write::Write), Math(Math), @@ -13,7 +15,7 @@ pub enum Kind { Filter(Filter), } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub enum Math { Add, Subtract, @@ -21,7 +23,7 @@ pub enum Math { Divide, } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub enum Blend { Normal, Multiply, @@ -34,14 +36,46 @@ pub enum Blend { Lighten, } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub enum Noise { Perlin, Simplex, Voronoi, } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub enum Filter { Invert, } + +// TODO: given that this basically matches on all instructions, we may need to use +// the visitor pattern in future here, or at least get them behind traits +// which should allow far more nuanced description +impl Kind { + /// Returns how many sockets this kind of instruction has. + #[must_use] + pub fn socket_count(&self) -> SocketCount { + match self { + Self::Read(_) => (0, 1), + Self::Write(_) => (1, 0), + Self::Math(_) | Self::Blend(_) => (2, 1), + Self::Noise(_) => { + todo!("how many arguments does noise take? how many outputs does it have?") + } + Self::Filter(Filter::Invert) => (1, 1), + } + .into() + } +} + +/// How many sockets are on an instruction? +pub struct SocketCount { + pub inputs: u16, + pub outputs: u16, +} + +impl From<(u16, u16)> for SocketCount { + fn from((inputs, outputs): (u16, u16)) -> Self { + Self { inputs, outputs } + } +} diff --git a/crates/ir/src/instruction/read.rs b/crates/ir/src/instruction/read.rs index 7f68524..dfb2275 100644 --- a/crates/ir/src/instruction/read.rs +++ b/crates/ir/src/instruction/read.rs @@ -1,18 +1,18 @@ use serde::{Deserialize, Serialize}; use std::path::PathBuf; -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct Read { pub source: SourceType, pub format: SourceFormat, } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub enum SourceType { File(PathBuf), } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub enum SourceFormat { Jpeg, Png, diff --git a/crates/ir/src/instruction/write.rs b/crates/ir/src/instruction/write.rs index 973fab8..8e9311b 100644 --- a/crates/ir/src/instruction/write.rs +++ b/crates/ir/src/instruction/write.rs @@ -1,18 +1,18 @@ use serde::{Deserialize, Serialize}; use std::path::PathBuf; -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct Write { pub target: TargetType, pub format: TargetFormat, } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub enum TargetType { File(PathBuf), } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub enum TargetFormat { Jpeg, Png, diff --git a/crates/ir/src/lib.rs b/crates/ir/src/lib.rs index b7ac34c..316e31d 100644 --- a/crates/ir/src/lib.rs +++ b/crates/ir/src/lib.rs @@ -1,13 +1,17 @@ -use std::ops::RangeInclusive; +use std::{ + collections::{BTreeMap, BTreeSet}, + ops::RangeInclusive, +}; +use instruction::SocketCount; use serde::{Deserialize, Serialize}; pub mod id; pub mod instruction; pub mod semi_human; -pub type Map = ahash::AHashMap; -pub type Set = ahash::AHashSet; +pub type Map = std::collections::BTreeMap; +pub type Set = std::collections::BTreeSet; /// Gives you a super well typed graph IR for a given human-readable repr. /// @@ -64,10 +68,127 @@ pub struct GraphIr { /// How the data flows forward. **Dependencies** map to **dependents** here. edges: Map>, /// How the data flows backward. **Dependents** map to **dependencies** here. - rev_edges: Map>, + rev_edges: Map, } -#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] -pub struct Span { - range: RangeInclusive, +impl GraphIr { + // TODO: this function, but actually the whole module, screams for tests + /// Returns the instruction corresponding to the given ID. + /// Returns [`None`] if there is no such instruction in this graph IR. + /// + /// Theoretically this could be fixed easily at the expense of some memory + /// by just incrementing and storing some global counter, + /// however, at the moment there's no compelling reason + /// to actually have multiple [`GraphIr`]s at one point in time. + /// Open an issue if that poses a problem for you. + #[must_use] + pub fn resolve<'ir>(&'ir self, id: &id::Instruction) -> Option> { + let (id, kind) = self.instructions.get_key_value(id)?; + + // just try each slot and see if it's connected + // very crude, but it works for a proof of concept + let SocketCount { inputs, outputs } = kind.socket_count(); + let socket = |id: &id::Instruction, idx| id::Socket { + belongs_to: id.clone(), + // impossible since the length is limited to a u16 already + #[allow(clippy::cast_possible_truncation)] + idx: id::SocketIdx(idx as u16), + }; + + let mut inputs_from = vec![None; inputs.into()]; + for (idx, slot) in inputs_from.iter_mut().enumerate() { + let input = id::Input(socket(id, idx)); + *slot = self.rev_edges.get(&input); + } + + let mut outputs_to = vec![None; outputs.into()]; + for (idx, slot) in outputs_to.iter_mut().enumerate() { + let output = id::Output(socket(id, idx)); + *slot = self.edges.get(&output); + } + + Some(Instruction { + id, + kind, + inputs_from, + outputs_to, + }) + } + + /// Returns the instruction this input belongs to. + /// + /// The same caveats as for [`GraphIr::resolve`] apply. + #[must_use] + pub fn owner_of_input<'ir>(&'ir self, input: &id::Input) -> Option> { + self.resolve(&input.socket().belongs_to) + } + + /// Returns the instruction this output belongs to. + /// + /// The same caveats as for [`GraphIr::resolve`] apply. + #[must_use] + pub fn owner_of_output<'ir>(&'ir self, output: &id::Output) -> Option> { + self.resolve(&output.socket().belongs_to) + } + + #[must_use] + pub fn topological_sort(&self) -> Vec { + // count how many incoming edges each vertex has + // chances are the BTreeMap is overkill + let incoming_counts: BTreeMap<_, _> = self + .rev_edges + .iter() + .map(|(input, _)| (self.owner_of_input(input), 1)) + .collect(); + + todo!() + } +} + +/// A full instruction in context, with its inputs and outputs. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Instruction<'ir> { + pub id: &'ir id::Instruction, + pub kind: &'ir instruction::Kind, + + // can't have these two public since then a user might corrupt their length + inputs_from: Vec>, + outputs_to: Vec>>, +} + +impl<'ir> Instruction<'ir> { + /// Where this instruction gets its inputs from. + /// + /// [`None`] means that this input is unfilled, + /// and must be filled before the instruction can be ran. + #[must_use] + pub fn inputs_from(&self) -> &[Option<&'ir id::Output>] { + &self.inputs_from + } + + /// To whom outputs are sent. [`None`] means that this output is unused. + #[must_use] + pub fn outputs_to(&self) -> &[Option<&'ir BTreeSet>] { + &self.outputs_to + } +} + +/// Some part referred to in source code. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub struct Span { + // would love to use an actual [`std::ops::RangeInclusive`], but those don't implement + // `PartialOrd` and `Ord` unfortunately + /// At which byte this span starts, inclusively. + pub from: usize, + /// At which byte this span ends, inclusively. + pub to: usize, +} + +impl From> for Span { + fn from(range: RangeInclusive) -> Self { + Self { + from: *range.start(), + to: *range.end(), + } + } } diff --git a/crates/ir/src/semi_human.rs b/crates/ir/src/semi_human.rs index 84bc494..6e63176 100644 --- a/crates/ir/src/semi_human.rs +++ b/crates/ir/src/semi_human.rs @@ -21,11 +21,12 @@ pub struct GraphIr { /// See [`crate::GraphIr::instructions`], just that a simple number is used for the ID instead /// of a proper span. pub(crate) instructions: Map, - /// See [`crate::GraphIr::edges`]. RON wants you to type the set as if it were a list. + /// See [`crate::GraphIr::edges`], the forward edges. + /// RON wants you to type the set as if it were a list. pub(crate) edges: Map>, } -#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)] pub struct Socket { /// ID of the instruction this socket is on. pub(crate) on: usize, @@ -36,7 +37,8 @@ impl From for id::Socket { fn from(source: Socket) -> Self { Self { belongs_to: (id::Instruction(Span { - range: source.on..=source.on, + from: source.on, + to: source.on, })), idx: id::SocketIdx(source.idx), } @@ -50,10 +52,9 @@ impl From for crate::GraphIr { instructions: source .instructions .into_iter() - .map(|(id, kind)| (id::Instruction(Span { range: id..=id }), kind)) + .map(|(id, kind)| (id::Instruction(Span { from: id, to: id }), kind)) .collect(), edges: type_edges(source.edges), - // same as above, but also reverse the mapping rev_edges: reverse_and_type_edges(edges), } } @@ -70,7 +71,7 @@ fn type_edges(edges: Map>) -> Map .collect() } -fn reverse_and_type_edges(edges: Map>) -> Map> { +fn reverse_and_type_edges(edges: Map>) -> Map { edges .into_iter() .fold(Map::new(), |mut rev_edges, (output, inputs)| { @@ -78,7 +79,10 @@ fn reverse_and_type_edges(edges: Map>) -> Map