Select Git revision
hunks.js 11.06 KiB
/* --------------------------- ---------------------------- */
/* ------------------------ HUNKITUP ------------------------- */
/* --------------------------- ---------------------------- */
import {
TSET
} from '../typeset.js'
function Hunkify(hunk) {
// scripting languages should name hunks by their script location and filename
// compiled languages will assign a string
// because this is always added by .addHunk(path) (path == name)
// we set this during load, only once, this avoids some confusion
hunk.type = null
hunk.name = null
// we keep a copy of our position-in-the-array ...
hunk.ind = null
hunk.log = function(msg) {
let str = `LG from ${hunk.name} at ${hunk.ind}: `
for (let i = 0; i < arguments.length; i++) {
str += arguments[i]
if (i < arguments.length - 1) str += ', '
}
console.log(str)
}
// input, output, and state: ay balls, here we go
hunk.inputs = new Array()
hunk.outputs = new Array()
hunk.states = new Array()
// top-secret backdoor
hunk.mgr = {}
}
/* --------------------------- ---------------------------- */
/* -------------------------- INPUT -------------------------- */
/* --------------------------- ---------------------------- */
function Input(type, name, parent, linktype) {
// naming, etc
this.name = name
this.type = type
this.parent = parent
// we keep access to the outputs we're hooked up to here
this.connections = new Array()
this.io = () => {
// if any of our connections have data that we haven't read once yet,
for (var i = 0; i < this.connections.length; i++) {
// have to update calls to .io -> .io()
if (this.connections[i].io) return true
}
return false
}
// get calls are now a bit messier,
// we have multiple wires,
this.lastGet = 0 // place we read from last (have multiple wires case)
this.copyOut = {}
this.get = () => {
// we want to (roughly) round robin these
var i = this.lastGet
// find the next occupied conn
for(var u = 0; u < this.connections.length; u ++){
// increment, wrap
i ++
if(i >= this.connections.length) i = 0
// do work,
// console.log('work for', i, this.connections[i])
if (this.connections[i].io) {
// and set that wire to read / empty
this.connections[i].io = false
// and we'd like to remember from whence we last took data
this.lastGet = i
// get this data out ... we have to copy it out again, otherwise
// downstream operators may write to objects that have handles elsewhere
return this.copyOut(i)
}
}
// if we pass this for w/o hitting return,
console.error("Warn! Hunk pulling on empty input!")
return null
}
// typing ... odd ?
if (type === "number" || type === "boolean" || type === "string") { // these are genuine base types and are not passed by reference,
this.copyOut = (indice) => {
//console.log('cout reg')
return this.connections[indice].op.data
}
} else if (type === 'reference') {
this.copyOut = (indice) => {
//console.log('cout ref')
return this.connections[indice].op.ref
}
} else { // this would cover "Object" and "Any" and whatever else, do your typechecking there
this.copyOut = (indice) => {
//console.log('cout else')
return deepCopy(this.connections[indice].op.data)
}
}
this.findOwnIndex = () => {
// find self in parent's inputs
let index = this.parent.inputs.findIndex((cand) => {
return (cand.name === this.name && cand.type === this.type)
})
if (index === -1) throw new Error('input could not find itself in parent')
return index
}
this.disconnect = (output) => {
let index = this.connections.findIndex((cand) => {
return (cand.op = output)
})
if (index === -1) throw new Error('during output disconnect, input cannot find output...')
this.connections.splice(index, 1)
// find the reference in our array,
}
this.disconnectAll = () => {
for (let cn of this.connections) {
cn.op.remove(this)
}
}
// inputs / outputs for 'link' (a hunk) ports have more state,
if (linktype) {
// assume clear upstream on startup, maybe dangerous?
this.icus = true
}
}
/* --------------------------- ---------------------------- */
/* ------------------------- OUTPUT -------------------------- */
/* --------------------------- ---------------------------- */
function Output(type, name, parent, linktype) {
// naming, etc
this.name = name
this.type = type
this.parent = parent
// store,
this.ref = null
this.data = null
// hookup
this.connections = new Array()
// check in realtime, manager does less
this.io = () => {
for (var i = 0; i < this.connections.length; i++) {
// connection arrays contain 'wires' ... these are stateful
if (this.connections[i].io) return true
}
return false
}
// when we put, we use this to set states
this.setIo = () => {
for (var i = 0; i < this.connections.length; i++) {
this.connections[i].io = true
}
}
/* --------------------------- ---------------------------- */
/* ------------ OUTPUT PUT AND TRANSPORT (TYPED) ------------- */
/* --------------------------- ---------------------------- */
// yonder put call, transport call
// copy-in (chances are that after .put call the referenced object changes, before it is copied out)
// and copy-out (same on the other side, we are potentially passing to many downstream, need a different copy for each)
this.put = {}
// typing ... odd ?
if (type === "number" || type === "boolean" || type === "string") { // these are genuine base types and are not passed by reference,
this.put = (data) => {
if (!(this.io())) {
this.ref = data
this.data = data // so this is effectively a pass
this.setIo()
return true
} else {
console.error(`ERROOOOOOR: put called on occupied output: output ${this.name} in ${this.parent.name}`)
return false
}
}
} else if (type === 'reference') {
this.put = (data) => {
if (!this.io()) {
this.ref = data
this.data = data
this.setIo()
return true
} else {
console.error(`ERROOOOOOR: put called on occupied output: output ${this.name} in ${this.parent.name}`)
return false
}
}
} else { // this would cover "Object" and "Any" and whatever else, do your typechecking there
// no rules bb
// names still gotta match tho
// scratch that, truly no rules
//if(!(type === "Object" || type === "Any")) throw new Error(`unknown type specified at hunk, wyd? type: ${this.type}, name: ${this.name}`)
this.put = (data) => {
if (!this.io()) {
// HACK for view
this.ref = data
// make case for byteArray: themr buffers
this.data = deepCopy(data)
this.setIo()
return true
} else {
console.error(`WARN: put called on occupied output: output ${this.name} in ${this.parent.name}`)
return false
}
}
}
/* --------------------------- ---------------------------- */
/* ---------------- OUTPUT MANAGE CONNECTIONS ---------------- */
/* --------------------------- ---------------------------- */
this.attach = (input) => {
// no rules on types ATM, historic placeholder
if (this.type === input.type || input.type === 'any' || true) {
// the wire is the stateful part,
let wire = {
op: this,
ip: input,
io: false
}
input.connections.push(wire)
this.connections.push(wire)
return true
} else {
throw new Error('attempt to attach mismatched types')
}
}
this.remove = (input) => {
// find by names pls
let ind = this.connections.findIndex((cand) => {
return (cand.ip === input)
})
if (ind !== -1) {
input.disconnect(this)
this.connections.splice(ind, 1)
return true
} else {
console.log('remove err, was looking for', input, 'in', this.connections)
return false
}
}
this.findOwnIndex = () => {
// find self in parent's inputs
let index = this.parent.outputs.findIndex((cand) => {
return (cand.name === this.name && cand.type === this.type)
})
if (index === -1) {
console.log(`output could not find itself in parent: ${index}`, this, 'unstrung', this.parent.outputs, 'strung', JSON.parse(JSON.stringify(this.parent.outputs)))
throw new Error(`output could not find itself in parent: ${index}`)
}
return index
}
this.disconnectAll = () => {
for (let cn of this.connections) {
// here we're disconnecting the input, meaning the input is removing this from its list of connections
cn.ip.disconnect(this)
}
// this is actually a genuine way to delete an array in js, http://shrugguy.com
this.connections.length = 0
}
/* --------------------------- ---------------------------- */
/* ----------- OUTPUT TRICKS FOR LINK CONNECTIONS ------------ */
/* --------------------------- ---------------------------- */
if (linktype) {
// has stash ?
this.needsAck = false;
}
}
function State(type, name, startup) {
this.name = name
this.type = type
// have to be a type we can recognize,
if (!isOKType(type)) {
//throw new Error(`unknown type specified at state, wyd? type: ${this.type}, name: ${this.name}`)
}
// TODO pls add check for missing startup value ?
this.value = startup
// this is still something of a hack
// during load, the manager gets in here and dishes a function 2 us
this.hookup = (newval) => {
console.error('@ state change request, no manager hookup')
}
// from within a hunk, we typically call this
// to update the value and ship it out to any views
this.set = (value) => {
this.value = value
this.hookup(this.value, this.msgid)
this.msgid = null
}
this.msgid = null
// a view change calls this function, which we can
// also manually overwrite (say, to set a change handler / checker)
this.change = (value, msgid) => {
// coupla steps here, we track and then call internal fn,
this.msgid = msgid
if (this.onChange) {
this.onChange(value)
} else if (this.onchange) {
this.onchange(value)
} else {
this.set(value)
}
}
}
function deepCopy(obj) {
// turns out parse/stringify is the fastest,
//console.log('to dc', obj)
//console.log(JSON.stringify(obj))
if (obj instanceof ImageData) {
let newData = new ImageData(obj.data, obj.width, obj.height);
// console.log(newData)
return newData;
};
return JSON.parse(JSON.stringify(obj)) // I believe this dehydrates objects.
}
// depricated: used this to check (on input / output startup)
function isOKType(type) {
// etc, lowecase to match with typeof ~ queeeries ~
let ind = TSET.findIndex((cand) => {
return cand.name === type
})
if (type === 'any') {
console.log('WARN!, type of "any" is a bit risky, and cannot traverse a link')
return true
} else if (ind === -1) {
return false
} else {
return true
}
}
export {
Hunkify,
Input,
Output,
State
}