diff --git a/README.md b/README.md index fdcaf83415897e3e79e87bd5a2d4807da91e47e0..f84ccd55d02f17b75decf768635b3d6860c2c820 100644 --- a/README.md +++ b/README.md @@ -16,122 +16,66 @@ Native Cuttlefish 'Hunks' are given access to a DOM/div element, making them nic 'aye, but as the legends state: software doth make the hardware singeth' -## The Current Pickle +IAA ... it's all addressing -> -> -while writing up the patchset tool, to load 'patches' (collections of hunks and links), and proceeded by a realization earlier this morning, I am thinking that I should re-cast messages more explicitly as trees: in particular, that I should index and ID objects (hunks, states, etc) by their position in the arrays where they live. this prevents me from needing to do string-based lookups (which is easy in js, but slow in real life) to make local calls via the network. i.e - it's routing all the way down. I'm going to split this branch here, and start re-rolling through serialization to go again. +remote managers can't just throw messages about: they have to have some contextual awareness of the view ? or their connectedness ? can keep existing structure and just wait until have-said-hello or not -as I'm rolling through this I'm coming to a question about state item management. right now, what I've set out to do its: - - load hunk(s) - - and then, if program-saved state is different in a hunk, make a 'statechange request', and wait for those to come back in whatever state they do. - - this means that I don't get updated state when programs init, which is how it would it would presumably work inside of a manager opening up a program : load the hunk, write the state values, init, loop - - instead I'll see load -> loop -> make statechange requests - - while this feels a bit cumbersome at the moment, it might be nice because this way when I'm writing hunks I'm only writing 'onchange' functions for state, and I can assume that init happens with default values. - - i'm weary of this approach for systems that I want to run without the view in play, but that point is a bit farther away. code is easy to rewrite. + -> added 'isConnectedTo' feature, won't work when connections disappear though ! -I'm also thinking that state-messages might want to be 'routed' - like stateChange should go by state[index], rather than looking up names. the same might be nice of hunks in a list, but then I am having to assign key to each one ! ... let's see how much of a PITA id-lookups are in the next ponyo loop +... so, and view needs to be sensitive to that, and watch state on a refresh also ? -w/r/t ponyo - I'll have to see how difficult this is when writing motor control hunks; what about enable pins etc, that are important to startup in known conditions ?? +... towards decent views ... and a debug-thru-cuttlefish portal for ponyo + - patchload is nice, ok, this direction + - layout ... save positions in patches ? unf- force layout ? + - write cobs, and a cobs patch -> proto logs out into a browser + - ... link work ? ... links agreeing with one another before startup + - results in link saying 'updatehunkdefinition' and manager ... reporting ? or not ? + - load -> then load -> then log, up through a few links -another way to write these is to send 'addhunk' requests with names, potentially ids, and potentially state-index updates, before inits happen. this feels cleaner, and I think I need to do this now +## On Friday the Hardware Singeth -ok, I want to chart a path through this ... I need to - - load programs, - - sidebar: re-write addHunk(name, id, state) ... and messages accordingly, and manager. ok. - - double sidebar: everything-by-index-of-list, with lenBytes ? - - hunk - alive (index) - descr ... (also tree) - - removed (index) eom (and assume bump-down?) - - update (index) - state (index) - value - - links - descr() - - problems: - - ids !== indexes ? both ? uuids ? +ok, patches via this (numexcludes) hack ... that's ... just fine +before walking over, probably a few final rites + - just sanity check the manager, probably write something (including notes about what-happens-next) ... and, like, where are you at with the manager message set? is this a complete set? consider embedded ... minimizing embedded work, and the corner cases like state loading and state-before-init ... ! + - before you 'take the dive', wrap up typeset, and write those things down also + - also, to avoid insanity, maybe take tuesday morning for hardware / swd debugging ? load that bootloader, say hello to ponyo ? - - some of the issue is a worry about the speed of string == calls in cpp, - - and regardless, a find-by-lookup rather than a route-to-item structure - - I think, given the world as it is, route-to-item makes a lot of sense - - this is also going to pinch message sizes - - fak - - this is a lot to re-write +really, genuinely, things you'll want as you go + - bring back splash loading ? save positions into patches? I'm imagining a more complete system with in heavy program-reps-in-json only world ... +ok, for the future, save will only be possible with a 'save selected' patch or something similar: picking whether bootstrap hunks should be added etc ... I can't make this decision right now - - 2nd sidebar: states by index ? +for now, just don't save those... you know which ones they are, and you can check the version in the view, and have a standard (known) bootstrap program set. ok... then we can rip thru adding programs ! - - load a program that speaks to nautilus - - (and then) load a nautilus program that speaks to ponyo - - then I'm on trax for ponyo dev, that's ok - - I think I can hit all of these steps today ... nt soon, - - it's probably worth some time at that point to organize the net-via-net-via-view issue? this problem as it goes ... +still need to work out those relative-links-and-indices -once I'm back at ponyo, wakeup, I'll minmax some pnp time to make the headboards, and a stepper - - -> view - - could save some headache, place contextmenu in top-level dom, not on plane? makes some sense - - style the save-patch input box to match states - - -> typeset - - do name -> names: ['float64', 'number'], for same byte-cast for colloquial type cast ... i.e. uint8 and byte, ? what else ? is it worth it, or just copy pasta ? should also be lists of things that can be hooked up ! - - what of these hunks that will autoload each time, the bootstraps? - ... np, fam, when we send a request to the manager to add them, - managers should just reply w/ an error - we have paths for that - this is a bug we can iron out later and ignore - - -> with programs, prototype a bootstrap that loads cuttlefish and waits ... then loads a program there ? - -maybe this is something of a JAMTOC moment ... the view stores and manipulates represenations, we only need a minimum set of messages to load that representation remotely - -I'm very close, though, and want to focus. I should be talking to a nautilus manager tonight and prototyping how I want remote resources to open up from cuttlefish. - -OK. I'm just through serializing things. I'd really like to be into ponyo early tomorrow. - -So I'm going to take a minute and see if I can find a reasonable way to scrape view into a few different files... - - view.js ... is messaging entry point, loop and init ... - - vdom.js ... writedefdom, etc (done) - - vmsg.js ... that message board (pull this out first) (done) - - vbzt.js ... bztools (this next) (done) - - vflo.js ... force layout (next time) - - then I can do like vprgmem.js (ok) - -questions - - why does a new view init with the same 'ok' in the titlebox / msgbox ? - - because #ids are not really unique and you were jquerying to the (0th) instance +this is an impatience thing, I should note. on another loop through the view, this will be first order. ... -then, go to nautilus, write cobserial, consider how to quickload program for ponyo test -The next meaningful work on ponyo happens when I can: - - easily load the messy program-via-links that is cuttlefish -> nautilus with-a-link, from cuttlefish, with minimal clicks (assuming a running nautilus scratch) +monday circuits, programming, ponyo hello / refresher, and instron assembly ? --> manager cleanup, ref to old code ... - - refresh wipes and then asks for currently-running program, - - i doth want to break the view up into constituent parts, +cuttlefish -> nautilus on tuesday, and see about drawing links // coordinating across links 'well' (but what of the split link ?) --> now, to have a day at it, finish writing and go through view as you do - - at the very moment, to test removing a link, find link-rm-message-writing code again in view, and modify it to work as a serial message ... then fill in on the manager side. this is a good time to break view up! - - then hunk state changes ... - - then adding links ... etc +ponyo late tueday, and wednesday, +thursday machine assy ... friday same, probably - - now (maybe) bring program loading back into the view? saving programs? +## A Pickle Averted -Here's my list of things I think I will need ... + - ctrl f for INDSWAP + - and RETURN -## Views can send ... +program save / load + - rewrite / integrate with briefstate messages ? + - once up, test deleting etc, some uncertainty about connection unhooking -hello -> eom -reqprogram -> eom -reqlistavail -> eom -reqaddhunk -> (string) name -reqstatechange -> (string) hunkid, (uint8) index, (type) value -reqaddlink -> (string) outId, (uint8) otpi, (string) inId, (uint8) inpti + // for interest, 'startStateRefresh()' inside of vptch.js is where you broke off -## Managers can reply ... + // to restart, go back to saving patches: do by index, etc... and then on reloads, you'll have to contend with that same-ness issue ... i.e. those first two hunks, or whatever other bootstrap structure is being replaced -brief -> (string) interpreter, (uint16) numhunks, (uint16) numlinks -listofavail -> (list of strings) -hunkalive -> (hunk serialization) -hunkremoved -> (string) id -hunkstatechange -> (state change args) -linkalive -> (link args) -linkremoved -> (link args) +links, cuttlefish + - going to have to wrastle type conversion allowances, i.e. want to go from num -> uint8 at the link, fluidly, and want to be able to name js types with 'uint8' - particularely at the link, try this first, that's your entry bug + - link also doesn't build inputs / outputs when the names are 1 char long --- diff --git a/bootstrap.js b/bootstrap.js index dc6bc8b7369d2ba3f727fc462da49442d9e31082..aca6859cfc80a50fc95fa5f6713d71bde449534c 100644 --- a/bootstrap.js +++ b/bootstrap.js @@ -16,38 +16,41 @@ import Manager from './hunks/manager.js' // our invisible overlord let NROL = new Manager() -NROL.name = 'manager' -NROL.id = 'NROL39_0' NROL.init() // this is going to sit in the outbuffer until the view resolves function bootloop() { - // js u wyldin ! - try{ - NROL.loop() - } catch (err) { - console.log('top level err:', err) - } - setTimeout(bootloop) + // js u wyldin ! (this is probably slow) + //let go = true + try { + NROL.loop() + } catch (err) { + //go = false + console.error('top level err:', err) + } + setTimeout(bootloop) } // want handles on this we think ? let View = {} window.onload = () => { - console.log('BOOTUP') - NROL.addHunk('view', 'TLView').then((view) => { - // console.log('ADDHUNK VIEW RESOLVES') - View = view - $(View.dom).attr('id', 'NROLVIEW') - $('#wrapper').get(0).append(View.dom) - }).then(() => { - NROL.addLink(View.id, 'msgs', NROL.id, 'msgs') - NROL.addLink(NROL.id, 'msgs', View.id, 'msgs') - bootloop() - // kick this later - setTimeout(View.refresh, 150) - }).catch((err) => { - console.log(err) - }) + console.log('BOOTUP') + NROL.addHunk('view').then((view) => { + // console.log('ADDHUNK VIEW RESOLVES') + View = view + view.isTopLevelView = true + // not really sure about this yet + $(View.dom).attr('id', 'NROLVIEW') + $('#wrapper').get(0).append(View.dom) + }).then(() => { + // outHunkIndex, outIndex, inHunkIndex, inIndex, debug + NROL.addLink(1, 0, 0, 0, false) + NROL.addLink(0, 0, 1, 0, false) + bootloop() + // kick this later + setTimeout(View.refresh, 150) + }).catch((err) => { + console.log(err) + }) } diff --git a/hunks/comm/websocketclient.js b/hunks/comm/websocketclient.js index c6a53fcf1a7e44ae612f7fb5606ac1654ed63bd3..d94956c5c5d8429b33ac7a8e7cf22b44728c12bf 100644 --- a/hunks/comm/websocketclient.js +++ b/hunks/comm/websocketclient.js @@ -12,21 +12,24 @@ import { } from '../hunks.js' function WebSocketClient() { - Hunkify(this, 'WebSocketClient') + Hunkify(this) - let dtin = new Input('Object', 'data') + let debug = false + + let dtin = new Input('byteArray', 'data', this) this.inputs.push(dtin) - let dtout = new Output('Object', 'data') + let dtout = new Output('byteArray', 'data', this) this.outputs.push(dtout) // TODO is tackling state sets / updates / onupdate fn's // this is hunk -> manager commune ... - let statusMessage = new State('String', 'status', 'closed') - let retryCountHandle = new State('Number', 'retrycount', 3) - let addressState = new State('String', 'address', '127.0.0.1') - let portState = new State('String', 'port', '2042') - this.state.push(statusMessage, retryCountHandle, addressState, portState) + let statusMessage = new State('string', 'status', 'closed') + let retryCountHandle = new State('number', 'retrycount', 3) + let resetRetryHandle = new State('boolean', 'retryreset', false) + let addressState = new State('string', 'address', '127.0.0.1') + let portState = new State('number', 'port', 2042) + this.states.push(statusMessage, retryCountHandle, resetRetryHandle, addressState, portState) // this ws is a client, let ws = {} @@ -37,21 +40,27 @@ function WebSocketClient() { setTimeout(startWs, 500) } + resetRetryHandle.change = (value) => { + retryCountHandle.set(3) + startWs() + // to actually change the value, we would do: + // resetRetryHandle.set(value) + } + let startWs = () => { // manager calls this once // it is loaded and state is updated (from program) url = 'ws://' + addressState.value + ':' + portState.value - console.log('INIT WS', url) + if(debug) console.log('INIT WS', url) ws = new WebSocket(url) + ws.binaryType = "arraybuffer" ws.onopen = (evt) => { this.log('ws opened') - this.log('the ws object', ws) - this.log(evt) - statusMessage.set('opened') + statusMessage.set('open') } ws.onerror = (evt) => { this.log('ws error, will reset to check') - console.log(evt) + if(debug) console.log(evt) statusMessage.set('error') setCheck(500) } @@ -59,12 +68,18 @@ function WebSocketClient() { this.log('ws close') setCheck(500) } - ws.onmessage = (msg) => { - console.log("WS RX:", msg.data) - if (!dtout.io && this.outbuffer.length === 0) { - dtout.put(JSON.parse(msg.data)) + ws.onmessage = (message) => { + // this should be a buffer + if(debug) console.log('WS receives', message.data) + // tricks? + // ok, message.data is a blob, we know it's str8 up bytes, want that + // as an array + let msgAsArray = new Uint8Array(message.data) + if(debug) console.log('WS receive, as an array:', msgAsArray); + if (dtout.ie && this.outbuffer.length === 0) { + dtout.put(msgAsArray) } else { - this.outbuffer.push(msg.data) + this.outbuffer.push(msgAsArray) } } statusMessage.set('connecting ...') @@ -126,10 +141,12 @@ function WebSocketClient() { if (ws !== null && ws.readyState === 1) { // no buffering if (dtin.io) { - let message = dtin.get() + let arr = dtin.get() + if(debug) console.log('WS transmission as array', arr) + let bytesOut = Uint8Array.from(arr) // HERE insertion -> buffer.from() ? - console.log("WS SENDING", message) - ws.send(JSON.stringify(message)) + if(debug) console.log("WS sending buffer", bytesOut.buffer) + ws.send(bytesOut.buffer) } } diff --git a/hunks/hidden/primitives/objects/move.js b/hunks/hidden/primitives/objects/move.js index f17afd4c3fde76a1c2052c3fb56121e060d3283e..ec29f85e0b00efb69c921c033093d358ed03654c 100644 --- a/hunks/hidden/primitives/objects/move.js +++ b/hunks/hidden/primitives/objects/move.js @@ -15,7 +15,7 @@ function Name() { this.inputs.a = Input('type', 'name') this.outputs.b = Output('type', 'name') - this.state.item = State('type', 'name') + this.states.item = State('type', 'name') this.init = () => { // manager calls this once diff --git a/hunks/hidden/primitives/objects/primal/array.js b/hunks/hidden/primitives/objects/primal/array.js index f17afd4c3fde76a1c2052c3fb56121e060d3283e..ec29f85e0b00efb69c921c033093d358ed03654c 100644 --- a/hunks/hidden/primitives/objects/primal/array.js +++ b/hunks/hidden/primitives/objects/primal/array.js @@ -15,7 +15,7 @@ function Name() { this.inputs.a = Input('type', 'name') this.outputs.b = Output('type', 'name') - this.state.item = State('type', 'name') + this.states.item = State('type', 'name') this.init = () => { // manager calls this once diff --git a/hunks/hidden/primitives/string.js b/hunks/hidden/primitives/string.js index f17afd4c3fde76a1c2052c3fb56121e060d3283e..ec29f85e0b00efb69c921c033093d358ed03654c 100644 --- a/hunks/hidden/primitives/string.js +++ b/hunks/hidden/primitives/string.js @@ -15,7 +15,7 @@ function Name() { this.inputs.a = Input('type', 'name') this.outputs.b = Output('type', 'name') - this.state.item = State('type', 'name') + this.states.item = State('type', 'name') this.init = () => { // manager calls this once diff --git a/hunks/hidden/primitives/template.js b/hunks/hidden/primitives/template.js index f17afd4c3fde76a1c2052c3fb56121e060d3283e..ec29f85e0b00efb69c921c033093d358ed03654c 100644 --- a/hunks/hidden/primitives/template.js +++ b/hunks/hidden/primitives/template.js @@ -15,7 +15,7 @@ function Name() { this.inputs.a = Input('type', 'name') this.outputs.b = Output('type', 'name') - this.state.item = State('type', 'name') + this.states.item = State('type', 'name') this.init = () => { // manager calls this once diff --git a/hunks/hunks.js b/hunks/hunks.js index ca04ed14ec7cf5212d6c9cd2611e3d38a786332e..492dbbf675cd72193aed85ec6e402a56d455a5a9 100644 --- a/hunks/hunks.js +++ b/hunks/hunks.js @@ -2,22 +2,29 @@ /* ------------------------ HUNKITUP ------------------------- */ /* --------------------------- ---------------------------- */ -function Hunkify(hunk, name) { +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 - hunk.id = null + // because this is always added by .addHunk(path) (path == name) + // we set this during load, only once, this avoids some confusion 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++) { - console.log('LG:', hunk.id, ':', arguments[i]) + str += arguments[i] + ', ' } + console.log(str) } // input, output, and state: ay balls, here we go hunk.inputs = new Array() hunk.outputs = new Array() - hunk.state = new Array() + hunk.states = new Array() // top-secret backdoor hunk.mgr = {} @@ -27,14 +34,16 @@ function Hunkify(hunk, name) { /* -------------------------- INPUT -------------------------- */ /* --------------------------- ---------------------------- */ -function Input(type, name, linktype) { +function Input(type, name, parent, linktype) { this.name = name this.type = type + // need this ... + this.parent = parent this.ref = null this.data = null this.io = false // rules, baby - if(!isOKType(type)){ + if (!isOKType(type)) { throw new Error(`unknown type specified at input, wyd? type: ${this.type}, name: ${this.name}`) } @@ -48,7 +57,7 @@ function Input(type, name, linktype) { } else { this.data = data // minutia hack for view function - if(ref) this.ref = ref + if (ref) this.ref = ref this.io = true } } @@ -64,8 +73,33 @@ function Input(type, name, linktype) { } } - // hurmmm - if(linktype){ + 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 + } + + // we keep a list as well, + this.connections = new Array() + this.disconnect = (output) => { + let index = this.connections.findIndex((cand) => { + return (cand.parent.ind === output.parent.ind && cand.name === output.name && cand.type === output.type) + }) + 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 op of this.connections) { + op.disconnect(this) + } + } + + // inputs / outputs for 'link' (a hunk) ports have more state, + if (linktype) { // assume clear upstream on startup, maybe dangerous? this.icus = true } @@ -75,9 +109,10 @@ function Input(type, name, linktype) { /* ------------------------- OUTPUT -------------------------- */ /* --------------------------- ---------------------------- */ -function Output(type, name, linktype) { +function Output(type, name, parent, linktype) { this.name = name this.type = type + this.parent = parent this.ref = null this.data = null this.posted = false @@ -108,8 +143,8 @@ function Output(type, name, linktype) { } } this.transport = () => { - for(let pair of this.connections){ - pair.input.put(this.data) + for (let input of this.connections) { + input.put(this.data) } this.posted = true } @@ -133,8 +168,8 @@ function Output(type, name, linktype) { } } this.transport = () => { - for(let pair of this.connections){ - pair.input.put(deepCopy(this.data), this.ref) + for (let input of this.connections) { + input.put(deepCopy(this.data), this.ref) } this.posted = true } @@ -149,50 +184,57 @@ function Output(type, name, linktype) { /* ---------------- OUTPUT MANAGE CONNECTIONS ---------------- */ /* --------------------------- ---------------------------- */ - this.attach = (input, pid) => { + this.attach = (input) => { // no rules on types ATM, historic placeholder - if (this.type === input.type || input.type === 'Any' || true ) { - this.connections.push({ - input: input, - parentId: pid - }) + if (this.type === input.type || input.type === 'any' || true) { + input.connections.push(this) + this.connections.push(input) + return true } else { throw new Error('attempt to attach mismatched types') } } - this.remove = (input, pid) => { + this.remove = (input) => { // find by names pls - let recip = { - input: input, - parentId: pid - } - let pos = -1 - let conn = this.connections.find((cn, i) => { - pos = i - return ((cn.parentId === recip.parentId) && (cn.input.name === recip.input.name)) + let ind = this.connections.findIndex((cand) => { + return (cand.name === input.name && cand.type === input.type) }) - if (conn !== undefined | conn !== null) { - this.connections.splice(pos, 1) + if (ind !== -1) { + input.disconnect(this) + this.connections.splice(ind, 1) return true } else { - console.log('was looking for ', recip, 'in', this.connections) + console.log('remove err, was looking for', input, 'in', this.connections) return false } } - this.unhook = (pid) => { - for (let conn of this.connections) { - if (conn.parentId === pid) { - this.remove(conn) - } + 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 ip of this.connections) { + // here we're disconnecting the input, meaning the input is removing this from its list of connections + 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){ + if (linktype) { // has stash ? this.needsAck = false; } @@ -203,7 +245,7 @@ function State(type, name, startup) { this.name = name this.type = type // have to be a type we can recognize, - if(!isOKType(type)){ + 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 ? @@ -235,27 +277,21 @@ function deepCopy(obj) { return JSON.parse(JSON.stringify(obj)) } -function isOKType(type){ +function isOKType(type) { // etc, lowecase to match with typeof ~ queeeries ~ - return (type === 'number' - || type === 'boolean' - || type === 'string' - || type === 'object' - || type === 'any' - || type === 'byte' - || type === 'byteArray' - || type === 'uint8' - || type === 'uint8Array' - || type === 'int8' - || type === 'int8Array' - || type === 'uint32' - || type === 'uint32Array' - || type === 'int32' - || type === 'int32Array' - ) + let ind = TSET.findIndex((cand) => { + return cand.name === type + }) + if(ind === -1){ + return false + } else if (type === 'any') { + console.log('WARN!, type of "any" is a bit risky, and cannot traverse a link') + return true + } else { + return true + } } - export { Hunkify, Input, diff --git a/hunks/input/string.js b/hunks/input/string.js index 4272342fb498db1a2d8413168d3715e86e7d9996..27f3d4fae839e6bdf6882ee30b6016603e11604a 100644 --- a/hunks/input/string.js +++ b/hunks/input/string.js @@ -17,8 +17,8 @@ function Strang() { let stringOutput = new Output('string', 'string') this.outputs.push(stringOutput) - // this.state.prefix = new State('string', 'prefix', 'LOG:') - // this.state.onchange = new State('boolean', 'onchange', true) + // this.states.prefix = new State('string', 'prefix', 'LOG:') + // this.states.onchange = new State('boolean', 'onchange', true) this.dom = {} diff --git a/hunks/interface/button.js b/hunks/interface/button.js index 20fd611657d41eb542df72cd834e9af202d90e1b..d7d9707a0e0cd35eae83564cfde190c245f6c117 100644 --- a/hunks/interface/button.js +++ b/hunks/interface/button.js @@ -16,11 +16,11 @@ function Button() { this.outputs.mouseup = new Output('event', 'mouseup') */ - this.state.strang = new State('string', 'strng', 'start value') - this.state.numbr = new State('number', 'nmbr', 101) - this.state.blen = new State('boolean', 'bln', false) + this.states.strang = new State('string', 'strng', 'start value') + this.states.numbr = new State('number', 'nmbr', 101) + this.states.blen = new State('boolean', 'bln', false) - this.state.strang.update('value is the real part') + this.states.strang.update('value is the real part') this.dom = {} diff --git a/hunks/interface/logger.js b/hunks/interface/logger.js index b4c8d1b2ffea2495035c2e1a80d03ac012d8eee8..542950c0149b16dc8a0c581101772d10d01a2000 100644 --- a/hunks/interface/logger.js +++ b/hunks/interface/logger.js @@ -14,7 +14,7 @@ function Logger() { let prefix = new State('string', 'prefix', 'LOG:') let logToConsole = new State('boolean', 'console', true) - this.state.push(prefix, logToConsole) + this.statess.push(prefix, logToConsole) this.dom = {} diff --git a/hunks/interface/number.js b/hunks/interface/number.js index a531fdaf1651efbf6d8b55da9d4577def2fc611e..c97a338a1f0db76c17b950cebe1f6dec849d7d15 100644 --- a/hunks/interface/number.js +++ b/hunks/interface/number.js @@ -12,13 +12,13 @@ import { } from '../hunks.js' function Number() { - Hunkify(this, 'Number Input') + Hunkify(this) - let numout = new Output('number', 'num') + let numout = new Output('number', 'num', this) this.outputs.push(numout) let numrep = new State('number', 'numrep', 275074) - this.state.push(numrep) + this.states.push(numrep) // as is tradition, this.dom = {} diff --git a/hunks/link.js b/hunks/link.js index dcc147f13267838a9545b8d0cabb53d3b49cb371..3095aa1c5d0910718fb0527c10f35818855ac806 100644 --- a/hunks/link.js +++ b/hunks/link.js @@ -1,6 +1,6 @@ /* -line input +hookup, */ @@ -13,21 +13,23 @@ import { } from './hunks.js' // END HEADER -import { TSET, MSGKEY_ACK } from '../typeset.js' +import { TSET, LK, MSGS } from '../typeset.js' function Link() { - Hunkify(this, 'Link') + Hunkify(this) - let dtin = new Input('byteArray', 'data') + let debug = false + + let dtin = new Input('byteArray', 'data', this) this.inputs.push(dtin) - let dtout = new Output('byteArray', 'data') + let dtout = new Output('byteArray', 'data', this) this.outputs.push(dtout) // default messages -> manager, besides also data link let inputList = new State('string', 'inputList', "mgrMsgs (byteArray)") let outputList = new State('string', 'outputList', "mgrMsgs (byteArray)") - this.state.push(inputList, outputList) + this.states.push(inputList, outputList) /* --------------------------- ---------------------------- */ /* ------------------ OP ON KEYS FROM STATE ------------------ */ @@ -80,9 +82,9 @@ function Link() { } else { // the object doesn't already exist, if(input){ - this.inputs[kp + 1] = new Input(nks[kp].typeKey, nks[kp].nameKey, true) + this.inputs[kp + 1] = new Input(nks[kp].typeKey, nks[kp].nameKey, this, true) } else { - this.outputs[kp + 1] = new Output(nks[kp].typeKey, nks[kp].nameKey, true) + this.outputs[kp + 1] = new Output(nks[kp].typeKey, nks[kp].nameKey, this, true) } } } @@ -104,14 +106,15 @@ function Link() { swapLists(value, true) // if OK inputList.set(value) - this.mgr.serializeAndSendHunk(this) + // we have to report this ... + this.mgr.evaluateHunk(this) } outputList.change = (value) => { swapLists(value, false) // if ok outputList.set(value) - this.mgr.serializeAndSendHunk(this) + this.mgr.evaluateHunk(this) } /* --------------------------- ---------------------------- */ @@ -125,12 +128,12 @@ function Link() { // just add in order let ipKeys = getTypeAndNameKeys(inputList.value) for(let kp of ipKeys){ - this.inputs.push(new Input(kp.typeKey, kp.nameKey, true)) + this.inputs.push(new Input(kp.typeKey, kp.nameKey, this, true)) } let opKeys = getTypeAndNameKeys(outputList.value) for(let kp of opKeys){ - this.outputs.push(new Output(kp.typeKey, kp.nameKey, true)) + this.outputs.push(new Output(kp.typeKey, kp.nameKey, this, true)) } } @@ -146,7 +149,7 @@ function Link() { // data[0] is the route, the link we need to bump on msg.port = data[0] // check for ack, - if(data[1] === MSGKEY_ACK){ + if(data[1] === LK.ACK){ msg.isAck = true return msg } @@ -155,7 +158,8 @@ function Link() { 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, 2) + msg.data = phy.read(data, 1).item + if(debug) console.log('demsg:', msg) return msg } @@ -164,22 +168,22 @@ function Link() { // this ... let ack = (port) => { let msg = new Array() - msg.push(port, MSGKEY_ACK) + msg.push(port, LK.ACK) outbuffer.push(msg) } // serialize messages: let sermsg = (port, data, type) => { - let msg = new Array() if(typeof port !== 'number' || port > 254) throw new Error('port is no good at serialize') - msg.push(port) // type via this handy object: let phy = TSET.find((item) => { return item.name === type }) if(phy === undefined) throw new Error(`type not found at serialization: ${type}`) - msg.push(phy.key) - msg = msg.concat(phy.write(data)) + // we r ready, + let msg = [port] + MSGS.writeTo(msg, data, type) + if(debug) console.log('sermsg to outbuffer', msg) outbuffer.push(msg) } @@ -192,6 +196,7 @@ function Link() { // pull it and deserialize it, let msg = demsg(dtin.get()) 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') @@ -207,6 +212,7 @@ function Link() { // 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, @@ -219,10 +225,10 @@ function Link() { }// 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(!(dtout.io) && outbuffer.length > 0){ // if we can send things to the world, do so // because of looping sys, we can only do this once per turn let turn = outbuffer.shift() - console.log('LINK OUT ->>', turn) + if(debug) console.log('LINK OUT ->>', turn) dtout.put(turn) } @@ -240,6 +246,7 @@ function Link() { // only pull off of inputs if we are known to be clear downstream if(this.inputs[i].icus && this.inputs[i].io){ let data = this.inputs[i].get() + if(debug) console.log(`link pulling message from ${i}, key ${data[0]}`) sermsg(i, data, this.inputs[i].type) } } diff --git a/hunks/manager.js b/hunks/manager.js index 1eb02e4f4edd6db2efdac77afd605f44bf109641..fdb3965f6b83138f2245a053546093e313221f5d 100644 --- a/hunks/manager.js +++ b/hunks/manager.js @@ -21,8 +21,8 @@ function Manager() { // need this tool let gg = new GoGetter - let msgsin = new Input('byteArray', 'msgs') - let msgsout = new Output('byteArray', 'msgs') + let msgsin = new Input('byteArray', 'msgs', this) + let msgsout = new Output('byteArray', 'msgs', this) this.inputs.push(msgsin) this.outputs.push(msgsout) @@ -31,9 +31,13 @@ function Manager() { let hunks = new Array() this.hunks = hunks + // we keep track of whether-or-not we have any connections ... + this.isConnectedTo = false + // debug flags let verbose = false let msgverbose = false + let addHunkVerbose = false /* --------------------------- ---------------------------- */ /* ---------------------- BUILDING LIST ---------------------- */ @@ -56,13 +60,18 @@ function Manager() { let serializeHunk = (hunk, bytes) => { // write the hunk into this array - bytes.push(HK.ID) - MSGS.writeTo(bytes, hunk.id, 'string') + bytes.push(HK.IND) + // here, we're counting on the hunk containing its own address-within-the-array + // that gets set during an add, or decremented during a remove + MSGS.writeTo(bytes, hunk.ind, 'uint16') + // an id, also, is nice, for optional human names ? maybe ? bytes.push(HK.NAME) MSGS.writeTo(bytes, hunk.name, 'string') // write inputs, if (hunk.inputs.length > 0) { for (let ip of hunk.inputs) { + // these should perhaps get their index, as well? + // but we're sending them in order, so ... bytes.push(HK.INPUT) MSGS.writeTo(bytes, ip.name, 'string') MSGS.writeTo(bytes, ip.type, 'string') @@ -77,13 +86,13 @@ function Manager() { } } // write state, - if (hunk.state.length > 0) { - for (let st of hunk.state) { + if (hunk.states.length > 0) { + for (let st of hunk.states) { bytes.push(HK.STATE) MSGS.writeTo(bytes, st.name, 'string') MSGS.writeTo(bytes, st.type, 'string') // yarr, last is a debug flag - MSGS.writeTo(bytes, st.value, st.type, true) + MSGS.writeTo(bytes, st.value, st.type) } } } @@ -92,21 +101,44 @@ function Manager() { /* ------------------- ADD / REMOVE A HUNK ------------------- */ /* --------------------------- ---------------------------- */ - let addHunkVerbose = true + let stateSetFromSerial = (msg, inc) => { + // pull state index / value pairs + let temp + let stateSet = [] + while(inc < msg.length){ + if(msg[inc] !== HK.STATE) throw new Error(`error reading in state set, starts with ${msg[start]} at ${start}, should be ${HK.STATE}`) + let stateItem = {} + inc += 1 + stateItem.ind = MSGS.readFrom(msg, inc, 'uint8').item + inc += 2 + // pull type by key + let phy = TSET.find((cand) => { + return cand.key === msg[inc] + }) + if(!phy) throw new Error(`error finding a key for this type, key is ${msg[inc]}`) + stateItem.type = phy.name + temp = MSGS.readFrom(msg, inc, stateItem.type) + stateItem.value = temp.item + stateSet.push(stateItem) + if(inc < 1) throw new Error('dangerous looping, exiting') + inc += temp.increment + } + return stateSet + } - this.addHunk = (hunkName, id, state) => { + this.addHunk = (hunkName, state) => { // hunks are named by path in js // so that we can add // so that we can link // so that we can go to links, websockets, node let path = './hunks/' + hunkName + '.js' - console.log('ok, from path', path) + if(verbose) console.log('ok, loading hunk from path', path) return new Promise((resolve, reject) => { - gg.importSource(path).then((src) => { + gg.importSource(path, false).then((src) => { if (addHunkVerbose) console.log('gg importing source resolves', src) try { - let hunk = sourceToHunk(src, hunkName, id, state) + let hunk = sourceToHunk(src, hunkName, state) hunks.push(hunk) resolve(hunk) } catch (err) { @@ -117,53 +149,69 @@ function Manager() { reject(err) }) }) - } + } // end addHunk function, - let sourceToHunk = (src, name, id, state) => { + let sourceToHunk = (src, name, state) => { // make the instance from the constructor let hunk = {} try { hunk = new src() if (addHunkVerbose) console.log('cast to hunk as thing is ok', hunk) } catch (err) { - console.log('new() error', err) + console.error('new() error', err) throw new Error('cannot cast hunk as new thing()') } + // name it (this is just the path) hunk.name = name - // id it, with a new ID if it doesn't have one, or - // with the ID from its program (occasionally provided) s - if (id !== undefined && id !== null) { - // TODO: BIGBOI: also check for duplicate ids ?? totally possible, very likely - hunk.id = id - } else { - // TODO: probably there is a better id, and what of program loads? - hunk.id = 'hnk_' + hunks.length - } + // and ref it (this is its address in the array of hunks) + // it's going to get pushed into this array next, so + hunk.ind = hunks.length + // a backdoor, a greeting, an ouroboros + hunk.mgr = this // now we open our doors to state changes - if (Array.isArray(hunk.state)) { - for (let st of hunk.state) { - st.hookup = (value) => { - console.log('MGR->View StateChange return via hookup') + // also: instead of this, since hunks have mgr hooks, something else? + if (Array.isArray(hunk.states)) { + for (let st in hunk.states) { + hunk.states[st].hookup = (value) => { + console.log('MGR -> View StateChange Return ... ') let msg = [MK.HUNKSTATECHANGE] - MSGS.writeTo(msg, hunk.id, 'string') - MSGS.writeTo(msg, st.name, 'string') - MSGS.writeTo(msg, value, st.type) + MSGS.writeTo(msg, hunk.ind, 'uint16') + // these calls to 'parseInt' on what look like indexed for-loops + // i.e. this for (let ...) is a javascriptism: + // an Array is just a JS object, so indices are also js 'key' + // and in a 'for item in iterable' loop in js, it assigns to each + // instance of item the 'key', keys being strings, hence the index + // coming through as a string... jsArray[1] is the same as jsArray["1"] + MSGS.writeTo(msg, parseInt(st), 'uint8') + MSGS.writeTo(msg, value, hunk.states[st].type) writeMessage(msg) } } } - // a backdoor, a greeting, an ouroboros - hunk.mgr = this + // if we have state arguments, set those now, + if(state){ + for(let item of state){ + try{ + if(hunk.states[item.ind].type !== item.type){ + console.error('skipping state update during hunk load, mismatched types') + } else { + hunk.states[item.ind].value = item.value + } + } catch (err) { + console.error('probably non-existent state while lookup via ind', err) + } + } + } - // startup code if it exists + // run init code (apres state setup!) if (hunk.init != null) { try { hunk.init() } catch (err) { - throw err + console.error('ERR caught while running hunk init code', err) } } @@ -171,39 +219,50 @@ function Manager() { if (hunk.dom !== null && hunk.dom !== undefined) { // this is only allowed in the tlview, and we manage that... let tlv = hunks.find((cnd) => { - return cnd.id === 'TLView' + return cnd.isTopLevelView }) - if (tlv === undefined) { - if (hunk.id !== 'TLView') writeErrMessage(`hunk appears with dom element, but no view is present ${hunk.id}`) + // via bootloop, tlv is always 2nd element + if (hunk.ind === 1 && hunk.name === 'view') { + if(verbose) console.log('passing on tlview') + // this is the top level, it's fine, we add in bootstrap.js + } else if (tlv === undefined) { + writeErrMessage(`something is up ... no toplevelview, trying to add native hunk with dom element`) } else { - console.log('cf taking native hunk') + tlv.msgbox.write('cf taking native hunk DOM element') tlv.take(hunk) } - } + } // end if-have-dom return hunk } - this.removeHunk = (id) => { - let hnk = hunks.find((element) => { - return id === element.id - }) - if (hnk !== undefined) { - if (hnk.name === 'Manager' || id === 'TLView') { - writeErrMessage("I can't let you do that, dave") - return false - } - // rm then - hunks.splice(hunks.indexOf(hnk), 1) - // and unlink, - for (let hks of hunks) { - for (let otp of hks.outputs) { - otp.unhook(id) - } + this.removeHunk = (ind) => { + let hnk = hunks[ind] + if (hnk === undefined) return false + // else, + for (let ip in hnk.inputs) { + // dconn from outputs, + hnk.inputs[ip].disconnectAll() + } + // outputs, of whom a reference is stored in previously-attached inputs + for (let op in hnk.outputs) { + hnk.outputs[op].disconnectAll() + } + // then rm + hunks.splice(ind, 1) + // now, hunks have been reordered, so + onHunkReorder() + // and confirm we have done the deed, the body is in the river + return true + } + + let onHunkReorder = () => { + for (let ind in hunks) { + // alright look, we keep a copy of this + if (hunks[ind].ind !== parseInt(ind)) { + console.log(`swapping ${hunks[ind].ind} for ${parseInt(ind)}`) + hunks[ind].ind = parseInt(ind) } - return true - } else { - return false } } @@ -215,35 +274,62 @@ function Manager() { writeMessage(msg) } + // for links, + + let serializeAndSendLink = (output, input) => { + console.log('sending this link again', output, input) + let msg = [MK.LINKALIVE] + MSGS.writeTo(msg, output.parent.ind, 'uint16') + MSGS.writeTo(msg, output.findOwnIndex(), 'uint8') + MSGS.writeTo(msg, input.parent.ind, 'uint16') + MSGS.writeTo(msg, input.findOwnIndex(), 'uint8') + writeMessage(msg) + } + + // one for updating hunks, + + this.evaluateHunk = (hunk) => { + // input and output lists have probably changed, so there's some work to do. + // first, we reply with a new definition, + this.serializeAndSendHunk(hunk) + // on a renewed definition, the view will remove all links associated. + // now we need to walk inputs and outputs, and message about the links + // that still exist + // so we can search over inputs ... since references are maintained + // if there's anything attached to them, those are links to send + for (let ip in hunk.inputs) { + for (let op in hunk.inputs[ip].connections) { + serializeAndSendLink(hunk.inputs[ip].connections[op], hunk.inputs[ip]) + } + } + // same with the outputs, + for (let op in hunk.outputs) { + for (let ip in hunk.outputs[op].connections) { + serializeAndSendLink(hunk.outputs[op], hunk.outputs[op].connections[ip]) + } + } + } + /* --------------------------- ---------------------------- */ /* ----------------- LINKS HELLO / GOODBYTE ------------------ */ /* --------------------------- ---------------------------- */ - this.addLink = (outHunkId, outputName, inHunkId, inputName) => { + this.addLink = (outHunkIndex, outputIndex, inHunkIndex, inputIndex, debug) => { // synchronous, doesn't need to be a promise - let outHunk = hunks.find((hunk) => { - return hunk.id == outHunkId - }) - let inHunk = hunks.find((hunk) => { - return hunk.id == inHunkId - }) + let outHunk = hunks[outHunkIndex] + let inHunk = hunks[inHunkIndex] // throw possible errs, if (outHunk === undefined) { writeErrMessage("MGR on add link: outHunk is undefined, on request for: " + outHunkId) return false - } - if (inHunk === undefined) { + } else if (inHunk === undefined) { writeErrMessage("MGR on add link: inHunk is undefined, on request for: " + inHunkId) return false } // find outputs, inputs - let otp = outHunk.outputs.find((op) => { - return op.name === outputName - }) - let inp = inHunk.inputs.find((ip) => { - return ip.name === inputName - }) + let otp = outHunk.outputs[outputIndex] + let inp = inHunk.inputs[inputIndex] // throw errs, if (otp === undefined) { writeErrMessage('MGR on add link: output is undefined. on request for hunk: ' + outHunkId + " and output: " + outputName) @@ -251,54 +337,33 @@ function Manager() { } else if (inp === undefined) { writeErrMessage('MGR on add link: input is null or undefined. for hunk: ' + inHunk.id + " and input: " + inputName) return false - } else { - if (verbose) this.log(`hooking ${outHunk.id}, ${otp.name} to ${inHunk.id}, ${inp.name}`) - otp.attach(inp, inHunk.id) - // ok we gucc, - return true } + // if we're here, have passed all selection gauntlets, so + if (debug) this.log(`hooking ${outHunk.name} ${outHunk.ind}, ${otp.name} to ${inHunk.name} ${inHunk.ind}, ${inp.name}`) + return otp.attach(inp) } - this.removeLink = (outHunkId, outputName, inHunkId, inputName) => { - // synchronous, doesn't need to be a promise - let outHunk = hunks.find((hunk) => { - return hunk.id == outHunkId - }) - let inHunk = hunks.find((hunk) => { - return hunk.id == inHunkId - }) - // throw possible errs, + this.removeLink = (outHunkIndex, outputIndex, inHunkIndex, inputIndex, debug) => { + let outHunk = hunks[outHunkIndex] + let inHunk = hunks[inHunkIndex] if (outHunk === undefined) { - writeErrMessage("MGR on remove link: outHunk is undefined, on request for: " + outHunkId) + writeErrMessage("MGR on add link: outHunk is undefined, on request for: " + outHunkId) return false - } - if (inHunk === undefined) { - writeErrMessage("MGR on remove link: inHunk is undefined, on request for: " + inHunkId) + } else if (inHunk === undefined) { + writeErrMessage("MGR on add link: inHunk is undefined, on request for: " + inHunkId) return false } - - // find outputs, inputs - let otp = outHunk.outputs.find((op) => { - return op.name === outputName - }) - let inp = inHunk.inputs.find((ip) => { - return ip.name === inputName - }) - // throw errs, + let otp = outHunk.outputs[outputIndex] + let inp = inHunk.inputs[inputIndex] if (otp === undefined) { - writeErrMessage('MGR on remove link: output is undefined. on request for hunk: ' + outHunkId + " and output: " + outputName) + writeErrMessage('MGR on add link: output is undefined. on request for hunk: ' + outHunkId + " and output: " + outputName) return false } else if (inp === undefined) { - writeErrMessage('MGR on remove link: input is null or undefined. for hunk: ' + inHunk.id + " and input: " + inputName) + writeErrMessage('MGR on add link: input is null or undefined. for hunk: ' + inHunk.id + " and input: " + inputName) return false - } else { - if (verbose) this.log(`hooking ${outHunk.id}, ${otp.name} to ${inHunk.id}, ${inp.name}`) - if (otp.remove(inp, inHunk.id)) { - return true - } else { - return false - } } + if (debug) this.log(`hooking ${outHunk.id}, ${otp.name} to ${inHunk.id}, ${inp.name}`) + return otp.remove(inp) } // programmatically, and as makes sense in memory, link connections are lists that are stored @@ -309,15 +374,18 @@ function Manager() { let writeLinkList = () => { let links = new Array() // for hunks, - for (let hnk of hunks) { + for (let hnk in hunks) { // have outputs, - for (let otp of hnk.outputs) { - for (let cn of otp.connections) { + for (let otp in hunks[hnk].outputs) { + for (let cn in hunks[hnk].outputs[otp].connections) { + // we can know the output index, and the hunk index, + // but we need to find the index-relative position for the input + // RETURN links.push({ - outHunkId: hnk.id, - outputName: otp.name, - inHunkId: cn.parentId, - inputName: cn.input.name + outInd: parseInt(hnk), // truth in index + outputInd: parseInt(otp), // a num, + inInd: hunks[hnk].outputs[otp].connections[cn].parent.ind, + inputInd: hunks[hnk].outputs[otp].connections[cn].findOwnIndex() }) } } @@ -326,28 +394,6 @@ function Manager() { return links } - /* --------------------------- ---------------------------- */ - /* ---------------------- STATE CHANGES ---------------------- */ - /* --------------------------- ---------------------------- */ - - this.stateChange = (hunkId, stateName, newValue) => { - // this is probably a good example of whomst to implement things-like-this - return new Promise((resolve, reject) => { - if (verbose) this.log(`to change ${stateName} to ${newValue} in ${hunkId}`) - let theHunk = hunks.find((hunk) => { - return hunk.id == hunkId - }) - if (theHunk === undefined) reject(new Error('no hunk found for statechange')) - let theState = theHunk.state.find((st8) => { - return st8.name === stateName - }) - if (theState === undefined) reject(new Error('no state found for statechange')) - // otherwise, do business - theState.change(newValue) - resolve() - }) - } - /* --------------------------- ---------------------------- */ /* ---------------------- STARTUP, LOOP ---------------------- */ /* --------------------------- ---------------------------- */ @@ -355,32 +401,37 @@ function Manager() { this.init = () => { // startup by giving ourselves an ID if we haven't been assigned one? // and then adding ourselves to ourselves ? - if (this.id === null || this.id === undefined) throw new Error('managers with no IDs are no good 4 u') + // have to cover these bases ourselves + this.name = 'manager' + this.ind = 0 // nest in self, hunks.push(this) - this.log(`manager hello, id is ${this.id}`) + this.log(`manager hello`) } this.loop = () => { // getting messages if (msgsin.io) { let msg = msgsin.get() - if (msgverbose) this.log(`gets msg ${header}`) + if (msgverbose) console.log('MGR RX MSG:', msg) if (!Array.isArray(msg)) throw new Error(`manager throwing object message, having header ${msg.header}`) // once let resp = new Array() // rules: bytes in this switch, objects elsewhere ? switch (msg[0]) { case MK.HELLO: + if (msgverbose) console.log('MGR MSG is hello') writeMessage([MK.HELLO]) break case MK.REQDESCRIBESELF: + if (msgverbose) console.log('MGR MSG is a brief request') + this.isConnectedTo = true + // with ids, this is ok resp.push(MK.BRIEF) - MSGS.writeTo(resp, this.id, 'string') MSGS.writeTo(resp, gg.interpreterName, 'string') MSGS.writeTo(resp, gg.interpreterVersion, 'string') - MSGS.writeTo(resp, hunks.length, 'uint32') - MSGS.writeTo(resp, writeLinkList().length, 'uint32') + MSGS.writeTo(resp, hunks.length, 'uint16') + MSGS.writeTo(resp, writeLinkList().length, 'uint16') writeMessage(resp) // now start sending hunks, for (let hnk of hunks) { @@ -392,14 +443,15 @@ function Manager() { let links = writeLinkList() for (let lnk of links) { let serlink = [MK.LINKALIVE] - MSGS.writeTo(serlink, lnk.outHunkId, 'string') - MSGS.writeTo(serlink, lnk.outputName, 'string') - MSGS.writeTo(serlink, lnk.inHunkId, 'string') - MSGS.writeTo(serlink, lnk.inputName, 'string') + MSGS.writeTo(serlink, lnk.outInd, 'uint16') + MSGS.writeTo(serlink, lnk.outputInd, 'uint8') + MSGS.writeTo(serlink, lnk.inInd, 'uint16') + MSGS.writeTo(serlink, lnk.inputInd, 'uint8') writeMessage(serlink) } break case MK.REQLISTAVAIL: + if (msgverbose) console.log('MGR MSG is a request for available items') // allow for error path, getListOfAvailableComponents().then((list) => { // probable success, @@ -417,23 +469,26 @@ function Manager() { }) break case MK.REQADDHUNK: + if (msgverbose) console.log('MGR MSG is a request to add a hunk') // pull the rest out, // unknown-len types return with return.item and return.increment let reqaddinc = 1 let strname = MSGS.readFrom(msg, reqaddinc, 'string') reqaddinc += strname.increment strname = strname.item - let strid = undefined - if(reqaddinc < msg.length) { - strid = MSGS.readFrom(msg, reqaddinc, 'string').item + // might exist, + let state + if(reqaddinc < msg.length){ + state = stateSetFromSerial(msg, reqaddinc) + console.log('MGR to add', strname, 'with state included:', state) + } else { + console.log('MGR to add', strname) } - // then do, // a meta comment is that this add promise doesn't fail for // things like path-loading errors, that would be inside of gogetter.js // i.e. pulling a hunk loads it into that import script hack, that just // gets evaluated into the dom ... - console.log('manager to add', strname) - this.addHunk(strname, strid).then((hunk) => { + this.addHunk(strname, state).then((hunk) => { console.log('successfully added hunk', hunk) // serialize resp.push(MK.HUNKALIVE) @@ -445,80 +500,66 @@ function Manager() { }) break case MK.REQSTATECHANGE: + if (msgverbose) console.log('MGR MSG is a state change request') // ok, // mgrMsgs (byteArray), chonker (uint32) - // - let stchinc = 1 - let stChId = MSGS.readFrom(msg, stchinc, 'string') - stchinc += stChId.increment - // ye olden javascript switch - stChId = stChId.item - let stChName = MSGS.readFrom(msg, stchinc, 'string') - stchinc += stChName.increment - stChName = stChName.item - // debugging atm - console.log('MGR state change searcheth', stChId, stChName) - // seek out the hunk, - let stHunk = hunks.find((cand) => { - return cand.id === stChId - }) - if (stHunk === undefined) { - writeErrMessage(`on state change, could not find hunk with id ${stChId}`) - break - } - // and the state item, if it exists - let stSt = stHunk.state.find((cand) => { - return cand.name === stChName - }) - if (stSt === undefined) { - writeErrMessage(`on state change, could not find state item with name ${stChName} within ${stChId}`) - break - } - // now this is knowable, - let stChType = stSt.type - // so we can use the right tools, - let value = MSGS.readFrom(msg, stchinc, stChType) - // yarrr ... ok, do all readFrom calls need to return an incrment? - // tharin is the problem - // here *is* a workaround - console.log('for state change, going to send value', value) - if (Object.keys(value).includes('item') && Object.keys(value).includes('increment')) { - // an inc-type appears, - stSt.change(value.item) - } else { - // yarrr, primitives - stSt.change(value) + // TODO + let stchHnkInd = MSGS.readFrom(msg, 1, 'uint16').item + let stchStInd = MSGS.readFrom(msg, 4, 'uint8').item + // to pull, we need to know the type at the end + let stItem + try { + stItem = hunks[stchHnkInd].states[stchStInd] + } catch (err) { + writeErrMessage('probably messed up state indexing here') + console.error(err) } + console.log('would swap at', stItem) + console.log('with type', stItem.type) + let stValRequest = MSGS.readFrom(msg, 6, stItem.type).item + console.log('for value', stValRequest) + // going + stItem.change(stValRequest) break case MK.REQRMHUNK: - let rmid = MSGS.readFrom(msg, 1, 'string').item - console.log('requests rm of', rmid) - if (this.removeHunk(rmid)) { + // it's gonna be messy ! ... HERE NOW + if (msgverbose) console.log('MGR MSG is a request to remove a hunk') + let rmind = MSGS.readFrom(msg, 1, 'uint16').item + if (this.removeHunk(rmind)) { let rmconf = [MK.HUNKREMOVED] - MSGS.writeTo(rmconf, rmid, 'string') + MSGS.writeTo(rmconf, rmind, 'uint16') writeMessage(rmconf) } else { - writeErrMessage(`failure to remove hunk ${rmid} as requested`) + writeErrMessage(`failure to remove hunk ${rmind} as requested`) } break case MK.REQADDLINK: - let adargs = MSGS.readListFrom(msg, 1, 'string') - if (this.addLink(adargs[0], adargs[1], adargs[2], adargs[3])) { + if (msgverbose) console.log('MGR MSG is a request to add a link') + // these, and then draw links again ... and send links ? + let addOutInd = MSGS.readFrom(msg, 1, 'uint16').item + let addOutputInt = MSGS.readFrom(msg, 4, 'uint8').item + let addInInd = MSGS.readFrom(msg, 6, 'uint16').item + let addInputInd = MSGS.readFrom(msg, 9, 'uint8').item + if (this.addLink(addOutInd, addOutputInt, addInInd, addInputInd)) { // identical arguments, so let reply = [MK.LINKALIVE].concat(msg.slice(1)) writeMessage(reply) } else { - writeErrMessage(`failure to add link from ${adargs[0]} ${adargs[1]} to ${adargs[2]} ${adargs[3]} as requested`) + writeErrMessage(`failure to add link as requested`) } break case MK.REQRMLINK: - let rmargs = MSGS.readListFrom(msg, 1, 'string') - if (this.removeLink(rmargs[0], rmargs[1], rmargs[2], rmargs[3])) { + if (msgverbose) console.log('MGR MSG is a request to remove a link') + let rmOutInd = MSGS.readFrom(msg, 1, 'uint16').item + let rmOutputInt = MSGS.readFrom(msg, 4, 'uint8').item + let rmInInd = MSGS.readFrom(msg, 6, 'uint16').item + let rmInputInd = MSGS.readFrom(msg, 9, 'uint8').item + if (this.removeLink(rmOutInd, rmOutputInt, rmInInd, rmInputInd)) { // identical arguments, so let reply = [MK.LINKREMOVED].concat(msg.slice(1)) writeMessage(reply) } else { - writeErrMessage(`failure to remove link from ${rmargs[0]} ${rmargs[1]} and ${rmargs[2]} ${rmargs[3]} as requested`) + writeErrMessage(`failure to remove link as requested`) } break default: @@ -542,6 +583,8 @@ function Manager() { } } // end msgs output check + // TRANSPORT + hunks.forEach((hunk) => { // begin transport, walk each output and push along hunk.outputs.forEach((output) => { @@ -553,8 +596,8 @@ function Manager() { // if it has not been posted to inputs (it's fresh) // double check that all inputs are clear let clear = true - for (let pair of output.connections) { - if (pair.input.io) { + for (let input of output.connections) { + if (input.io) { clear = false } } @@ -568,8 +611,8 @@ function Manager() { } else { // output has been posted, check if all clear let clear = true - for (let pair of output.connections) { - if (pair.input.io) { + for (let input of output.connections) { + if (input.io) { clear = false } } @@ -602,14 +645,18 @@ function Manager() { 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) + if(this.isConnectedTo){ + if (!msgsout.io && outmsgbuffer.length < 1) { + // str8 shooters + if (msgverbose) this.log('msg out', bytes) + msgsout.put(bytes) + } else { + // gotta buffer + outmsgbuffer.push(bytes) + if (msgverbose) this.log('MGR OUTBUFFER LEN', outmsgbuffer.length) + } } else { - // gotta buffer - outmsgbuffer.push(bytes) - this.log('MGR OUTBUFFER LEN', outmsgbuffer.length) + console.log('mgr tossing message', bytes) } } diff --git a/hunks/template.js b/hunks/template.js index b42afbba5b30c55ce299f3ff19ff4ef021664f16..a4f55e5da8ce87e9650777d025dd1566e5764ba3 100644 --- a/hunks/template.js +++ b/hunks/template.js @@ -20,7 +20,7 @@ function Name() { this.outputs.push(outB) let stateItem = new State('type', 'name') - this.state.push(stateItem) + this.states.push(stateItem) this.init = () => { // manager calls this once diff --git a/hunks/view.js b/hunks/view.js index c64a1d878fcf63dcccc1ecc5cc254488f64d0313..3ba9e1e3c5522ec2918e0d6fe064eb60f19f0fd7 100644 --- a/hunks/view.js +++ b/hunks/view.js @@ -26,6 +26,9 @@ import { 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' @@ -33,13 +36,13 @@ import MessageBox from '../view/vmsg.js' import PatchSet from '../view/vptch.js' function View() { - Hunkify(this, 'View') + Hunkify(this) - let verbose = true - let msgverbose = true + let verbose = false + let msgverbose = false - let msgsin = new Input('byteArray', 'msgs') - let msgsout = new Output('byteArray', 'msgs') + let msgsin = new Input('byteArray', 'msgs', this) + let msgsout = new Output('byteArray', 'msgs', this) this.inputs.push(msgsin) this.outputs.push(msgsout) @@ -50,10 +53,18 @@ function View() { // 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 @@ -112,28 +123,30 @@ function View() { // key listeners // keys are global, so ... idk how to unfoof this yet, but - if (this.id === 'TLView') { - document.addEventListener('keydown', (evt) => { - if (evt.key === 'l') { - //writeMessage('addhunk', 'link') - } else if (evt.key === 'c') { - //writeMessage('addprogram', 'ntlink') - } else if (evt.key === 'v') { - //writeMessage('addprogram', 'llink') - } else if (evt.keyCode === 27) { - // escapekey - $(this.plane).children('.contextmenu').remove() - // also find floaters ... - } - }) - } + document.addEventListener('keydown', (evt) => { + if (evt.key === 'l') { + //writeMessage('addhunk', 'link') + } else if (evt.key === 'c') { + //writeMessage('addprogram', 'ntlink') + } else if (evt.key === 'v') { + //writeMessage('addprogram', 'llink') + } else if (evt.keyCode === 27) { + // escapekey + console.log('escape!') + $(this.dom).find('.contextmenu').remove() + // also find floaters ... + } + }) } // END INIT CODE this.refresh = () => { // wipe ya docs, and ask yonder manager for a complete description // everything is friggen jquery, so check it out - $(this.plane).children('.block').remove() + console.log("REFRESHING THE VIEW") + $(this.plane).children('.def').remove() + // also, + defs.length = 0 // and then say hello, writeMessage([MK.REQDESCRIBESELF]) // that's fine, we can wait for a response, but we have to track and setup the next move @@ -176,18 +189,6 @@ function View() { ct.y += evt.movementY dt.writeTransform(this.plane, ct) dt.writeBackgroundTransform(this.dom, ct) - - /* - this.pan.x += evt.movementX - this.pan.y += evt.movementY - this.plane.style.backgroundPosition = `${this.pan.x}px ${this.pan.y}px` - $(this.plane).children().each((index, div) => { - let tf = dt.readTransform(div) - tf.x += evt.movementX - tf.y += evt.movementY - dt.writeTransform(div, tf) - }) - */ } let canvasUpListener = (evt) => { @@ -197,33 +198,22 @@ function View() { // CONTEXT MENU let onContextMenu = (evt) => { - $(this.plane).find('.contextmenu').remove() + $(this.dom).find('.contextmenu').remove() //console.log(evt) evt.preventDefault() evt.stopPropagation() // more like - /* - -> add a hunk - -> load a program - -> save this program - -> reload this view - -> heartbeat checkin - */ console.log('context...', evt) // make the menu, let menu = $('<div>').addClass('contextmenu').get(0) - // and place, - let pt = dt.readTransform(this.plane) - console.log('plane transform', pt) - // HERE: there's still some mess, not landing in the right place, // (and there's compounding scale issues on dragging the canvas) dt.writeTransform(menu, { s: 1, - x: ((evt.clientX - pt.x) / pt.s), // (pt.s + -0.1 * (pt.s-1))), - y: ((evt.clientY - pt.y) / pt.s) // + -0.1 * (pt.s-1))) + x: evt.layerX, // (pt.s + -0.1 * (pt.s-1))), + y: evt.layerY // + -0.1 * (pt.s-1))) }) // - $(this.plane).append(menu) + $(this.dom).append(menu) // hmmm this.changeContextTitle('you can... ') // on of the options will ... @@ -239,58 +229,24 @@ function View() { }) this.addContextOption('save this patch', (evt) => { - patchset.saveCurrent(evt, true) + patchset.saveCurrent(evt, defs, true) }) // writeMessage([MK.REQLISTAVAIL]) } // takes 'under' argument this.addContextOption = (text, click) => { - $(this.plane).find('.contextmenu').get(0).append($('<li>' + text + '</li>').click((click)).get(0)) + $(this.dom).find('.contextmenu').get(0).append($('<li>' + text + '</li>').click((click)).get(0)) } this.changeContextTitle = (text) => { // clear, - $(this.plane).find('.contextmenu').children().remove() + $(this.dom).find('.contextmenu').children().remove() // overkill, but fun - let menu = $(this.plane).find('.contextmenu').get(0) + let menu = $(this.dom).find('.contextmenu').get(0) let title = $(`<div>${text}</div>`).addClass('contextTitle').get(0) - title.onmousedown = (evt) => { - evt.preventDefault() - evt.stopPropagation() - let domElemMouseMove = (evt) => { - // TRANSFORMS here to move div about on drag - evt.preventDefault() - evt.stopPropagation() - let ct = dt.readTransform(menu) - let pt = dt.readTransform(menu.parentElement) // think that's just this.plane ? - ct.x += evt.movementX / pt.s - ct.y += evt.movementY / pt.s - dt.writeTransform(menu, ct) - this.drawLinks() - } - - function rmOnMouseUp(evt) { - document.removeEventListener('mousemove', domElemMouseMove) - document.removeEventListener('mouseup', rmOnMouseUp) - } - document.addEventListener('mousemove', domElemMouseMove) - document.addEventListener('mouseup', rmOnMouseUp) - } - $(this.plane).find('.contextmenu').append(title) - } - - /* - this.changeContextTitle('list of available:') - for(let item of list){ - this.addContextOption(item, (evt) => { - let msg = [MK.REQADDHUNK] - MSGS.writeTo(msg, item, 'string') - writeMessage(msg) - $(evt.target).append(' > requested ... ') - }) + $(this.dom).find('.contextmenu').append(title) } - */ /* --------------------------- ---------------------------- */ /* ---------------------- FORCE LAYOUT ----------------------- */ @@ -501,31 +457,18 @@ function View() { // here is where you rm'd drawing & moving this.drawLinks = () => { - // from within the div - let outputs = $(this.plane).children('.block').children('.outputs').children('.output') - // clear all links + // drawing from scratch every time ! could be faster, probably bzt.clear(this.plane) - // and draw new ones - for (let output of outputs) { - // finding the children to hookup to - for (let conn of output.connectedTo) { - // find looks down *all* branches of the dom tree - // with children().children() we can reduce that tree - let hookup = $(this.plane).find(conn) - if (hookup.length !== 1) { - // this can happen when a dependent is not loaded yet - console.log('mismatched connection', hookup.length, conn) - } else { - let hk = hookup.get(0) - let head = bzt.getRightHandle(output) - let tail - if (hk.id === 'floater') { - tail = bzt.getFloaterHandle(hk) - } else { - tail = bzt.getLeftHandle(hk) - } - // a bit many arguments, but - bzt.drawLink(head, tail, output, hk, conn) + // 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) } } } @@ -545,97 +488,97 @@ function View() { return nameresp.increment + typeresp.increment } - let defBySerial = (bytes, start) => { + let specBySerial = (bytes, start, debug) => { // deserialize - let def = {} + let spec = { + ind: null, + name: null + } // inputs, outputs, state - def.inputs = new Array() - def.outputs = new Array() - def.state = new Array() + 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.ID: + case HK.IND: i += 1 - temp = MSGS.readFrom(bytes, i, 'string') - def.id = temp.item + temp = MSGS.readFrom(bytes, i, 'uint16') + spec.ind = temp.item i += temp.increment break case HK.NAME: i += 1 temp = MSGS.readFrom(bytes, i, 'string') - def.name = temp.item + spec.name = temp.item i += temp.increment break case HK.INPUT: i += 1 // expecting two strings here, name and then type - i += deserializeNameType(def.inputs, bytes, i) + i += deserializeNameType(spec.inputs, bytes, i) break case HK.OUTPUT: i += 1 - i += deserializeNameType(def.outputs, bytes, i) + i += deserializeNameType(spec.outputs, bytes, i) break case HK.STATE: i += 1 - i += deserializeNameType(def.state, bytes, i) + 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, - temp = MSGS.readFrom(bytes, i, def.state[def.state.length - 1].type) - - def.state[def.state.length - 1].value = temp.item + temp = MSGS.readFrom(bytes, i, spec.states[spec.states.length - 1].type) + spec.states[spec.states.length - 1].value = temp.item i += temp.increment break default: - throw new Error(`unexpected key encountered in hunk deserialization at ${i}: ${bytes[i]}`) + throw new Error(`unexpected key encountered during hunk deserialization at position ${i}: ${bytes[i]}`) break outer } } // a check, - console.log(`broke outer, len at ${bytes.length}, i at ${i}`) + 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, - console.log('def apres deserialize', def) + if (debug) console.log('spec apres deserialize', spec) // we gucc ? - return def + return spec } /* --------------------------- ---------------------------- */ /* ------------------- SERIAL -> HOTMESSES ------------------- */ /* --------------------------- ---------------------------- */ - let putDef = (def) => { - // hmmm ok, - if (verbose) console.log('ready write', def.id) - if (verbose) console.log('the def', def) + 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) // do we have this id already? could be an update; + let def = new HunkDefinition(spec, this, dt, false) + + // ... let mt = {} - let match = $(this.plane).find('#' + def.id).get(0) let menu = $(this.plane).children('.contextmenu').get(0) - if (match) { - // we have one already, we are likely updating it - // we need to be more careful about this - msgbox.write(`received new definition for the hunk with id "${def.id}", replacing that...`) - // need to carefully walk outputs to replace, - mt = dt.readTransform(match) - } else if (menu !== undefined) { + if (menu !== undefined) { mt = dt.readTransform(menu) - } else if (def.id === "NROL39_0") { // TEMPORARY until better management with force layout + $(menu).remove() + } else if (def.ind === 0 && def.name === 'manager') { // TEMPORARY until better management with force layout mt = { s: 1, x: 100, y: 100 } - } else if (def.id === "TLView") { // TEMPORARY until better management with force layout + } else if (def.ind === 1 && def.name === 'view') { // TEMPORARY until better management with force layout mt = { s: 1, x: 100, y: 200 } - } else { // also, initial placement could be done better + } else { // also, initial placement should be determined by force layout mt = { s: 1, x: Math.random() * 1000, @@ -643,115 +586,131 @@ function View() { } } - // write the def dom - let de = dt.writeDefDom(def, true) - - // later, we can walk for similar outputs, to maintain those links - if (match) { - // poll new outputs, replace with old outputs (these contain link information, should retain) - let newOtps = $(de).children('.outputs').children('.output') - for(let notp of newOtps){ - let equiv = $(match).children('.outputs').children('#' + notp.id).get(0) - if(equiv){ - msgbox.write(`maintaining output for ${equiv.id}`) - $(de).children('.outputs').children('#' + notp.id).replaceWith(equiv) - } else { - msgbox.write(`no equivalent output for ${equiv.id}, adding that`) - } - } - $(match).remove() - } else { - // shouldn't have to do this for refreshing descriptions... - // if it's in the ref-to-add - let native = cuttlefishHunkRef.find((hnk) => { - return hnk.id === def.id - }) - - if (native !== undefined && native !== null) { - console.log("trying to add native hunk's dom: native:", native) - try { - $(de).append($(native.dom).addClass('cuttlefishhunkdom')) - if ($(native.dom).is('.view')) { - de.style.height = '800px' - de.style.width = '1200px' - // leader on starting to reize these ... - $(de).append($('<div>').addClass('rsHandle').on('mousedown', (evt) => { - console.log('rshandle down') - })) - } - } catch (err) { - msgbox.write(`native hunk attach fails ${err}`) + // if it's in the ref-to-add *this is confusing* + let native = cuttlefishHunkRef.find((hunk) => { + return hunk.ind === def.ind + }) + + if (native !== undefined && native !== null) { + console.log("trying to add native hunk's dom: native:", native) + try { // try to add the dom element to the def's domelement + $(def.de).append($(native.dom).addClass('cuttlefishhunkdom')) + if ($(native.dom).is('.view')) { + def.de.style.height = '800px' + def.de.style.width = '1200px' + // leader on starting to reize these ... + /* + $(de).append($('<div>').addClass('rsHandle').on('mousedown', (evt) => { + console.log('rshandle down') + }))*/ } + } catch (err) { + msgbox.write(`native hunk attach fails ${err}`) } } - - // - dt.writeTransform(de, mt) - // rm menu if it's around - if (menu !== undefined) $(menu).remove() - - // add the def to the view - $(this.plane).append(de) - - // for the sim, add an x and y - // and we keep a list, but maybe don't need to? - blocks.push(de) - - // AS Policy, since this thing *is* a stateful view, - // we keep state in the view. no mirror, def is a throwaway + // ok, ready to place and append, + dt.writeTransform(def.de, mt) + // add the def's domelement to the view, + $(this.plane).append(def.de) + // and add it to our list + defs.push(def) // i.e. we won't return it: it contains everything we need this.drawLinks() + // occasionally useful, + return def + } - // here is an OK place to do the sorting ... - // TODO: add to D3 - updateForceLoop() + let replaceDef = (spec) => { + // the old boy, + let od = defs[spec.ind] + // as a rule, when we replace defs, we unhook everything. + // the manager (in its wisdom) 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() + } + // that was considerate of the others, but this whole thing is just going to get rm'd, so outputs don't matter + let mt = dt.readTransform(od.de) + // now we can *eliminate it* + $(od.de).remove() + defs[spec.ind] = null + // 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, + dt.writeTransform(nd.de, mt) + // add the def's domelement to the view, + $(this.plane).append(nd.de) + // and replace it in the list, + defs[spec.ind] = nd + // and re-render + this.drawLinks() + // occasionally useful, + return nd } + // for cuttlefish hunks, let cuttlefishHunkRef = new Array() - // for cuttlefish hunks, this.take = (hunk) => { cuttlefishHunkRef.push(hunk) } - let removeDef = (id) => { - // get the inputs that we'll have to watch removal for - let inpts = $(this.plane).children('#' + id).children('.inputs').children('.input') - // remove the def / block - if ($(this.plane).children('#' + id).length === 0) throw new Error('trouble on remove, cannot find block with given id') - // remove it, - $(this.plane).children('#' + id).remove() - // we have to walk over the other outputs and disconnect them, - let otps = $(this.plane).find('.output') - for (let otp of otps) { - for (let inp of inpts) { - if (otp.connectedTo.includes('#' + inp.id)) { - otp.connectedTo.splice(otp.connectedTo.indexOf('#' + inp.id)) - } + let removeDef = (index) => { + // 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.de).remove() + } + + 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)) } } - this.drawLinks() - updateForceLoop() } - let putLink = (outId, outName, inId, inName) => { + let putLink = (outInd, outputInd, inInd, inputInd) => { try { - let outp = $(this.plane).children('.block').children('.outputs').children('#' + outId + '_output_' + outName).get(0) - outp.connectedTo.push('#' + inId + '_input_' + inName) + let outputdef = defs[outInd].outputs[outputInd] + let inputdef = defs[inInd].inputs[inputInd] + outputdef.connect(inputdef) this.drawLinks() updateForceLoop() } catch (err) { console.log('ERR at putlink', err) - msgbox.write('ERR at putlink' + err) + msgbox.write('ERR at putlink: ' + err) + return false } return true } - let removeLink = (outId, outName, inId, inName) => { + let cutLink = (outInd, outputInd, inInd, inputInd) => { try { - let outp = $(this.plane).children('.block').children('.outputs').children('#' + outId + '_output_' + outName).get(0) - outp.connectedTo.splice(outp.connectedTo.indexOf('#' + inId + '_input_' + inName), 1) + let outputdef = defs[outInd].outputs[outputInd] + let inputdef = defs[inInd].inputs[inputInd] + console.log('to disconn', outputdef, 'from', inputdef) + outputdef.disconnect(inputdef) this.drawLinks() } catch (err) { console.log('ERR at rmlink', err) @@ -773,42 +732,58 @@ function View() { writeMessage([MK.HELLO]) } - this.requestAddHunk = (name, id) => { + this.requestAddHunk = (name, states) => { let msg = [MK.REQADDHUNK] MSGS.writeTo(msg, name, 'string') - if (id) MSGS.writeTo(msg, id, 'string') + // probably loading from a program 'state' object: + for (let st of states) { + //console.log('VIEW REQ STATE ON ADD', st) + msg.push(HK.STATE) + MSGS.writeTo(msg, st.ind, 'uint8') + MSGS.writeTo(msg, st.value, st.type) + } writeMessage(msg) } - this.requestRemoveHunk = (id) => { + this.requestRemoveHunk = (ind) => { let msg = [MK.REQRMHUNK] - MSGS.writeTo(msg, id, 'string') + MSGS.writeTo(msg, ind, 'uint16') + writeMessage(msg) + } + + this.requestAddLink = (output, input) => { + let msg = [MK.REQADDLINK] + 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') writeMessage(msg) } - this.requestAddLink = (outId, outputName, inId, inputName) => { + this.requestAddLinkLikeACaveman = (outInd, outputInd, inInd, inputInd) => { let msg = [MK.REQADDLINK] - MSGS.writeTo(msg, outId, 'string') - MSGS.writeTo(msg, outputName, 'string') - MSGS.writeTo(msg, inId, 'string') - MSGS.writeTo(msg, inputName, 'string') + MSGS.writeTo(msg, outInd, 'uint16') + MSGS.writeTo(msg, outputInd, 'uint8') + MSGS.writeTo(msg, inInd, 'uint16') + MSGS.writeTo(msg, inputInd, 'uint8') writeMessage(msg) } - this.requestRemoveLink = (outId, outputName, inId, inputName) => { + this.requestRemoveLink = (output, input) => { let msg = [MK.REQRMLINK] - MSGS.writeTo(msg, outId, 'string') - MSGS.writeTo(msg, outputName, 'string') - MSGS.writeTo(msg, inId, 'string') - MSGS.writeTo(msg, inputName, 'string') + 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') writeMessage(msg) } - this.requestStateChange = (parentId, state, value) => { - // ok then, + // , numoot (number) + this.requestStateChange = (state, value) => { + // ok then, THISNOW let msg = [MK.REQSTATECHANGE] - MSGS.writeTo(msg, parentId, 'string') - MSGS.writeTo(msg, state.name, 'string') + MSGS.writeTo(msg, state.parent.ind, 'uint16') + MSGS.writeTo(msg, state.ind, 'uint8') // not unlikely mess here, try { MSGS.writeTo(msg, value, state.type) @@ -819,53 +794,6 @@ function View() { } } - // responses (change state / add link) - - let receiveStateChange = (parentId, name, bytes, index) => { - let blkstate = $(this.plane).find('#' + parentId + '_state_' + name) - if (blkstate !== null && blkstate !== undefined) { - // the blkstate is the div element, inside of a <li> it has (type) - // we pick that out of the string *like a monkey* because truth is what we see - let li = $(blkstate).children().get(0).innerText - let tString = li.substring(li.indexOf('(') + 1, li.indexOf(')')) - // ok, - let val - try { - val = MSGS.readFrom(bytes, index, tString) - } catch (err) { - writeErrMessage('no good type matching at view reception of state change') - console.log(err) - } - // assuming that went well, it's a system type. we don't need to know which type apart from: - if (Object.keys(val).includes('item') && Object.keys(val).includes('increment')) { - val = val.item - } - // and finally, update the dom - switch (typeof val) { - case 'string': - let strinput = $(blkstate).children('input').get(0) - strinput.value = val - break - case 'number': - let numput = $(blkstate).children('input').get(0) - numput.value = val.toString(10) - break - case 'boolean': - if (val) { - $(blkstate).children('span').text('true') - } else { - $(blkstate).children('span').text('false') - } - break - default: - throw new Error('state type no bueno wyd?') - break - } - } else { - writeErrMessage("couldn't find the requested state item on a received-state-change message") - } - } - /* --------------------------- ---------------------------- */ /* --------------------- MESSAGES OUTPUT --------------------- */ /* --------------------------- ---------------------------- */ @@ -895,40 +823,46 @@ function View() { if (msgsin.io) { let msg = msgsin.get() // at view, read in and deserialize list - if (msgverbose) console.log('VIEW MSG:', msg) + if (msgverbose) console.log('VIEW RX MSG:', msg) if (!Array.isArray(msg)) throw new Error(`view throwing object message, having header ${msg.header}`) - + // for item, increment pulls + let temp // ok, switch (msg[0]) { case MK.ERR: + if (msgverbose) console.log('VIEW MSG is an error') msgbox.write(MSGS.readFrom(msg, 1, 'string').item) break case MK.HELLO: + if (msgverbose) console.log('VIEW MSG is hello') msgbox.write(`manager says hello, took ${performance.now() - helloTime}ms`) break case MK.BRIEF: + if (msgverbose) console.log('VIEW MSG is a manager brief') // title, name of manager, lsit of unloaded hunks ? msgbox.write('manger sends program brief, will begin loading...') // serial -> js, by procedure du jakhey let brief = {} let bi = 1 // reading strings returns item, increment - let id = MSGS.readFrom(msg, bi, 'string') - bi += id.increment - brief.interpreterId = id.item - let intrprtrnm = MSGS.readFrom(msg, bi, 'string') - bi += intrprtrnm.increment - brief.interpreterName = intrprtrnm.item - let intrprtrv = MSGS.readFrom(msg, bi, 'string') - bi += intrprtrv.increment - brief.interpreterVersion = intrprtrv.item - brief.numHunks = MSGS.readFrom(msg, bi, 'uint32') - brief.numLinks = MSGS.readFrom(msg, bi + 5, 'uint32') + temp = MSGS.readFrom(msg, bi, 'string') + bi += temp.increment + brief.interpreterName = temp.item + temp = MSGS.readFrom(msg, bi, 'string') + bi += temp.increment + brief.interpreterVersion = temp.item + temp = MSGS.readFrom(msg, bi, 'uint16') + bi += temp.increment + brief.numHunks = temp.item + brief.numLinks = MSGS.readFrom(msg, bi, 'uint16').item + // set local, this.interpreterName = brief.interpreterName this.interpreterVersion = brief.interpreterVersion + // and write it down msgbox.briefState.setFromBrief(brief) break case MK.LISTOFAVAIL: + if (msgverbose) console.log('VIEW MSG is a list of available items') let stringlist = MSGS.readListFrom(msg, 1, 'string') this.changeContextTitle('available hunks:') for (let item of stringlist) { @@ -941,36 +875,67 @@ function View() { } break case MK.HUNKALIVE: - console.log('hunk alive, going to deserialize') - let def = defBySerial(msg, 1) - putDef(def) - patchset.onHunkLoaded(def) + // 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, 1, false) + // so first we check for an existing hunk, + if (defs[spec.ind] === undefined) { + let nd = newDef(spec) + msgbox.briefState.decrementHunks() + msgbox.write(`added a new hunk: ${spec.name}`) + patchset.onHunkLoaded(nd) + $(this.dom).find('.contextmenu').remove() + } else { + if(verbose) console.log('replacing hunk at', spec.ind) + let nd = replaceDef(spec) + msgbox.write(`replaced ${spec.name}_${spec.ind} with a new definition`) + patchset.onHunkLoaded(nd) + } + // hmmm ... + // // bfstate should mixin to vptches - msgbox.briefState.decrementHunks() break case MK.HUNKSTATECHANGE: - let stchinc = 1 - let stChId = MSGS.readFrom(msg, stchinc, 'string') - stchinc += stChId.increment - stChId = stChId.item - let stChName = MSGS.readFrom(msg, stchinc, 'string') - stchinc += stChName.increment - stChName = stChName.item - receiveStateChange(stChId, stChName, msg, stchinc) + if (msgverbose) console.log('VIEW MSG is a state change') + let stchHnkInd = MSGS.readFrom(msg, 1, 'uint16').item + let stchStInd = MSGS.readFrom(msg, 4, 'uint8').item + let stDef = defs[stchHnkInd].states[stchStInd] + let stValUpdate = MSGS.readFrom(msg, 6, stDef.type).item + stDef.set(stValUpdate) + patchset.onStateChanged(stDef) + msgbox.write(`changed state at ${stchHnkInd} ${stDef.name} to ${stValUpdate}`) break case MK.HUNKREMOVED: - let rmid = MSGS.readFrom(msg, 1, 'string').item + if (msgverbose) console.log('VIEW MSG is a hunk to remove') + let rmid = MSGS.readFrom(msg, 1, 'uint16').item removeDef(rmid) + msgbox.write(`removed hunk #${rmid}`) break case MK.LINKALIVE: - let alal = MSGS.readListFrom(msg, 1, 'string') - putLink(alal[0], alal[1], alal[2], alal[3]) - patchset.onLinkLoaded() - msgbox.briefState.decrementLinks() + if (msgverbose) console.log('VIEW MSG is a link to put') + let addOutInd = MSGS.readFrom(msg, 1, 'uint16').item + let addOutputInd = MSGS.readFrom(msg, 4, 'uint8').item + let addInInd = MSGS.readFrom(msg, 6, 'uint16').item + let addInputInd = MSGS.readFrom(msg, 9, 'uint8').item + if(putLink(addOutInd, addOutputInd, addInInd, addInputInd)){ + //patchset.onLinkLoaded() + msgbox.write(`added a link`) + msgbox.briefState.decrementLinks() + patchset.onLinkLoaded(addOutInd, addOutputInd, addInInd, addInputInd) + } break case MK.LINKREMOVED: - let rmal = MSGS.readListFrom(msg, 1, 'string') - removeLink(rmal[0], rmal[1], rmal[2], rmal[3]) + if (msgverbose) console.log('VIEW MSG is a link to cut') + let rmOutInd = MSGS.readFrom(msg, 1, 'uint16').item + let rmOutputInt = MSGS.readFrom(msg, 4, 'uint8').item + let rmInInd = MSGS.readFrom(msg, 6, 'uint16').item + let rmInputInd = MSGS.readFrom(msg, 9, 'uint8').item + if(cutLink(rmOutInd, rmOutputInt, rmInInd, rmInputInd)){ + msgbox.write(`removed a link`) + } else { + throw new Error('error during link cut') + } break default: throw new Error(`view receives message with no switch: ${msg[0]}`) diff --git a/programs/cuttlefish/wstst.json b/programs/cuttlefish/wstst.json new file mode 100644 index 0000000000000000000000000000000000000000..f87cc4f78ec507ba75541c10e214b076fcb95665 --- /dev/null +++ b/programs/cuttlefish/wstst.json @@ -0,0 +1,81 @@ +{ + "interpreterName": "cuttlefish", + "interpreterVersion": "v0.1", + "hunks": [{ + "name": "manager", + "states": [] + }, { + "name": "view", + "states": [] + }, { + "name": "link", + "states": [{ + "ind": 0, + "type": "string", + "value": "mgrMsgs (byteArray), numtype (number)" + }, { + "ind": 1, + "type": "string", + "value": "mgrMsgs (byteArray), numoot (number)" + }] + }, { + "name": "view", + "states": [] + }, { + "name": "interface/number", + "states": [{ + "ind": 0, + "type": "number", + "value": 12 + }] + }, { + "name": "comm/websocketclient", + "states": [{ + "ind": 0, + "type": "string", + "value": "closed" + }, { + "ind": 1, + "type": "number", + "value": 3 + }, { + "ind": 2, + "type": "boolean", + "value": false + }, { + "ind": 3, + "type": "string", + "value": "127.0.0.1" + }, { + "ind": 4, + "type": "number", + "value": 2042 + }] + }], + "links": [{ + "outInd": 2, + "outputInd": 0, + "inInd": 5, + "inputInd": 0 + }, { + "outInd": 2, + "outputInd": 1, + "inInd": 3, + "inputInd": 0 + }, { + "outInd": 3, + "outputInd": 0, + "inInd": 2, + "inputInd": 1 + }, { + "outInd": 4, + "outputInd": 0, + "inInd": 2, + "inputInd": 2 + }, { + "outInd": 5, + "outputInd": 0, + "inInd": 2, + "inputInd": 0 + }] +} diff --git a/programs/nautilus/ntThru.json b/programs/nautilus/ntThru.json new file mode 100644 index 0000000000000000000000000000000000000000..ba12aaf896ccbc4f04319c1ad73e5a419d1aac25 --- /dev/null +++ b/programs/nautilus/ntThru.json @@ -0,0 +1 @@ +{"interpreterName":"nautilus","interpreterVersion":"v0.1","hunks":[{"name":"manager","states":[]},{"name":"link","states":[{"ind":0,"type":"string","value":"mgrMsgs (byteArray), numtoview (number)"},{"ind":1,"type":"string","value":"mgrMsgs (byteArray), numfromview (number)"}]},{"name":"comm/websocketserver","states":[{"ind":0,"type":"string","value":"connected"},{"ind":1,"type":"number","value":2042}]},{"name":"comm/cobserial","states":[{"ind":0,"type":"string","value":"closed"},{"ind":1,"type":"string","value":"8022"},{"ind":2,"type":"boolean","value":false}]},{"name":"link","states":[{"ind":0,"type":"string","value":"mgrMsgs (byteArray), numthru (number)"},{"ind":1,"type":"string","value":"mgrMsgs (byteArray), numback (number)"}]}],"links":[{"outInd":0,"outputInd":0,"inInd":1,"inputInd":1},{"outInd":1,"outputInd":0,"inInd":2,"inputInd":0},{"outInd":1,"outputInd":1,"inInd":0,"inputInd":0},{"outInd":1,"outputInd":2,"inInd":4,"inputInd":2},{"outInd":2,"outputInd":0,"inInd":1,"inputInd":0},{"outInd":3,"outputInd":0,"inInd":4,"inputInd":0},{"outInd":4,"outputInd":0,"inInd":3,"inputInd":0},{"outInd":4,"outputInd":2,"inInd":1,"inputInd":2}]} \ No newline at end of file diff --git a/programs/nautilus/ntsave.json b/programs/nautilus/ntsave.json new file mode 100644 index 0000000000000000000000000000000000000000..3522a987f2fe42b7905e1f28b6d7ba4e6cde2aa1 --- /dev/null +++ b/programs/nautilus/ntsave.json @@ -0,0 +1,22 @@ +{ + "interpreterName": "cuttlefish", + "interpreterVersion": "v0.1", + "hunks": [{ + "name": "comm/websocketserver", + "states": [{ + "ind": 0, + "type": "string", + "value": "connected" + }, { + "ind": 1, + "type": "number", + "value": 2042 + }] + }], + "links": [{ + "outInd": 2, + "outputInd": 0, + "inInd": 1, + "inputInd": 0 + }] +} diff --git a/programs/ptest.json b/programs/ptest.json deleted file mode 100644 index bd114b60d46c9b0ce922bee3e161857b163db932..0000000000000000000000000000000000000000 --- a/programs/ptest.json +++ /dev/null @@ -1 +0,0 @@ -{"interpreterName":"cuttlefish","interpreterVersion":"v0.1","hunks":[{"id":"NROL39_0","name":"manager"},{"id":"TLView","name":"view"},{"id":"hnk_3","name":"view"},{"id":"hnk_2","name":"link","state":[{"name":"inputList","type":"string","value":"mgrMsgs (byteArray), num (number)"},{"name":"outputList","type":"string","value":"mgrMsgs (byteArray)"}]}],"links":[{"outhunk":"NROL39_0","outname":"msgs","inhunk":"TLView","inname":"msgs"},{"outhunk":"TLView","outname":"msgs","inhunk":"NROL39_0","inname":"msgs"},{"outhunk":"hnk_3","outname":"msgs","inhunk":"hnk_2","inname":"data"},{"outhunk":"hnk_2","outname":"data","inhunk":"hnk_3","inname":"msgs"}]} \ No newline at end of file diff --git a/style.css b/style.css index 10a89ccae33cf02157f645de6ed021b200f0d7ca..efe1d8af719fc96308b45e50776fe1c7b2a9ab7f 100644 --- a/style.css +++ b/style.css @@ -97,15 +97,6 @@ body { cursor: se-resize; } -.contextmenu { - position: absolute; - overflow: hidden; - width: 245px; - padding: 10px; - background-color: #303030; - color: #eee; -} - #programMenu { position: absolute; width: 245px; @@ -136,7 +127,7 @@ body { color: #eee; } -.block { +.def { width: 500px; overflow: hidden; position: absolute; @@ -149,7 +140,7 @@ body { background-color: #4d4c4c; } -.blockid { +.deftitle { font-size: 15px; font-weight: bold; font-style: italic; @@ -160,12 +151,12 @@ body { color: #eee; } -.blockid:hover{ +.deftitle:hover{ background-color: #969696; cursor: grab; } -.blockid:active{ +.deftitle:active{ cursor: grabbing; } @@ -196,12 +187,12 @@ body { cursor: grab; } -.state { +.states { padding: 0 123px 0 123px; color: #eee; } -.stateItem { +.state { font-size: 11px; padding: 3px; } @@ -224,17 +215,38 @@ textarea { } */ -.state input { +.stateItem { + margin: 3px; +} + +.stateItem input { background-color: #1a1a1a; color: #fcd17b; font-size: 12px; width: 70%; margin-bottom: 2px; - float: right; + margin-top: 4px; + padding: 2px; border: 1px solid black; border-radius: 3px; } +.stateNumInput input { + float: right; + clear: both; + align: right; +} + +.stateBooleanItem{ + padding-top: 8px; + padding-bottom: 8px; +} + +.stateBooleanItem:hover{ + background-color: #1a1a1a; + cursor: pointer; +} + .dg.a { margin-right: 0px; } @@ -278,16 +290,39 @@ li:active{ padding: 7px 5px 6px 5px; } + +/* .contextTitle:hover { background-color: #969696; cursor: grab; } +*/ .contextTitle:active { background-color: #d1d1d1; cursor: grabbing; } +.contextmenu { + position: absolute; + overflow: hidden; + width: 245px; + padding: 10px; + background-color: #303030; + color: #eee; +} + +.contextmenu input { + background-color: #1a1a1a; + color: #fcd17b; + font-size: 12px; + width: 70%; + margin-bottom: 2px; + padding: 2px; + border: 1px solid black; + border-radius: 3px; +} + /* USING THESE WITHIN BOYOS */ .cuttlefishhunkdom { diff --git a/typeset.js b/typeset.js index d82b473248624c16f387604538da854d77a1d669..f7ada08d0f7f2f5c50435f613979ba7f4d8bcfc3 100644 --- a/typeset.js +++ b/typeset.js @@ -1,62 +1,67 @@ -// as an array, to use .find((candidate) => {}) - +// typeset: functional types -> bytes for js -> embedded sys // bless up @ modern js https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays -const MSGKEY_ACK = 254 -/* -const MSGKEY_ACK = 254 -// counting down from -const MSGKEY_OBJECT = 249 -// hmmm .... -const MSGKEY_NUMBER = 248 -const MSGKEY_UINT64 = 247 +let tsdebug = false + +// oy, + +const checkKey = (type, arr, start) => { + if (arr[start] !== type.key) throw new Error(`mismatched key on phy read: for ${type.name}, find ${arr[start]} instead of ${type.key} ... at ${start} index`) +} + +const checkBoolean = (value) => { + if (typeof value !== 'boolean') throw new Error('cannot cast non-boolean to bool at phy') +} -MSGKEY_BYTEARRAY: 33, -MSGKEY_UINT8: 34, -MSGKEY_UINT8ARRAY: 35, -MSGKEY_INT8: 36, -MSGKEY_INT8ARRAY: 37, -MSGKEY_DOUBLEFLOAT: 33 -*/ +const checkNumber = (value) => { + if (typeof value !== 'number') throw new Error(`for uint8 cannot cast non-number into physical world "${value}", ${typeof value}`) +} + +const checkString = (value) => { + if (typeof value !== 'string') throw new Error(`cannot cast non-string to string at phy! "${value}", "${typeof value}"`) +} + +const checkArray = (thing) => { + if (!Array.isArray(thing)) throw new Error('this thing is not an array!') +} + +const checkUnsigned = (value, bits) => { + if(value > Math.pow(2, bits)) throw new Error('value out of byte bounds') +} + +const checkSigned = (value, bits) => { + let comparator = Math.pow(2, bits-1) + if(value > comparator || value < -comparator) throw new Error('value out of byte bounds') +} const findPhy = (type) => { let phy = TSET.find((cand) => { return cand.name === type }) - if(phy === undefined) throw new Error(`could not find phy for datatype: ${type}`) + if (phy === undefined) throw new Error(`could not find phy for datatype: ${type}`) return phy } const TSET = [{ - name: 'byte', + name: 'boolean', key: 32, write: function(value) { - // convert to, - }, - read: function(arr) { - // retun number - } - }, - { - name: 'boolean', - key: 33, - write: function(value){ - if (typeof value !== 'boolean') throw new Error('cannot cast non-boolean to bool at phy') + checkBoolean(value) let rtarr = [this.key] - if(value){ + if (value) { rtarr.push(1) - }else{ + } else { rtarr.push(0) } return rtarr }, - read: function(arr, start){ - if(arr[start] !== this.key) throw new Error(`mismatched key on phy read: ${arr[start]}, ${this.key}`) + read: function(arr, start) { + checkKey(this, arr, start) let item - if(arr[start + 1] === 1){ + if (arr[start + 1] === 1) { item = true - } else if(arr[start + 1] === 0){ + } else if (arr[start + 1] === 0) { item = false } else { throw new Error(`non std boolean byte, ${arr[start+1]}`) @@ -66,45 +71,134 @@ const TSET = [{ increment: 2 } } + }, // booleanArray: 33, + { + name: 'byte', + key: 34, + write: function(value) { + checkNumber(value) + // truncate or error ? + checkUnsigned(value, 8) + return [this.key, value] + }, + read: function(arr, start) { + checkKey(this, arr, start) + return { + item: arr[start + 1], + increment: 2 + } + } }, + { + name: 'byteArray', + key: 35, + write: function(value) { + // assume justice has already been served, + checkArray(value) + let rtarr = writeLenBytes(value.length).concat(value) + rtarr.unshift(this.key) + if(tsdebug) console.log('byteArray sanity check:', value, 'written as:', rtarr) + return rtarr + }, + read: function(arr, start) { + checkKey(this, arr, start) + let lb = readLenBytes(arr, start + 1) + // okey + let narr = new Array() + for(let i = 0; i < lb.len; i++){ + narr.push(arr[start + 1 + lb.numBytes + i]) + } + return { + item: narr, + increment: lb.len + lb.numBytes + 1 + } + } + }, // char 36, string 37, { name: 'string', - key: 38, - write: function(str){ - if (typeof str !== 'string') throw new Error('cannot cast non-string to string at phy') + key: 37, + write: function(str) { + checkString(str) let rtarr = new Array() - for(let i = 0; i < str.length; i ++){ + for (let i = 0; i < str.length; i++) { rtarr.push(str.charCodeAt(i)) } // length bytes are 7-bit 'msb for continue' numbers ... + // the -1 because we are not counting the length of the key let lb = writeLenBytes(rtarr.length) rtarr = lb.concat(rtarr) - // key always rtarr.unshift(this.key) return rtarr }, read: function(arr, start) { - if(arr[start] !== this.key) throw new Error(`mismatched key on phy read: ${arr[start]}, ${this.key}`) + checkKey(this, arr, start) let lb = readLenBytes(arr, start + 1) //console.log('lenbytes', lb) let str = new String() - for(let i = 0; i < lb.len; i ++){ + for (let i = 0; i < lb.len; i++) { str += String.fromCharCode(arr[start + 1 + lb.numBytes + i]) } - // TODO: it would be *super handy* if I could pass in an i to increment ... maybe I pass in an object, - // containing that incrementer ? - // this could be optional for all? - // more complex types, we need to know upstream how far to increment counter as well, - // the +1 here is assuming incrementing over the leading key as well, so that at arr[start + increment] we find a next key, return { item: str, increment: lb.len + lb.numBytes + 1 } } }, + { + name: 'uint8', + key: 38, + write: function(value) { + checkNumber(value) + checkUnsigned(value, 8) + if (value > 255 || value < 0) throw new Error('num too large to represent with cast type, will contencate') + // dont' need to type-buffer this, + let rtarr = [this.key, value] + return rtarr + }, + read: function(arr, start) { + // assume we're reading out of an array + // start[] should === key + if (arr[start] !== this.key) throw new Error(`mismatched key on phy read: ${arr[start]}, ${this.key}`) + if (arr[start + 1] > 255 || arr[start + 1] < 0) throw new Error('whaky read-in on uint8') + return { + item: arr[start + 1], + increment: 2 + } + } + }, // uint8Array 39 + { + name: 'uint16', + key: 40, + write: function(value) { + if (typeof value !== 'number') throw new Error(`cannot cast non-number into physical world "${value}"`) + if (value > 65536 || value < 0) throw new Error('num too large to represent with cast type, will contencate') + let tparr = new Uint16Array(1) + tparr[0] = value + let btarr = new Uint8Array(tparr.buffer) + //place + let rtarr = Array.from(btarr).reverse() + rtarr.unshift(this.key) + return rtarr + }, + read: function(arr, start) { + // assume we're reading out of an array + // start[] should === key + if (arr[start] !== this.key) throw new Error(`mismatched key on phy read: ${arr[start]}, ${this.key}`) + let rdarr = arr.slice(start + 1, start + 3).reverse() + let btarr = Uint8Array.from(rdarr) + if (tsdebug) console.log('bytes on read of uint16 (little eadian)', btarr) + // now make uint32 view on this ... + let vlarr = new Uint16Array(btarr.buffer) + if (tsdebug) console.log('vlarr', vlarr) + return { + item: vlarr[0], + increment: 3 + } + } + }, // uint16 array 41 { name: 'uint32', - key: 39, + key: 42, write: function(value) { if (typeof value !== 'number') throw new Error('cannot cast non-number into physical world') if (value > 4294967296) throw new Error('num too large to represent with cast type, will contencate') @@ -119,23 +213,32 @@ const TSET = [{ read: function(arr, start) { // assume we're reading out of an array // start[] should === key - if(arr[start] !== this.key) throw new Error(`mismatched key on phy read: ${arr[start]}, ${this.key}`) + if (arr[start] !== this.key) throw new Error(`mismatched key on phy read: ${arr[start]}, ${this.key}`) let rdarr = arr.slice(start + 1, start + 5).reverse() let btarr = Uint8Array.from(rdarr) - console.log('bts on read of uint32', btarr) + if (tsdebug) console.log('bts on read of uint32', btarr) // now make uint32 view on this ... let vlarr = new Uint32Array(btarr.buffer) - console.log('vlarr', vlarr) + if (tsdebug) console.log('vlarr', vlarr) return { item: vlarr[0], increment: 5 } } - }, + }, // uint32array 43, + /* + uint64 44, uint64array 45, + int8 46, int8array 47, + int16 48, int16array 49, + int32 50, int32array 50, + int64 52, int64array 53, + float32 54, float32array 55, + float64 56, float64array 57 (these are === javascript 'numbers') ... how to alias ? + */ { name: 'number', - key: 40, - write: function(value){ + key: 56, + write: function(value) { if (typeof value !== 'number') throw new Error('cannot cast non-number into physical world') let tparr = new Float64Array(1) tparr[0] = value @@ -145,13 +248,13 @@ const TSET = [{ rtarr.unshift(this.key) return rtarr }, - read: function(arr, start){ - if(arr[start] !== this.key) throw new Error(`mismatched key on phy read: ${arr[start]}, ${this.key}`) + read: function(arr, start) { + if (arr[start] !== this.key) throw new Error(`mismatched key on phy read: ${arr[start]}, ${this.key}`) let rdarr = arr.slice(start + 1, start + 9).reverse() let btarr = Uint8Array.from(rdarr) - console.log('bts on read of float64', btarr) + if (tsdebug) console.log('bts on read of float64', btarr) let vlarr = new Float64Array(btarr.buffer) - console.log('vlarr', vlarr) + if (tsdebug) console.log('vlarr', vlarr) return { item: vlarr[0], increment: 9 @@ -164,19 +267,19 @@ const TSET = [{ const writeLenBytes = (len) => { // return array of len bytes for this number let bts = new Array() - for(let i = 0; i < 3; i ++){ + for (let i = 0; i < 3; i++) { // chonk, - let sevenup = (len >>> (7 * i)) & 127 + let sevenup = (len >> (7 * i)) & 127 // and if any are remaining, - if((len >>> (7 * i)) > 127){ + if ((len >> (7 * i)) > 127) { // drop a 1 in the front, (128 == 1000000) - sevenup = sevenup & 128 + sevenup = sevenup | 128 bts.push(sevenup) } else { bts.push(sevenup) break } - if(i > 2) throw new Error('suspiciously long len cast', bts) + if (i > 2) throw new Error('suspiciously long len cast', bts) } //console.log(`for len ${len}, wrote bytes`, bts) return bts @@ -188,17 +291,17 @@ const readLenBytes = (arr, start) => { // there is at least one, let nb = 0 // carefully, and no more than four - for(let i = 0; i < 3; i ++){ + for (let i = 0; i < 3; i++) { // bitwise operations assume (?) that numbers are 32 bit signed integers, // place the nth in position, len = len | ((arr[start + nb] & 127) << (7 * nb)) // if this byte was < 127, there was no 1 in the MSB, that was the last len byte, - nb ++ + nb++ // go forth, go back - if(arr[start + nb - 1] < 127) break + if (arr[start + nb - 1] < 127) break // otherwise there is a trailing len byte, continue this for // if we grow past 4 len bytes, we have a size > 268 million, more than likely some mistakes were made - if(nb > 4) throw new Error('nb > 4 while reading lenBytes') + if (nb > 4) throw new Error('nb > 4 while reading lenBytes') } // need 2 know how many to increment as well, return { @@ -209,37 +312,37 @@ const readLenBytes = (arr, start) => { // heavy mixin of functional programming const MSGS = { - writeTo: function(bytes, thing, type, debug){ + writeTo: function(bytes, thing, type, debug) { let phy = findPhy(type) let block = phy.write(thing) - if(debug) console.log(`writing for type ${type} and thing '${thing}' the following block of bytes`, block) + if (debug) console.log(`writing for type ${type} and thing '${thing}' the following block of bytes`, block) // write-in to msg like this block.forEach((byte) => { bytes.push(byte) }) }, - readFrom: function(bytes, place, type){ + readFrom: function(bytes, place, type) { let phy = findPhy(type) // check that type exists at place, rip it oot and return it return phy.read(bytes, place) }, - readListFrom: function(bytes, place, type){ + readListFrom: function(bytes, place, type) { // using this where I expect a lit of values, i.e. the addLink(str,str,str,str) arguments, // plucks thru, continuing to pull values as long as the next in the serialized list is of // the right type let phy = findPhy(type) // the list of items, let list = new Array() - while(place < bytes.length){ + while (place < bytes.length) { let res = phy.read(bytes, place) list.push(res.item) place += res.increment - if(bytes[place] !== phy.key) break + if (bytes[place] !== phy.key) break // this could throw us into infinite loops, so - if(res.increment < 1) throw new Error('dangerous increment while reading list') + if (res.increment < 1) throw new Error('dangerous increment while reading list') } - if(list.length < 1) throw new Error('reading list, found no items...') - console.log('read list as', list) + if (list.length < 1) throw new Error('reading list, found no items...') + if (tsdebug) console.log('read list as', list) return list } } @@ -248,41 +351,50 @@ const MSGS = { // manager keys const MK = { // bzzt - ERR: 254, // (str) message + ERR: 254, // (str) message // heartbeats, wakeup - HELLO: 253, // (eom) + HELLO: 253, // (eom) // request a top-level description - REQDESCRIBESELF: 251, // (eom) - BRIEF: 250, // (str) name of interpreter, # hunks, # links (and then begin firing list back) + REQDESCRIBESELF: 251, // (eom) + BRIEF: 250, // (str) name of interpreter, # hunks, # links (and then begin firing list back) // please show what is available - REQLISTAVAIL: 249, // (eom) - LISTOFAVAIL: 248, // (list)(str) names 'dirs/like/this' (includes programs ?) (this might be multiple packets?) + REQLISTAVAIL: 249, // (eom) + LISTOFAVAIL: 248, // (list)(str) names 'dirs/like/this' (includes programs ?) (this might be multiple packets?) // business ... we should be able to centralize all control w/i view.js if we can write these - REQADDHUNK: 247, // (str) name - HUNKALIVE: 246, // (hunkdescription): name, id, inputlist, outputlist, statelist + REQADDHUNK: 247, // (str) name + HUNKALIVE: 246, // (hunkdescription): name, id, inputlist, outputlist, statelist REQSTATECHANGE: 245, HUNKSTATECHANGE: 244, - REQRMHUNK: 243, // (str) id - HUNKREMOVED: 242, // (str) id - REQADDLINK: 241, // (str) id, (str) outname, (str) id, (str) inname - LINKALIVE: 240, // (str) id, (str) outname, (str) id, (str) inname - REQRMLINK: 239, // (str) id, (str) outname, (str) id, (str) inname - LINKREMOVED: 238, // (str) id, (str) outname, (str) id, (str) inname + REQRMHUNK: 243, // (str) id + HUNKREMOVED: 242, // (str) id + REQADDLINK: 241, // (str) id, (str) outname, (str) id, (str) inname + LINKALIVE: 240, // (str) id, (str) outname, (str) id, (str) inname + REQRMLINK: 239, // (str) id, (str) outname, (str) id, (str) inname + LINKREMOVED: 238, // (str) id, (str) outname, (str) id, (str) inname } // hunk description keys, const HK = { NAME: 253, - ID: 252, + IND: 252, DESCR: 251, INPUT: 249, OUTPUT: 247, STATE: 245, } +// link keys, +const LK = { + ACK: 254 +} + // should write out as list of pairs ? // or write fn to do key(stringtype) export { - TSET, MSGKEY_ACK, MK, HK, MSGS + TSET, + MK, // manager keys + HK, // hunk def keys + LK, // link keys + MSGS } diff --git a/view/vbzt.js b/view/vbzt.js index ae817b983feb6fecfeb66798be3fbe4c66564893..b9fb4fc6752bfa90cd7007d2619939c8841902ed 100644 --- a/view/vbzt.js +++ b/view/vbzt.js @@ -62,7 +62,7 @@ function BezierTools(View) { return bz } - this.writeBezier = (head, tail, id) => { + this.writeBezier = (head, tail) => { let svg = document.createElementNS(svgns, 'svg') svg.style.position = 'absolute' svg.style.left = head.x + 'px' @@ -72,15 +72,18 @@ function BezierTools(View) { svg.setAttribute('width', 12) svg.setAttribute('height', 12) svg.setAttribute('class', 'svg') - svg.setAttribute('id', 'svg' + '_' + id) + //svg.setAttribute('id', 'svg' + '_' + id) let bz = newBezier(head, tail, 7) svg.appendChild(bz.elem) view.plane.append(svg) return bz.elem } - this.drawLink = (head, tail, outdiv, hk, conn) => { - let bz = this.writeBezier(head, tail, outdiv.id + '_to_' + hk.id) + // these are 'def' objects + this.drawLink = (output, input) => { + let head = this.getRightHandle(output.de) + let tail = this.getLeftHandle(input.de) + let bz = this.writeBezier(head, tail) bz.addEventListener('mouseenter', (evt) => { bz.style.stroke = '#2889af' }) @@ -91,12 +94,7 @@ function BezierTools(View) { evt.preventDefault() evt.stopPropagation() // nice - let outId = outdiv.id.substring(0, outdiv.id.indexOf('_output_')) - let outName = outdiv.id.substring(outdiv.id.indexOf('_output_') + 8) - let inId = conn.substring(1, conn.indexOf('_input_')) - let inName = conn.substring(conn.indexOf('_input_') + 7) - // and the serialized message via the view, nice - view.requestRemoveLink(outId, outName, inId, inName) + view.requestRemoveLink(output, input) }) } } diff --git a/view/vdef.js b/view/vdef.js new file mode 100644 index 0000000000000000000000000000000000000000..edc368ab0c3733ae5fcd013ed7e13fe5fbf7b56f --- /dev/null +++ b/view/vdef.js @@ -0,0 +1,341 @@ +// a def is not a hunk, it's a definition of a hunk - the mirror image + +// new this - +function HunkDefinition(spec, view, dt, debug) { + // the basics, + this.ind = spec.ind + this.name = spec.name + this.inputs = new Array() + this.outputs = new Array() + this.states = new Array() + + // dom element + this.de = $('<div>').addClass('def').attr('id', `${this.name}_${this.ind}`).get(0) + let title = $(`<div>${this.name}_${this.ind}</div>`).addClass('deftitle').get(0) + + // title right-click handler + title.oncontextmenu = (evt) => { + evt.preventDefault() + evt.stopPropagation() + // write menu for requesting delete and copy + let menu = $('<div>').addClass('contextmenu').get(0) + // title.offsetWidth is 400 + // de.style.left, de.style.top, de.clientWidth, + let ct = dt.readTransform(this.de) + let x = ct.x + 500 + 10 + 'px' + dt.writeTransform(menu, { + s: 1, + x: ct.x + ((500 + 10)), + y: ct.y + }) + $(view.plane).append(menu) + $(menu).append($('<li>remove hunk</li>').on('click', (evt) => { + view.requestRemoveHunk(this.ind) + $(menu).remove() + })) + $(menu).append($('<li>copy hunk</li>').on('click', (evt) => { + view.requestAddHunk(this.name) + $(menu).remove() + })) + } + + // title drag handler, + title.onmousedown = (evt) => { + evt.preventDefault() + evt.stopPropagation() + + let domElemMouseMove = (evt) => { + // TRANSFORMS here to move div about on drag + evt.preventDefault() + evt.stopPropagation() + let ct = dt.readTransform(this.de) + let pt = dt.readTransform(view.plane) + //console.log(ct, pt) + ct.x += evt.movementX / pt.s + ct.y += evt.movementY / pt.s + dt.writeTransform(this.de, ct) + view.drawLinks() + } + + function rmOnMouseUp(evt) { + document.removeEventListener('mousemove', domElemMouseMove) + document.removeEventListener('mouseup', rmOnMouseUp) + } + + document.addEventListener('mousemove', domElemMouseMove) + document.addEventListener('mouseup', rmOnMouseUp) + } + + $(this.de).append(title) + + // write inputs + if (spec.inputs.length > 0) { + let idom = $('<div>').addClass('inputs') + for (let ip in spec.inputs) { + let input = new InputDefinition(spec.inputs[ip], ip, this, debug) + this.inputs.push(input) + $(idom).append(input.de) + } + $(this.de).append(idom) + } + // write outputs, + if (spec.outputs.length > 0) { + let odom = $('<div>').addClass('outputs') + for (let op in spec.outputs) { + let output = new OutputDefinition(spec.outputs[op], op, this, view, dt, debug) + this.outputs.push(output) + $(odom).append(output.de) + } + $(this.de).append(odom) + } + + if (spec.states.length > 0) { + let sdom = $('<div>').addClass('states') + for (let st in spec.states) { + let state = new StateDefinition(spec.states[st], st, this, view, debug) + this.states.push(state) + $(sdom).append(state.de) + } + $(this.de).append(sdom) + } + + this.newInd = (ind) => { + this.ind = ind + // and those titles, and ids ... + $(this.de).attr('id', `${this.name}_${this.ind}`) + $(title).text(`${this.name}_${this.ind}`) + for(let ip of this.inputs){ + ip.newParentInd() + } + for(let op of this.outputs){ + op.newParentInd() + } + for(let st of this.states){ + st.newParentInd() + } + } + + if (debug) console.log('HunkDefinition', this) +} + +function InputDefinition(ipspec, ind, def, debug) { + // keep track of name and type, + this.name = ipspec.name + this.type = ipspec.type + this.parent = def + this.ind = parseInt(ind) + // a dom element + this.de = $(`<li>${this.name} (${this.type})</li>`).addClass('input').get(0) + this.de.id = `${this.parent.name}_${this.parent.ind}_input_${this.name}` + this.newParentInd = () => { + this.de.id = `${this.parent.name}_${this.parent.ind}_input_${this.name}` + } + // we also keep a list, + this.connections = new Array() + this.disconnect = (output) => { + let index = this.connections.findIndex((cand) => { + return (cand.parent.ind === output.parent.ind && cand.name === output.name && cand.type === output.type) + }) + if(index === -1) throw new Error('during output disconnect, input cannot find output...') + this.connections.splice(index, 1) + } + this.disconnectAll = () => { + for(let op of this.connections){ + op.disconnect(this) + } + this.connections.length = 0 + } + // to get this object via the dom, circular... apparently that is fine ? + this.de.hookup = this +} + +function OutputDefinition(opspec, ind, def, view, dt, debug) { + // keep track of name and type, + this.parent = def + this.name = opspec.name + this.type = opspec.type + this.ind = parseInt(ind) + // a dom element + this.de = $(`<li>(${this.type}) ${this.name}</li>`).addClass('output').get(0) + this.de.id = `${this.parent.name}_${this.parent.ind}_output_${this.name}` + this.newParentInd = () => { + this.de.id = `${this.parent.name}_${this.parent.ind}_output_${this.name}` + } + // outputs handle all of the dragging-etc + this.connections = new Array() // of inputdefs, ! + this.connect = (inputdef) => { + inputdef.connections.push(this) + this.connections.push(inputdef) + } + this.disconnect = (inputdef) => { + inputdef.disconnect(this) + let iof = this.connections.findIndex((cand) => { + return (cand.name === inputdef.name && cand.parent.ind === inputdef.parent.ind) + }) + if(iof === -1) throw new Error('could not find input to disconnect') + this.connections.splice(iof, 1) + return true + } + this.disconnectAll = () => { + for(let ip of this.connections){ + ip.disconnect(this) + } + this.connections.length = 0 + } + // the dragging + this.floater = {} + this.hasFloater = false + // ondrag, attached later + let evtDrag = (evt) => { + // jake wants dead reckoning + evt.preventDefault() + evt.stopPropagation() + //let cp = this.readTransform(floater) + let pt = dt.readTransform(view.plane) + let thet = {} + // put it there ... + thet.s = 1 + //console.log(pt.s) + // ... + let fltheight = this.floater.clientHeight + let fltwidth = this.floater.clientWidth + //console.log('mouse', evt.clientY, 'pt', pt.y, 'height', fltheight) + //console.log(fltheight, fltwidth) + thet.x = (evt.clientX - pt.x) / pt.s - ((fltwidth + 5) / pt.s) + thet.y = (evt.clientY - pt.y) / pt.s - (fltheight + (pt.s - 1) * (-5)) / pt.s // - ((fltheight * pt.s) / 2) / pt.s + dt.writeTransform(this.floater, thet) + view.drawLinks() + } + // to remove the below + let dragMouseUp = (evt) => { + if(debug) console.log('MOUSEUP ON', evt.target.id) + // 1st, make sure it's an input + if ($(evt.target).is('.input')) { + // HERE: searcheth by input id text? or + let hk = evt.target.hookup + if(!hk) throw new Error('missing some data at this input...') + // use a dom data flag, to find that input and output id? + view.requestAddLink(this, hk) + // do things to conn, then + } + // cleanup + document.removeEventListener('mouseup', dragMouseUp) + document.removeEventListener('mousemove', evtDrag) + // remove the floater flag + this.hasFloater = false + // remove the floater itself + $(view.plane).find('#floater').remove() + view.drawLinks() + } + // eeeeehntry + this.de.onmousedown = (evt) => { + evt.stopPropagation() + evt.preventDefault() + if (debug) console.log('mousedown for', this) + // this and that flag will be read-in on drawlinks, to draw that link + this.floater = $('<div>').attr('id', 'floater').append(this.type).get(0) + this.hasFloater = true + this.floater.style.zIndex = '1' + // the plane position + let pt = dt.readTransform(view.plane) + // plonk: have to do this now or else clientHeight / width are 0 + view.plane.appendChild(this.floater) + // init out floater position, and put it in the dom + dt.writeTransform(this.floater, { + s: 1, + x: (evt.clientX - pt.x) / pt.s - ((this.floater.clientWidth + 5) / pt.s), + y: (evt.clientY - pt.y) / pt.s - (this.floater.clientHeight + (pt.s - 1) * (-5)) / pt.s // - ((fltheight * pt.s) / 2) / pt.s + }) + // handlers to drag, and remove + document.addEventListener('mousemove', evtDrag) + // and delete / act when mouse comes up + document.addEventListener('mouseup', dragMouseUp) + } +} + +function StateDefinition(stspec, ind, def, view, debug){ + this.parent = def + this.name = stspec.name + this.type = stspec.type + this.ind = parseInt(ind) + // business, + this.value = stspec.value + // ok, + this.de = $('<div>' + this.name + " (" + this.type + ")" + '</div>').addClass('stateItem').get(0) + this.de.id = `${this.parent.name}_${this.parent.ind}_state_${this.name}` + this.newParentInd = () => { + this.de.id = `${this.parent.name}_${this.parent.ind}_state_${this.name}` + } + // ui for these ... we can just cover the basics of js types because yonder serializations + // etc will throw errors for other types. of course, this could help more, but we're in a rush + switch (typeof this.value) { + case 'string': + //dom.append($('<br>').get(0)) + let strinput = $('<input>').attr('type', 'text').attr('size', 32).attr('value', this.value).css('width', '240px').get(0) + strinput.addEventListener('change', (evt) => { + // ask for a change, + // TODO HERE NOW: this is the state change request you want to write + // do it like writeMessage() instead + // requestStateChange(def.id, state, strinput.value) + // but assert that we don't change the definition unless + view.requestStateChange(this, strinput.value) + strinput.value = this.value + }) + this.de.append(strinput) + this.set = (value) => { + if(typeof value === 'string'){ + strinput.value = value + this.value = value + } else { + throw new Error('bad type put into state dom') + } + } + break // end string types + case 'number': + let ninput = $('<input>').addClass('stateNumInput').attr('type', 'text').attr('size', 24).attr('value', this.value.toString()).css('width', '100px').get(0) + ninput.addEventListener('change', (evt) => { + // ask for a change, + view.requestStateChange(this, parseFloat(ninput.value)) + // but assert that we don't change the definition unless + ninput.value = this.value + }) + this.de.append(ninput) + this.set = (value) => { + if(typeof value === 'number'){ + // quite sure js does this conversion no problem + ninput.value = value + this.value = value + } else { + throw new Error('bad type put into state dom') + } + } + break // end numnber type + case 'boolean': + let span = $('<span style="float:right;">' + this.value.toString() + '</span>').get(0) + $(this.de).addClass('stateBooleanItem') + this.de.append(span) + this.de.addEventListener('click', (evt) => { + // read the current 'state' (as written) and send the opposite + let txt = $(span).text() + if (txt === 'true') { + view.requestStateChange(this, false) + } else { + view.requestStateChange(this, true) + } + }) + this.set = (value) => { + if(typeof value === boolean){ + $(span).text(value.toString()) + this.value = value + } else { + throw new Error('bad type put into state dom') + } + } + break// end boolean type + default: + console.error(`unaccounted for type at input pull for state change, ${typeof state.value}`) + break + } +} + +export default HunkDefinition diff --git a/view/vdom.js b/view/vdom.js index 0fa53fd438de640942e5048fedfb658f28c2939c..c1f97c6ca4ccc1a274523c40ccb36ac9b0feed20 100644 --- a/view/vdom.js +++ b/view/vdom.js @@ -63,101 +63,6 @@ function DomTools(View) { /* ---------------------- WRITING DEFS ----------------------- */ /* --------------------------- ---------------------------- */ - // write *the* dom element (.block) - this.writeDefDom = (def, debug) => { - // debug - if (debug) console.log('writing for def', def) - // a div to locate it - let de = document.createElement('div') - $(de).addClass('block').attr('id', def.id) - - // more html: the title - $(de).append($('<div>' + de.id + '</div>').addClass('blockid').append('<span style="float:right;">(' + def.name + ')</span>')) - - let title = $(de).children('.blockid').get(0) - - title.oncontextmenu = (evt) => { - evt.preventDefault() - evt.stopPropagation() - // write menu for requesting delete and copy - let menu = $('<div>').addClass('contextmenu').get(0) - // title.offsetWidth is 400 - // de.style.left, de.style.top, de.clientWidth, - let ct = this.readTransform(de) - let x = ct.x + 500 + 10 + 'px' - this.writeTransform(menu, { - s: 1, - x: ct.x + ((500 + 10)), - y: ct.y - }) - $(view.plane).append(menu) - $(menu).append($('<li>remove hunk</li>').on('click', (evt) => { - view.requestRemoveHunk(def.id) - $(menu).remove() - })) - $(menu).append($('<li>copy hunk</li>').on('click', (evt) => { - view.requestAddHunk(def.name) - $(menu).remove() - })) - } - - // drag title - title.onmousedown = (evt) => { - evt.preventDefault() - evt.stopPropagation() - - let domElemMouseMove = (evt) => { - // TRANSFORMS here to move div about on drag - evt.preventDefault() - evt.stopPropagation() - let ct = this.readTransform(de) - let pt = this.readTransform(de.parentElement) - ct.x += evt.movementX / pt.s - ct.y += evt.movementY / pt.s - this.writeTransform(de, ct) - view.drawLinks() - } - - function rmOnMouseUp(evt) { - // would do save of position state here - // TODO /\ - document.removeEventListener('mousemove', domElemMouseMove) - document.removeEventListener('mouseup', rmOnMouseUp) - // atm this doesn't work because update looks for changes in list size - //updateForceLoop() - } - - document.addEventListener('mousemove', domElemMouseMove) - document.addEventListener('mouseup', rmOnMouseUp) - } - - if (def.inputs.length > 0) { - let idom = $('<div>').addClass('inputs') - for (let ip of def.inputs) { - $(idom).append(writePortDom(ip, def, 'input', debug)) - } - $(de).append(idom) - } - - if (def.outputs.length > 0) { - let odom = $('<div>').addClass('outputs') - for (let op of def.outputs) { - $(odom).append(writePortDom(op, def, 'output', debug)) - } - $(de).append(odom) - } - - if (def.state.length > 0) { - let sdom = $('<div>').addClass('state') - for (let st of def.state) { - $(sdom).append(writeStateDom(st, def, debug)) - } - $(de).append(sdom) - } - - return de - } // END writeDefDom - // write the ports: inputs, outputs let writePortDom = (port, def, inout, debug) => { if (debug) console.log('port dom', port, def.id, inout) diff --git a/view/vmsg.js b/view/vmsg.js index 418cfcec6df3d21885d10c8213d251d846941170..da952e56623d9d8c3d80708609c63e31ba43598d 100644 --- a/view/vmsg.js +++ b/view/vmsg.js @@ -7,18 +7,26 @@ function MessageBox(View) { // tracking a load, this.briefState = { - recipId: '', recipName: '', + recipVersion: '', numHunksLeft: 0, numLinksLeft: 0, + isStateHappenning: false, setFromBrief: function(brief) { - this.recipId = brief.interpreterId this.recipVer = brief.interpreterVersion this.recipName = brief.interpreterName this.numHunksLeft = brief.numHunks this.numLinksLeft = brief.numLinks this.postToDom() }, + stateIsHappening: function() { + this.isStateHappenning = true + this.postToDom() + }, + stateIsNotHappening: function() { + this.isStateHappenning = false + this.postToDom() + }, decrementHunks: function() { this.numHunksLeft-- this.postToDom() @@ -31,16 +39,18 @@ function MessageBox(View) { this.numLinksLeft-- this.postToDom() }, - incrementHunks: function() { + incrementLinks: function() { this.numLinksLeft++ this.postToDom() }, postToDom: function() { let str - if (this.numHunksLeft > 0 || this.numLinksLeft > 0) { - str = `manager id: ${this.recipId} <br>interpreter: ${this.recipName} ${this.recipVer} <br> awaiting ${this.numHunksLeft} hunks and ${this.numLinksLeft} links` + if(this.stateIsHappening){ + str = `manager interpreter: ${this.recipName} ${this.recipVer} <br> awaiting state ...` + } else if (this.numHunksLeft > 0 || this.numLinksLeft > 0) { + str = `manager interpreter: ${this.recipName} ${this.recipVer} <br> awaiting ${this.numHunksLeft} hunks and ${this.numLinksLeft} links` } else { - str = `manager id: ${this.recipId} <br>interpreter: ${this.recipName} ${this.recipVer} <br> all loaded OK` + str = `manager interpreter: ${this.recipName} ${this.recipVer} <br> all loaded OK` } $(themsgbox).find('#titleBox').html(str) } @@ -53,7 +63,7 @@ function MessageBox(View) { /* --------------------------- ---------------------------- */ this.init = () => { - view.log('message box alive') + //view.log('message box alive') // a box, themsgbox = $('<div>').addClass('msgbox').get(0) // the title, and id of your manager @@ -64,6 +74,7 @@ function MessageBox(View) { }).get(0)) // a refresh button themsgbox.append($('<div>').addClass('msgboxbutton').addClass('msgboxmsg').append('~ refresh view ~').click((evt) => { + evt.preventDefault() view.refresh() }).get(0)) // zoom extents object, @@ -100,11 +111,11 @@ function MessageBox(View) { // if too tall, remove let ch = themsgbox.clientHeight if (heightcheck() > ch) { - console.log('rm 1', heightcheck(), ch) + //console.log('rm 1', heightcheck(), ch) $(themsgbox).children().get(4).remove() // two at most, sloppy but fast if (heightcheck() > ch) { - console.log('rm 2', heightcheck(), ch) + //console.log('rm 2', heightcheck(), ch) $(themsgbox).children().get(4).remove() } } diff --git a/view/vptch.js b/view/vptch.js index 514bdd49fc478b0629eec552e93a1ab64e13495d..894d4b321068e6494e0d8e7fd83c72bfc5116116 100644 --- a/view/vptch.js +++ b/view/vptch.js @@ -14,8 +14,12 @@ function PatchSet(View, MsgBox) { // load from server should be assumed, just rip that *baybie* this.findPatches = (evt) => { // and then, + if (!(view.interpreterName)) { + msgbox.write('view on unknown interpeter, cannot save ... refresh view first') + return false + } $(evt.target).append(' > loading a list ...') - gg.recursivePathSearch('programs', '.json', true).then((list) => { + gg.recursivePathSearch('programs/' + view.interpreterName + '/', '.json', true).then((list) => { console.log('returns list', list) // title, and options view.changeContextTitle('available patches:') @@ -27,82 +31,146 @@ function PatchSet(View, MsgBox) { } catch (err) { msgbox.write('caught an error while starting program load') } - $(view.plane).find('.contextmenu').remove() + $(view.dom).find('.contextmenu').remove() }) } }) } - let lastPatch = {} + let patchStep = 0 + let linkLoadingStarted = false let unloadedHunks = [] - let unloadedStates = [] + let awaitStates = [] let unloadedLinks = [] let reqNextHunk = () => { - // from 0th position in array - view.requestAddHunk(unloadedHunks[0].name, unloadedHunks[0].id) + // patchStep is the expected current place in an array of hunks, if + if (view.defs[patchStep] !== undefined) { // have one of these already, + if (view.defs[patchStep].name === unloadedHunks[0].name) { + msgbox.write(`found write-over for ${unloadedHunks[0].name}, checking for req change`) + // look at each state, + if (view.defs[patchStep].states.length < 1) { + unloadedHunks.shift() + patchStep ++ + reqNextHunk() // recursive? pretty sure this is fn completion + } else { + for (let st in view.defs[patchStep].states) { + // might be a few of these, and there might be none + if (view.defs[patchStep].states[st].value !== unloadedHunks[0].states[st].value) { + view.requestStateChange(view.defs[patchStep].states[st], unloadedHunks[0].states[st].value) + // we have to track this ... + msgbox.briefState.stateIsHappening() + awaitStates.push({ + defname: view.defs[patchStep].name, + defind: view.defs[patchStep].ind, + statename: view.defs[patchStep].states[st].name + }) + } + } + } + } else { + // an error, the in-place hunk doesn't exist here + // this is a hack for now ... we are assuming all contexts are starting from bootstrap, + // and that exactly that bootstrap is running already + msgbox.write('lost bootstrap, or existing program, while loading a patch') + console.error(`lost bootstrap, or existing program, while loading a patch: ${patchStep}, ulh, ${unloadedHunks[0].name}, defs, ${view.defs[patchStep].name}`); + } + } else { + // regular ops, + msgbox.briefState.incrementHunks() + console.log('loading hunk from', unloadedHunks[0]) + view.requestAddHunk(unloadedHunks[0].name, unloadedHunks[0].states) + } } - // callbacks, - this.onHunkLoaded = (def) => { - // not interested, - if(unloadedHunks.length < 1) return true - // find in list, rm - let place = unloadedHunks.findIndex((cand) => { - return (cand.id === def.id && cand.name === def.name) + this.onStateChanged = (stateDef) => { + if(awaitStates.length < 1) return true + let indOf = awaitStates.findIndex((cand) => { + return (cand.statename === stateDef.name) }) - if(place !== -1) { - if(place !== 0) console.error("watch out of order loading for programs... shouldn't be like that but it do") - unloadedHunks.shift() - msgbox.write(`patchset placed ${def.id}, ${unloadedHunks.length} hunks remaining`) - if(unloadedHunks.length < 1){ - msgbox.write(`patchset loaded all hunks, now updating state`) - startStateRefresh() - } else { + if(indOf === -1){ + console.error(`misplaced state: ${stateDef.name}`) + } else { + awaitStates.splice(indOf, 1) + console.log('splicing state item', stateDef) + if(awaitStates.length < 0){ + msgbox.briefState.stateIsNotHappening() + unloadedHunks.shift() reqNextHunk() } - } else { - console.error(`hunk not found in unloaded hunks, ${def.id}`) } - // if place, else bad news ... - // HERE - // ... - // and when complete, load first link } - // BRRRRT nevermind, making state-before-init change. - let startStateRefresh = () => { - // so, we have a list of hunks each with some state - msgbox.write(`beginning state sweep`) - // at this point, they should all be loaded into the dom, so we should be able to find them all - for(let hnk of $(view.plane).children('.block')){ - // we can compare each one to the patch - let loaded = lastPatch.hunks.find((cand) => { - return cand.id === hnk.id - }) - if(loaded){ - console.log('for state search over this hunk', hnk, 'w/r/t', loaded) - if(loaded.state){ - // check for differences, and add an item to the list if we need to // id, name, value - } + // callbacks, + // this happens *also* when we update some link state, yikes, + this.onHunkLoaded = (def) => { + // not interested, + if (unloadedHunks.length < 1) return true + // ok, is it something we were asking for a state change on ? + // we *also* receive a state update message, which is nice, + let iopossible = awaitStates.findIndex((cand) => { + return (cand.defname === def.name && cand.defind === def.ind) + }) + if(iopossible !== -1) return true + // ur loaded, + patchStep++ + // ok ... + // find in list, rm + console.log('checking', def, 'against', unloadedHunks[0]) + // check that we *are* receiving in order + let checksOut = true + if (def.name !== unloadedHunks[0].name) checksOut = false + // on a state update case, we might have different states ... for now, avoiding this problem + for (let st in def.states) { + if (def.states[st].value !== unloadedHunks[0].states[st].value) checksOut = false + } + if (!checksOut) { + console.error('find error while loading hunks from a patch: bad state, or bad name') + } else { + unloadedHunks.shift() + if (unloadedHunks.length < 1) { + msgbox.write(`patchset loaded all hunks`) + reqNextLink() + linkLoadingStarted = true } else { - msgbox.write(`ignoring patch-exterior hunk ${hnk.id}`) + msgbox.write(`patchset placed ${def.name}_${def.ind}, ${unloadedHunks.length} hunks remaining`) + reqNextHunk() } } } // try for a one-at-a-time cycle, - this.loadHunkList = (list) => { - + let reqNextLink = () => { + msgbox.briefState.incrementLinks() + console.log('loading link from', unloadedLinks[0]) + let lnk = unloadedLinks[0] + view.requestAddLinkLikeACaveman(lnk.outInd, lnk.outputInd, lnk.inInd, lnk.inputInd) } - this.onLinkLoaded = (outId, outName, inId, inName) => { + this.onLinkLoaded = (addOutInd, addOutputInd, addInInd, addInputInd) => { + // not interested in this + if (!linkLoadingStarted) return true + if (unloadedLinks.length < 1) return true // find in list, rm, + let lnk = unloadedLinks[0] + // this is exhausting, arrays would be nice + if (lnk.outInd !== addOutInd || lnk.outputInd !== addOutputInd || lnk.inInd !== addInInd || lnk.inputInd !== addInputInd) { + console.error('unexpected link notification while loading program') + } else { + unloadedLinks.shift() + if (unloadedLinks.length < 1) { + msgbox.write(`patchset loaded all links, FIN DU LOAD`) + linkLoadingStarted = false + } else { + msgbox.write(`patchset placed a link, ${unloadedLinks.length} links remaining`) + reqNextLink() + } + } } this.loadPatch = (name) => { msgbox.write(`ok, loading a patch from: ${name}`) - gg.getJson('/programs/' + name + '.json').then((patch) => { + gg.getJson('/programs/' + view.interpreterName + '/' + name + '.json').then((patch) => { console.log('the patch', patch) if (patch.interpreterName !== view.interpreterName) { msgbox.write(`WARN: loading patch built in a different interpreter... some hunks may not exist: patch for: "${patch.interpreterName}", but view is connected to "${view.interpreterName}"`) @@ -113,7 +181,6 @@ function PatchSet(View, MsgBox) { // new so that we don't shift out of the original list, unloadedHunks = JSON.parse(JSON.stringify(patch.hunks)) unloadedLinks = JSON.parse(JSON.stringify(patch.links)) - lastPatch = patch // kick it off by sending this message, reqNextHunk() // continue loading ... write a bootload of messages, in queu, waiting for each response? @@ -123,78 +190,55 @@ function PatchSet(View, MsgBox) { }) } - this.saveCurrent = (evt, debug) => { + this.saveCurrent = (evt, defs, debug) => { if (debug) console.log('saving ...') - // riperoni ok + // riperoni ok, save them all let patch = { - interpreterName: gg.interpreterName, - interpreterVersion: gg.interpreterVersion, + // cerntakt + interpreterName: view.interpreterName, + interpreterVersion: view.interpreterVersion, hunks: [], links: [] + // links added later } - // js, u wyld - for (let hnk of $(view.plane).children('.block')) { - // dom is truth - if (debug) console.log('hnk', hnk) - let name = $(hnk).children('.blockid').find('span').text() - name = name.substring(1, name.length - 1) - let phnk = { - id: hnk.id, - name: name - } - let state = [] - for (let st of $(hnk).children('.state').children('.stateItem')) { - // text is faithful, - let text = $(st).find('p').text() - let name = text.substring(0, text.indexOf(' ')) - let type = text.substring(text.indexOf('(') + 1, text.indexOf(')')) - if (debug) console.log('state: name', `"${name}"`, 'type', `"${type}"`) - // the value... (typing on program saves? not if we JSON ...) - let value - if (type === 'boolean') { - let vstring = $(st).find('span').text() - vstring = vstring.substring(vstring.indexOf('(') + 1, vstring.indexOf(')')) - if (vstring === 'true') { - value = true - } else if (vstring === 'false') { - value = false - } else { - console.error('error saving boolean value, setting to false') - value = false - } - } else if (type === 'string') { - value = $(st).find('input').val() - } else { - // must be some kind of number, - value = parseFloat($(st).find('input').val()) - } // end if-on-types sequence - state.push({ - name: name, - type: type, - value: value - }) - } // end roll over states, - if (state.length > 0) { - phnk.state = state + // the hunks, + for (let df of defs) { + // we save them all, + // on load, compare to existing in same position + console.log('writing prep for', df) + // in order, u c + let prep = { + name: df.name, + states: [] } - patch.hunks.push(phnk) - // now roll over links in the hunk, to write outputs - for (let otp of $(hnk).children('.outputs').children('.output')) { - if (debug) console.log('and output', otp) - let outId = otp.id.substring(0, otp.id.indexOf('_output_')) - let outName = otp.id.substring(otp.id.indexOf('_output_') + 8) - for (let conn of otp.connectedTo) { - let inId = conn.substring(1, conn.indexOf('_input_')) - let inName = conn.substring(conn.indexOf('_input_') + 7) - patch.links.push({ - outhunk: outId, - outname: outName, - inhunk: inId, - inname: inName + // HERE: we save state just with the index / value ? + if (df.states !== undefined) { + for (let st of df.states) { + prep.states.push({ + ind: st.ind, + type: st.type, + value: st.value }) } - } // end loop over outputs, for links ... - } // end loop over hunks + } + // roll for links, + // outputs + if (df.outputs !== undefined) { + for (let otp in df.outputs) { + for (let cn in df.outputs[otp].connections) { + patch.links.push({ + outInd: df.ind, // truth in index + outputInd: parseInt(otp), // a num, + inInd: df.outputs[otp].connections[cn].parent.ind, + inputInd: df.outputs[otp].connections[cn].ind + }) + } + } + } + // + console.log('wrote ', prep) + patch.hunks.push(prep) + } // we have this now, if (debug) console.log('a patch', patch) // prompt for name? ... via the (evt) on the callback ?? @@ -218,7 +262,7 @@ function PatchSet(View, MsgBox) { $(evt.target).append(anchor) anchor.click() // finally, rip - $(view.plane).find('.contextmenu').remove() + $(view.dom).find('.contextmenu').remove() //saveAs(bleb, 'file.json') } })