aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/chips2.js327
1 files changed, 288 insertions, 39 deletions
diff --git a/lib/chips2.js b/lib/chips2.js
index 2c4c786..dae4f53 100644
--- a/lib/chips2.js
+++ b/lib/chips2.js
@@ -1,5 +1,4 @@
//Gods-awful type-checking logic.
-
//TypeName: string, Params: Array<string>
function Type(TypeName,Params=[]) {
if (!Type.all) Type.all = {};
@@ -177,6 +176,21 @@ Type.prototype.getClasses = function() {
}
return Array.from(ret);
}
+Type.prototype.getParams = function(set=new Set()) {
+ switch(this.mode) {
+ case 'param':
+ set.add(this.typename);
+ this.template.getParams(set);
+ break;
+ case 'templated':
+ case 'union':
+ case 'tuple':
+ this.template.map(t => t.getParams(set));
+ break;
+ }
+ return set;
+}
+
//just makes my life that much easier when writing code. Type.any is nicer than new Type('any')
Type.typeDef = (function(typename, name, params=[]) {
@@ -258,60 +272,286 @@ function intersect(type1, type2) {
}
return null;
}
-function intersectParams(type1, type2) {
- const sect = intersect(type1, type2);
- if (!(sect ?? false)) return null;
-
- function walkDown(t, tres) {
- const ret = {};
- if (t.mode == "param") {
- ret[t.typename] = tres;
- }
- else if (
- t.mode == tres.mode &&
- t.typename == tres.typename &&
- t.template?.length == tres.template?.length
- ) {
- for (const p in t.template)
- ret = {...ret, ...walkDown(t.template[p], tres.template[p])};
+function walkDownParams(t, tres) {
+ let ret = {};
+ if (t.mode == "param") {
+ ret = {[t.typename]: tres};
+ }
+ else if (
+ t.mode == tres.mode &&
+ t.typename == tres.typename &&
+ t.template?.length == tres.template?.length
+ ) {
+ for (const p in t.template)
+ ret = {...ret, ...walkDownParams(t.template[p], tres.template[p])};
+ }
+ return ret;
+}
+
+function Port(name, type, parent, input) {
+ //basics
+ this.parent = parent;
+ this.name = name;
+ //what have we here
+ this.base = (type == "exec") ? "exec" : parent.passed.reduce((t, k) => t.resolve(k, parent.params[k]), new Type(type, parent.passed));
+ this.resolve()
+ //what does it do
+ this.conn = input ? null : new Set();
+ this.side = input ? "in" : "out";
+ //enter your mommy's SSN
+ this.id = crypto.randomUUID();
+ Port.ports.set(this.id, this);
+ //on TV!
+ this._el = {};
+ Object.defineProperty(this, 'el', {get: () => this._el, set: val => {this._el.id = ''; this._el = val; val.id = this.id}})
+}
+Port.ports = new Map();
+Port.prototype.resolve = function () {
+ this.type =
+ (this.base == "exec") ?
+ "exec" :
+ this.parent.passed.reduce((t, k) => t.resolve(k, this.parent.params[k]), new Type(this.base, this.parent.passed));
+}
+Port.prototype.getIDStr = function () {
+ return `${this.parent.id}/${this.side}/${this.id}`;
+}
+Port.getByID = function(str) {
+ const [id, side, id2] = str.split('/');
+ if (
+ id !== undefined &&
+ side !== undefined &&
+ id2 !== undefined
+ ) {
+ const side = Chip.chips.get(id)[side+'s'];
+ return side.filter(p => p.id == id2)[0];
+ }
+}
+Port.prototype.remove = function(cb = false) {
+ if (this.side == "in")
+ unwireDataIn(this);
+ else
+ unwireDataOut(this);
+
+ if (cb) this.parent[this.side + 's'] = this.parent[this.side + 's'].filter(p => p != this);
+
+ Port.ports.delete(this.id);
+}
+
+
+function paramRecurse(ports, portCB, cancelCB=()=>false, seenchips=new Set()) {
+ for (const p of ports) {
+ if (cancelCB(p)) return;
+ const c = p.parent;
+ if (seenchips.has(c)) continue;
+ portCB(p);
+ seenchips.add(c);
+ const params = p.base.getParams();
+ if (!params.size) continue;
+ paramRecurse([...c.ins, ...c.outs].filter(fp => {
+ for (const par of fp.base.getParams())
+ if (params.has(par)) return true;
+ return false;
+ }).flatMap(p => (p.conn instanceof Set) ? [...p.conn] : p.conn).filter(p => p), portCB, cancelCB, seenchips);
+ }
+ return seenchips;
+}
+
+//TODO:
+//Please someone other than me go in and optimize this absolute shitshow.
+//There are better ways than this to write code but I am too tired to write them.
+//Of the things for future me, or some kind soul:
+// refactor this to only do one pass of the graph.
+// cache things like getParams() and toString() on first call.
+// do something clever to progressively update the types for everything. maybe make related wires share a reference to an object and mutate that.
+//Sorry in advance to all people on potato computers trying to run this slow piece of junk
+
+//future me here, tbh its not too bad and modern js zooms. my perf tests have put me at ease.
+//it ciuld be faster, but it should be good for normal use
+function regenTypes(ports, base=Type.any) {
+ if (ports instanceof Port) ports = [ports];
+ for (const port of ports)
+ if (!(port instanceof Port)) throw new TypeError("Can only regen Ports!");
+
+ //step 1: recursively search for any issues with typing.
+ // 1A: get all connected chips - Done, see paramRecurse
+ // 1B: only get chips that share a chain of params. - Done, see paramRecurse
+ // 1C: aggregate types as you go on. - Done
+ // 1D: fail if null - Done
+ const basechips = [...ports];
+ let finaltype = base;
+ const sc = paramRecurse(
+ basechips,
+ p => {finaltype = intersect(finaltype, p.base)},
+ () => finaltype==null
+ );
+ if (finaltype == null) return false;
+
+ for (const chip of sc)
+ chip.root.append(`seen! ${finaltype}`);
+
+ //step 2: recursively set all relevant type params, regen UI
+ paramRecurse(basechips, p => {
+ let type = finaltype.copy();
+ let sect = walkDownParams(p.base, type);
+ console.log(sect);
+ p.parent.setParams(sect);
+ p.parent.regenChip();
+ });
+
+ //step 3: cleanup, clear markers
+
+ return finaltype;
+
+}
+function wireData(port1, port2) {
+ //step 1: check things
+ // 1.0: both data?
+ if (!(port1.base instanceof Type && port2.base instanceof Type)) return false;
+
+ // 1.1: same side?
+ if (port1.side == port2.side) return false;
+
+ // 1.2: already connected?
+ const [input, output] = port1.side == "in" ? [port1, port2] : [port2, port1];
+ if (output.conn.has(input)) return true;
+
+ // 1.3: disconnect input
+ let oldconn;
+ if (input.conn != null) {
+ oldconn = input.conn;
+ input.conn = null;
+ oldconn.conn.delete(input)
+ }
+
+ //step 2: do the actual stuff
+ // 2A: they're identical
+ if (input.type.toString() == output.type.toString()) {
+ // 2A.1: make the connections
+ output.conn.add(input);
+ input.conn = output;
+ Chip.regenconnections();
+ return true;
+ }
+
+ // 2B: they're different, one+ has params
+ if (output.base.getParams().size + input.base.getParams().size) {
+ // 2B.1: make the connections temporarily
+ output.conn.add(input);
+ input.conn = output;
+ // 2B.2: try recursing down them
+ const type = regenTypes(input);
+ // 2B.3: rollback if necessarry
+ if(!type) {
+ unwireDataIn(input);
+ if (oldconn) wireData(input, oldconn);
}
- return ret;
+ Chip.regenconnections();
+ return true;
}
- const resolution1 = walkDown(type1, sect);
- const resolution2 = walkDown(type2, sect);
- return [resolution1, resolution2];
+
+ // 2C: they're different
+ // 2C.1: rollback everything
+ if (oldconn) wireData(input, oldconn);
+ return false;
+
+ //step 3: final bits
+ // 2.1: regen connections
+ Chip.regenconnections();
}
+function unwireDataIn(input, regen=true) {
+ const output = input.conn;
+ if (output == null) return null;
+ input.conn = null;
+ output.conn.delete(input);
+ if(regen) {
+ const regens = [input, output].filter(p => p.base.getParams().size);
+ regens.map(r => regenTypes(r));
+ }
+ Chip.regenconnections();
+}
+function unwireDataOut(output, regen=true) {
+ const inputs = [...output.conn];
+ if (inputs.length == 0) return [];
+ output.conn.clear();
+ for (const i of inputs)
+ i.conn = null;
+ if(regen) {
+ const regens = [...inputs, output].filter(p => p.base.getParams().size);
+ regens.map(r => regenTypes(r));
+ }
+ Chip.regenconnections();
+}
+
+function Chip(Entry, NodeDesc, el) {
+ this.id = crypto.randomUUID();
-function Chip(Entry) {
- this.types = [];
- if (Entry.ChipNameSource != "FirstNodeDesc") throw new TypeError("Chip is not FirstNodeDesc mode!");
+ this.root = el;
- const cur = Entry.NodeDescs[0];
- const params = cur.ReadonlyTypeParams;
+ const cur = NodeDesc;
+ const params = Object.fromEntries(Object.entries(cur.ReadonlyTypeParams).map(([n,t]) => [n, new Type(t)]));
const passed = Object.keys(params)
-
- this.root = newEl('div', 'chip');
+
+ this.chip = Entry;
+ this.nd = cur;
+ this.defparams = {...params};
+ this.params = {...params};
+ this.passed = Object.keys(params);
+
+ const portToType = (input, idx) => ((port) => new Port(port.Name, port.ReadonlyType, this, input, idx));
+ let i = 0;
+ this.ins = cur.Inputs.map(portToType(true, i++));
+ i = 0;
+ this.outs = cur.Outputs.map(portToType(false, i++));
+
+ this.regenChip();
+ //this.regenUI();
+ Chip.chips.set(this.id, this);
+}
+Chip.chips = new Map(); // list that chips register themselves into when constructed.
+Chip.connections = new Set();
+Chip.regenconnections = function() {
+ Chip.connections.clear();
+ for (const chip of Chip.chips.values()) {
+ for (const input of chip.ins)
+ if (input.type != "exec" && input.conn)
+ Chip.connections.add(`${input.conn.id} => ${input.id}`);
+ for (const output of chip.outs)
+ if (output.type == "exec" && output.conn)
+ Chip.connections.add(`${output.id} => ${output.conn.id}`);
+ }
+}
+
+Chip.prototype.setParams = function (params) {
+ for (const p of Object.keys(params).filter(p => this.passed.includes(p)))
+ this.params[p] = params[p];
+ for (const port of [...this.ins, ...this.outs])
+ port.resolve();
+}
+
+Chip.prototype.regenChip = function () {
+ this.root.querySelector(".chip")?.remove();
+ const chip = newEl('div', 'chip');
+ chip.id = this.id;
const header = newEl('div', 'chipheader');
- header.innerText = cur.Name;
+ header.innerText = this.nd.Name;
const input = newEl('div', 'input');
const output = newEl('div', 'output');
- this.root.append(header, input, output);
+ chip.append(header, input, output);
const genPort = (port) => {
let classes,name;
- if (port.ReadonlyType != "exec") {
- const type = Object.entries(params).reduce((t,[k,v])=>t.resolve(k,v),new Type(port.ReadonlyType, passed));
- Object.entries(params).forEach(t => type.resolve(t[0], t[1]));
- classes = type.getClasses();
- name = type.toString();
- this.types.push(type);
+ if (port.type != "exec") {
+ classes = port.type.getClasses();
+ name = port.type.toString();
} else {
classes = ["exec"];
name = "exec";
}
const ret = newEl('div', '');
- ret.innerHTML = port.Name + "&nbsp;";
+ ret.id = port.id
+ ret.innerHTML = port.name + "&nbsp;";
classes.forEach(p => ret.classList.add(p))
+ port.el = ret;
const tooltip = newEl('div', 'type');
tooltip.innerText = name;
@@ -319,11 +559,20 @@ function Chip(Entry) {
return [ret, tooltip];
}
- input.append(...cur.Inputs.map(genPort).flat());
- output.append(...cur.Outputs.map(genPort).flat());
+ input.append(...this.ins.flatMap(genPort));
+ output.append(...this.outs.flatMap(genPort));
+ this.root.prepend(chip);
+}
+Chip.prototype.remove = function() {
+ for (const p of [...this.ins, ...this.outs])
+ p.remove();
+ Chip.chips.delete(this.id);
+ this.root.remove();
+ //regenconnections
}
+
//chips.js reimplemented with the above
let root = document.documentElement;