// Copyright (c) 2022 Weird Constructor // This file is a part of HexoDSP. Released under GPL-3.0-or-later. // See README.md and COPYING for details. use std::cell::RefCell; use std::rc::Rc; use std::collections::HashMap; use std::collections::HashSet; use std::collections::VecDeque; use serde_json::{json, Value}; pub trait BlockView { fn rows(&self) -> usize; fn contains(&self, idx: usize) -> Option; fn expanded(&self) -> bool; fn label(&self, buf: &mut [u8]) -> usize; fn has_input(&self, idx: usize) -> bool; fn has_output(&self, idx: usize) -> bool; fn input_label(&self, idx: usize, buf: &mut [u8]) -> usize; fn output_label(&self, idx: usize, buf: &mut [u8]) -> usize; fn custom_color(&self) -> Option; } pub trait BlockCodeView { fn area_header(&self, id: usize) -> Option<&str>; fn area_size(&self, id: usize) -> (usize, usize); fn block_at(&self, id: usize, x: i64, y: i64) -> Option<&dyn BlockView>; fn origin_at(&self, id: usize, x: i64, y: i64) -> Option<(i64, i64)>; fn generation(&self) -> u64; } #[derive(Debug, Clone)] pub struct BlockIDGenerator { counter: Rc>, } impl BlockIDGenerator { pub fn new() -> Self { Self { counter: Rc::new(RefCell::new(0)) } } pub fn new_with_id(id: usize) -> Self { Self { counter: Rc::new(RefCell::new(id)) } } pub fn current(&self) -> usize { *self.counter.borrow_mut() } pub fn next(&self) -> usize { let mut c = self.counter.borrow_mut(); *c += 1; *c } } /// This structure represents a block inside the [BlockArea] of a [BlockFun]. /// It stores everything required for calculating a node of the AST. /// /// A [BlockType::instanciate_block] is used to create a new instance of this /// structure. /// /// You usually don't use this structure directly, but you use the /// position of it inside the [BlockFun]. The position of a block /// is specified by the `area_id`, and the `x` and `y` coordinates. #[derive(Debug, Clone)] pub struct Block { /// An ID to track this block. id: usize, /// How many rows this block spans. A [Block] can only be 1 cell wide. rows: usize, /// Up to two sub [BlockArea] can be specified here by their ID. contains: (Option, Option), /// Whether the sub areas are visible/drawn. expanded: bool, /// The type of this block. It's just a string set by the [BlockType] /// and it should be everything that determines what this block is /// going to end up as in the AST. typ: String, /// The label of the block. lbl: String, /// The input ports, the index into the [Vec] is the row. The [String] /// is the label of the input port. inputs: Vec>, /// The output ports, the index into the [Vec] is the row. The [String] /// is the label of the output port. outputs: Vec>, /// The color index of this block. color: usize, } impl Block { pub fn clone_with_new_id(&self, new_id: usize) -> Self { Self { id: new_id, rows: self.rows, contains: self.contains.clone(), expanded: self.expanded, typ: self.typ.clone(), lbl: self.lbl.clone(), inputs: self.inputs.clone(), outputs: self.outputs.clone(), color: self.color, } } /// Takes the (input) port at row `idx` and pushed it one row further /// down, wrapping around at the end. If `output` is true, the /// output port at `idx` is shifted. pub fn shift_port(&mut self, idx: usize, output: bool) { if self.rows <= 1 { return; } let v = if output { &mut self.outputs } else { &mut self.inputs }; if v.len() < self.rows { v.resize(self.rows, None); } let idx_from = idx; let idx_to = (idx + 1) % v.len(); let elem = v.remove(idx_from); v.insert(idx_to, elem); } /// Calls `f` for every output port that is available. /// `f` gets passed the row index. pub fn for_output_ports(&self, mut f: F) { for i in 0..self.rows { if let Some(p) = self.outputs.get(i) { if let Some(p) = p { f(i, p); } } } } /// Returns the number of output ports of this [Block]. pub fn count_outputs(&self) -> usize { let mut count = 0; for i in 0..self.rows { if let Some(o) = self.outputs.get(i) { if o.is_some() { count += 1; } } } count } /// Calls `f` for every input port that is available. /// `f` gets passed the row index. pub fn for_input_ports(&self, mut f: F) { for i in 0..self.rows { if let Some(p) = self.inputs.get(i) { if let Some(p) = p { f(i, p); } } } } /// Calls `f` for every input port that is available. /// `f` gets passed the row index. pub fn for_input_ports_reverse(&self, mut f: F) { for i in 1..=self.rows { let i = self.rows - i; if let Some(p) = self.inputs.get(i) { if let Some(p) = p { f(i, p); } } } } /// Serializes this [Block] into a [Value]. Called by [BlockArea::serialize]. pub fn serialize(&self) -> Value { let mut inputs = json!([]); let mut outputs = json!([]); if let Value::Array(inputs) = &mut inputs { for p in self.inputs.iter() { inputs.push(json!(p)); } } if let Value::Array(outputs) = &mut outputs { for p in self.outputs.iter() { outputs.push(json!(p)); } } let c0 = if let Some(c) = self.contains.0 { c.into() } else { Value::Null }; let c1 = if let Some(c) = self.contains.1 { c.into() } else { Value::Null }; json!({ "id": self.id as i64, "rows": self.rows as i64, "contains": json!([c0, c1]), "expanded": self.expanded, "typ": self.typ, "lbl": self.lbl, "color": self.color, "inputs": inputs, "outputs": outputs, }) } /// Deserializes this [Block] from a [Value]. Called by [BlockArea::deserialize]. pub fn deserialize(v: &Value) -> Result, serde_json::Error> { let mut inputs = vec![]; let mut outputs = vec![]; let inps = &v["inputs"]; if let Value::Array(inps) = inps { for v in inps.iter() { inputs.push(if v.is_string() { Some(v.as_str().unwrap_or("").to_string()) } else { None }) } } let outs = &v["outputs"]; if let Value::Array(outs) = outs { for v in outs.iter() { outputs.push(if v.is_string() { Some(v.as_str().unwrap_or("").to_string()) } else { None }) } } Ok(Box::new(Block { id: v["id"].as_i64().unwrap_or(0) as usize, rows: v["rows"].as_i64().unwrap_or(0) as usize, contains: ( if v["contains"][0].is_i64() { Some(v["contains"][0].as_i64().unwrap_or(0) as usize) } else { None }, if v["contains"][1].is_i64() { Some(v["contains"][1].as_i64().unwrap_or(0) as usize) } else { None }, ), expanded: v["expanded"].as_bool().unwrap_or(true), typ: v["typ"].as_str().unwrap_or("?").to_string(), lbl: v["lbl"].as_str().unwrap_or("?").to_string(), inputs, outputs, color: v["color"].as_i64().unwrap_or(0) as usize, })) } } impl BlockView for Block { fn rows(&self) -> usize { self.rows } fn contains(&self, idx: usize) -> Option { if idx == 0 { self.contains.0 } else { self.contains.1 } } fn expanded(&self) -> bool { true } fn label(&self, buf: &mut [u8]) -> usize { use std::io::Write; let mut bw = std::io::BufWriter::new(buf); match write!(bw, "{}", self.lbl) { Ok(_) => bw.buffer().len(), _ => 0, } } fn has_input(&self, idx: usize) -> bool { self.inputs.get(idx).map(|s| s.is_some()).unwrap_or(false) } fn has_output(&self, idx: usize) -> bool { self.outputs.get(idx).map(|s| s.is_some()).unwrap_or(false) } fn input_label(&self, idx: usize, buf: &mut [u8]) -> usize { use std::io::Write; if let Some(lbl_opt) = self.inputs.get(idx) { if let Some(lbl) = lbl_opt { let mut bw = std::io::BufWriter::new(buf); match write!(bw, "{}", lbl) { Ok(_) => bw.buffer().len(), _ => 0, } } else { 0 } } else { 0 } } fn output_label(&self, idx: usize, buf: &mut [u8]) -> usize { use std::io::Write; if let Some(lbl_opt) = self.outputs.get(idx) { if let Some(lbl) = lbl_opt { let mut bw = std::io::BufWriter::new(buf); match write!(bw, "{}", lbl) { Ok(_) => bw.buffer().len(), _ => 0, } } else { 0 } } else { 0 } } fn custom_color(&self) -> Option { Some(self.color) } } /// Represents a connected collection of blocks. Is created by /// [BlockFun::retrieve_block_chain_at] or [BlockArea::chain_at]. /// /// After creating a [BlockChain] structure you can decide to /// clone the blocks from the [BlockArea] with [BlockChain::clone_load] /// or remove the blocks from the [BlockArea] and store them /// inside this [BlockChain] via [BlockChain::remove_load]. /// /// The original positions of the _loaded_ blocks is stored too. /// If you want to move the whole chain in the coordinate system /// to the upper left most corner, you can use [BlockChain::normalize_load_pos]. #[derive(Debug)] pub struct BlockChain { /// The area ID this BlockChain was created from. #[allow(dead_code)] area_id: usize, /// Stores the positions of the blocks of the chain inside the [BlockArea]. blocks: HashSet<(i64, i64)>, /// Stores the positions of blocks that only have output ports. #[allow(dead_code)] sources: HashSet<(i64, i64)>, /// Stores the positions of blocks that only have input ports. #[allow(dead_code)] sinks: HashSet<(i64, i64)>, /// This field stores _loaded_ blocks from the [BlockArea] /// into this [BlockChain] for inserting or analyzing them. /// /// Stores the blocks themself, with their position in the [BlockArea], /// which can be normalized (moved to the upper left) with /// [BlockChain::normalize_load_pos]. /// /// The blocks in this [Vec] are stored in sorted order. /// They are stored in ascending order of their `x` coordinate, /// and for the same `x` coordinate in /// ascending order of their `y` coordinate. load: Vec<(Box, i64, i64)>, } impl BlockChain { pub fn move_by_offs(&mut self, xo: i64, yo: i64) { for (_, x, y) in &mut self.load { *x += xo; *y += yo; //d// println!("MOVE_BY_OFFS TO x={:3} y={:3}", *x, *y); } } /// Normalizes the position of all loaded blocks and returns /// the original top left most position of the chain. pub fn normalize_load_pos(&mut self) -> (i64, i64) { let mut min_x = 100000000; let mut min_y = 100000000; for (_, xo, yo) in &self.load { min_x = min_x.min(*xo); min_y = min_y.min(*yo); } for (_, xo, yo) in &mut self.load { *xo -= min_x; *yo -= min_y; } self.sort_load_pos(); (min_x, min_y) } fn sort_load_pos(&mut self) { self.load.sort_by(|&(_, x0, y0), &(_, x1, y1)| x0.cmp(&x1).then(y0.cmp(&y1))); } pub fn get_connected_inputs_from_load_at_x(&self, x_split: i64) -> Vec<(i64, i64)> { let mut output_points = vec![]; for (block, x, y) in &self.load { if *x == x_split { block.for_output_ports(|row, _| { output_points.push(y + (row as i64)); }); } } let mut connection_pos = vec![]; for (block, x, y) in &self.load { if *x == (x_split + 1) { block.for_input_ports(|row, _| { if output_points.iter().find(|&&out_y| out_y == (y + (row as i64))).is_some() { connection_pos.push((*x, y + (row as i64))); } }); } } connection_pos } // pub fn join_load_after_x(&mut self, x_join: i64, y_split: i64) -> bool { // let filler_pos : Vec<(i64, i64)> = // self.get_connected_inputs_from_load_at_x(x_split); // if filler_pos.len() > 1 // || (filler_pos.len() == 1 && filler_pos[0] != (x_join, y_split) // } pub fn split_load_after_x( &mut self, x_split: i64, y_split: i64, filler: Option<&BlockType>, id_gen: BlockIDGenerator, ) { let filler_pos: Vec<(i64, i64)> = self.get_connected_inputs_from_load_at_x(x_split); for (_block, x, _y) in &mut self.load { if *x > x_split { *x += 1; } } if let Some(filler) = filler { for (x, y) in filler_pos { if y == y_split { continue; } let filler_block = filler.instanciate_block(None, id_gen.clone()); self.load.push((filler_block, x, y)); } } self.sort_load_pos(); } pub fn clone_load(&mut self, area: &mut BlockArea, id_gen: BlockIDGenerator) { self.load.clear(); for b in &self.blocks { if let Some((block, xo, yo)) = area.ref_at_origin(b.0, b.1) { self.load.push((Box::new(block.clone_with_new_id(id_gen.next())), xo, yo)); } } self.sort_load_pos(); } pub fn remove_load(&mut self, area: &mut BlockArea) { self.load.clear(); for b in &self.blocks { if let Some((block, xo, yo)) = area.remove_at(b.0, b.1) { self.load.push((block, xo, yo)); } } self.sort_load_pos(); } pub fn place_load(&mut self, area: &mut BlockArea) { let load = std::mem::replace(&mut self.load, vec![]); area.set_blocks_from(load); } pub fn try_fit_load_into_space(&mut self, area: &mut BlockArea) -> bool { for (xo, yo) in &[ (0, 0), // where it currently is (0, -1), (0, -2), (0, -3), (-1, 0), (-1, -1), (-1, -2), (-1, -3), (1, 0), (1, -1), (1, -2), (1, -3), (0, 1), (0, 2), (0, 3), (-1, 1), (-1, 2), (-1, 3), (1, 1), (1, 2), (1, 3), ] { println!("TRY {},{}", *xo, *yo); if self.area_has_space_for_load(area, *xo, *yo) { self.move_by_offs(*xo, *yo); return true; } //d// println!("RETRY xo={}, yo={}", *xo, *yo); } return false; } pub fn area_has_space_for_load( &mut self, area: &mut BlockArea, xoffs: i64, yoffs: i64, ) -> bool { for (block, x, y) in self.load.iter() { if !area.check_space_at(*x + xoffs, *y + yoffs, block.rows) { return false; } } true } pub fn area_is_subarea_of_loaded(&mut self, area: usize, fun: &mut BlockFun) -> bool { let mut areas = vec![]; for (block, _, _) in self.load.iter() { fun.all_sub_areas_of(block.as_ref(), &mut areas); } for a_id in areas.iter() { if *a_id == area { return true; } } return false; } } #[derive(Debug, Clone)] pub struct BlockArea { blocks: HashMap<(i64, i64), Box>, origin_map: HashMap<(i64, i64), (i64, i64)>, size: (usize, usize), auto_shrink: bool, header: String, } impl BlockArea { fn new(w: usize, h: usize) -> Self { Self { blocks: HashMap::new(), origin_map: HashMap::new(), size: (w, h), auto_shrink: false, header: "".to_string(), } } pub fn set_header(&mut self, header: String) { self.header = header; } pub fn set_auto_shrink(&mut self, shrink: bool) { self.auto_shrink = shrink; } pub fn auto_shrink(&self) -> bool { self.auto_shrink } pub fn chain_at(&self, x: i64, y: i64) -> Option> { let (_block, xo, yo) = self.ref_at_origin(x, y)?; let mut dq: VecDeque<(i64, i64)> = VecDeque::new(); dq.push_back((xo, yo)); let mut blocks: HashSet<(i64, i64)> = HashSet::new(); let mut sources: HashSet<(i64, i64)> = HashSet::new(); let mut sinks: HashSet<(i64, i64)> = HashSet::new(); let mut check_port_conns = vec![]; while let Some((x, y)) = dq.pop_front() { check_port_conns.clear(); // First we find all adjacent output/input port positions // and collect them in `check_port_conns`. // // While are at it, we also record which blocks are only // sinks and which are only sources. Might be important for // other algorithms that do things with this. if let Some((block, xo, yo)) = self.ref_at_origin(x, y) { if blocks.contains(&(xo, yo)) { continue; } blocks.insert((xo, yo)); let mut has_input = false; let mut has_output = false; block.for_input_ports(|idx, _| { check_port_conns.push((xo - 1, yo + (idx as i64), true)); has_input = true; }); block.for_output_ports(|idx, _| { check_port_conns.push((xo + 1, yo + (idx as i64), false)); has_output = true; }); if !has_input { sources.insert((xo, yo)); } if !has_output { sinks.insert((xo, yo)); } } // Then we look if there is a block at that position, with // a corresponding input or output port at the right // row inside the block. for (x, y, is_output) in &check_port_conns { if let Some((_block, xo, yo, _row)) = self.find_port_at(*x, *y, *is_output) { dq.push_back((xo, yo)); } } } Some(Box::new(BlockChain { area_id: 0, blocks, sources, sinks, load: vec![] })) } pub fn find_last_unconnected_output(&self) -> Option<(i64, i64, String)> { let mut max_x = 0; let mut max_y = 0; let mut port: Option<(i64, i64, String)> = None; for ((x, y), block) in &self.blocks { let (x, y) = (*x, *y); block.for_output_ports(|row, _| { let y = y + (row as i64); if self.find_port_at(x + 1, y, false).is_none() { if y > max_y { max_y = y; max_x = x; port = Some(( max_x, max_y, block .outputs .get(row) .cloned() .flatten() .unwrap_or_else(|| "".to_string()), )); } else if y == max_y && x > max_x { max_x = x; port = Some(( max_x, max_y, block .outputs .get(row) .cloned() .flatten() .unwrap_or_else(|| "".to_string()), )); } } }) } port } /// Collects the sinks in this area. /// It returns a list of [Block] positions inside the /// area. For unconnected outputs, which are also evaluated /// and returned as possible last value of an [BlockArea], /// the output row is also given. /// /// The result is sorted so, that the bottom right most element /// is the first one in the result list. pub fn collect_sinks(&self) -> Vec<(i64, i64, Option)> { let mut sinks_out = vec![]; for ((x, y), block) in &self.blocks { if block.count_outputs() == 0 { sinks_out.push((*x, *y, None)); } else { block.for_output_ports(|row, _| { if self.find_port_at(*x + 1, *y + (row as i64), false).is_none() { sinks_out.push((*x, *y + (row as i64), Some(row))); } }); } } sinks_out.sort_by(|&(x0, y0, _), &(x1, y1, _)| y1.cmp(&y0).then(x1.cmp(&x0))); sinks_out } fn ref_at(&self, x: i64, y: i64) -> Option<&Block> { let (xo, yo) = self.origin_map.get(&(x, y))?; self.blocks.get(&(*xo, *yo)).map(|b| b.as_ref()) } fn ref_at_origin(&self, x: i64, y: i64) -> Option<(&Block, i64, i64)> { let (xo, yo) = self.origin_map.get(&(x, y))?; let (xo, yo) = (*xo, *yo); self.blocks.get(&(xo, yo)).map(|b| (b.as_ref(), xo, yo)) } fn ref_mut_at(&mut self, x: i64, y: i64) -> Option<&mut Block> { let (xo, yo) = self.origin_map.get(&(x, y))?; self.blocks.get_mut(&(*xo, *yo)).map(|b| b.as_mut()) } fn ref_mut_at_origin(&mut self, x: i64, y: i64) -> Option<(&mut Block, i64, i64)> { let (xo, yo) = self.origin_map.get(&(x, y))?; let (xo, yo) = (*xo, *yo); self.blocks.get_mut(&(xo, yo)).map(|b| (b.as_mut(), xo, yo)) } fn find_port_at( &self, x: i64, y: i64, expect_output: bool, ) -> Option<(&Block, i64, i64, usize)> { let (block, xo, yo) = self.ref_at_origin(x, y)?; let port_y = (y - yo).max(0) as usize; if expect_output { if let Some(o) = block.outputs.get(port_y) { if o.is_some() { return Some((block, xo, yo, port_y)); } } } else { if let Some(i) = block.inputs.get(port_y) { if i.is_some() { return Some((block, xo, yo, port_y)); } } } None } fn set_blocks_from(&mut self, list: Vec<(Box, i64, i64)>) { for (block, x, y) in list.into_iter() { self.blocks.insert((x, y), block); } self.update_origin_map(); } fn set_block_at(&mut self, x: i64, y: i64, block: Box) { self.blocks.insert((x, y), block); self.update_origin_map(); } fn remove_at(&mut self, x: i64, y: i64) -> Option<(Box, i64, i64)> { let (xo, yo) = self.origin_map.get(&(x, y))?; if let Some(block) = self.blocks.remove(&(*xo, *yo)) { let (xo, yo) = (*xo, *yo); self.update_origin_map(); Some((block, xo, yo)) } else { None } } fn set_size(&mut self, w: usize, h: usize) { self.size = (w, h); } fn get_direct_sub_areas(&self, out: &mut Vec) { for ((_x, _y), block) in &self.blocks { if let Some(sub_area) = block.contains.0 { out.push(sub_area); } if let Some(sub_area) = block.contains.1 { out.push(sub_area); } } } /// Calculates only the size of the area in the +x/+y quadrant. /// The negative areas are not counted in. fn resolve_size (usize, usize)>(&self, resolve_sub_areas: F) -> (usize, usize) { let mut min_w = 1; let mut min_h = 1; for ((ox, oy), _) in &self.origin_map { let (ox, oy) = ((*ox).max(0) as usize, (*oy).max(0) as usize); if min_w < (ox + 1) { min_w = ox + 1; } if min_h < (oy + 1) { min_h = oy + 1; } } for ((x, y), block) in &self.blocks { let (x, y) = ((*x).max(0) as usize, (*y).max(0) as usize); let mut prev_h = 1; // one for the top block if let Some(sub_area) = block.contains.0 { let (sub_w, mut sub_h) = resolve_sub_areas(sub_area); sub_h += prev_h; prev_h += sub_h; if min_w < (x + sub_w + 1) { min_w = x + sub_w + 1; } if min_h < (y + sub_h + 1) { min_h = y + sub_h + 1; } } if let Some(sub_area) = block.contains.1 { let (sub_w, mut sub_h) = resolve_sub_areas(sub_area); sub_h += prev_h; if min_w < (x + sub_w + 1) { min_w = x + sub_w + 1; } if min_h < (y + sub_h + 1) { min_h = y + sub_h + 1; } } } if self.auto_shrink { (min_w, min_h) } else { ( if self.size.0 < min_w { min_w } else { self.size.0 }, if self.size.1 < min_h { min_h } else { self.size.1 }, ) } } fn update_origin_map(&mut self) { self.origin_map.clear(); for ((ox, oy), block) in &self.blocks { for r in 0..block.rows { self.origin_map.insert((*ox, *oy + (r as i64)), (*ox, *oy)); } } } fn check_space_at(&self, x: i64, y: i64, rows: usize) -> bool { for i in 0..rows { let yo = y + (i as i64); if self.origin_map.get(&(x, yo)).is_some() { return false; } } true } /// Serializes this [BlockArea] to a JSON [Value]. /// Usually called by [BlockFunSnapshot::serialize]. pub fn serialize(&self) -> Value { let mut v = json!({ "size": [self.size.0 as i64, self.size.1 as i64], "header": self.header, "auto_shrink": self.auto_shrink, }); let mut blks = json!([]); if let Value::Array(blks) = &mut blks { for ((x, y), b) in self.blocks.iter() { blks.push(json!({ "x": x, "y": y, "block": b.serialize(), })); } } v["blocks"] = blks; v } /// Deserializes a from a JSON [Value]. /// Usually called by [BlockFunSnapshot::deserialize]. pub fn deserialize(v: &Value) -> Result, serde_json::Error> { let mut blocks = HashMap::new(); let blks = &v["blocks"]; if let Value::Array(blks) = blks { for b in blks.iter() { let x = b["x"].as_i64().unwrap_or(0); let y = b["y"].as_i64().unwrap_or(0); blocks.insert((x, y), Block::deserialize(&b["block"])?); } } let size = ( v["size"][0].as_i64().unwrap_or(0) as usize, v["size"][1].as_i64().unwrap_or(0) as usize, ); let auto_shrink = v["auto_shrink"].as_bool().unwrap_or(true); let header = v["header"].as_str().unwrap_or("").to_string(); let mut ba = Box::new(BlockArea { blocks, origin_map: HashMap::new(), size, auto_shrink, header }); ba.update_origin_map(); Ok(ba) } } #[derive(Debug, Clone, Copy, PartialEq)] pub enum BlockUserInput { None, Float, Integer, Identifier, ClientDecision, } impl Default for BlockUserInput { fn default() -> Self { Self::None } } impl BlockUserInput { pub fn needs_input(&self) -> bool { *self != BlockUserInput::None } } #[derive(Debug, Clone, Default)] pub struct BlockType { pub category: String, pub name: String, pub rows: usize, pub inputs: Vec>, pub outputs: Vec>, pub area_count: usize, pub user_input: BlockUserInput, pub description: String, pub color: usize, } impl BlockType { fn touch_contains(&self, block: &mut Block) { block.contains = match self.area_count { 0 => (None, None), 1 => (Some(1), None), 2 => (Some(1), Some(1)), _ => (None, None), }; } pub fn instanciate_block( &self, user_input: Option, id_gen: BlockIDGenerator, ) -> Box { let mut block = Box::new(Block { id: id_gen.next(), rows: self.rows, contains: (None, None), expanded: true, typ: self.name.clone(), lbl: if let Some(inp) = user_input { inp } else { self.name.clone() }, inputs: self.inputs.clone(), outputs: self.outputs.clone(), color: self.color, }); self.touch_contains(&mut *block); block } } #[derive(Debug, Clone)] pub struct BlockLanguage { types: HashMap, identifiers: HashMap, } impl BlockLanguage { pub fn new() -> Self { Self { types: HashMap::new(), identifiers: HashMap::new() } } pub fn define_identifier(&mut self, id: &str) { let v = id.to_string(); self.identifiers.insert(id.to_string(), v); } pub fn define(&mut self, typ: BlockType) { self.types.insert(typ.name.clone(), typ); } pub fn is_identifier(&self, id: &str) -> bool { self.identifiers.get(id).is_some() } pub fn list_identifiers(&self) -> Vec { let mut identifiers: Vec = self.identifiers.keys().cloned().collect(); identifiers.sort(); identifiers } pub fn get_type_outputs(&self, typ: &str) -> Option<&[Option]> { let typ = self.types.get(typ)?; Some(&typ.outputs) } pub fn get_type_inputs(&self, typ: &str) -> Option<&[Option]> { let typ = self.types.get(typ)?; Some(&typ.inputs) } pub fn get_output_name_at_index(&self, typ: &str, idx: usize) -> Option { if let Some(outs) = self.get_type_outputs(typ) { let mut i = 0; for o in outs.iter() { if let Some(outname) = o { if i == idx { return Some(outname.to_string()); } i += 1; } } } None } pub fn type_output_count(&self, typ: &str) -> usize { let mut cnt = 0; if let Some(outs) = self.get_type_outputs(typ) { for o in outs.iter() { if o.is_some() { cnt += 1; } } } cnt } pub fn get_type_list(&self) -> Vec<(String, String, BlockUserInput)> { let mut out = vec![]; for (_, typ) in &self.types { out.push((typ.category.clone(), typ.name.clone(), typ.user_input)); } out } } pub trait BlockASTNode: std::fmt::Debug + Clone { fn from(id: usize, typ: &str, lbl: &str) -> Self; fn add_node(&self, in_port: String, out_port: String, node: Self); fn add_structural_node(&self, node: Self) { self.add_node("".to_string(), "".to_string(), node); } } #[derive(Debug, Clone, PartialEq)] pub enum BlockDSPError { UnknownArea(usize), UnknownLanguageType(String), NoBlockAt(usize, i64, i64), CircularAction(usize, usize), NoSpaceAvailable(usize, i64, i64, usize), } #[derive(Debug, Clone)] pub struct BlockFunSnapshot { areas: Vec>, cur_id: usize, } impl BlockFunSnapshot { pub fn serialize(&self) -> Value { let mut v = json!({ "VERSION": 1, }); v["current_block_id_counter"] = self.cur_id.into(); let mut areas = json!([]); if let Value::Array(areas) = &mut areas { for area in self.areas.iter() { areas.push(area.serialize()); } } v["areas"] = areas; v } pub fn deserialize(v: &Value) -> Result { let mut a = vec![]; let areas = &v["areas"]; if let Value::Array(areas) = areas { for v in areas.iter() { a.push(BlockArea::deserialize(v)?); } } Ok(BlockFunSnapshot { areas: a, cur_id: v["current_block_id_counter"].as_i64().unwrap_or(0) as usize, }) } } #[derive(Debug, Clone)] pub struct BlockFun { language: Rc>, areas: Vec>, size_work_dq: VecDeque, area_work_dq: VecDeque, id_gen: BlockIDGenerator, generation: u64, } #[derive(Debug)] enum GenTreeJob { Node { node: N, out: N }, Output { area_id: usize, x: i64, y: i64, in_port: String, out: N }, Sink { area_id: usize, x: i64, y: i64, out: N }, Area { area_id: usize, out: N }, } impl BlockFun { pub fn new(lang: Rc>) -> Self { Self { language: lang, areas: vec![Box::new(BlockArea::new(16, 16))], size_work_dq: VecDeque::new(), area_work_dq: VecDeque::new(), id_gen: BlockIDGenerator::new(), generation: 0, } } pub fn is_unset(&self) -> bool { self.generation == 0 } pub fn block_language(&self) -> Rc> { self.language.clone() } pub fn block_ref(&self, id: usize, x: i64, y: i64) -> Option<&Block> { let area = self.areas.get(id)?; area.ref_at(x, y) } pub fn block_ref_mut(&mut self, id: usize, x: i64, y: i64) -> Option<&mut Block> { let area = self.areas.get_mut(id)?; area.ref_mut_at(x, y) } pub fn shift_port(&mut self, id: usize, x: i64, y: i64, row: usize, output: bool) { if let Some(block) = self.block_ref_mut(id, x, y) { block.shift_port(row, output); self.generation += 1; } } pub fn save_snapshot(&self) -> BlockFunSnapshot { BlockFunSnapshot { areas: self.areas.iter().cloned().collect(), cur_id: self.id_gen.current(), } } pub fn load_snapshot(&mut self, repr: &BlockFunSnapshot) { self.areas = repr.areas.iter().cloned().collect(); self.id_gen = BlockIDGenerator::new_with_id(repr.cur_id); self.recalculate_area_sizes(); self.generation += 1; } pub fn generate_tree(&self, null_typ: &str) -> Result { // This is a type for filling in unfilled outputs: let lang = self.language.borrow(); let null_typ = lang .types .get(null_typ) .ok_or(BlockDSPError::UnknownLanguageType(null_typ.to_string()))? .name .to_string(); // Next we build the root AST node set: let mut tree_builder: Vec> = vec![]; let main_node = Node::from(0, "", ""); tree_builder.push(GenTreeJob::::Area { area_id: 0, out: main_node.clone() }); // A HashMap to store those blocks, that have multiple outputs. // Their AST nodes need to be shared to multiple parent nodes. let mut multi_outs: HashMap<(usize, i64, i64), Node> = HashMap::new(); // We do a depth first search here: while let Some(job) = tree_builder.pop() { match job { GenTreeJob::::Area { area_id, out } => { let area = self.areas.get(area_id).ok_or(BlockDSPError::UnknownArea(area_id))?; let sinks = area.collect_sinks(); let area_node = Node::from(0, "", ""); out.add_structural_node(area_node.clone()); for (x, y, uncon_out_row) in sinks { if let Some(_row) = uncon_out_row { let result_node = Node::from(0, "", ""); tree_builder.push(GenTreeJob::::Output { area_id, x, y, in_port: "".to_string(), out: result_node.clone(), }); tree_builder.push(GenTreeJob::::Node { node: result_node, out: area_node.clone(), }); } else { tree_builder.push(GenTreeJob::::Sink { area_id, x, y, out: area_node.clone(), }); } } } GenTreeJob::::Node { node, out } => { out.add_structural_node(node); } GenTreeJob::::Sink { area_id, x, y, out } => { let area = self.areas.get(area_id).ok_or(BlockDSPError::UnknownArea(area_id))?; if let Some((block, xo, yo)) = area.ref_at_origin(x, y) { let (node, needs_init) = if let Some(node) = multi_outs.get(&(area_id, xo, yo)) { (node.clone(), false) } else { (Node::from(block.id, &block.typ, &block.lbl), true) }; out.add_structural_node(node.clone()); if needs_init { multi_outs.insert((area_id, xo, yo), node.clone()); if let Some(cont_area_id) = block.contains.1 { tree_builder.push(GenTreeJob::::Area { area_id: cont_area_id, out: node.clone(), }); } if let Some(cont_area_id) = block.contains.0 { tree_builder.push(GenTreeJob::::Area { area_id: cont_area_id, out: node.clone(), }); } block.for_input_ports_reverse(|row, port_name| { tree_builder.push(GenTreeJob::::Output { area_id, x: xo - 1, y: yo + (row as i64), in_port: port_name.to_string(), out: node.clone(), }); }); } } } GenTreeJob::::Output { area_id, x, y, in_port, out } => { let area = self.areas.get(area_id).ok_or(BlockDSPError::UnknownArea(area_id))?; if let Some((block, xo, yo)) = area.ref_at_origin(x, y) { let row = y - yo; let (node, needs_init) = if let Some(node) = multi_outs.get(&(area_id, xo, yo)) { (node.clone(), false) } else { (Node::from(block.id, &block.typ, &block.lbl), true) }; if let Some(out_name) = block.outputs.get(row as usize).cloned().flatten() { out.add_node(in_port, out_name, node.clone()); } else { let node = Node::from(0, &null_typ, ""); out.add_node(in_port, "".to_string(), node.clone()); } if needs_init { multi_outs.insert((area_id, xo, yo), node.clone()); if let Some(cont_area_id) = block.contains.1 { tree_builder.push(GenTreeJob::::Area { area_id: cont_area_id, out: node.clone(), }); } if let Some(cont_area_id) = block.contains.0 { tree_builder.push(GenTreeJob::::Area { area_id: cont_area_id, out: node.clone(), }); } block.for_input_ports_reverse(|row, port_name| { tree_builder.push(GenTreeJob::::Output { area_id, x: xo - 1, y: yo + (row as i64), in_port: port_name.to_string(), out: node.clone(), }); }); } } else { let node = Node::from(0, &null_typ, ""); out.add_node(in_port, "".to_string(), node.clone()); } } } } Ok(main_node) } pub fn recalculate_area_sizes(&mut self) { let mut parents = vec![0; self.areas.len()]; let mut sizes = vec![(0, 0); self.areas.len()]; // First we dive downwards, to record all the parents // and get the sizes of the (leafs). self.area_work_dq.clear(); self.size_work_dq.clear(); let parents_work_list = &mut self.area_work_dq; let size_work_list = &mut self.size_work_dq; // Push the root area: parents_work_list.push_back(0); let mut cur_sub = vec![]; while let Some(area_idx) = parents_work_list.pop_back() { cur_sub.clear(); self.areas[area_idx].get_direct_sub_areas(&mut cur_sub); // XXX: The resolver gets (0, 0), thats wrong for the // areas with sub areas. But it resolves the leaf area // sizes already correctly! let (w, h) = self.areas[area_idx].resolve_size(|_id| (0, 0)); sizes[area_idx] = (w, h); if cur_sub.len() == 0 { size_work_list.push_front(area_idx); } else { for sub_idx in &cur_sub { // XXX: Record the parent: parents[*sub_idx] = area_idx; parents_work_list.push_back(*sub_idx); } } } // XXX: Invariant now is: // - `parents` contains all the parent area IDs. // - `size_work_list` contains all the leaf area IDs. // - `sizes` contains correct sizes for the leafs // (but wrong for the non leafs). // Next we need to work through the size_work_list upwards. // That means, for each leaf in front of the Deque, // we push the parent to the back. while let Some(area_idx) = size_work_list.pop_front() { // XXX: The invariant as we walk upwards is, that once we // encounter a parent area ID in the size_work_list, // we know that all sub areas already have been computed. let (w, h) = self.areas[area_idx].resolve_size(|id| sizes[id]); sizes[area_idx] = (w, h); self.areas[area_idx].set_size(w, h); // XXX: area_idx == 0 is the root area, so skip that // when pushing further parents! if area_idx > 0 { size_work_list.push_back(parents[area_idx]); } } } pub fn area_is_subarea_of(&mut self, area_id: usize, a_id: usize, x: i64, y: i64) -> bool { let mut areas = vec![]; let block = if let Some(block) = self.block_ref(a_id, x, y) { block.clone() } else { return false; }; self.all_sub_areas_of(&block, &mut areas); for a_id in &areas { if area_id == *a_id { return true; } } return false; } pub fn all_sub_areas_of(&mut self, block: &Block, areas: &mut Vec) { let contains = block.contains.clone(); let area_work_list = &mut self.area_work_dq; area_work_list.clear(); if let Some(area_id) = contains.0 { area_work_list.push_back(area_id); } if let Some(area_id) = contains.1 { area_work_list.push_back(area_id); } if area_work_list.len() <= 0 { return; } let mut cur_sub = vec![]; while let Some(area_idx) = area_work_list.pop_front() { areas.push(area_idx); cur_sub.clear(); self.areas[area_idx].get_direct_sub_areas(&mut cur_sub); for sub_idx in &cur_sub { area_work_list.push_back(*sub_idx); } } } pub fn retrieve_block_chain_at( &mut self, id: usize, x: i64, y: i64, remove_blocks: bool, ) -> Option> { let area = self.areas.get_mut(id)?; let mut chain = area.chain_at(x, y)?; if remove_blocks { chain.remove_load(area); } else { chain.clone_load(area, self.id_gen.clone()); } Some(chain) } pub fn clone_block_from_to( &mut self, id: usize, x: i64, y: i64, id2: usize, x2: i64, mut y2: i64, ) -> Result<(), BlockDSPError> { let lang = self.language.clone(); let (mut block, _xo, yo) = if let Some(area) = self.areas.get_mut(id) { let (block, xo, yo) = area.ref_mut_at_origin(x, y).ok_or(BlockDSPError::NoBlockAt(id, x, y))?; let mut new_block = Box::new(block.clone_with_new_id(self.id_gen.next())); if let Some(typ) = lang.borrow().types.get(&new_block.typ) { typ.touch_contains(new_block.as_mut()); } (new_block, xo, yo) } else { return Err(BlockDSPError::UnknownArea(id)); }; self.create_areas_for_block(block.as_mut()); // check if the user grabbed at a different row than the top row: if y > yo { // if so, adjust the destination: let offs = y - yo; y2 = (y2 - offs).max(0); } let area2 = self.areas.get_mut(id2).ok_or(BlockDSPError::UnknownArea(id2))?; let rows = block.rows; if area2.check_space_at(x2, y2, block.rows) { area2.set_block_at(x2, y2, block); self.generation += 1; Ok(()) } else { Err(BlockDSPError::NoSpaceAvailable(id2, x2, y2, rows)) } } pub fn split_block_chain_after( &mut self, id: usize, x: i64, y: i64, filler_type: Option<&str>, ) -> Result<(), BlockDSPError> { let mut area_clone = self.areas.get(id).ok_or(BlockDSPError::UnknownArea(id))?.clone(); let mut chain = area_clone.chain_at(x, y).ok_or(BlockDSPError::NoBlockAt(id, x, y))?; chain.remove_load(area_clone.as_mut()); let lang = self.language.borrow(); let typ: Option<&BlockType> = if let Some(filler_type) = filler_type { Some( lang.types .get(filler_type) .ok_or(BlockDSPError::UnknownLanguageType(filler_type.to_string()))?, ) } else { None }; chain.split_load_after_x(x, y, typ, self.id_gen.clone()); if !chain.area_has_space_for_load(&mut area_clone, 0, 0) { return Err(BlockDSPError::NoSpaceAvailable(id, x, y, 0)); } chain.place_load(&mut area_clone); self.generation += 1; self.areas[id] = area_clone; Ok(()) } pub fn move_block_chain_from_to( &mut self, id: usize, x: i64, y: i64, id2: usize, x2: i64, y2: i64, ) -> Result<(), BlockDSPError> { let mut area_clone = self.areas.get(id).ok_or(BlockDSPError::UnknownArea(id))?.clone(); let mut chain = area_clone.chain_at(x, y).ok_or(BlockDSPError::NoBlockAt(id, x, y))?; chain.remove_load(area_clone.as_mut()); self.generation += 1; if id2 == id { let move_x_offs = x2 - x; let move_y_offs = y2 - y; chain.move_by_offs(move_x_offs, move_y_offs); if !chain.try_fit_load_into_space(&mut area_clone) { return Err(BlockDSPError::NoSpaceAvailable(id, x2, y2, 0)); } chain.place_load(&mut area_clone); self.areas[id] = area_clone; } else { // id2 != id if chain.area_is_subarea_of_loaded(id2, self) { return Err(BlockDSPError::CircularAction(id, id2)); } let (xo, yo) = chain.normalize_load_pos(); let (grab_x_offs, grab_y_offs) = (xo - x, yo - y); // println!("xo={}, yo={}, grab_x={}, grab_y={}, x2={}, y2={}", // xo, yo, grab_x_offs, grab_y_offs, x2, y2); // XXX: .max(0) prevents us from moving the // chain outside the subarea accendentally! chain.move_by_offs((grab_x_offs + x2).max(0), (grab_y_offs + y2).max(0)); let mut area2_clone = self.areas.get(id2).ok_or(BlockDSPError::UnknownArea(id))?.clone(); if !chain.try_fit_load_into_space(&mut area2_clone) { return Err(BlockDSPError::NoSpaceAvailable(id, x2, y2, 1)); } chain.place_load(&mut area2_clone); self.areas[id] = area_clone; self.areas[id2] = area2_clone; } self.generation += 1; // let mut chain = // self.retrieve_block_chain_at(id, x, y, true) // .ok_or(BlockDSPError::NoBlockAt(id, x, y))?; // // chain.normalize_load_pos(); Ok(()) } pub fn move_block_from_to( &mut self, id: usize, x: i64, y: i64, id2: usize, x2: i64, mut y2: i64, ) -> Result<(), BlockDSPError> { if self.area_is_subarea_of(id2, id, x, y) { return Err(BlockDSPError::CircularAction(id, id2)); } let (block, xo, yo) = if let Some(area) = self.areas.get_mut(id) { area.remove_at(x, y).ok_or(BlockDSPError::NoBlockAt(id, x, y))? } else { return Err(BlockDSPError::UnknownArea(id)); }; // check if the user grabbed at a different row than the top row: if y > yo { // if so, adjust the destination: let offs = y - yo; y2 = (y2 - offs).max(0); } let area2 = self.areas.get_mut(id2).ok_or(BlockDSPError::UnknownArea(id2))?; let rows = block.rows; self.generation += 1; if area2.check_space_at(x2, y2, block.rows) { area2.set_block_at(x2, y2, block); Ok(()) } else { if let Some(area) = self.areas.get_mut(id) { area.set_block_at(xo, yo, block); } Err(BlockDSPError::NoSpaceAvailable(id2, x2, y2, rows)) } } fn create_areas_for_block(&mut self, block: &mut Block) { if let Some(area_id) = &mut block.contains.0 { let mut area = Box::new(BlockArea::new(1, 1)); area.set_auto_shrink(true); self.areas.push(area); *area_id = self.areas.len() - 1; } if let Some(area_id) = &mut block.contains.1 { let mut area = Box::new(BlockArea::new(1, 1)); area.set_auto_shrink(true); self.areas.push(area); *area_id = self.areas.len() - 1; } } pub fn instanciate_at( &mut self, id: usize, x: i64, y: i64, typ: &str, user_input: Option, ) -> Result<(), BlockDSPError> { let mut block = { let lang = self.language.borrow(); if let Some(area) = self.areas.get_mut(id) { if let Some(typ) = lang.types.get(typ) { if !area.check_space_at(x, y, typ.rows) { return Err(BlockDSPError::NoSpaceAvailable(id, x, y, typ.rows)); } } } else { return Err(BlockDSPError::UnknownArea(id)); } let typ = lang.types.get(typ).ok_or(BlockDSPError::UnknownLanguageType(typ.to_string()))?; typ.instanciate_block(user_input, self.id_gen.clone()) }; self.create_areas_for_block(block.as_mut()); self.generation += 1; if let Some(area) = self.areas.get_mut(id) { area.set_block_at(x, y, block); } Ok(()) } pub fn remove_at(&mut self, id: usize, x: i64, y: i64) -> Result<(), BlockDSPError> { let area = self.areas.get_mut(id).ok_or(BlockDSPError::UnknownArea(id))?; area.remove_at(x, y).ok_or(BlockDSPError::NoBlockAt(id, x, y))?; self.generation += 1; Ok(()) } pub fn area_size(&self, id: usize) -> (usize, usize) { self.areas.get(id).map(|a| a.size).unwrap_or((0, 0)) } pub fn block_at(&self, id: usize, x: i64, y: i64) -> Option<&dyn BlockView> { let area = self.areas.get(id)?; Some(area.blocks.get(&(x, y))?.as_ref()) } pub fn origin_at(&self, id: usize, x: i64, y: i64) -> Option<(i64, i64)> { self.areas.get(id).map(|a| a.origin_map.get(&(x, y)).copied()).flatten() } } impl BlockCodeView for BlockFun { fn area_header(&self, id: usize) -> Option<&str> { self.areas.get(id).map(|a| &a.header[..]) } fn area_size(&self, id: usize) -> (usize, usize) { self.area_size(id) } fn block_at(&self, id: usize, x: i64, y: i64) -> Option<&dyn BlockView> { self.block_at(id, x, y) } fn origin_at(&self, id: usize, x: i64, y: i64) -> Option<(i64, i64)> { self.origin_at(id, x, y) } fn generation(&self) -> u64 { self.generation } } #[cfg(feature = "synfx-dsp-jit")] #[cfg(test)] mod test { use super::*; #[test] fn check_blockfun_serialize_empty() { let dsp_lib = synfx_dsp_jit::get_standard_library(); let lang = crate::blocklang_def::setup_hxdsp_block_language(dsp_lib); let mut bf = BlockFun::new(lang.clone()); let sn = bf.save_snapshot(); let serialized = sn.serialize().to_string(); assert_eq!(serialized, "{\"VERSION\":1,\"areas\":[{\"auto_shrink\":false,\"blocks\":[],\"header\":\"\",\"size\":[16,16]}],\"current_block_id_counter\":0}"); let v: Value = serde_json::from_str(&serialized).unwrap(); let sn = BlockFunSnapshot::deserialize(&v).expect("No deserialization error"); let mut bf2 = BlockFun::new(lang); let bf2 = bf2.load_snapshot(&sn); } #[test] fn check_blockfun_serialize_1() { let dsp_lib = synfx_dsp_jit::get_standard_library(); let lang = crate::blocklang_def::setup_hxdsp_block_language(dsp_lib); let mut bf = BlockFun::new(lang.clone()); bf.instanciate_at(0, 0, 0, "+", None); let sn = bf.save_snapshot(); let serialized = sn.serialize().to_string(); assert_eq!(serialized, "{\"VERSION\":1,\"areas\":[{\"auto_shrink\":false,\"blocks\":[{\"block\":{\"color\":4,\"contains\":[null,null],\"expanded\":true,\"id\":1,\"inputs\":[\"\",\"\"],\"lbl\":\"+\",\"outputs\":[\"\"],\"rows\":2,\"typ\":\"+\"},\"x\":0,\"y\":0}],\"header\":\"\",\"size\":[16,16]}],\"current_block_id_counter\":1}"); let v: Value = serde_json::from_str(&serialized).unwrap(); let sn = BlockFunSnapshot::deserialize(&v).expect("No deserialization error"); let mut bf2 = BlockFun::new(lang); bf2.load_snapshot(&sn); let bv = bf2.block_at(0, 0, 0).unwrap(); assert!(bv.has_input(0)); } }