Skip to content
Snippets Groups Projects
Select Git revision
  • a34b69562859db085171eceb3d686f69db002baf
  • master default protected
  • leo
  • dex
  • pendulum
  • apfelstruder
  • littlerascal
7 results

view.js

Blame
  • hunks.js 11.06 KiB
    /* ---------------------------    ---------------------------- */
    /* ------------------------ HUNKITUP ------------------------- */
    /* ---------------------------    ---------------------------- */
    
    import {
      TSET
    } 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 ...
      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()
    
      // top-secret backdoor
      hunk.mgr = {}
    }
    
    /* ---------------------------    ---------------------------- */
    /* -------------------------- INPUT -------------------------- */
    /* ---------------------------    ---------------------------- */
    
    function Input(type, name, parent, linktype) {
      // naming, etc
      this.name = name
      this.type = type
      this.parent = parent
      // 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.copyOut = {}
      this.get = () => {
        // we want to (roughly) round robin these
        var i = this.lastGet
        // find the next occupied conn
        for(var u = 0; u < this.connections.length; u ++){
          // increment, wrap
          i ++
          if(i >= this.connections.length) i = 0
          // do work,
          // console.log('work for', i, this.connections[i])
          if (this.connections[i].io) {
            // and set that wire to read / empty
            this.connections[i].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
            return this.copyOut(i)
          }
        }
        // if we pass this for w/o hitting return,
        console.error("Warn! Hunk pulling on empty input!")
        return null
      }
    
      // typing ... odd ?
      if (type === "number" || type === "boolean" || type === "string") { // these are genuine base types and are not passed by reference,
        this.copyOut = (indice) => {
          //console.log('cout reg')
          return this.connections[indice].op.data
        }
      } else if (type === 'reference') {
        this.copyOut = (indice) => {
          //console.log('cout ref')
          return this.connections[indice].op.ref
        }
      } else { // this would cover "Object" and "Any" and whatever else, do your typechecking there
        this.copyOut = (indice) => {
          //console.log('cout else')
          return deepCopy(this.connections[indice].op.data)
        }
      }
    
      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
      }
    
    }
    
    /* ---------------------------    ---------------------------- */
    /* ------------------------- OUTPUT -------------------------- */
    /* ---------------------------    ---------------------------- */
    
    function Output(type, name, parent, linktype) {
      // naming, etc
      this.name = name
      this.type = type
      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)
      this.put = {}
      // typing ... odd ?
      if (type === "number" || type === "boolean" || type === "string") { // these are genuine base types and are not passed by reference,
        this.put = (data) => {
          if (!(this.io())) {
            this.ref = data
            this.data = data // so this is effectively a pass
            this.setIo()
            return true
          } else {
            console.error(`ERROOOOOOR: put called on occupied output: output ${this.name} in ${this.parent.name}`)
            return false
          }
        }
      } else if (type === 'reference') {
        this.put = (data) => {
          if (!this.io()) {
            this.ref = data
            this.data = data
            this.setIo()
            return true
          } else {
            console.error(`ERROOOOOOR: put called on occupied output: output ${this.name} in ${this.parent.name}`)
            return false
          }
        }
      } else { // this would cover "Object" and "Any" and whatever else, do your typechecking there
        // no rules bb
        // names still gotta match tho
        // scratch that, truly no rules
        //if(!(type === "Object" || type === "Any")) throw new Error(`unknown type specified at hunk, wyd? type: ${this.type}, name: ${this.name}`)
        this.put = (data) => {
          if (!this.io()) {
            // HACK for view
            this.ref = data
            // make case for byteArray: themr buffers
            this.data = deepCopy(data)
            this.setIo()
            return true
          } else {
            console.error(`WARN: put called on occupied output: output ${this.name} in ${this.parent.name}`)
            return false
          }
        }
      }
    
      /* ---------------------------    ---------------------------- */
      /* ---------------- OUTPUT MANAGE CONNECTIONS ---------------- */
      /* ---------------------------    ---------------------------- */
    
      this.attach = (input) => {
        // no rules on types ATM, historic placeholder
        if (this.type === input.type || input.type === 'any' || true) {
          // 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 {
          throw new Error('attempt to attach mismatched types')
        }
      }
    
      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('remove err, 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;
      }
    
    }
    
    function State(type, name, startup) {
      this.name = name
      this.type = type
      // have to be a type we can recognize,
      if (!isOKType(type)) {
        //throw new Error(`unknown type specified at state, wyd? type: ${this.type}, name: ${this.name}`)
      }
      // 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('@ state change request, no manager hookup')
      }
      // from within a hunk, we typically call this
      // to update the value and ship it out to any views
      this.set = (value) => {
        this.value = value
        this.hookup(this.value, this.msgid)
        this.msgid = null
      }
    
      this.msgid = null
    
      // a view change calls this function, which we can
      // also manually overwrite (say, to set a change handler / checker)
      this.change = (value, msgid) => {
        // coupla steps here, we track and then call internal fn,
        this.msgid = msgid
        if (this.onChange) {
          this.onChange(value)
        } else if (this.onchange) {
          this.onchange(value)
        } else {
          this.set(value)
        }
      }
    }
    
    function deepCopy(obj) {
      // turns out parse/stringify is the fastest,
      //console.log('to dc', obj)
      //console.log(JSON.stringify(obj))
      if (obj instanceof ImageData) {
        let newData = new ImageData(obj.data, obj.width, obj.height);
        // console.log(newData)
    
        return newData;
      };
    
      return JSON.parse(JSON.stringify(obj)) // I believe this dehydrates objects.
    }
    
    // depricated: used this to check (on input / output startup)
    function isOKType(type) {
      // etc, lowecase to match with typeof ~ queeeries ~
      let ind = TSET.findIndex((cand) => {
        return cand.name === type
      })
      if (type === 'any') {
        console.log('WARN!, type of "any" is a bit risky, and cannot traverse a link')
        return true
      } else if (ind === -1) {
        return false
      } else {
        return true
      }
    }
    
    export {
      Hunkify,
      Input,
      Output,
      State
    }