Select Git revision
view.js 64.19 KiB
/*
VIEW
- scrape to manager,
- manager gogetter ... and then, hello click
- and then, nicely:
- also - link for flowcontrol when downstream non-conn ? the init-over-link question? a few states ...
- div structure ... ? open something up just barely, to test node and scraper
- beer 2 celly
- div structure: the div(div) scaling unfuckery
*/
import {
Hunkify,
Input,
Output,
State
} from './hunks.js'
import {
TSET, // typset
MK, // manager keys,
HK, // hunk keys,
MSGS // messaging
} from '../typeset.js'
// yonder def, the ui mirror on hunks
import HunkDefinition from '../view/vdef.js'
// to file-organize view.js, a monster
import DomTools from '../view/vdom.js'
import BezierTools from '../view/vbzt.js'
import MessageBox from '../view/vmsg.js'
import PatchSet from '../view/vptch.js'
// the force loop
import Floop from '../view/vfloop.js'
function View() {
Hunkify(this)
let verbose = false
let msgverbose = false
let msgsin = new Input('byteArray', 'msgs', this)
let msgsout = new Output('byteArray', 'msgs', this)
this.inputs.push(msgsin)
this.outputs.push(msgsout)
// das UI globals
// our dom is the space we're allotted,
this.dom = {}
// the plane, one layer beneath, is where divs live
this.plane = {}
// we have a list of definitions,
let defs = new Array()
// #ref, sloppy
this.defs = defs
// tools to write dom-representations of hunks,
let dt = new DomTools(this)
// a handy box, for stuff
let msgbox = new MessageBox(this)
this.msgbox = msgbox
this.interpreterName = null
this.interpreterVersion = null
// and tools for the links,
let bzt = new BezierTools(this)
// and for program (patch) management
let patchset = new PatchSet(this, msgbox)
this.patchset = patchset
// here ... at this point,
/*
an init order, etc, for each view to have top-level access ...
for tls stuff
for contextmenu , do (tlv.oncontext(this, event))
... aaaand ?
*/
// the toplevel view,
this.tls = 1
// this is one of the biggest outstanding issues w/ this whole thing:
// views have child issues
this.tlv = this
/* --------------------------- ---------------------------- */
/* -------------------- INIT, LISTENERS ---------------------- */
/* --------------------------- ---------------------------- */
// oddity, init happens when loaded into the runtime
// onload when we have a dom element
// these are separate events due to cuttlefish implementation, sawry
this.init = () => {
// in the case of UIs, we have the dom before init runs,
// so this is kind of like the 'window.onload' function
// but we do need to write our dom object to start,
// then cf will drop it wherever we plan ...
console.log(`INIT for ${this.name}`)
this.dom = $('<div>').addClass('view').get(0)
this.dom.hunk = this
}
// END INIT CODE
// onload is called in vdef.js ~ L350
this.onload = () => {
console.log(`ONLOAD for ${this.name}`)
// for nested dom elements,
this.plane = $('<div>').addClass('plane').get(0)
// this needs to start with some transform ..
dt.writeTransform(this.plane, {
x: 0,
y: 0,
s: 1
})
// append
$(this.dom).append(this.plane)
// more space
this.requestResize(1200, 800)
}
// we also have events that a level above us can fire...
this.onresize = () => {
// don't think too hard, do
this.kick()
// console.error('onresize: need to move things about, probably')
// this.drawLinks()
}
// dummy, will b replaced
this.requestResize = (x, y) => {
let bounds = this.getCurrentBounds()
if (x < bounds.w && y < bounds.h) {
// it's fine
} else {
console.warn('requestResize not yet implemented by parent')
}
}
this.requestSizeIncrease = (x, y) => {
//console.log('req', x, y)
let bounds = this.getCurrentBounds()
this.requestResize(bounds.w + x, bounds.h + y)
}
// here bc related to resizing, sizes
// this is re-written during .makeTopLevel
this.getCurrentBounds = () => {
// more like
let x1 = 0
let y1 = 0
let x2 = this.dom.clientWidth
let y2 = this.dom.clientHeight
// console.log(`bounds: ${this.name}: ${w}, ${h}`, this.dom)
return {
x1: x1,
y1: y1,
x2: x2,
y2: y2,
w: x2 - x1,
h: y2 - y1
}
}
/* --------------------------- ---------------------------- */
/* ------------------ QUEEN OF THE MOUNTAIN ------------------ */
/* --------------------------- ---------------------------- */
this.isTopLevel = false
// the top level view, gifted to this hunk by omniscience
this.makeTopLevel = () => {
console.log(`MAKE TOP LEVEL ${this.name}`)
this.isTopLevel = true
$(this.dom).attr('id', 'NROLVIEW')
$(this.plane).attr('id', 'tlplane')
// this is that (0,0) marker,
// also needs more space ...
$(this.plane).css('background', 'url("asset/bg.png")').css('width', '100px').css('height', '100px')
// init transform of the plane,
let dft = {
s: 1,
x: 0,
y: 0,
ox: 0,
oy: 0
}
dt.writeTransform(this.plane, dft)
dt.writeBackgroundTransform(this.dom, dft)
this.getCurrentBounds = () => {
let ct = dt.readTransform(this.plane)
let w = this.dom.clientWidth / ct.s
let h = this.dom.clientHeight / ct.s
let x1 = -ct.x / ct.s
let y1 = -ct.y / ct.s
let x2 = w - x1
let y2 = h - y1
// move & shimmy by
return {
x1: x1,
y1: y1,
x2: x2,
y2: y2,
w: w,
h: h
}
}
// to zoom,
this.dom.addEventListener('wheel', (evt) => {
if (!$(evt.target).is('.view') && !$(evt.target).is('#floater')) {
return false
}
evt.preventDefault()
evt.stopPropagation()
let ox = evt.clientX
let oy = evt.clientY
let ds
if (evt.deltaY > 0) {
ds = 0.025
} else {
ds = -0.025
}
let ct = dt.readTransform(this.plane)
ct.s *= 1 + ds
ct.x += (ct.x - ox) * ds
ct.y += (ct.y - oy) * ds
this.tls = ct.s
// arduous, but
for (let def of defs) {
if (def.type === 'view' && def.name !== 'tlview') {
def.hunk.tls = ct.s
}
}
if (ct.s < 1) {
dt.writeTransform(this.plane, ct)
dt.writeBackgroundTransform(this.dom, ct)
}
this.drawLinks()
})
let zoomExtents = () => {
// collector
let psns = []
for (let def of defs) {
for (let fltr of def.floaters) {
fltr.calculateSizes()
psns.push({
x: fltr.x,
y: fltr.y,
x1: fltr.bb.x1,
y1: fltr.bb.y1,
x2: fltr.bb.x2,
y2: fltr.bb.y2
})
}
}
// ok then, probably bounds like
let minx = 0
let miny = 0
let maxx = 500
let maxy = 500
for (let ps of psns) {
if (ps.x + ps.x1 < minx) minx = ps.x + ps.x1
if (ps.x + ps.x2 > maxx) maxx = ps.x + ps.x2
if (ps.y + ps.y1 < miny) miny = ps.y + ps.y1
if (ps.y + ps.y2 > maxy) maxy = ps.y + ps.y2
}
// currently,
let ct = dt.readTransform(this.plane)
let wd = this.dom.clientWidth
let ht = this.dom.clientHeight
// so, scale is
let pfsx = (wd) / (maxx - minx)
let pfsy = (ht) / (maxy - miny)
let pfs = Math.min(pfsx, pfsy)
// and we can write
ct.s = pfs * 0.8 // breathing room,
ct.x = -minx * pfs
ct.y = -miny * pfs
// and then,
if (ct.s > 1) {
ct.s = 1
ct.x = -minx
ct.y = -miny
}
// also,
this.tls = ct.s
for (let def of defs) {
if (def.type === 'view' && def.name !== 'tlview') {
def.hunk.tls = ct.s
}
}
// contact,
dt.writeTransform(this.plane, ct)
dt.writeBackgroundTransform(this.dom, ct)
this.drawLinks()
} // end zoom extents
this.zoomExtents = zoomExtents
let canvasDragListener = (evt) => {
evt.preventDefault()
evt.stopPropagation()
let ct = dt.readTransform(this.plane)
ct.x += evt.movementX
ct.y += evt.movementY
dt.writeTransform(this.plane, ct)
dt.writeBackgroundTransform(this.dom, ct)
this.drawLinks()
}
let canvasUpListener = (evt) => {
window.removeEventListener('mousemove', canvasDragListener)
window.removeEventListener('mouseup', canvasUpListener)
}
// to pan,
this.dom.addEventListener('mousedown', (evt) => {
if ($(evt.target).is('.view')) {
evt.preventDefault()
evt.stopPropagation()
window.addEventListener('mousemove', canvasDragListener)
window.addEventListener('mouseup', canvasUpListener)
} else if ($(evt.target).is('.input')) {
// no op
} else {
// prevents bubbling up outside of the view, potentially a bug farm ?
// evt.preventDefault()
// evt.stopPropagation()
}
})
// key listeners (are global, bc ...) KEYCODES
document.addEventListener('keydown', (evt) => {
if (evt.key === 'k' && evt.ctrlKey) {
evt.preventDefault()
evt.stopPropagation()
this.kick()
//writeMessage('addhunk', 'link')
} else if (evt.key === 'z' && evt.ctrlKey) {
evt.preventDefault()
evt.stopPropagation()
zoomExtents()
} else if (evt.key === 'd' && evt.ctrlKey) {
evt.preventDefault()
evt.stopPropagation()
// your debug land,
this.restoreEntireSystem('pdevone').then(() => {
this.defs[2].floaters[0].fixTo(400, 300)
this.defs[2].fixWithDataPort()
this.zoomExtents()
console.warn("RESTORE COMPLETE FINALLY, refreshing ponyo...")
this.refreshAnyFreshViews()
})
//writeMessage('addprogram', 'ntlink')
} else if (evt.key === 'v' && evt.ctrlKey) {
//writeMessage('addprogram', 'llink')
} else if (evt.keyCode === 27) {
// escapekey
console.log('escape!')
this.floop.sim.stop()
for (let def of defs) {
if (def.type === 'view' && def.name !== 'tlview') {
def.hunk.floop.sim.stop()
}
}
$(this.dom).find('.contextmenu').remove()
// also find floaters ...
}
return false
})
/* QUEEN RECEPIES */
// trace should return, for an output, the next input.
// if the input is a link, it should try to traverse
let trace = (output) => {
//console.log(`TRACE: tracing from ${output.name} in hunk ${output.parent.name} in view ${this.name}`)
// ok, traces through links / heirarchy, returning final destination
// of *connected* links
if (output.connections.length !== 1) {
// no connections exist,
return false
}
let next = output.connections[0]
//console.log(`TRACE: NEXT:`, next)
if (next.parent.type === 'link') {
// this is the heirarchy dive
let thru = next.parent.reciprocalLink
//console.log(`TRACE: NEXT: LINK`, thru)
if (thru) {
// a mirror,
let otp = thru.outputs[next.ind]
if (otp) {
// return / recurse
return trace(otp)
} else {
console.warn('on trace, found link, but no output on the other side')
return false
}
} else {
// could try doing a globalOrganize, or refresh ... cumbersome
console.warn('on trace, at link boundary, find no reciprocal link')
return false
}
} else {
//console.log(`TRACE: RETURN:`, next)
return next
}
}
this.trace = trace
// notes on building a route:
/*
this is a pretty critical routine, and it's *perty neet*
to make it real, needs to
- operate when possible paths (unoccupied & same-type) or partial paths already exist
- operate across multiple levels!
- in deep future: choose shortest (& least busy?) path through existing graph
*/
this.buildRoute = (output, input) => {
return new Promise((resolve, reject) => {
// first, we can check
let pt = trace(output)
if (pt) {
resolve()
return
}
// ok, first off, are these things in the same view? can I find a view from an outputs?
let opv = output.parent.parentView
let ipv = input.parent.parentView
if (opv === ipv) {
// we r on par
opv.requestAddLink(output, input).then(() => {
resolve()
})
} else {
console.log("BUILD: diving")
let lcounter = 0
// well, ya didn't know it, but you're here to write a graph traversal scheme
// one must start somewhere,
// let's find the links of the output's parent
for (let df of opv.defs) {
if (df.type === 'link') {
console.log(`BUILD: find link ${df.name}`)
let dfr = df.reciprocalLink
if (dfr) {
console.log(`BUILD: find reciprocal link ${dfr.name} for ${df.name}`)
// that's one traverse, let's check
if (dfr.parentView === ipv) {
// this is the 'other side'
// let's add that side first ... bottom - up
// this is the link's state item describing it's outputs, as it stands:
let opList = dfr.states[3].value
opList += `, ldive_${lcounter} (${output.type})`
console.log("BUILD: to req state for", opList)
// have to req via this view,
dfr.parentView.requestStateChange(dfr.states[3], opList).then(() => {
// tricky, tricky, and dangerous:
// we requested a state change on the dfr object,
// that swapped in a new definition (bc this state change req's an addition to outputs)
// so,
dfr = dfr.parentView.defs[dfr.ind]
return dfr.parentView.requestAddLink(dfr.outputs[dfr.outputs.length - 1], input)
}).then(() => {
let ipList = df.states[2].value
ipList += `, ldive_${lcounter} (${output.type})`
return df.parentView.requestStateChange(df.states[2], ipList)
}).then(() => {
// swap happened,
df = df.parentView.defs[df.ind]
return df.parentView.requestAddLink(output, df.inputs[df.inputs.length - 1])
}).then(() => {
// note: !: since we repalced a few link definitions,
// we need to hook back their .reciprocalLinks,
// we can do that by calling:
this.globalOrganize()
// and finally,
resolve()
})
} else {
console.error('no second layer recursion yet, pls write, thx')
// would do same search over defs, for links, at this layer ...
}
} else {
console.log(`BUILD: no reciprocal link for ${df.name}`)
}
}
}
}
})
}
this.globalOrganize = (debug) => {
console.log("GO: KICKOFF ORGANIZING PARTY")
// this is a request made:
/*
(1) when any view refreshes,
(2) when we load a new patch
(3) when we load a new system
(4) when we build a route with .buildRoute(output, input)
- this doesn't change topologies, or make any requests to managers,
- it just organizes visually
*/
// we need to recurse here,
let recursor = (scope, order) => {
if (debug) console.log(`Global Organize: recurses ${scope.name} at ${order}`)
// scope is a view (hunk)
// order is nth- level down tree, with us (toplevel) at root 0
if (debug) console.log(`GO: scope defs, tl defs`)
for (let df of scope.defs) {
if (debug) console.log(`GO: df over ${df.name} in ${scope.name}`)
// edgecased is a block to not walk back-up heirarchy,
if (df.type === 'link' && df.grouptype !== 'edgecased') {
// find recirprocal view relationship via trace, which returns an input, thru links,
// given some output
if (debug) console.log(`GO: TRACELOG FROM ${df.name}`)
let rvi = trace(df.outputs[1])
if (debug) console.log(`GO: TRACELOG RETURNS`, rvi)
if (rvi) {
// we have ah link definition, and ah view definition, connected by routing,
// so we are safe to do
let rvd = rvi.parent
// if the rvd is a manager, this is the bottom level -> a link thru to a manager,
if (rvd.type === 'manager') continue
// mark,
if (order % 2) {
$(rvd.hunk.dom).css('background-color', '#d6d6d6')
} else {
$(rvd.hunk.dom).css('background-color', '#e5e5e5')
}
// and,
if (debug) console.log(`GO: wrap ${df.name} around ${rvd.name}`)
df.wrapon(rvd)
rvd.unwrap()
// now, if we have ll data,
if (rvd.hunk.hasRefreshed) {
// find the interior link (by search for ol state)
let oind = df.states.find((cnd) => {
return cnd.name === 'otherLink'
}).value
if (!oind) throw new Error('cannot find link oind state for hookup')
// doth it ?
let internalLink = rvd.hunk.defs[oind]
if (internalLink) {
if (internalLink.type !== 'link') {
console.error('link mixup alert')
console.error(internalLink);
}
// hook em up
df.reciprocalLink = internalLink
internalLink.reciprocalLink = df
// and do,
internalLink.edgecase()
// still logging these, bc it's nice 2 kno
if (debug) console.log(`GO cn link ${df.name} to ${internalLink.name}`)
// done w/ internal, now we can
recursor(rvd.hunk, ++order)
} else {
console.error("organizing ... cannot find a reciprocal link")
}
}
}
}
}
// and roll zerbraHeirarchy in here also, using order ...
}
// kickoff w/
recursor(this, 1)
} // end globalOrganize
this.zebraHeirarchy = () => {
// can just do this ... by ... children ?
// ... ...
let lvl = 0
let traverse = (view) => {
if (lvl > 3) {
console.log(`exit on maxlevel`)
return
}
for (let df of view.defs) {
// this *won't* work beyond one, the view defs all live in the top
// level ... search for link defs instead
if (df.type === 'view' && df.name !== 'tlview') {
lvl++
if (lvl % 2 === 0) {
//
} else {
$(df.deg.native).children('.view').css('background-color', '#d6d6d6')
}
traverse(df.hunk)
}
}
}
traverse(this)
// walk views, make diff. colorrs
}
this.expandLink = (linkDef) => {
return new Promise((resolve, reject) => {
// to avoid mayhem, do
linkDef.floaters[0].fix()
// and then,
this.requestAddHunk('view').then((viewDef) => {
// jquery moves automatically ?
console.log('expanding link: the view', viewDef.name)
console.log('expanding link: the link', linkDef.name)
// now we'd like to find a route from the view to the link
// since we're global, we could try to build the 1st link,
this.buildRoute(viewDef.outputs[0], linkDef.inputs[1]).then(() => {
console.log("EXPANDING: Build Route Down Complete")
return this.buildRoute(linkDef.outputs[1], viewDef.inputs[0])
}).then(() => {
console.log("EXPANDING: Build Route UP Complete")
this.globalOrganize()
resolve(viewDef)
}).catch((err) => {
console.error('probable error during route')
reject(err)
})
})
}).catch((err) => {
console.log('err during link expansion', err)
reject(err)
})
} // end expand recipe
/* QUEEN HANDLERS */
window.onresize = () => {
this.onresize()
}
this.refreshAnyFreshViews = () => {
for (let def of defs) {
if (def.type == 'view' && def.name !== 'tlview') {
if (def.hunk.hasRefreshed) {
} else {
console.log("NONREFRESHED")
def.hunk.refresh()
}
}
}
}
// built fast, should live with patchset
this.restoreEntireSystem = (name) => {
return new Promise((resolve, reject) => {
patchset.getSystem(name).then((sys) => {
console.log('sys object', sys)
// startup, track views to look for
let recount = [];
// ah recursor:
let rec = (view, slice) => {
view.patchset.mergePatch(slice).then(() => {
// done here,
recount.splice(recount.indexOf(view.name), 1)
// check if more to do
for (let df of view.defs) {
if (df.type === 'link') {
// match link / contains to ...
let vw = trace(df.outputs[1])
if (vw) {
// this is a hack,
// if we're going to add more shit, we should increase by at least ...
if (vw.name === 'tlview') continue
vw = vw.parent.hunk
// if there's lower level work to do... (if this link 'contains' another patch, recursing)
let nl = slice.hunks[df.ind].contains
if (nl) {
console.log('would like to load', nl)
recount.push(vw.name);
vw.refresh().then(() => {
rec(vw, nl)
})
} else {
}
}
}
}
//console.warn("RECOUNT NOW: ", recount)
if (recount.length < 1) {
//console.warn("RESTORE COMPLETE")
resolve()
}
})
}
// startup,
recount.push(this.name)
rec(this, sys)
})
})
}
// get the context menu on righ click
this.dom.addEventListener('contextmenu', (evt) => {
// exit if we're not on target
if (!$(evt.target).is('.view')) return false
// global contextemenu rm (we are toplevel)
$(this.dom).find('.contextmenu').remove()
// one only,
evt.preventDefault()
evt.stopPropagation()
// the view we would issue from,
console.log(`contextmenu for ${evt.target.hunk.name}`)
let vw = evt.target.hunk
// make the menu,
let menu = $('<div>').addClass('contextmenu').get(0)
// lay it down in screen-space
dt.writeTransform(menu, {
s: 1,
x: evt.clientX, // (pt.s + -0.1 * (pt.s-1))),
y: evt.clientY - 29 - 31 * 2 // + -0.1 * (pt.s-1)))
})
$(this.dom).append(menu)
// hmmm
this.changeContextTitle('you can... ')
// on of the options will ...
// LOCAL:
this.addContextOption(`<i class="em em-twisted_rightwards_arrows"></i> refresh the view`, (evt) => {
// this actually gets wiped when the first hunk arrives, so
$(evt.target).text('refreshing...')
vw.refresh().then(() => {
$(evt.target).text(`refresh complete !`)
setTimeout(() => {
$(this.dom).find('.contextmenu').fadeOut(400, function() {
$(this).remove()
})
}, 200)
})
})
this.addContextOption(`<i class="em em-wave"></i> say hello`, (evt) => {
$(evt.target).text(`view says hello ...`)
vw.sayHelloToManager().then((ms) => {
$(evt.target).text(`msg took ${ms}ms to return`)
setTimeout(() => {
$(this.dom).find('.contextmenu').fadeOut(400, function() {
$(this).remove()
})
}, 1000)
})
})
// req a new hunk,
this.addContextOption('<i class="em em-construction_worker"></i> add a hunk', (evt) => {
//vw.msgbox.write('requested a list of hunks...')
$(evt.target).text('requesting a list of hunks...')
vw.requestListAvail().then((stringlist) => {
this.changeContextTitle('available hunks:')
// sort the list...
let treelike = [{
parentname: '',
list: []
}]
for (let item of stringlist) {
let slash = item.indexOf('/')
if(slash > 0){
let head = item.substring(0, slash)
// add to or make,
let parent = treelike.find((cand) => {
return cand.parentname === head
})
if(parent){
parent.list.push(item)
} else {
treelike.push({
parentname: head,
list: [item]
})
}
} else {
treelike[0].list.push(item)
}
}
for(let branch of treelike){
this.addContextTitle(branch.parentname)
for(let item of branch.list){
this.addContextOption(item, (evt) => {
$(evt.target).append(' > requested ... ')
//vw.msgbox.write(`requested one new ${item}`)
vw.requestAddHunk(item).then((def) => {
$('.contextmenu').remove()
//console.log('one hunk as promised', def)
}).catch((err) => {
this.changeContextTitle('error, see consoles')
setTimeout(() => {
$(this.dom).find('.contextmenu').fadeOut(400, function() {
$(this).remove()
})
}, 1000)
})
})
}
}
})
})
// save patches
this.addContextOption('<i class="em em-blue_book"></i> save this context', (evt) => {
let ptch = patchset.writeCurrent()
$(evt.target).text('')
let tinput = $('<input>').attr('type', 'text').attr('size', 24).attr('value', 'patchName').get(0)
$(evt.target).append(tinput) // etc
$(tinput).focus()
$(tinput).select()
$(tinput).on('keyup', (evt) => {
if (evt.keyCode == 13) {
let bleb = JSON.stringify(ptch)
let url = URL.createObjectURL(new Blob([JSON.stringify(ptch, null, 2)], {
type: "application/json"
}))
// hack to trigger the download,
let anchor = $('<a>ok</a>').attr('href', url).attr('download', tinput.value + '.json').get(0)
$(evt.target).append(anchor)
anchor.click()
// finally, rip
$(this.dom).find('.contextmenu').remove()
//saveAs(bleb, 'file.json')
}
})
})
// load patches,
this.addContextOption('<i class="em em-hammer"></i> restore into this context', (evt) => {
$(evt.target).append(' > loading a list ...')
vw.patchset.findPatches().then((list) => {
// title, and options
this.changeContextTitle('available server contexts:')
for (let item of list) {
this.addContextOption(item, (evt) => {
try {
vw.patchset.getPatch(item).then((ptch) => {
return vw.patchset.mergePatch(ptch)
}).then((defs) => {
//vw.msgbox.write(`great success merging patches`)
this.globalOrganize()
}).catch((errmsg) => {
console.error(errmsg)
console.error('here, write failure-at-this-point and fade')
})
} catch (err) {
//vw.msgbox.write('caught an error while starting program load')
}
})
}
})
})
// GLOBAL
if (vw.name === 'tlview') {
// save the entire system,
this.addContextOption('<i class="em em-books"></i> save this entire system', (evt) => {
// the top,
let tlp = patchset.writeCurrent()
// heirarchichal traverse,
let traverse = (ld) => {
// ld -> link def
let vw = trace(ld.outputs[1])
// if it has one,
if (vw && vw.parent.type === 'view' && vw.parent.hunk.hasRefreshed) {
// for the reciprocal link, nest
vw = vw.parent.hunk
// write it up
let nlp = vw.patchset.writeCurrent()
// put 'er thre
tlp.hunks[ld.ind].contains = nlp
// find the 'otherLink' state object
let oli
for (let st of ld.states) {
if (st.name === 'otherLink') {
oli = st.value
}
}
// and continue,
for (let dfi in vw.defs) {
// don't walk backwards !
if (dfi === oli) continue
let df = vw.defs[dfi]
if (df.type === 'link') {
traverse(df)
}
}
} else {
// no view for this link, don't traverse
}
} // fin traverse
//
// kick off firstverse
for (let df of this.defs) {
if (df.type === 'link') {
traverse(df)
}
} // end recursive cycle ?
// have thing
console.log('BIG UPS: a system patch ... ', tlp)
// now present-and-save
$(evt.target).text('')
let tinput = $('<input>').attr('type', 'text').attr('size', 24).attr('value', 'systemName').get(0)
$(evt.target).append(tinput) // etc
$(tinput).focus()
$(tinput).select()
$(tinput).on('keyup', (evt) => {
if (evt.keyCode == 13) {
let bleb = JSON.stringify(tlp)
let url = URL.createObjectURL(new Blob([JSON.stringify(tlp, null, 2)], {
type: "application/json"
}))
// hack to trigger the download,
let anchor = $('<a>ok</a>').attr('href', url).attr('download', tinput.value + '.json').get(0)
$(evt.target).append(anchor)
anchor.click()
// finally, rip
$(this.dom).find('.contextmenu').remove()
//saveAs(bleb, 'file.json')
}
})
}) // end save entire system
// MERGE A SYSTEM
// careful: two 'evt' variables in lower scope ?
this.addContextOption('<i class="em em-hammer_and_wrench"></i> restore an entire system', (evt) => {
// get the data ...
patchset.findSystems().then((list) => {
// title, and options
this.changeContextTitle('available server systems:')
for (let item of list) {
this.addContextOption(item, (evt) => {
this.restoreEntireSystem(item)
})
}
})
})
}
}) // end contextmenu
this.addContextTitle = (text) => {
$(this.dom).find('.contextmenu').get(0).append($('<ul>' + text + '/</li>').get(0))
}
// takes 'under' argument
this.addContextOption = (text, click) => {
$(this.dom).find('.contextmenu').get(0).append($('<li>' + text + '</li>').click((click)).get(0))
}
this.changeContextTitle = (text) => {
// clear,
$(this.dom).find('.contextmenu').children().remove()
// overkill, but fun
let menu = $(this.dom).find('.contextmenu').get(0)
let title = $(`<div>${text}</div>`).addClass('contextTitle').get(0)
$(this.dom).find('.contextmenu').append(title)
}
} // end makeTopLevel
/* --------------------------- ---------------------------- */
/* ------------------- END MAKETOPLEVEL ---------------------- */
/* --------------------------- ---------------------------- */
/* --------------------------- ---------------------------- */
/* ----------------------- REDRAWING ------------------------- */
/* --------------------------- ---------------------------- */
this.floop = new Floop(this)
this.drawLinks = () => {
// drawing from scratch every time ! could be faster, probably
bzt.clear(this.plane)
// 1st,
//this.followRules()
// draw 'em all
for (let def of defs) {
for (let output of def.outputs) {
for (let input of output.connections) {
bzt.drawLink(output, input)
}
if (output.hasFloater) {
let head = bzt.getRightHandle(output.de)
let tail = bzt.getFloaterHandle(output.floater)
bzt.writeBezier(head, tail)
}
}
}
}
// kick: topology changes
this.kick = () => {
//msgbox.checkHeight()
this.floop.reset()
this.drawLinks()
}
// tick: size / position changes
this.tick = () => {
this.floop.sim.alpha(1)
this.floop.sim.restart()
}
/* --------------------------- ---------------------------- */
/* ------------------- SERIAL -> HOTMESSES ------------------- */
/* --------------------------- ---------------------------- */
let deserializeNameType = (arr, bytes, i) => {
let nameresp = MSGS.readFrom(bytes, i, 'string')
let typeresp = MSGS.readFrom(bytes, i + nameresp.increment, 'string')
arr.push({
name: nameresp.item,
type: typeresp.item
})
return nameresp.increment + typeresp.increment
}
let specBySerial = (bytes, start, debug) => {
// deserialize
let spec = {
ind: null,
name: null,
type: null
}
// inputs, outputs, state
spec.inputs = new Array()
spec.outputs = new Array()
spec.states = new Array()
// hold,
let temp
// starting at 2, msgs[0] is 'hnkalive'
let i = start
// lets write a goddang real structure an object (spec) with mirror type dom and access fn's ...
// this will make building better and better code mucho bueno
// ripperoni,
outer: while (i < bytes.length) {
switch (bytes[i]) {
case HK.IND:
i += 1
temp = MSGS.readFrom(bytes, i, 'uint16')
spec.ind = temp.item
i += temp.increment
break
case HK.TYPE:
i += 1
temp = MSGS.readFrom(bytes, i, 'string')
spec.type = temp.item
i += temp.increment
break
case HK.NAME:
i += 1
temp = MSGS.readFrom(bytes, i, 'string')
spec.name = temp.item
i += temp.increment
break
case HK.INPUT:
i += 1
// expecting two strings here, name and then type
i += deserializeNameType(spec.inputs, bytes, i)
break
case HK.OUTPUT:
i += 1
i += deserializeNameType(spec.outputs, bytes, i)
if (bytes[i] === HK.CONNECTIONS) {
let cn = 0
i++
spec.outputs[spec.outputs.length - 1].connections = []
//console.log("HK B4 CONN")
while (bytes[i] === HK.CONNECTION) {
console.log("HK CONNECTION");
let frdarr = bytes.slice(i + 1, i + 3)
console.log("HK bytes for inHunkIndice", frdarr)
let fbtarr = Uint8Array.from(frdarr)
let fvlarr = new Uint16Array(fbtarr.buffer)
i += 2
spec.outputs[spec.outputs.length - 1].connections[cn] = [fvlarr[0], bytes[i + 1]]
i += 2
}
}
break
case HK.STATE:
i += 1
i += deserializeNameType(spec.states, bytes, i)
// ok, and the value should trail
// we don't *need* to know the type, could just read by the key, but,
//console.log(`${this.name} STATE READ from ${i}`, bytes[i], bytes);
temp = MSGS.readFrom(bytes, i, spec.states[spec.states.length - 1].type)
//console.log(`${this.name} TEMP`, temp);
spec.states[spec.states.length - 1].value = temp.item
i += temp.increment
break
default:
console.log('spec so far', spec)
console.log('bytes', bytes)
throw new Error(`unexpected key encountered during hunk deserialization at position ${i}: ${bytes[i]}`)
break outer
}
}
// a check,
if (debug) console.log(`broke outer, len at ${bytes.length}, i at ${i}`)
// we should be able to kind of pick-thru and append based on,
if (debug) console.log('spec apres deserialize', spec, 'from bytes', bytes)
// we gucc ?
if (spec.name == null || spec.type == null) {
throw new Error('null spec, wyd?')
}
return spec
}
/* --------------------------- ---------------------------- */
/* -------------------- DEFS in the DOM ---------------------- */
/* --------------------------- ---------------------------- */
let newDef = (spec) => {
// hmmm ok, we have checked that this is new, not an update,
if (verbose) console.log('ready write new spec to def', spec)
// writing the hunk definition auto-drops it into this.plane ...
let def = new HunkDefinition(spec, this, dt, false)
defs.push(def)
// got 'em, and writing it sets it into the dom, at 0,0
// now let's see if there is any 'native' content
// we can only do this in a top level view,
let native, ni
if (this.isTopLevel) {
ni = cuttlefishHunkRef.findIndex((hunk) => {
return hunk.ind === def.ind
})
native = cuttlefishHunkRef[ni]
if (native && def.name !== 'tlview') {
try { // try to add the dom element to the def's domelement
// native is a handle on the hunk itself,
//console.log('def', def)
def.takeNative(native)
// clear from the other list,
cuttlefishHunkRef.splice(ni, 1)
// if it's a child,
if (native.type === 'view' && this.name === 'tlview') {
native.tlv = this
}
console.log(`VW ${this.name} wrote a native hunk into the dom for ${def.name}`)
//msgbox.write(`wrote a native hunk into the dom: ${def.name}`)
} catch (err) {
console.log(err)
//msgbox.write(`native hunk attach fails ${err}`)
}
}
}
// ok, we have the def, now we need to (1) place it and also (2)
// hook it up to any native dom elements (cuttlefish only)
// we'll find the menu, if it's about, this is a nice place to place
let mt
let menu = $(this.dom).children('.contextmenu').get(0)
if (menu !== undefined) {
mt = dt.readTransform(menu)
$(menu).remove()
}
// we want to make sure that's in-bounds ...
let bounds = this.getCurrentBounds()
// console.log('bounds', bounds)
// console.log(`view ${this.name} adding ${def.name}, bounds`, this.getCurrentBounds())
if (!mt) {
// todo: put this somewhere on-screen ?
// but if no menu is up, likely a load from patch
mt = {}
mt.x = Math.random() * bounds.w - 100
mt.y = Math.random() * bounds.h - 100
def.floaters[0].moveTo(mt.x, mt.y)
} else {
if (mt.x > bounds.w) mt.x = bounds.w - 100
if (mt.y > bounds.h) mt.y = bounds.h - 100
def.floaters[0].fixTo(mt.x, mt.y)
}
// kick does mucho stuff, mostly incl. restarting the sim
this.kick()
// ...
return def
} // FIN NEWDEF
let updateDef = (spec) => {
// console.error('updateDef probably error-filled after floop (?)')
// update should change names, values, but not types or orders
// with an update, we maintain link topology
// with a replace, we wipe link topology,
// and manager is responsible for updating us with new links
console.log('updateDef with this spec:', spec)
let cdef = defs[spec.ind]
// check name,
if (spec.name !== cdef.name) {
cdef.updateName(spec.name)
}
// check states,
for (let st in spec.states) {
if (spec.states[st].value !== cdef.states[st].value) {
cdef.states[st].set(spec.states[st].value)
}
}
} // end update
let replaceDef = (spec) => {
console.warn(`REPLACE DEF at ${spec.ind}`)
let od = defs[spec.ind]
// as a rule, when we replace defs, we unhook everything.
// the manager is responsible for following up by sending us
// links that are still alive,
// console.log('the od', od)
// so first rm those links (this won't properly be tested until program loading, i'd bet)
for (let ip in od.inputs) {
od.inputs[ip].disconnectAll()
}
for (let op in od.outputs) {
od.outputs[op].disconnectAll()
}
// now we can pull any state out that we'd like, i.e.
let oldFloatGroupType = od.floatGroupType
// probably want this special case,
let nt
if (oldFloatGroupType === 'wrappedon') {
// get it ...
nt = od.floaters[0].bag.find((cnd) => {
return cnd.type === 'native'
})
if (!nt) throw new Error("couldn't grab wrapped item during def replace")
}
let oldFloatPositions = []
for (let flt of od.floaters) {
oldFloatPositions.push({
x: flt.x,
y: flt.y,
isFixed: flt.isFixed
})
}
// now the rm,
od.cleanup()
delete defs[spec.ind]
// and replace it,
let nd = new HunkDefinition(spec, this, dt, false)
// write-over and cut all connections, we'll receive new ones
// ok, ready to place and append,
if (oldFloatGroupType === 'unwrapped') {
nd.unwrap()
} else if (oldFloatGroupType === 'wrappedon') {
// ok, so, a likely-bugs-forward au-manuel implementation,
nd.floatGroupType = 'wrappedon'
nd.reciprocalView = $(nt.de).find('.view').get(0).hunk
// oh god, look away !
// this works, but doesn't retain the resizing element, soooo
nd.floaters[0].grouptype = 'wrapped'
nd.floaters[0].bag.push(nt)
nd.floaters[0].typeset.push('native')
dt.makeResizeable(nt.de, nd.floaters[0].onElementResize, nd.reciprocalView.onresize)
nd.floaters[0].onChange()
} else if (oldFloatGroupType === 'edgecased') {
nd.edgecase()
}
// and reset the positions
for (let ps in oldFloatPositions) {
let pos = oldFloatPositions[ps]
let odf = nd.floaters[ps]
if (odf && pos) {
if (pos.isFixed) {
odf.fixTo(pos.x, pos.y)
} else {
odf.moveTo(pos.x, pos.y)
}
}
}
// and replace it in the list,
defs[spec.ind] = nd
// and re-render
this.kick()
// occasionally useful,
return nd
}
/* --------------------------- ---------------------------- */
/* ------------------ NATIVE HUNK MANAGEMENT ----------------- */
/* --------------------------- ---------------------------- */
// for cuttlefish hunks, with dom elements ...
let cuttlefishHunkRef = new Array()
this.take = (hunk) => {
cuttlefishHunkRef.push(hunk)
}
/* --------------------------- ---------------------------- */
/* ---------------------- REMOVE DEFS ------------------------ */
/* --------------------------- ---------------------------- */
let removeDef = (index) => {
console.log('removeDef, also many floop likely errors')
// ok ok ok
let od = defs[index]
if (od === undefined) throw new Error('no hunk to delete!')
// else,
for (let ip in od.inputs) {
od.inputs[ip].disconnectAll()
}
for (let op in od.outputs) {
od.outputs[op].disconnectAll()
}
// ok ...
defs.splice(index, 1)
// ordering
onDefReorder()
// and x2thegrave
od.cleanup()
}
let onDefReorder = () => {
for (let ind in defs) {
if (defs[ind].ind !== parseInt(ind)) {
console.log(`swapping ${defs[ind].ind} for ${parseInt(ind)}`)
defs[ind].newInd(parseInt(ind))
}
}
}
/* --------------------------- ---------------------------- */
/* ------------------- PUT AND CUT LINKS --------------------- */
/* --------------------------- ---------------------------- */
let putLink = (outInd, outputInd, inInd, inputInd) => {
try {
let outputdef = defs[outInd].outputs[outputInd]
let inputdef = defs[inInd].inputs[inputInd]
outputdef.connect(inputdef)
this.kick()
} catch (err) {
console.error('ERR at putlink', err)
//msgbox.write('ERR at putlink: ' + err)
console.log(`outInd ${outInd}, outputInd ${outputInd}, inInd ${inInd}, inputInd ${inputInd}`)
return false
}
return true
}
let cutLink = (outInd, outputInd, inInd, inputInd) => {
try {
let outputdef = defs[outInd].outputs[outputInd]
let inputdef = defs[inInd].inputs[inputInd]
//console.log('dc', performance.now())
//console.log('to disconn', outputdef, 'from', inputdef)
outputdef.disconnect(inputdef)
this.kick()
return true
} catch (err) {
//console.log('ERR at rmlink', err)
//msgbox.write('ERR at rmlink' + err)
return false
}
return false
}
/* --------------------------- ---------------------------- */
/* ----------------- HOTMESSES -> SERIALMSGS ----------------- */
/* --------------------------- ---------------------------- */
// herein lay each of our unit requests, as promises,
// hello is a ping,
this.sayHelloToManager = () => {
return new Promise((resolve, reject) => {
let helloTime = performance.now()
//msgbox.write(`said hello to manager at ${helloTime}ms`)
promiseThis([MK.HELLO], () => {
//msgbox.write(`manager says hello, took ${performance.now() - helloTime}ms`)
resolve(performance.now() - helloTime)
}, (errmsg) => {
reject(errmsg)
})
})
}
// we can ask for top-level descriptions, or hunks descriptions (by index)
this.queryManager = (ind) => {
return new Promise((resolve, reject) => {
if (ind !== undefined) {
//msgbox.write(`querying manager for hunk at ${ind}`)
let req = [MK.QUERY]
MSGS.writeTo(req, ind, 'uint16')
promiseThis(req, (def) => {
resolve(def)
}, (errmsg) => {
reject(errmsg)
})
} else {
//msgbox.write(`querying top level of manager`)
promiseThis([MK.QUERY], (brief) => {
//msgbox.write(`manager has ${brief.numHunks} hunks`)
resolve(brief)
}, (errmsg) => {
reject(errmsg)
})
}
})
}
// we can ask for a list of available new-stuff to add,
this.requestListAvail = (prefix) => {
// todo: where prefix is like dir/subdir/etc .. to heirarchy dive
if (prefix) console.error('list query with prefixes not written...')
return new Promise((resolve, reject) => {
promiseThis([MK.REQLISTAVAIL], (list) => {
resolve(list)
}, (errmsg) => {
reject(errmsg)
})
})
}
// ask to add one,
this.requestAddHunk = (type, name, states) => {
//console.log('type, states, name', type, states, name)
return new Promise((resolve, reject) => {
let msg = [MK.REQADDHUNK]
MSGS.writeTo(msg, type, 'string')
// when requesting a hunk with state option,
// we just throw down (similar to descriptions) a list of
// names, types, values ...
// these will not always be used, but this is an
// exercise in consistency at the moment
// in an array
// if its named,
if (name) {
msg.push(HK.NAME)
MSGS.writeTo(msg, name, 'string')
}
if (states) {
for (let st of states) {
msg.push(HK.STATE)
MSGS.writeTo(msg, st.name, 'string')
MSGS.writeTo(msg, st.type, 'string')
MSGS.writeTo(msg, st.value, st.type)
}
}
promiseThis(msg, (def) => {
resolve(def)
}, (errmsg) => {
console.error('error while adding hunk', errmsg)
reject(errmsg)
})
})
}
// to name them
this.requestRenameHunk = (ind, name) => {
return new Promise((resolve, reject) => {
let msg = [MK.REQNAMECHANGE]
MSGS.writeTo(msg, ind, 'uint16')
MSGS.writeTo(msg, name, 'string')
promiseThis(msg, (def) => {
resolve(def)
}, (errmsg) => {
reject(errmsg)
})
})
}
// to change their state
this.requestStateChange = (state, value) => {
return new Promise((resolve, reject) => {
let msg = [MK.REQSTATECHANGE]
MSGS.writeTo(msg, state.parent.ind, 'uint16')
MSGS.writeTo(msg, state.ind, 'uint8')
// not unlikely mess here,
try {
MSGS.writeTo(msg, value, state.type)
} catch (err) {
reject(err)
console.log('that type err:', err)
//msgbox.write('err serializing state change request, see console for that type err')
}
promiseThis(msg, (stdef) => {
//console.log('promise returns for state change for def', stdef)
resolve(stdef)
}, (errmsg) => {
//console.log('promise rejects for state change bc error', errmsg)
reject(errmsg)
})
})
}
// or remove them,
this.requestRemoveHunk = (ind) => {
return new Promise((resolve, reject) => {
let msg = [MK.REQRMHUNK]
MSGS.writeTo(msg, ind, 'uint16')
promiseThis(msg, (rmid) => {
resolve(rmid)
}, (errmsg) => {
console.error('error while removing hunk', errmsg)
reject(errmsg)
})
})
}
// and we can add links
this.requestAddLink = (output, input) => {
return this.requestAddLinkLikeACaveman(output.parent.ind, output.ind, input.parent.ind, input.ind)
}
this.requestAddLinkLikeACaveman = (outInd, outputInd, inInd, inputInd) => {
return new Promise((resolve, reject) => {
let msg = [MK.REQADDLINK]
MSGS.writeTo(msg, outInd, 'uint16')
MSGS.writeTo(msg, outputInd, 'uint8')
MSGS.writeTo(msg, inInd, 'uint16')
MSGS.writeTo(msg, inputInd, 'uint8')
promiseThis(msg, (link) => {
resolve(link)
}, (errmsg) => {
reject(errmsg)
})
})
}
// or remove them
this.requestRemoveLink = (output, input) => {
return new Promise((resolve, reject) => {
let msg = [MK.REQRMLINK]
MSGS.writeTo(msg, output.parent.ind, 'uint16')
MSGS.writeTo(msg, output.ind, 'uint8')
MSGS.writeTo(msg, input.parent.ind, 'uint16')
MSGS.writeTo(msg, input.ind, 'uint8')
promiseThis(msg, (link) => {
resolve(link)
}, (errmsg) => {
reject(errmsg)
})
})
}
/* --------------------------- ---------------------------- */
/* ------------------------ RECEPIES ------------------------- */
/* --------------------------- ---------------------------- */
// with recepies, we take the atomic units above and do larger order operations
this.refresh = () => {
return new Promise((resolve, reject) => {
// wipe ya docs, and ask yonder manager for a complete description
// everything is friggen jquery, so check it out
console.log("REFRESHING THE VIEW")
//msgbox.clear()
//msgbox.write('setting up to refresh the view')
for (let def of defs) {
def.cleanup()
}
// also,
defs.length = 0
// since this occasionally takes time, complete the wipe:
this.kick()
// hello first,
this.sayHelloToManager().then(() => {
this.queryManager().then((brief) => {
console.log('BRIEF', brief)
//this.msgbox.briefState.setFromBrief(brief)
// should have a list then, do that brief then ?
let nh = brief.numHunks
// the repeater / promise walker
let cbnh = (n) => {
this.queryManager(n).then((def) => {
n++
if (n < nh) {
// case not-complete, so continue
//msgbox.write(`caught ${n}th hunk, continuing...`)
cbnh(n)
} else {
// now that we have it all, we can read in the
// yikes w/r/t this tree
for (let df in defs) {
let def = defs[df]
for (let op in def.outputs) {
let otp = def.outputs[op]
if (otp.specConnections) {
for (let sc of otp.specConnections) {
try {
putLink(df, op, sc[0], sc[1])
} catch (err) {
console.error('cannot reconnect from spec', err)
//msgbox.write(`err while trying to make links from spec`)
}
}
delete op.specConnections
}
}
}
// finally
//msgbox.write(`~ view refresh complete ~`)
this.hasRefreshed = true
this.tlv.globalOrganize()
resolve(this)
}
}).catch((err) => {
console.error(err)
//msgbox.write(`trouble while requesting ${n}th hunk`)
reject(err)
})
}
// the kickoff,
cbnh(0)
})
})
})
}
this.reloadHunk = (indice, restoreState) => {
// reloads from source (devtool) if restoreState is set,
// ... not going to write the version of this that restores state
// we ask for state on the reset
let od = this.defs[indice]
let odx = od.floaters[0].x
let ody = od.floaters[0].y
// this in conjunction with error catching during the addhunk step ...
// we can wrap all of this up in try / catch ? and delete things only when
// those all pass ??
// first, let's add in the new hunk:
this.requestAddHunk(od.type).then((def) => {
// let's compare these ? for matching inputs, outputs, reconnect:
let matching = (odp, ndp, ind) => {
if (odp[ind]) {
if (odp[ind].name === ndp[ind].name && odp[ind].type === ndp[ind].type) return true
} else {
return false
}
}
// these links are all added on a promise, meaning we aren't sure (by the time this runs to completion)
// whether or not they are successful...
// with better view -> manager integration, this would be mucho easier
for (let ip in def.inputs) {
if (matching(od.inputs, def.inputs, ip)) {
for (let cn of od.inputs[ip].connections) {
this.requestAddLink(cn, def.inputs[ip])
}
}
}
for (let op in def.outputs) {
if (matching(od.outputs, def.outputs, op)) {
for (let cn of od.outputs[op].connections) {
this.requestAddLink(def.outputs[op], cn)
}
}
}
// we should wait for everything all async like, but
this.requestRemoveHunk(od.ind).then(() => {
$(this.dom).find('.contextmenu').fadeOut(400, function() {
$(this).remove()
})
// and also,
def.floaters[0].fixTo(odx, ody)
})
}).catch((err) => {
this.changeContextTitle('error on reload, see consoles')
setTimeout(() => {
$(this.dom).find('.contextmenu').fadeOut(400, function() {
$(this).remove()
})
}, 2000)
console.error('caught request add hunk error from reload site')
})
// let's load one in w/ the changes ... if we can do this we'll go thru the rest
}
/* --------------------------- ---------------------------- */
/* --------------------- MESSAGES OUTPUT --------------------- */
/* --------------------------- ---------------------------- */
let outmsgbuffer = new Array()
let writeMessage = (bytes) => {
if (!msgsout.io && outmsgbuffer.length < 1) {
// str8 shooters
if (msgverbose) this.log('msg out', bytes)
msgsout.put(bytes)
} else {
// gotta buffer
outmsgbuffer.push(bytes)
this.log('VIEW OUTBUFFER LEN', outmsgbuffer.length)
}
}
// it's often very useful tracking completion, so
let callbackSets = new Array()
let cbid = 0
let promiseThis = (bytes, callback, errback) => {
cbid++
if (cbid > 254) {
cbid = 0
} // could be safe about this, for now we take for granted less than 255 out,
// remember it
callbackSets.push({
key: bytes[0],
id: cbid,
callback: callback,
errback: errback
})
// so our header is like - we don't explicitly type the id, it's a byte
let head = [MK.MSGID, cbid]
// and we concat this to
bytes = head.concat(bytes)
writeMessage(bytes)
}
/* --------------------------- ---------------------------- */
/* ------------------ MESSAGES INPUT, LOOP ------------------- */
/* --------------------------- ---------------------------- */
// still looking for clear function naming / message naming
this.loop = () => {
// THE Q: is it trees or is it inputs / outputs ? ports ...
if (msgsin.io) {
let msg = msgsin.get()
if (msgverbose) console.log('VIEW RX MSG:', msg)
if (!Array.isArray(msg)) throw new Error(`view throwing object message, having header ${msg.header}`)
// ... mirror for views
let temp
let inc = 0
// track the msg id response, and that callback
let msgid, cbd
// find the element, set to cbd ref and delete from array
if (msg[0] === MK.MSGID) {
msgid = msg[1]
inc = 2
// and find the callback we registered
let cbdi = callbackSets.findIndex((cand) => {
return cand.id === msgid
})
if (cbdi !== -1) {
cbd = callbackSets[cbdi]
callbackSets.splice(cbdi, 1)
}
}
// if a msgid, find the callback
// ok, get the id and increment ...
switch (msg[inc]) {
case MK.ERR:
if (msgverbose) console.log('VIEW MSG is an error')
let errmsg = MSGS.readFrom(msg, inc + 1, 'string').item
console.error('manager returns an error', errmsg)
//msgbox.write(errmsg)
if (cbd) cbd.errback(errmsg)
break
case MK.HELLO:
if (msgverbose) console.log('VIEW MSG is hello')
if (cbd) {
cbd.callback()
} else {
console.log(`manager says hello!`)
//msgbox.write(`manager says hello!`)
}
break
case MK.BRIEF:
if (msgverbose) console.log('VIEW MSG is a manager brief')
let brief = {}
inc++
// reading strings returns item, increment
temp = MSGS.readFrom(msg, inc, 'string')
inc += temp.increment
brief.interpreterName = temp.item
temp = MSGS.readFrom(msg, inc, 'string')
inc += temp.increment
brief.interpreterVersion = temp.item
temp = MSGS.readFrom(msg, inc, 'uint16')
inc += temp.increment
brief.numHunks = temp.item
// set local,
this.interpreterName = brief.interpreterName
this.interpreterVersion = brief.interpreterVersion
// and write it down
//msgbox.briefState.setFromBrief(brief)
if (cbd) cbd.callback(brief)
break
case MK.LISTOFAVAIL:
if (msgverbose) console.log('VIEW MSG is a list of available items')
let stringlist = MSGS.readListFrom(msg, inc + 1, 'string')
if (cbd) {
cbd.callback(stringlist)
} else {
// this shouldn't happen w/o a request attached
console.error('something is up !')
}
break
case MK.HUNKALIVE:
// this can refer to new hunks, and modified hunk descriptions
// i.e. changing a list of outputs and inputs
if (msgverbose) console.log('VIEW MSG is a new hunk')
let spec = specBySerial(msg, inc + 1, false)
// so first we check for an existing hunk,
let nd
if (defs[spec.ind] === undefined) {
nd = newDef(spec)
//msgbox.briefState.decrementHunks()
msgbox.write(`added a new hunk: ${spec.name}`)
} else {
if (verbose) console.log('replacing hunk at', spec.ind)
nd = updateDef(spec)
//msgbox.write(`updated ${spec.name}_${spec.ind} with a new definition`)
}
if (cbd) cbd.callback(nd)
break
case MK.HUNKREPLACE:
// to add inputs or outputs, this is sent, all links are eliminated,
// and they are sent again by the manager
if (msgverbose) console.log('VIEW MSG is a hunk replacement')
let repSpec = specBySerial(msg, inc + 1, false)
if (defs[repSpec.ind] === undefined) {
console.error('received hunk replace for non existent hunk')
console.error('ere is the spec:', repSpec)
} else {
replaceDef(repSpec)
//msgbox.write(`replaced ${repSpec.name}_${repSpec.ind} with a new definition`)
if (cbd) cbd.callback(nd)
}
break
case MK.HUNKSTATECHANGE:
if (msgverbose) console.log('VIEW MSG is a state change')
let stchHnkInd = MSGS.readFrom(msg, inc + 1, 'uint16').item
let stchStInd = MSGS.readFrom(msg, inc + 4, 'uint8').item
let stDef
try {
stDef = defs[stchHnkInd].states[stchStInd]
let stValUpdate = MSGS.readFrom(msg, inc + 6, stDef.type).item
stDef.set(stValUpdate)
if (cbd) cbd.callback(stDef)
//msgbox.write(`changed state at ${stchHnkInd} ${stDef.name} to ${stValUpdate}`)
} catch {
// this is a likely early arrival of a state change update,
// meaning we are likely *still* going to rx the def, with the update
// so ... continue
console.warn('state change request cannot find stDef, likely async manager messages, probably ok')
}
break
case MK.HUNKREMOVED:
if (msgverbose) console.log('VIEW MSG is a hunk to remove')
let rmid = MSGS.readFrom(msg, inc + 1, 'uint16').item
removeDef(rmid)
if (cbd) cbd.callback(rmid)
//msgbox.write(`rm'd hunk ${rmid}`)
this.drawLinks()
break
case MK.LINKALIVE:
if (msgverbose) console.log('VIEW MSG is a link to put')
let nlnk = {}
//console.log("LINK MSG IS", msg)
nlnk.outInd = MSGS.readFrom(msg, inc + 1, 'uint16').item
nlnk.outputInd = MSGS.readFrom(msg, inc + 4, 'uint8').item
nlnk.inInd = MSGS.readFrom(msg, inc + 6, 'uint16').item
nlnk.inputInd = MSGS.readFrom(msg, inc + 9, 'uint8').item
if (putLink(nlnk.outInd, nlnk.outputInd, nlnk.inInd, nlnk.inputInd)) {
//msgbox.write(`added a link`)
if (cbd) cbd.callback(nlnk)
} else {
console.error('err for view to putLink')
if (cbd) cbd.errback('err for view to putLink')
}
break
case MK.LINKREMOVED:
if (msgverbose) console.log('VIEW MSG is a link to cut')
let rlnk = {}
rlnk.outInd = MSGS.readFrom(msg, inc + 1, 'uint16').item
rlnk.outputInd = MSGS.readFrom(msg, inc + 4, 'uint8').item
rlnk.inInd = MSGS.readFrom(msg, inc + 6, 'uint16').item
rlnk.inputInd = MSGS.readFrom(msg, inc + 9, 'uint8').item
if (cutLink(rlnk.outInd, rlnk.outputInd, rlnk.inInd, rlnk.inputInd)) {
//msgbox.write(`removed a link`)
if (cbd) cbd.callback(rlnk)
} else {
console.error('err for view to cutLink')
if (cbd) cbd.errback('err for view to cutLink')
}
break
default:
throw new Error(`view receives message with no switch: ${msg}`)
} // end msgs switch
} // end if-have-message
// MSGS output check,
if (outmsgbuffer.length > 0) {
let debug = true
if (!msgsout.io) {
if (debug) {
let msg = outmsgbuffer.shift()
if (msgverbose) this.log(`buffer release msg type: ${msg.header}`)
//console.log(msg.content)
msgsout.put(msg)
} else {
msgsout.put(outmsgbuffer.shift())
}
}
} // end msgs output check
} // end loop
}
export default View