feat(ir): implement resolve functionality (untested)

This commit is contained in:
multisn8 2024-01-19 00:45:01 +01:00
parent ce1d1e85a0
commit 1b93f5d7f0
Signed by: multisamplednight
GPG key ID: 6D525AA147CBDAE2
8 changed files with 203 additions and 101 deletions

70
Cargo.lock generated
View file

@ -8,20 +8,6 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 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]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.5" version = "0.6.5"
@ -287,17 +273,6 @@ dependencies = [
"spin", "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]] [[package]]
name = "gif" name = "gif"
version = "0.12.0" version = "0.12.0"
@ -346,7 +321,6 @@ dependencies = [
name = "ir" name = "ir"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"ahash",
"ron", "ron",
"serde", "serde",
] ]
@ -366,12 +340,6 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]]
name = "libc"
version = "0.2.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.11" version = "0.4.11"
@ -422,12 +390,6 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]] [[package]]
name = "png" name = "png"
version = "0.17.10" version = "0.17.10"
@ -587,18 +549,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" 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]] [[package]]
name = "weezl" name = "weezl"
version = "0.1.7" version = "0.1.7"
@ -671,26 +621,6 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" 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]] [[package]]
name = "zune-inflate" name = "zune-inflate"
version = "0.2.54" version = "0.2.54"

View file

@ -6,7 +6,6 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
ahash = { version = "0.8.7", features = ["serde"] }
ron = "0.8" ron = "0.8"
serde = { version = "1.0.193", features = ["derive"] } serde = { version = "1.0.193", features = ["derive"] }

View file

