/* --------------------------- ---------------------------- */ /* ------------------------ HUNKITUP ------------------------- */ /* --------------------------- ---------------------------- */ import { TSET, findPhy } from '../typeset.js' function Hunkify(hunk) { // scripting languages should name hunks by their script location and filename // compiled languages will assign a string // because this is always added by .addHunk(path) (path == name) // we set this during load, only once, this avoids some confusion hunk.type = null hunk.name = null // we keep a copy of our position-in-the-array ... of our parent ... our pip hunk.ind = null hunk.log = function(msg) { let str = `LG from ${hunk.name} at ${hunk.ind}: ` for (let i = 0; i < arguments.length; i++) { str += arguments[i] if (i < arguments.length - 1) str += ', ' } console.log(str) } // input, output, and state: ay balls, here we go hunk.inputs = new Array() hunk.outputs = new Array() hunk.states = new Array() hunk.input = (type, name) => { let ip = new Input(type, name, hunk) hunk.inputs.push(ip) return ip } hunk.output = (type, name) => { let op = new Output(type, name, hunk) hunk.outputs.push(op) return op } hunk.state = (type, name, dfault) => { let st = new State(type, name, dfault) hunk.states.push(st) return st } // top-secret backdoor hunk.mgr = {} } /* --------------------------- ---------------------------- */ /* ------------------------- OUTPUT -------------------------- */ /* --------------------------- ---------------------------- */ function Output(type, name, parent, linktype) { // naming, etc this.name = name this.type = type this.phy = findPhy(this.type) // checks, throws error if type doesn't exist this.parent = parent // store, this.ref = null this.data = null // hookup this.connections = new Array() // check in realtime, manager does less this.io = () => { for (var i = 0; i < this.connections.length; i++) { // connection arrays contain 'wires' ... these are stateful if (this.connections[i].io) return true } return false } // when we put, we use this to set states this.setIo = () => { for (var i = 0; i < this.connections.length; i++) { this.connections[i].io = true } } /* --------------------------- ---------------------------- */ /* ------------ OUTPUT PUT AND TRANSPORT (TYPED) ------------- */ /* --------------------------- ---------------------------- */ // yonder put call, transport call // copy-in (chances are that after .put call the referenced object changes, before it is copied out) // and copy-out (same on the other side, we are potentially passing to many downstream, need a different copy for each) // typing ... odd ? this.put = (data) => { if (!this.io()) { this.ref = data this.data = this.phy.copy[this.type](data) this.setIo() return true } else { console.error(`WARNING: put called on occupied output: output ${this.name} in ${this.parent.name}`) return false } } /* --------------------------- ---------------------------- */ /* ---------------- OUTPUT MANAGE CONNECTIONS ---------------- */ /* --------------------------- ---------------------------- */ this.attach = (input) => { // we can only do this if a fn exists for us to copy from-us-to-them if (this.phy.copy[input.type]) { // the wire is the stateful part, let wire = { op: this, ip: input, io: false } input.connections.push(wire) this.connections.push(wire) return true } else { console.error(`ERROR: missing type conversion from: ${this.type} to ${input.type}`) return false } } this.remove = (input) => { // find by names pls let ind = this.connections.findIndex((cand) => { return (cand.ip === input) }) if (ind !== -1) { input.disconnect(this) this.connections.splice(ind, 1) return true } else { console.log('ERROR: wire removal, was looking for', input, 'in', this.connections) return false } } this.findOwnIndex = () => { // find self in parent's inputs let index = this.parent.outputs.findIndex((cand) => { return (cand.name === this.name && cand.type === this.type) }) if (index === -1) { console.log(`output could not find itself in parent: ${index}`, this, 'unstrung', this.parent.outputs, 'strung', JSON.parse(JSON.stringify(this.parent.outputs))) throw new Error(`output could not find itself in parent: ${index}`) } return index } this.disconnectAll = () => { for (let cn of this.connections) { // here we're disconnecting the input, meaning the input is removing this from its list of connections cn.ip.disconnect(this) } // this is actually a genuine way to delete an array in js, http://shrugguy.com this.connections.length = 0 } /* --------------------------- ---------------------------- */ /* ----------- OUTPUT TRICKS FOR LINK CONNECTIONS ------------ */ /* --------------------------- ---------------------------- */ if (linktype) { // has stash ? this.needsAck = false; } } // FIN output /* --------------------------- ---------------------------- */ /* -------------------------- INPUT -------------------------- */ /* --------------------------- ---------------------------- */ function Input(type, name, parent, linktype) { // naming, etc this.name = name this.type = type this.parent = parent this.phy = findPhy(this.type) // we keep access to the outputs we're hooked up to here this.connections = new Array() this.io = () => { // if any of our connections have data that we haven't read once yet, for (var i = 0; i < this.connections.length; i++) { // have to update calls to .io -> .io() if (this.connections[i].io) return true } return false } // get calls are now a bit messier, // we have multiple wires, this.lastGet = 0 // place we read from last (have multiple wires case) this.get = () => { // we want to (roughly) round robin these, so we pick up from where we last pulled, var i = this.lastGet // find the next occupied conn: do iterations max. of total length of connections, for (var u = 0; u < this.connections.length; u++) { // increment, wrap i++ if (i >= this.connections.length) i = 0 // do work, if (this.connections[i].io) { let wire = this.connections[i] // and set that wire to read / empty wire.io = false // and we'd like to remember from whence we last took data this.lastGet = i // get this data out ... we have to copy it out again, otherwise // downstream operators may write to objects that have handles elsewhere // here we call the function from typeset.js that is custom between the-output -> and the-input return wire.op.phy.copy[this.type](wire.op.data) } } // if we pass this for w/o hitting return, console.error(`WARNING: get called on occupied input: ${this.name} in ${this.parent.name}`) return null } this.findOwnIndex = () => { // find self in parent's inputs let index = this.parent.inputs.findIndex((cand) => { return (cand.name === this.name && cand.type === this.type) }) if (index === -1) throw new Error('input could not find itself in parent') return index } this.disconnect = (output) => { let index = this.connections.findIndex((cand) => { return (cand.op = output) }) if (index === -1) throw new Error('during output disconnect, input cannot find output...') this.connections.splice(index, 1) // find the reference in our array, } this.disconnectAll = () => { for (let cn of this.connections) { cn.op.remove(this) } } // inputs / outputs for 'link' (a hunk) ports have more state, if (linktype) { // assume clear upstream on startup, maybe dangerous? this.icus = true } } // working the onChange / change function (onChange feels more better?) // if we do .on('change', (val) => { //}) // we can potentially add ... .on('startup') or something, to set hard defaults ? // and then: write out copy fn's per item ... function State(type, name, startup) { this.name = name this.type = type this.phy = findPhy(this.type) // types must have *some* code -> by finding one here, we check that it exists // TODO pls add check for missing startup value ? this.value = startup // this is still something of a hack // during load, the manager gets in here and dishes a function 2 us this.hookup = (newval) => { console.error('ERROR: when state-change request made, no hookup function yet - probable manager error while loading hunk') } // from within a hunk, we typically call this // to update the value and ship it out to any views this.set = (value) => { // for good measure, we copy-in state as well. who knows. this.value = this.phy.copy[this.type](value) this.hookup(this.value, this.msgid) this.msgid = null } this.msgid = null // the manager calls this fn, when a request is made. // this.tryChange = (value, msgid) => { // coupla steps here, we track and then call internal fn, this.msgid = msgid // not sure, but we could clean these here by doing this.phy.copy[this.type](value) if (this.onChange) { this.onChange(value) } else { this.set(value) } } } export { Hunkify, Input, Output, State }