forked from katzen-cafe/iowo
chore: rename executor -> evaluator and integrate into app
This commit is contained in:
parent
de9ca81b65
commit
3c529c3a1a
16 changed files with 229 additions and 115 deletions
|
@ -6,14 +6,16 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = { workspace = true, features = [ "derive", "env" ] }
|
||||
serde = { workspace = true, features = [ "derive" ] }
|
||||
ron = "0.8"
|
||||
serde_json = "1.0"
|
||||
ariadne = "0.4"
|
||||
time = { version = "0.3", features = [ "local-offset" ] }
|
||||
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" ] }
|
||||
serde_json = "1.0"
|
||||
time = { version = "0.3", features = [ "local-offset" ] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
|
||||
use self::{
|
||||
|
@ -10,6 +12,9 @@ mod config_file;
|
|||
|
||||
/// this struct may hold all configuration
|
||||
pub struct Config {
|
||||
pub source: PathBuf,
|
||||
pub evaluator: eval::Available,
|
||||
|
||||
pub startup_msg: bool,
|
||||
}
|
||||
|
||||
|
@ -17,37 +22,37 @@ impl Config {
|
|||
/// Get the configs from all possible places (args, file, env...)
|
||||
pub fn read() -> Self {
|
||||
let args = Args::parse();
|
||||
let config_path = if let Some(config_path) = args.config_path {
|
||||
Ok(config_path)
|
||||
let config = if let Some(config) = args.config_path {
|
||||
Ok(config)
|
||||
} else {
|
||||
find_config_file()
|
||||
};
|
||||
|
||||
// try to read a maybe existing config file
|
||||
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) => {
|
||||
let config = config.ok().and_then(|path| {
|
||||
Configs::read(path).map_or_else(
|
||||
|e| {
|
||||
eprintln!("Config error: {e:?}");
|
||||
eprintln!("Proceeding with defaults or cli args...");
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
},
|
||||
Some,
|
||||
)
|
||||
});
|
||||
|
||||
if let Some(file_config) = file_config {
|
||||
if let Some(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_config.no_startup_message),
|
||||
startup_msg: !(args.no_startup_message || file.no_startup_message),
|
||||
}
|
||||
} else {
|
||||
Self {
|
||||
source: args.source,
|
||||
startup_msg: !args.no_startup_message,
|
||||
evaluator: args.evaluator.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +61,7 @@ impl Config {
|
|||
pub mod error {
|
||||
/// Errors that can occur when reading configs
|
||||
#[derive(Debug)]
|
||||
pub enum ConfigError {
|
||||
pub enum Config {
|
||||
/// The config dir doesn't exist
|
||||
NoConfigDir,
|
||||
/// We didn't find a config file in the config dir
|
||||
|
@ -73,19 +78,19 @@ pub mod error {
|
|||
SerdeRonError(ron::error::SpannedError),
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for ConfigError {
|
||||
impl From<std::io::Error> for Config {
|
||||
fn from(value: std::io::Error) -> Self {
|
||||
Self::IoError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for ConfigError {
|
||||
impl From<serde_json::Error> for Config {
|
||||
fn from(value: serde_json::Error) -> Self {
|
||||
Self::SerdeJsonError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ron::error::SpannedError> for ConfigError {
|
||||
impl From<ron::error::SpannedError> for Config {
|
||||
fn from(value: ron::error::SpannedError) -> Self {
|
||||
Self::SerdeRonError(value)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,14 @@ 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<eval::Available>,
|
||||
|
||||
/// Read this config file.
|
||||
#[arg(short, long)]
|
||||
pub config_path: Option<PathBuf>,
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::{
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::error::ConfigError;
|
||||
use super::error::Config;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Configs {
|
||||
|
@ -13,6 +13,7 @@ pub struct Configs {
|
|||
pub example_value: i32,
|
||||
#[serde(default)]
|
||||
pub no_startup_message: bool,
|
||||
pub evaluator: Option<eval::Available>,
|
||||
}
|
||||
|
||||
/// what the fuck serde why do i need this
|
||||
|
@ -21,9 +22,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<PathBuf, ConfigError> {
|
||||
pub(super) fn find_config_file() -> Result<PathBuf, Config> {
|
||||
let Some(config_path) = dirs::config_dir() else {
|
||||
return Err(ConfigError::NoConfigDir);
|
||||
return Err(Config::NoConfigDir);
|
||||
};
|
||||
|
||||
let ron_path = config_path.with_file_name("config.ron");
|
||||
|
@ -34,19 +35,19 @@ pub(super) fn find_config_file() -> Result<PathBuf, ConfigError> {
|
|||
} else if Path::new(&json_path).exists() {
|
||||
Ok(json_path)
|
||||
} else {
|
||||
Err(ConfigError::NoConfigFileFound)
|
||||
Err(Config::NoConfigFileFound)
|
||||
}
|
||||
}
|
||||
|
||||
impl Configs {
|
||||
pub fn read(p: PathBuf) -> Result<Self, ConfigError> {
|
||||
pub fn read(p: PathBuf) -> Result<Self, Config> {
|
||||
match p
|
||||
.extension()
|
||||
.map(|v| v.to_str().expect("config path to be UTF-8"))
|
||||
{
|
||||
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(ConfigError::UnknownExtension(e.map(str::to_string))),
|
||||
e => Err(Config::UnknownExtension(e.map(str::to_string))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::fs;
|
||||
|
||||
use config::Config;
|
||||
use welcome_msg::print_startup_msg;
|
||||
|
||||
|
@ -14,4 +16,14 @@ fn main() {
|
|||
if cfg.startup_msg {
|
||||
print_startup_msg();
|
||||
}
|
||||
|
||||
let source =
|
||||
fs::read_to_string(cfg.source).expect("can't find source file lol handle me better please");
|
||||
|
||||
let ir =
|
||||
ir::from_ron(&source).expect("aww failed to parse source to graph ir handle me better");
|
||||
|
||||
let mut machine = cfg.evaluator.pick();
|
||||
machine.feed(ir);
|
||||
machine.eval_full();
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ edition = "2021"
|
|||
clap = { workspace = true, features = [ "derive" ] }
|
||||
image = "0.24"
|
||||
ir = { path = "../ir" }
|
||||
serde = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
pub(crate) struct CpuExecutor;
|
|
@ -1,37 +0,0 @@
|
|||
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<Dynamic>) -> Option<Dynamic> {
|
||||
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"),
|
||||
},
|
||||
))),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
57
crates/eval/src/kind/debug/mod.rs
Normal file
57
crates/eval/src/kind/debug/mod.rs
Normal file
|
@ -0,0 +1,57 @@
|
|||
use std::mem;
|
||||
|
||||
use ir::{
|
||||
instruction::{Filter, Kind},
|
||||
GraphIr, Instruction, InstructionRef,
|
||||
};
|
||||
|
||||
use crate::value::Dynamic;
|
||||
mod instr;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Evaluator {
|
||||
ir: GraphIr,
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
fn eval_full(&mut self) {
|
||||
let queue: Vec<Instruction> = 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) {
|
||||
let _ = match instr.kind {
|
||||
Kind::Read(details) => Some(Dynamic::Image(instr::read::read(details))),
|
||||
Kind::Write(details) => {
|
||||
instr::write::write(details, todo!());
|
||||
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"),
|
||||
},
|
||||
))),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
1
crates/eval/src/kind/mod.rs
Normal file
1
crates/eval/src/kind/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod debug;
|
|
@ -1,34 +1,43 @@
|
|||
use ir::instruction::Kind;
|
||||
use value::Dynamic;
|
||||
use ir::GraphIr;
|
||||
|
||||
mod debug;
|
||||
mod kind;
|
||||
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.
|
||||
/// 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,
|
||||
}
|
||||
|
||||
trait Executor {
|
||||
fn execute(instruction: Kind, input: Option<Dynamic>) -> Option<Dynamic>;
|
||||
}
|
||||
|
||||
pub fn execute_all(instructions: Vec<Kind>) {
|
||||
let mut tmp = None;
|
||||
|
||||
for instruction in instructions {
|
||||
tmp = debug::Executor::execute(instruction, tmp);
|
||||
impl Available {
|
||||
/// Selects the [`Evaluator`] corresponding to this label.
|
||||
#[must_use]
|
||||
pub fn pick(&self) -> Box<dyn Evaluator> {
|
||||
match self {
|
||||
Self::Debug => Box::new(kind::debug::Evaluator::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
|
@ -56,7 +56,7 @@ 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**.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct GraphIr {
|
||||
/// "Backbone" storage of all **instruction** IDs to
|
||||
/// what **kind of instruction** they are.
|
||||
|
@ -123,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<Instruction<'ir>> {
|
||||
pub fn resolve<'ir>(&'ir self, subject: &id::Instruction) -> Option<InstructionRef<'ir>> {
|
||||
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(Instruction {
|
||||
Some(InstructionRef {
|
||||
id,
|
||||
kind,
|
||||
input_sources,
|
||||
|
@ -141,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<Instruction<'ir>> {
|
||||
pub fn owner_of_input<'ir>(&'ir self, input: &id::Input) -> Option<InstructionRef<'ir>> {
|
||||
self.resolve(&input.socket().belongs_to)
|
||||
}
|
||||
|
||||
|
@ -149,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<Instruction<'ir>> {
|
||||
pub fn owner_of_output<'ir>(&'ir self, output: &id::Output) -> Option<InstructionRef<'ir>> {
|
||||
self.resolve(&output.socket().belongs_to)
|
||||
}
|
||||
|
||||
|
@ -163,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<Instruction> {
|
||||
pub fn topological_sort(&self) -> Vec<InstructionRef> {
|
||||
// count how many incoming edges each vertex has
|
||||
let mut nonzero_input_counts: Map<_, NonZeroUsize> =
|
||||
self.rev_edges
|
||||
|
@ -240,18 +240,58 @@ impl GraphIr {
|
|||
}
|
||||
}
|
||||
|
||||
/// A full instruction in context, with its inputs and outputs.
|
||||
/// 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.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Instruction<'ir> {
|
||||
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<Option<id::Output>>,
|
||||
output_targets: Vec<Set<id::Input>>,
|
||||
}
|
||||
|
||||
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<id::Output>] {
|
||||
&self.input_sources
|
||||
}
|
||||
|
||||
/// To whom outputs are sent.
|
||||
#[must_use]
|
||||
pub fn output_targets(&self) -> &[Set<id::Input>] {
|
||||
&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 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<Option<&'ir id::Output>>,
|
||||
output_targets: Vec<Option<&'ir Set<id::Input>>>,
|
||||
}
|
||||
|
||||
impl<'ir> Instruction<'ir> {
|
||||
impl<'ir> InstructionRef<'ir> {
|
||||
/// Where this instruction gets its inputs from.
|
||||
///
|
||||
/// [`None`] means that this input is unfilled,
|
||||
|
@ -268,10 +308,23 @@ impl<'ir> Instruction<'ir> {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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),
|
||||
// would love to use ToOwned but Rust has no specialization yet
|
||||
// and it'd hurt a blanket impl of ToOwned otherwise
|
||||
impl From<InstructionRef<'_>> 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue