Skip to content
Snippets Groups Projects
link.js 10.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • Jake Read's avatar
    Jake Read committed
    /*
    
    
    Jake Read's avatar
    Jake Read committed
    hookup,
    
    Jake Read's avatar
    Jake Read committed
    
    */
    
    
    Jake Read's avatar
    Jake Read committed
    // HEADER
    import {
      Hunkify,
      Input,
      Output,
      State
    } from './hunks.js'
    // END HEADER
    
    Jake Read's avatar
    Jake Read committed
    
    
    Jake Read's avatar
    Jake Read committed
    import {
      TSET,
      LK,
    
    Jake Read's avatar
    Jake Read committed
      MSGS,
      findPhy
    
    Jake Read's avatar
    Jake Read committed
    } from '../typeset.js'
    
    Jake Read's avatar
    Jake Read committed
    function Link() {
    
      Hunkify(this)
    
      let debug = false
    
      let dtin = new Input('byteArray', 'data', this)
    
      let dtout = new Output('byteArray', 'data', this)
    
    Jake Read's avatar
    Jake Read committed
      // 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(',')
    
    Jake Read's avatar
    Jake Read committed
        // console.log('keys', keys)
    
    Jake Read's avatar
    Jake Read committed
        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(' ('))
    
    Jake Read's avatar
    Jake Read committed
          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 {
    
    Jake Read's avatar
    Jake Read committed
            ks.push({
              typeKey: tk,
              nameKey: nk
            })
    
      let swapLists = (newList, input) => {
        // list,
        let nks = getTypeAndNameKeys(newList)
        // old list,
        let oks
    
    Jake Read's avatar
    Jake Read committed
        if (input) {
    
          oks = this.inputs
        } else {
          oks = this.outputs
        }
        // one by one, down the list we go
    
    Jake Read's avatar
    Jake Read committed
        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'
    
    Jake Read's avatar
    Jake Read committed
          for (let io in oks) {
            if (oks[io].name === nks[kp].nameKey && oks[io].type === nks[kp].typeKey) {
    
              ioe = io
              continue
            }
          }
    
    Jake Read's avatar
    Jake Read committed
          if (ioe >= 0) {
            if (input) {
    
              this.inputs[kp + 1] = oks[ioe]
            } else {
              this.outputs[kp + 1] = oks[ioe]
            }
    
    Jake Read's avatar
    Jake Read committed
          } else {
    
            // the object doesn't already exist,
    
    Jake Read's avatar
    Jake Read committed
            if (input) {
    
    Jake Read's avatar
    Jake Read committed
              // 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 {
    
    Jake Read's avatar
    Jake Read committed
              let phy = findPhy(nks[kp].typeKey)
    
    Jake Read's avatar
    Jake Read committed
              if(phy.key && phy.write){
                this.outputs[kp + 1] = new Output(nks[kp].typeKey, nks[kp].nameKey, this, true)
              }
    
    Jake Read's avatar
    Jake Read committed
        }
    
    
    Jake Read's avatar
    Jake Read committed
        if (input) {
          while (this.inputs.length - 1 > nks.length) {
    
            this.inputs.pop()
          }
        } else {
    
    Jake Read's avatar
    Jake Read committed
          while (this.outputs.length - 1 > nks.length) {
    
            this.outputs.pop()
          }
        }
      }
    
      // now the changes,
    
    Jake Read's avatar
    Jake Read committed
      inputList.onChange = (value) => {
    
        // OK: back up to this ...
        swapLists(value, true)
    
        // we have to report this ...
        this.mgr.evaluateHunk(this)
    
    Jake Read's avatar
    Jake Read committed
      outputList.onChange = (value) => {
    
        swapLists(value, false)
    
        // this has to follow on, to complete the promise ?
        // this necessitates that we write a manager-message-buffer on embedded,
    
    Jake Read's avatar
    Jake Read committed
        // and breaks one-program-item-at-a-time rules
    
      /* ---------------------------    ---------------------------- */
      /* -------------------------- 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)
    
    Jake Read's avatar
    Jake Read committed
        for (let kp of ipKeys) {
    
    Jake Read's avatar
    Jake Read committed
          if(findPhy(kp.typeKey).key && findPhy(kp.typeKey).read){
    
    Jake Read's avatar
    Jake Read committed
            this.inputs.push(new Input(kp.typeKey, kp.nameKey, this, true))
          }
    
        }
    
        let opKeys = getTypeAndNameKeys(outputList.value)
    
    Jake Read's avatar
    Jake Read committed
        for (let kp of opKeys) {
    
    Jake Read's avatar
    Jake Read committed
          if(findPhy(kp.typeKey).key && findPhy(kp.typeKey).write){
    
    Jake Read's avatar
    Jake Read committed
            this.outputs.push(new Output(kp.typeKey, kp.nameKey, this, true))
          }
    
    Jake Read's avatar
    Jake Read committed
      /* ---------------------------    ---------------------------- */
      /* ---------------------- 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
    
    Jake Read's avatar
    Jake Read committed
        // ??
        let msg = [LK.HELLO]
        MSGS.writeTo(msg, this.ind, 'uint16')
    
        if (reqResponse) {
    
    Jake Read's avatar
    Jake Read committed
          // if we're already open, we gucc
          MSGS.writeTo(msg, true, 'boolean')
    
          //console.log('link trying to open up', msg)
    
    Jake Read's avatar
    Jake Read committed
          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)
    
    Jake Read's avatar
    Jake Read committed
        }
        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')
    
    Jake Read's avatar
    Jake Read committed
          dtout.put(data)
        }
      }
    
    
      /* ---------------------------    ---------------------------- */
      /* ----------------------- SERIALIZE ------------------------- */
      /* ---------------------------    ---------------------------- */
    
      // deserialize msgs: data is an array of bytes
    
      let demsg = (data) => {
    
        if (!Array.isArray(data)) {
    
    Jake Read's avatar
    Jake Read committed
          console.log(data)
          throw new Error(`link demsg receives non-array, logged above`)
        }
    
    Jake Read's avatar
    Jake Read committed
        // this is quick here, but in c ...
    
        if (data[0] === LK.HELLO) {
    
          //console.log('demsg for hello', data)
    
    Jake Read's avatar
    Jake Read committed
          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]
    
    Jake Read's avatar
    Jake Read committed
        if (data[1] === LK.ACK) {
    
        // otherwise, get phy and write out
        let phy = TSET.find((item) => {
          return item.key === data[1]
        })
    
    Jake Read's avatar
    Jake Read committed
        if (phy === undefined) throw new Error(`type not found at deserialization for expected key ${data[1]}`)
    
        msg.data = phy.read(data, 1).item
    
    Jake Read's avatar
    Jake Read committed
        if (debug) console.log('demsg:', msg)
    
    Jake Read's avatar
    Jake Read committed
      let outbuffer = new Array()
    
    
      // this ...
      let ack = (port) => {
    
    Jake Read's avatar
    Jake Read committed
        let msg = new Array()
    
    Jake Read's avatar
    Jake Read committed
        msg.push(port, LK.ACK)
    
    Jake Read's avatar
    Jake Read committed
      }
    
      // serialize messages:
      let sermsg = (port, data, type) => {
    
    Jake Read's avatar
    Jake Read committed
        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)
    
    Jake Read's avatar
    Jake Read committed
        if (debug) console.log('LINK sermsg to outbuffer', msg)
    
    Jake Read's avatar
    Jake Read committed
        outbuffer.push(msg)
    
        if (dtin.io()) {
    
    Jake Read's avatar
    Jake Read committed
          // 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())
    
    Jake Read's avatar
    Jake Read committed
          // ack,
          if (msg.isAck) {
            if (debug) console.log('link ack conf on ', msg.port)
    
    Jake Read's avatar
    Jake Read committed
            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) {
    
    Jake Read's avatar
    Jake Read committed
            // HELLO OTHERLINK
            // if we're not open, now we are
    
            if (!isActive.value) {
    
    Jake Read's avatar
    Jake Read committed
              isActive.set(true)
    
            }
            isOpening = false
            otherLink.set(msg.otherLink)
            if (msg.reqReturn) {
              openup(false)
    
    Jake Read's avatar
    Jake Read committed
            }
    
    Jake Read's avatar
    Jake Read committed
            // not an ack or a hello, do regular msg stuff
    
            // check port existence
    
    Jake Read's avatar
    Jake Read committed
            if (this.outputs[msg.port] === undefined) {
    
              // new approach: blind ackit
              ack(msg.port)
              console.warn('blind ack')
    
    Jake Read's avatar
    Jake Read committed
              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
    
    Jake Read's avatar
    Jake Read committed
              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
    
    Jake Read's avatar
    Jake Read committed
              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
    
    Jake Read's avatar
    Jake Read committed
        } // end if(dataIn is occupied)
    
        if (!(dtout.io()) && outbuffer.length > 0) { // if we can send things to the world, do so
    
    Jake Read's avatar
    Jake Read committed
          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
    
    Jake Read's avatar
    Jake Read committed
          let turn = outbuffer.shift()
    
    Jake Read's avatar
    Jake Read committed
          if (debug) console.log('LINK OUT ->>', turn)
          dataout(turn)
    
        }
    
        // (3) check if we can ack, if data has been consumed
    
    Jake Read's avatar
    Jake Read committed
        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)
          }
        }
    
    
    Jake Read's avatar
    Jake Read committed
        // (4) look at inputs and see if we can put anything on the line
    
    Jake Read's avatar
    Jake Read committed
        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()) {
    
    Jake Read's avatar
    Jake Read committed
            if (!isActive.value) {
              // nc, but wants to send, so ... and don't pull offline yet
              // so ping to open up,
              openup(true)
              return
            }
    
    Jake Read's avatar
    Jake Read committed
            let data = this.inputs[i].get()
    
    Jake Read's avatar
    Jake Read committed
            if (debug) console.log(`link pulling message from input ${i}, data is ${data}`)
    
            // now we must wait for ack,
    
    Jake Read's avatar
    Jake Read committed
            this.inputs[i].icus = false;
    
    Jake Read's avatar
    Jake Read committed
            sermsg(i, data, this.inputs[i].type)
    
    Jake Read's avatar
    Jake Read committed
      } // end loop
    
    Jake Read's avatar
    Jake Read committed
    }
    
    
    Jake Read's avatar
    Jake Read committed
    // FOOTER
    export default Link
    // END FOOTER