/* hookup, */ // HEADER import { Hunkify, Input, Output, State } from './hunks.js' // END HEADER import { TSET, LK, MSGS, findPhy } from '../typeset.js' function Link() { Hunkify(this) let debug = false let dtin = new Input('byteArray', 'data', this) this.inputs.push(dtin) let dtout = new Output('byteArray', 'data', this) this.outputs.push(dtout) // needs 2 trak status let isActive = new State('boolean', 'isActive', false) let otherLink = new State('uint16', 'otherLink', 0) this.states.push(isActive, otherLink) // default messages -> manager, besides also data link let inputList = new State('string', 'inputList', "mgrMsgs (byteArray)") let outputList = new State('string', 'outputList', "mgrMsgs (byteArray)") this.states.push(inputList, outputList) /* --------------------------- ---------------------------- */ /* ------------------ OP ON KEYS FROM STATE ------------------ */ /* --------------------------- ---------------------------- */ let getTypeAndNameKeys = (str) => { let keys = str.split(',') // console.log('keys', keys) let ks = new Array() for (let i in keys) { let tk = keys[i].substring(keys[i].indexOf('(') + 1, keys[i].indexOf(')')) let nk = keys[i].substring(0, keys[i].indexOf(' (')) if (nk[0] == ' ') nk = nk.substring(1) if (tk.length < 2 || nk.length < 2) { this.log('bad key pair on inputs / outputs at link') } else { ks.push({ typeKey: tk, nameKey: nk }) } } return ks } let swapLists = (newList, input) => { // list, let nks = getTypeAndNameKeys(newList) // old list, let oks if (input) { oks = this.inputs } else { oks = this.outputs } // one by one, down the list we go for (let kp = 0; kp < nks.length; kp++) { // we'l walk the inputs array in step, // if the input we want already exists in oks, we'll place that let ioe = -1 // 'index of existing placement' for (let io in oks) { if (oks[io].name === nks[kp].nameKey && oks[io].type === nks[kp].typeKey) { ioe = io continue } } if (ioe >= 0) { if (input) { this.inputs[kp + 1] = oks[ioe] } else { this.outputs[kp + 1] = oks[ioe] } } else { // the object doesn't already exist, if (input) { // we can only make types we have serialization routines for: let phy = findPhy(nks[kp].typeKey) if(phy.key && phy.read){ this.inputs[kp + 1] = new Input(nks[kp].typeKey, nks[kp].nameKey, this, true) } } else { let phy = findPhy(nks[kp].typeKey) if(phy.key && phy.write){ this.outputs[kp + 1] = new Output(nks[kp].typeKey, nks[kp].nameKey, this, true) } } } } if (input) { while (this.inputs.length - 1 > nks.length) { this.inputs.pop() } } else { while (this.outputs.length - 1 > nks.length) { this.outputs.pop() } } } // now the changes, inputList.onChange = (value) => { // OK: back up to this ... swapLists(value, true) // we have to report this ... this.mgr.evaluateHunk(this) // if OK inputList.set(value) } outputList.onChange = (value) => { swapLists(value, false) this.mgr.evaluateHunk(this) // this has to follow on, to complete the promise ? // this necessitates that we write a manager-message-buffer on embedded, // and breaks one-program-item-at-a-time rules outputList.set(value) } /* --------------------------- ---------------------------- */ /* -------------------------- INIT --------------------------- */ /* --------------------------- ---------------------------- */ this.init = () => { // since we know this needs to default to nc, and many programs // will save with these states set 'true', we reset them now. //otherLink.set(0) //isActive.set(false) otherLink.value = 0 isActive.value = false // just add in order let ipKeys = getTypeAndNameKeys(inputList.value) for (let kp of ipKeys) { if(findPhy(kp.typeKey).key && findPhy(kp.typeKey).read){ this.inputs.push(new Input(kp.typeKey, kp.nameKey, this, true)) } } let opKeys = getTypeAndNameKeys(outputList.value) for (let kp of opKeys) { if(findPhy(kp.typeKey).key && findPhy(kp.typeKey).write){ this.outputs.push(new Output(kp.typeKey, kp.nameKey, this, true)) } } } /* --------------------------- ---------------------------- */ /* ---------------------- LINK STATE ------------------------- */ /* --------------------------- ---------------------------- */ // a wait-state let isOpening = false let openup = (reqResponse) => { // exit when already opening, this is called // whenever an input port is occupied (wanting to send) if (isOpening) return // ?? let msg = [LK.HELLO] MSGS.writeTo(msg, this.ind, 'uint16') if (reqResponse) { // if we're already open, we gucc MSGS.writeTo(msg, true, 'boolean') //console.log('link trying to open up', msg) isOpening = true } else { // otherwise we need to ask for a hello in return MSGS.writeTo(msg, false, 'boolean') // console.log('link replying to open up', msg) } dtout.put(msg) } let dataout = (data) => { if (!isActive.value) { console.error('attempt to put on not-active link') } else { if (!Array.isArray(data)) console.error('non-array put at link') dtout.put(data) } } /* --------------------------- ---------------------------- */ /* ----------------------- SERIALIZE ------------------------- */ /* --------------------------- ---------------------------- */ // deserialize msgs: data is an array of bytes let demsg = (data) => { if (!Array.isArray(data)) { console.log(data) throw new Error(`link demsg receives non-array, logged above`) } // WRITE IT let msg = {} // this is quick here, but in c ... if (data[0] === LK.HELLO) { //console.log('demsg for hello', data) msg.isHello = true msg.otherLink = MSGS.readFrom(data, 1, 'uint16').item msg.reqReturn = MSGS.readFrom(data, 4, 'boolean').item return msg } // data[0] is the route, the link we need to bump on msg.port = data[0] // check for ack, if (data[1] === LK.ACK) { msg.isAck = true return msg } // otherwise, get phy and write out let phy = TSET.find((item) => { return item.key === data[1] }) if (phy === undefined) throw new Error(`type not found at deserialization for expected key ${data[1]}`) msg.data = phy.read(data, 1).item if (debug) console.log('demsg:', msg) return msg } let outbuffer = new Array() // this ... let ack = (port) => { let msg = new Array() msg.push(port, LK.ACK) outbuffer.push(msg) } // serialize messages: let sermsg = (port, data, type) => { if (typeof port !== 'number' || port > 254) throw new Error('port is no good at serialize') // we r ready, let msg = [port] MSGS.writeTo(msg, data, type) if (debug) console.log('LINK sermsg to outbuffer', msg) outbuffer.push(msg) } this.loop = () => { // (1) check for data if (dtin.io()) { // if there's data on the line, we might be opening or operating ... // pulls every time, this is ok because we trust the other link // to be flow-controlling: this should either be for an open port or // an ack, // pull it and deserialize it, let msg = demsg(dtin.get()) // ack, if (msg.isAck) { if (debug) console.log('link ack conf on ', msg.port) // AN ACK if (this.inputs[msg.port].isNetClear) { throw new Error('received ack on unexpected port') } // is clear upstream this.inputs[msg.port].icus = true; } else if (msg.isHello) { // HELLO OTHERLINK // if we're not open, now we are if (!isActive.value) { isActive.set(true) } isOpening = false otherLink.set(msg.otherLink) if (msg.reqReturn) { openup(false) } } else { // not an ack or a hello, do regular msg stuff // check port existence if (this.outputs[msg.port] === undefined) { // new approach: blind ackit ack(msg.port) console.warn('blind ack') return //throw new Error(`link receives message for port not listed: ${msg.port}`) } // not an ack, for port, if open, put data if (!(this.outputs[msg.port].io())) { // clear ahead, typecheck and put if (debug) console.log('link putting', msg.data, 'on', msg.port) this.outputs[msg.port].put(msg.data) this.outputs[msg.port].needsAck = true // and ack when it is pulled off, } else { // oboy: we pulled it off of the link, so console.log(`WARN: link receives message for occupied port: ${msg.port}, ${this.outputs[msg.port].name}`) console.log('the msg', msg) } } // this is an array, we have to deserialize it } // end if(dataIn is occupied) // (2) check if we can put if (!(dtout.io()) && outbuffer.length > 0) { // if we can send things to the world, do so if (!isActive.value) { // clear-to-send upstream, we don't have sync state, so // want to open, want a reply openup(true) return } // because of looping sys, we can only do this once per turn let turn = outbuffer.shift() if (debug) console.log('LINK OUT ->>', turn) dataout(turn) } // (3) check if we can ack, if data has been consumed for (let o = 1; o < this.outputs.length; o++) { // if now clear, ack back if (this.outputs[o].needsAck && !this.outputs[o].io()) { // CLEANUP: .needsAck to false -> ack fn ? this.outputs[o].needsAck = false ack(o) } } // (4) look at inputs and see if we can put anything on the line for (let i = 1; i < this.inputs.length; i++) { // only pull off of inputs if we are known to be clear downstream if (this.inputs[i].icus && this.inputs[i].io()) { if (!isActive.value) { // nc, but wants to send, so ... and don't pull offline yet // so ping to open up, openup(true) return } let data = this.inputs[i].get() if (debug) console.log(`link pulling message from input ${i}, data is ${data}`) // now we must wait for ack, this.inputs[i].icus = false; sermsg(i, data, this.inputs[i].type) } } } // end loop } // FOOTER export default Link // END FOOTER