diff --git a/.gitignore b/.gitignore index bca6aa2..49067bd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,3 @@ .pre-commit-config.yaml *.pdf /docs/*.png -/testfiles/gen/* -!/testfiles/gen/.gitkeep diff --git a/Cargo.lock b/Cargo.lock index 7447d0d..e191631 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,8 +63,6 @@ dependencies = [ "ariadne", "clap", "dirs", - "eval", - "ir", "owo-colors", "ron", "serde", @@ -173,6 +171,15 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +[[package]] +name = "cli" +version = "0.1.0" +dependencies = [ + "clap", + "executor", + "ir", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -268,13 +275,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] -name = "eval" +name = "executor" version = "0.1.0" dependencies = [ "clap", "image", "ir", - "serde", ] [[package]] @@ -321,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" @@ -385,12 +380,6 @@ dependencies = [ "serde", ] -[[package]] -name = "itoa" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" - [[package]] name = "jpeg-decoder" version = "0.3.0" @@ -406,23 +395,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 = "libredox" -version = "0.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" -dependencies = [ - "bitflags 2.4.1", - "libc", - "redox_syscall", -] - [[package]] name = "lock_api" version = "0.4.11" @@ -473,27 +445,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_threads" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" -dependencies = [ - "libc", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "owo-colors" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" - [[package]] name = "png" version = "0.17.10" @@ -592,12 +543,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "ryu" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" - [[package]] name = "scopeguard" version = "1.2.0" @@ -742,12 +687,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" -[[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" @@ -886,12 +825,6 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" -[[package]] -name = "yansi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" - [[package]] name = "zune-inflate" version = "0.2.54" diff --git a/Cargo.toml b/Cargo.toml index 6d7f53d..f4aa395 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,13 @@ [workspace] members = [ - "crates/app", - "crates/eval", + "crates/executor", "crates/ir", + "crates/cli", ] resolver = "2" [workspace.dependencies] clap = { version = "4", features = ["derive"] } -serde = { version = "1.0", features = ["derive"] } # to enable all the lints below, this must be present in a workspace member's Cargo.toml: # [lints] diff --git a/crates/app/Cargo.toml b/crates/app/Cargo.toml index 2caaaf4..a0043ee 100644 --- a/crates/app/Cargo.toml +++ b/crates/app/Cargo.toml @@ -6,16 +6,11 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -ariadne = "0.4" clap = { workspace = true, features = [ "derive", "env" ] } -dirs = "5" -eval = { path = "../eval" } -ir = { path = "../ir" } -owo-colors = "4" -ron = "0.8" serde = { workspace = true, features = [ "derive" ] } +ron = "0.8" serde_json = "1.0" +ariadne = "0.4" time = { version = "0.3", features = [ "local-offset" ] } - -[lints] -workspace = true +dirs = "5" +owo-colors = "4" diff --git a/crates/app/src/config.rs b/crates/app/src/config.rs index b4d1ed1..9f2b15a 100644 --- a/crates/app/src/config.rs +++ b/crates/app/src/config.rs @@ -1,5 +1,3 @@ -use std::path::PathBuf; - use clap::Parser; use self::{ @@ -12,9 +10,6 @@ mod config_file; /// this struct may hold all configuration pub struct Config { - pub source: PathBuf, - pub evaluator: eval::Available, - pub startup_msg: bool, } @@ -22,37 +17,37 @@ impl Config { /// Get the configs from all possible places (args, file, env...) pub fn read() -> Self { let args = Args::parse(); - let config = if let Some(config) = args.config_path { - Ok(config) + let config_path = if let Some(config_path) = args.config_path { + Ok(config_path) } else { find_config_file() }; // try to read a maybe existing config file - let config = config.ok().and_then(|path| { - Configs::read(path).map_or_else( - |e| { + let file_config = if let Ok(config_path) = config_path { + let file_config = Configs::read(config_path); + + match file_config { + Ok(c) => Some(c), + Err(e) => { eprintln!("Config error: {e:?}"); eprintln!("Proceeding with defaults or cli args..."); None - }, - Some, - ) - }); + } + } + } else { + None + }; - if let Some(file) = config { + if let Some(file_config) = file_config { Self { - source: args.source, - evaluator: args.evaluator.and(file.evaluator).unwrap_or_default(), // this is negated because to an outward api, the negative is more intuitive, // while in the source the other way around is more intuitive - startup_msg: !(args.no_startup_message || file.no_startup_message), + startup_msg: !(args.no_startup_message || file_config.no_startup_message), } } else { Self { - source: args.source, startup_msg: !args.no_startup_message, - evaluator: args.evaluator.unwrap_or_default(), } } } @@ -61,7 +56,7 @@ impl Config { pub mod error { /// Errors that can occur when reading configs #[derive(Debug)] - pub enum Config { + pub enum ConfigError { /// The config dir doesn't exist NoConfigDir, /// We didn't find a config file in the config dir @@ -78,19 +73,19 @@ pub mod error { SerdeRonError(ron::error::SpannedError), } - impl From for Config { + impl From for ConfigError { fn from(value: std::io::Error) -> Self { Self::IoError(value) } } - impl From for Config { + impl From for ConfigError { fn from(value: serde_json::Error) -> Self { Self::SerdeJsonError(value) } } - impl From for Config { + impl From for ConfigError { fn from(value: ron::error::SpannedError) -> Self { Self::SerdeRonError(value) } diff --git a/crates/app/src/config/cli.rs b/crates/app/src/config/cli.rs index 1d9c57a..bdf769e 100644 --- a/crates/app/src/config/cli.rs +++ b/crates/app/src/config/cli.rs @@ -4,14 +4,6 @@ use clap::{builder::BoolishValueParser, ArgAction, Parser}; #[derive(Parser)] pub(crate) struct Args { - /// What file contains the pipeline to evaluate. - pub source: PathBuf, - - /// How to actually run the pipeline. - /// Overrides the config file. Defaults to the debug evaluator. - #[arg(short, long)] - pub evaluator: Option, - /// Read this config file. #[arg(short, long)] pub config_path: Option, diff --git a/crates/app/src/config/config_file.rs b/crates/app/src/config/config_file.rs index 15f660c..a0c4db1 100644 --- a/crates/app/src/config/config_file.rs +++ b/crates/app/src/config/config_file.rs @@ -5,7 +5,7 @@ use std::{ use serde::{Deserialize, Serialize}; -use super::error::Config; +use super::error::ConfigError; #[derive(Debug, Serialize, Deserialize)] pub struct Configs { @@ -13,7 +13,6 @@ pub struct Configs { pub example_value: i32, #[serde(default)] pub no_startup_message: bool, - pub evaluator: Option, } /// what the fuck serde why do i need this @@ -22,9 +21,9 @@ fn default_example_value() -> i32 { } /// Find the location of a config file and check if there is, in fact, a file -pub(super) fn find_config_file() -> Result { +pub(super) fn find_config_file() -> Result { let Some(config_path) = dirs::config_dir() else { - return Err(Config::NoConfigDir); + return Err(ConfigError::NoConfigDir); }; let ron_path = config_path.with_file_name("config.ron"); @@ -35,19 +34,16 @@ pub(super) fn find_config_file() -> Result { } else if Path::new(&json_path).exists() { Ok(json_path) } else { - Err(Config::NoConfigFileFound) + Err(ConfigError::NoConfigFileFound) } } impl Configs { - pub fn read(p: PathBuf) -> Result { - match p - .extension() - .map(|v| v.to_str().expect("config path to be UTF-8")) - { + pub fn read(p: PathBuf) -> Result { + match p.extension().map(|v| v.to_str().unwrap()) { Some("ron") => Ok(serde_json::from_str(&fs::read_to_string(p)?)?), Some("json") => Ok(ron::from_str(&fs::read_to_string(p)?)?), - e => Err(Config::UnknownExtension(e.map(str::to_owned))), + e => Err(ConfigError::UnknownExtension(e.map(|v| v.to_owned()))), } } } diff --git a/crates/app/src/error_reporting.rs b/crates/app/src/error_reporting.rs index bc4c44b..33ffff6 100644 --- a/crates/app/src/error_reporting.rs +++ b/crates/app/src/error_reporting.rs @@ -3,12 +3,12 @@ use std::process; use ron::error::Position; /// Report an `Error` from the `serde_json` crate -pub fn report_serde_json_err(src: &str, err: &serde_json::Error) -> ! { +pub fn report_serde_json_err(src: &str, err: serde_json::Error) -> ! { report_serde_err(src, err.line(), err.column(), err.to_string()) } /// Report a `SpannedError` from the `ron` crate -pub fn report_serde_ron_err(src: &str, err: &ron::error::SpannedError) -> ! { +pub fn report_serde_ron_err(src: &str, err: ron::error::SpannedError) -> ! { let Position { line, col } = err.position; report_serde_err(src, line, col, err.to_string()) } @@ -23,8 +23,8 @@ fn report_serde_err(src: &str, line: usize, col: usize, msg: String) -> ! { .with_message(msg) .with_note("We'd like to give better errors, but serde errors are horrible to work with...") .finish() - .eprint(("test", Source::from(src))) - .expect("writing error to stderr failed"); + .print(("test", Source::from(src))) + .unwrap(); process::exit(1); } diff --git a/crates/app/src/main.rs b/crates/app/src/main.rs index 58103a5..1347572 100644 --- a/crates/app/src/main.rs +++ b/crates/app/src/main.rs @@ -1,5 +1,3 @@ -use std::fs; - use config::Config; use welcome_msg::print_startup_msg; @@ -10,18 +8,10 @@ mod error_reporting; mod welcome_msg; fn main() { - // TODO: proper error handling across the whole function - // don't forget to also look inside `Config` + // TODO: proper error handling let cfg = Config::read(); if cfg.startup_msg { print_startup_msg(); } - - let source = fs::read_to_string(cfg.source).expect("can't find source file"); - let ir = ir::from_ron(&source).expect("failed to parse source to graph ir"); - - let mut machine = cfg.evaluator.pick(); - machine.feed(ir); - machine.eval_full(); } diff --git a/crates/eval/src/kind/debug/mod.rs b/crates/eval/src/kind/debug/mod.rs deleted file mode 100644 index 9b53ebe..0000000 --- a/crates/eval/src/kind/debug/mod.rs +++ /dev/null @@ -1,105 +0,0 @@ -use ir::{ - id, - instruction::{Filter, Kind}, - GraphIr, Instruction, Map, -}; - -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) { - 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); - } - } -} - -impl Evaluator { - #[allow(clippy::needless_pass_by_value)] - fn step(&mut self, instr: Instruction) { - // 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) => { - #[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 => { - #[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/kind/mod.rs b/crates/eval/src/kind/mod.rs deleted file mode 100644 index 2f36523..0000000 --- a/crates/eval/src/kind/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod debug; diff --git a/crates/eval/src/lib.rs b/crates/eval/src/lib.rs deleted file mode 100644 index c3a6c38..0000000 --- a/crates/eval/src/lib.rs +++ /dev/null @@ -1,43 +0,0 @@ -use ir::GraphIr; - -mod kind; -mod value; - -/// Can collapse a [`GraphIr`] in meaningful ways and do interesting work on it. -/// -/// It's surprisingly difficult to find a fitting description for this. -pub trait Evaluator { - /// Take some [`GraphIr`] which will then be processed later. - /// May be called multiple times, in which the [`GraphIr`]s should add up. - // TODO: atm they definitely don't add up -- add some functionality to GraphIr to - // make it combine two graphs into one - fn feed(&mut self, ir: GraphIr); - - /// Walk through the _whole_ [`GraphIr`] and run through each instruction. - fn eval_full(&mut self); - - // TODO: for an LSP or the like, eval_single which starts at a given instr -} - -/// The available [`Evaluator`]s. -/// -/// Checklist for adding new ones: -/// -/// 1. Create a new module under the [`kind`] module. -/// 2. Add a struct and implement [`Evaluator`] for it. -#[derive(Clone, Copy, Debug, Default, clap::ValueEnum, serde::Deserialize, serde::Serialize)] -pub enum Available { - /// Runs fully on the CPU. Single-threaded, debug-friendly and quick to implement. - #[default] - Debug, -} - -impl Available { - /// Selects the [`Evaluator`] corresponding to this label. - #[must_use] - pub fn pick(&self) -> Box { - match self { - Self::Debug => Box::new(kind::debug::Evaluator::default()), - } - } -} diff --git a/crates/eval/src/value/mod.rs b/crates/eval/src/value/mod.rs deleted file mode 100644 index b1d85d5..0000000 --- a/crates/eval/src/value/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -use image::DynamicImage; - -/// 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/eval/Cargo.toml b/crates/executor/Cargo.toml similarity index 86% rename from crates/eval/Cargo.toml rename to crates/executor/Cargo.toml index b542d2f..711b9b9 100644 --- a/crates/eval/Cargo.toml +++ b/crates/executor/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "eval" +name = "executor" version = "0.1.0" edition = "2021" @@ -9,7 +9,6 @@ edition = "2021" clap = { workspace = true, features = [ "derive" ] } image = "0.24" ir = { path = "../ir" } -serde = { workspace = true } [lints] workspace = true diff --git a/crates/executor/src/cpu/mod.rs b/crates/executor/src/cpu/mod.rs new file mode 100644 index 0000000..e82d20a --- /dev/null +++ b/crates/executor/src/cpu/mod.rs @@ -0,0 +1 @@ +pub(crate) struct CpuExecutor; diff --git a/crates/eval/src/kind/debug/instr/mod.rs b/crates/executor/src/debug/instructions/mod.rs similarity index 92% rename from crates/eval/src/kind/debug/instr/mod.rs rename to crates/executor/src/debug/instructions/mod.rs index 14afac8..b78c7b5 100644 --- a/crates/eval/src/kind/debug/instr/mod.rs +++ b/crates/executor/src/debug/instructions/mod.rs @@ -18,7 +18,6 @@ pub mod write { use ir::instruction::write::{TargetFormat, TargetType, Write}; pub fn write(Write { target, format }: Write, input_data: &DynamicImage) { - // TODO: actual error handling input_data .save_with_format( match target { @@ -29,7 +28,7 @@ pub mod write { TargetFormat::Png => ImageFormat::Png, }, ) - .expect("couldn't save file"); + .expect("couldn't save file — come back later and handle me properly please uwu"); } } diff --git a/crates/executor/src/debug/mod.rs b/crates/executor/src/debug/mod.rs new file mode 100644 index 0000000..b9cd743 --- /dev/null +++ b/crates/executor/src/debug/mod.rs @@ -0,0 +1,37 @@ +use ir::instruction::{Filter, Kind}; + +use crate::value::Dynamic; +mod instructions; + +pub struct Executor; + +impl crate::Executor for Executor { + fn execute(instruction: Kind, input: Option) -> Option { + match instruction { + Kind::Read(read_instruction) => { + Some(Dynamic::Image(instructions::read::read(read_instruction))) + } + Kind::Write(write_instruction) => { + instructions::write::write( + write_instruction, + match input { + Some(Dynamic::Image(ref img)) => img, + _ => panic!("awawwawwa"), + }, + ); + None + } + Kind::Math(_) => todo!(), + Kind::Blend(_) => todo!(), + Kind::Noise(_) => todo!(), + Kind::Filter(filter_instruction) => match filter_instruction { + Filter::Invert => Some(Dynamic::Image(instructions::filters::invert::invert( + match input { + Some(Dynamic::Image(img)) => img, + _ => panic!("invalid value type for invert"), + }, + ))), + }, + } + } +} diff --git a/crates/executor/src/lib.rs b/crates/executor/src/lib.rs new file mode 100644 index 0000000..9732bb0 --- /dev/null +++ b/crates/executor/src/lib.rs @@ -0,0 +1,34 @@ +use ir::instruction::Kind; +use value::Dynamic; + +mod debug; +mod value; + +/// The available executors +/// unused in early dev. +#[derive(Debug, Clone, Copy, clap::ValueEnum)] +pub enum RegisteredExecutor { + /// the debug executor is single threaded and really, *really* slow. And unstable. Don't use. Unless you're a dev working on this. + Debug, +} + +trait Executor { + fn execute(instruction: Kind, input: Option) -> Option; +} + +pub fn execute_all(instructions: Vec) { + let mut tmp = None; + + for instruction in instructions { + tmp = debug::Executor::execute(instruction, tmp); + } +} + +// scratchpad lol: +// execution structure: +// 1. take in rpl +// 2. analyse/validate structure against allowed executors +// 3. assign executors to instructions +// 4. optimize +// 5. prepare memory management patterns +// 6. run diff --git a/crates/executor/src/value/mod.rs b/crates/executor/src/value/mod.rs new file mode 100644 index 0000000..f3595d0 --- /dev/null +++ b/crates/executor/src/value/mod.rs @@ -0,0 +1,5 @@ +use image::DynamicImage; + +pub enum Dynamic { + Image(DynamicImage), +} diff --git a/crates/ir/src/id.rs b/crates/ir/src/id.rs index 4834a4f..8f456a8 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 Socket); // TODO: Restrict publicness to super +pub struct Output(pub(super) Socket); 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); // TODO: Restrict publicness to super +pub struct SocketIdx(pub u16); 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 702244c..ef7a23d 100644 --- a/crates/ir/src/lib.rs +++ b/crates/ir/src/lib.rs @@ -27,8 +27,6 @@ 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). @@ -58,28 +56,7 @@ 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)] +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] pub struct GraphIr { /// "Backbone" storage of all **instruction** IDs to /// what **kind of instruction** they are. @@ -146,13 +123,13 @@ impl GraphIr { /// 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, subject: &id::Instruction) -> Option> { + pub fn resolve<'ir>(&'ir self, subject: &id::Instruction) -> Option> { let (id, kind) = self.instructions.get_key_value(subject)?; let input_sources = self.input_sources(subject)?.collect(); let output_targets = self.output_targets(subject)?.collect(); - Some(InstructionRef { + Some(Instruction { id, kind, input_sources, @@ -164,7 +141,7 @@ impl GraphIr { /// /// The same caveats as for [`GraphIr::resolve`] apply. #[must_use] - pub fn owner_of_input<'ir>(&'ir self, input: &id::Input) -> Option> { + pub fn owner_of_input<'ir>(&'ir self, input: &id::Input) -> Option> { self.resolve(&input.socket().belongs_to) } @@ -172,7 +149,7 @@ impl GraphIr { /// /// The same caveats as for [`GraphIr::resolve`] apply. #[must_use] - pub fn owner_of_output<'ir>(&'ir self, output: &id::Output) -> Option> { + pub fn owner_of_output<'ir>(&'ir self, output: &id::Output) -> Option> { self.resolve(&output.socket().belongs_to) } @@ -186,7 +163,7 @@ impl GraphIr { #[must_use] // yes, this function could probably return an iterator and be lazy // no, not today - pub fn topological_sort(&self) -> Vec { + pub fn topological_sort(&self) -> Vec { // count how many incoming edges each vertex has let mut nonzero_input_counts: Map<_, NonZeroUsize> = self.rev_edges @@ -263,58 +240,18 @@ impl GraphIr { } } -/// Constructs an [`id::Socket`] a bit more tersely. -fn socket(id: &id::Instruction, idx: u16) -> id::Socket { - id::Socket { - belongs_to: id.clone(), - idx: id::SocketIdx(idx), - } -} - -/// A full instruction bundeled in context, with its inputs and outputs. +/// A full instruction in context, with its inputs and outputs. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Instruction { - pub id: id::Instruction, - pub kind: instruction::Kind, - - // can't have these two public since then a user might corrupt their length - input_sources: Vec>, - output_targets: Vec>, -} - -impl Instruction { - /// 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 input_sources(&self) -> &[Option] { - &self.input_sources - } - - /// To whom outputs are sent. - #[must_use] - pub fn output_targets(&self) -> &[Set] { - &self.output_targets - } -} - -/// [`Instruction`], but every single field is borrowed instead. -/// See its docs. -/// -/// Use the [`From`] impl to handily convert into an [`Instruction`]. -/// The other way around is unlikely to be wanted — since you already have an [`Instruction`], -/// chances are you just want to take a reference (`&`) of it. -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct InstructionRef<'ir> { +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 input_sources: Vec>, output_targets: Vec>>, } -impl<'ir> InstructionRef<'ir> { +impl<'ir> Instruction<'ir> { /// Where this instruction gets its inputs from. /// /// [`None`] means that this input is unfilled, @@ -331,23 +268,10 @@ impl<'ir> InstructionRef<'ir> { } } -// would love to use ToOwned but Rust has no specialization yet -// and it'd hurt a blanket impl of ToOwned otherwise -impl From> for Instruction { - fn from(source: InstructionRef<'_>) -> Self { - Self { - id: source.id.clone(), - kind: source.kind.clone(), - input_sources: source - .input_sources - .into_iter() - .map(Option::<&_>::cloned) - .collect(), - output_targets: source - .output_targets - .into_iter() - .map(|outputs| outputs.map(Clone::clone).unwrap_or_default()) - .collect(), - } +/// Constructs an [`id::Socket`] a bit more tersely. +fn socket(id: &id::Instruction, idx: u16) -> id::Socket { + id::Socket { + belongs_to: id.clone(), + idx: id::SocketIdx(idx), } } diff --git a/crates/ir/src/semi_human.rs b/crates/ir/src/semi_human.rs index e8d3bdd..7d7ff83 100644 --- a/crates/ir/src/semi_human.rs +++ b/crates/ir/src/semi_human.rs @@ -77,8 +77,7 @@ fn reverse_and_type_edges(edges: Map>) -> Map Runs through all functions in the graph IR. -It does not have any significantly different representation, +It does not have any significantly other representation, and despite its name there's _no_ bytecode involved. +Different runtimes are called executors. +Executors operate on instructions instead of functions. + === Scheduler -Looks at the graph IR and decides when which evaluator gets to evaluate what. +Looks at the graph IR and decides when the VM should execute what. -=== Evaluator - -Runs instructions given to it in a specific way, -such as for example on the GPU using OpenCL. +=== VM = Open questions diff --git a/docs/design/type-notation.typ b/docs/design/type-notation.typ index ad991ee..ee33517 100644 --- a/docs/design/type-notation.typ +++ b/docs/design/type-notation.typ @@ -34,5 +34,5 @@ One dimensional list of 32 bit signed integers: Due to inference, you'll also be able to use that in some mathematical operations with integers: -`[i32] + i32` is a valid operation, for example (of course, you can't add types.) But that wouldn't add the second one to the list, but rather add the single i32 to all values in the left hand side list). That would also work with more dimensional arrays and dynamic streams like videos. +`[i32] + i32` is a valid operation, for example (of course, you can't add types.) But that wouldn't add the second one to the list, but rather add the single i32 to all values in the left hand side list). That would also work with more dimensional arrays and dynamic streams like videos. diff --git a/docs/template.typ b/docs/template.typ index cedb5e6..a624cc7 100644 --- a/docs/template.typ +++ b/docs/template.typ @@ -33,23 +33,22 @@ // this'd require wrapping the whole document in a show rule // at which point `query` doesn't find anything anymore #let terms = ( - "processing stage", "source", "AST", "graph IR", "runtime", + "executor", "optimizer", "scheduler", - "evaluator", - "evaluation", + "VM", "function", "instruction", + "pipeline", "input", "argument", "consumer", "output", "streamer", "modifier", - "pipeline", ) // yes, the shadowing is intentional to avoid accidentally using the list #let terms = regex("\\b(" + terms.map(expand).join("|") + ")\\b") diff --git a/testfiles/bare.ron b/testfiles/bare.ron index de78944..f2a9621 100644 --- a/testfiles/bare.ron +++ b/testfiles/bare.ron @@ -1,12 +1,12 @@ ( instructions: { 0: Read(( - source: File("testfiles/rails.png"), - format: Png, + source: File("testfiles/juan.jpg"), + format: Jpeg, )), 1: Write(( - target: File("testfiles/gen/out.jpg"), - format: Jpeg, + target: File("testfiles/out.png"), + format: Png, )), }, edges: { diff --git a/testfiles/gen/.gitkeep b/testfiles/gen/.gitkeep deleted file mode 100644 index 9daccef..0000000 --- a/testfiles/gen/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -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 ce8e68d..3f1f48c 100644 --- a/testfiles/invert.ron +++ b/testfiles/invert.ron @@ -1,12 +1,12 @@ ( instructions: { 0: Read(( - source: File("testfiles/rails.png"), - format: Png, + source: File("testfiles/juan.jpg"), + format: Jpeg, )), 1: Filter(Invert), 2: Write(( - target: File("testfiles/gen/inverted.png"), + target: File("testfiles/inverted.png"), format: Png, )), },