@ -25,7 +25,7 @@ use crate::Span;
/// ///
/// It does **not** contain what kind of instruction this is. /// It does **not** contain what kind of instruction this is.
/// Refer to [`crate::instruction::Kind`] for this instead. /// 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); pub struct Instruction(pub(super) Span);
impl Instruction { impl Instruction {
@ -39,23 +39,37 @@ impl Instruction {
/// On an **instruction**, accepts incoming data. /// On an **instruction**, accepts incoming data.
/// ///
/// An **instruction** cannot run if any of these are not connected. /// 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); 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. /// On an **instruction**, returns outgoing data to be fed to [`Input`]s.
/// ///
/// In contrast to [`Input`]s, [`Output`]s may be used or unused. /// 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); pub struct Output(pub(super) Socket);
impl Output {
#[must_use]
pub fn socket(&self) -> &Socket {
&self.0
}
}
/// An unspecified socket on a specific **instruction**, /// An unspecified socket on a specific **instruction**,
/// and where it is on that **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 struct Socket {
pub belongs_to: Instruction, pub belongs_to: Instruction,
pub idx: SocketIdx, pub idx: SocketIdx,
} }
/// Where a [`Socket`] is on an **instruction**. /// 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); pub struct SocketIdx(pub u16);

View file

@ -3,8 +3,10 @@ use serde::{Deserialize, Serialize};
pub mod read; pub mod read;
pub mod write; pub mod write;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum Kind { 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), Read(read::Read),
Write(write::Write), Write(write::Write),
Math(Math), Math(Math),
@ -13,7 +15,7 @@ pub enum Kind {
Filter(Filter), Filter(Filter),
} }
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum Math { pub enum Math {
Add, Add,
Subtract, Subtract,
@ -21,7 +23,7 @@ pub enum Math {
Divide, Divide,
} }
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum Blend { pub enum Blend {
Normal, Normal,
Multiply, Multiply,
@ -34,14 +36,46 @@ pub enum Blend {
Lighten, Lighten,
} }
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum Noise { pub enum Noise {
Perlin, Perlin,
Simplex, Simplex,
Voronoi, Voronoi,
} }
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum Filter { pub enum Filter {
Invert, 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 }
}
}

View file

@ -1,18 +1,18 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::PathBuf; 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 struct Read {
pub source: SourceType, pub source: SourceType,
pub format: SourceFormat, pub format: SourceFormat,
} }
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum SourceType { pub enum SourceType {
File(PathBuf), File(PathBuf),
} }
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum SourceFormat { pub enum SourceFormat {
Jpeg, Jpeg,
Png, Png,

View file

@ -1,18 +1,18 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::PathBuf; 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 struct Write {
pub target: TargetType, pub target: TargetType,
pub format: TargetFormat, pub format: TargetFormat,
} }
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum TargetType { pub enum TargetType {
File(PathBuf), File(PathBuf),
} }
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum TargetFormat { pub enum TargetFormat {
Jpeg, Jpeg,
Png, Png,

View file

@ -1,13 +1,17 @@
use std::ops::RangeInclusive; use std::{
collections::{BTreeMap, BTreeSet},
ops::RangeInclusive,
};
use instruction::SocketCount;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub mod id; pub mod id;
pub mod instruction; pub mod instruction;
pub mod semi_human; pub mod semi_human;
pub type Map<K, V> = ahash::AHashMap<K, V>; pub type Map<K, V> = std::collections::BTreeMap<K, V>;
pub type Set<V> = ahash::AHashSet<V>; pub type Set<V> = std::collections::BTreeSet<V>;
/// Gives you a super well typed graph IR for a given human-readable repr. /// 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. /// How the data flows forward. **Dependencies** map to **dependents** here.
edges: Map<id::Output, Set<id::Input>>, edges: Map<id::Output, Set<id::Input>>,
/// How the data flows backward. **Dependents** map to **dependencies** here. /// How the data flows backward. **Dependents** map to **dependencies** here.
rev_edges: Map<id::Input, Set<id::Output>>, rev_edges: Map<id::Input, id::Output>,
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] impl GraphIr {
pub struct Span { // TODO: this function, but actually the whole module, screams for tests
range: RangeInclusive<usize>, /// 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<Instruction<'ir>> {
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<Instruction<'ir>> {
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<Instruction<'ir>> {
self.resolve(&output.socket().belongs_to)
}
#[must_use]
pub fn topological_sort(&self) -> Vec<Instruction> {
// 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<Option<&'ir id::Output>>,
outputs_to: Vec<Option<&'ir BTreeSet<id::Input>>>,
}
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<id::Input>>] {
&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<RangeInclusive<usize>> for Span {
fn from(range: RangeInclusive<usize>) -> Self {
Self {
from: *range.start(),
to: *range.end(),
}
}
} }

View file

@ -21,11 +21,12 @@ pub struct GraphIr {
/// See [`crate::GraphIr::instructions`], just that a simple number is used for the ID instead /// See [`crate::GraphIr::instructions`], just that a simple number is used for the ID instead
/// of a proper span. /// of a proper span.
pub(crate) instructions: Map<usize, instruction::Kind>, pub(crate) instructions: Map<usize, instruction::Kind>,
/// 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<Socket, Set<Socket>>, pub(crate) edges: Map<Socket, Set<Socket>>,
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
pub struct Socket { pub struct Socket {
/// ID of the instruction this socket is on. /// ID of the instruction this socket is on.
pub(crate) on: usize, pub(crate) on: usize,
@ -36,7 +37,8 @@ impl From<Socket> for id::Socket {
fn from(source: Socket) -> Self { fn from(source: Socket) -> Self {
Self { Self {
belongs_to: (id::Instruction(Span { belongs_to: (id::Instruction(Span {
range: source.on..=source.on, from: source.on,
to: source.on,
})), })),
idx: id::SocketIdx(source.idx), idx: id::SocketIdx(source.idx),
} }
@ -50,10 +52,9 @@ impl From<GraphIr> for crate::GraphIr {
instructions: source instructions: source
.instructions .instructions
.into_iter() .into_iter()
.map(|(id, kind)| (id::Instruction(Span { range: id..=id }), kind)) .map(|(id, kind)| (id::Instruction(Span { from: id, to: id }), kind))
.collect(), .collect(),
edges: type_edges(source.edges), edges: type_edges(source.edges),
// same as above, but also reverse the mapping
rev_edges: reverse_and_type_edges(edges), rev_edges: reverse_and_type_edges(edges),
} }
} }
@ -70,7 +71,7 @@ fn type_edges(edges: Map<Socket, Set<Socket>>) -> Map<id::Output, Set<id::Input>
.collect() .collect()
} }
fn reverse_and_type_edges(edges: Map<Socket, Set<Socket>>) -> Map<id::Input, Set<id::Output>> { fn reverse_and_type_edges(edges: Map<Socket, Set<Socket>>) -> Map<id::Input, id::Output> {
edges edges
.into_iter() .into_iter()
.fold(Map::new(), |mut rev_edges, (output, inputs)| { .fold(Map::new(), |mut rev_edges, (output, inputs)| {
@ -78,7 +79,10 @@ fn reverse_and_type_edges(edges: Map<Socket, Set<Socket>>) -> Map<id::Input, Set
for input in inputs { for input in inputs {
let input = id::Input(input.into()); let input = id::Input(input.into());
rev_edges.entry(input).or_default().insert(output.clone()); let previous = rev_edges.insert(input, output.clone());
if let Some(previous) = previous {
panic!("two or more outputs referred to the same input {previous:#?} — handle me better later with a TryFrom impl");
}
} }
rev_edges rev_edges