diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/chips2.js | 327 |
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 + " "; + ret.id = port.id + ret.innerHTML = port.name + " "; 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; |