const fs = require('fs') const Reps = require('./reps.js') const JSUnit = require('./lib/jsunit.js') let isStateKey = JSUnit.isStateKey function newProgram(name) { var program = { description: { name: name, id: name }, modules: {} } return program } function loadModuleFromSource(program, path, id) { // source -> heap if (fs.existsSync(path)) { var src = require(path) var mod = new src() // wants unique module id's if (program.description.counter == null) { program.description.counter = 0 } else { program.description.counter++ } // make unique name if (id) { mod.description.id = id } else { mod.description.id = mod.description.name + '-' + program.description.counter } mod.description.path = path /* ---------------------------------- */ // WARN! Corner Case should Go Away or Improve at next spiral if(mod.description.isLink){ for (mdlName in program.modules) { if (program.modules[mdlName].description.isLink) { console.log("PRGMEM ONLY BIG ENOUGH FOR ONE LINK") process.exit() } } } // end corner case code /* ---------------------------------- */ // add to program object program.modules[mod.description.id] = mod // input need references for later hookup for (key in mod.inputs) { mod.inputs[key].parentId = mod.description.id mod.inputs[key].key = key } // state updating, begs for update for (key in mod.state) { if (key == 'onChange' | key == 'emitChange' | key == 'emitters') { //console.log('rolling past change fn') } else { mod.state['_' + key] = mod.state[key] mod.state[key] = {} writeStateObject(mod, key) } } if (program.description.id == null) { if (program.description.name == null) { if (program.description == null) { program.description = {} } program.description.name = 'unnamed program' } program.description.id = program.description.name + '-' + 0 } console.log('ADDING', mod.description.id, 'to', program.description.id) /* ---------------------------------- */ // WARN! Corner Case should Go Away or Improve at next spiral // hardware corner case is hardware corner case // here's what we'll do // if it's hardware, and we're not loading from some saved program (no id) // if (mod.description.isHardware && !mod.description.isLink && id == null) { // make sure we haven't already done this, thx var lnk = null for (mdlName in program.modules) { if (program.modules[mdlName].description.isLink) { lnk = mdlName } } if (lnk) { console.log('CORNER CASE: LOADING HW MODULE, LINKING TO LINK') program.modules[lnk].attach(mod.route) } else { console.log('CORNER CASE: LOADING HW MODULE, ALSO LOADING LINK') var link = loadModuleFromSource(program, './modules/hardware/atkseriallink.js') // hook it up auto-like link.attach(mod.route) } } // also return it so that we can write programs without the UI return mod } else { console.log('ERR no module found at ', path) } } function writeStateObject(mod, key) { Object.defineProperty(mod.state, key, { set: function(x) { // update internal value this['_' + key] = x //console.log('SET', key, this['_' + key]) // push to internal state change handler // let's call emitChange from the server-side ... // so that we don't get into any heavy VIR // when we change it within the module // this.emitChange(key) // push to external view if (socket) { pushState(mod, key) } } }) Object.defineProperty(mod.state, key, { get: function() { //console.log('GET', key, this['_' + key]) return this['_' + key] } }) } function removeModuleFromProgram(program, id) { // this simple? delete program.modules[id] for (key in program.modules) { var mdl = program.modules[key] for (otKey in mdl.outputs) { mdl.outputs[otKey].checkLinks(id) } } } /* EXTERNAL HOOKS */ function assignSocket(sckt) { socket = sckt } function pushState(mod, key) { var data = { id: mod.description.id, key: key, val: mod.state[key] } socket.send('put state change', data) } function saveProgram(prgmem, path) { // ok, and we're interested in just copying the relevant things ... var svprgmem = { description: { name: prgmem.description.name, counter: prgmem.description.counter }, modules: {} } var mdls = prgmem.modules for (key in mdls) { var mdl = mdls[key] var og = Reps.makeFromModule(mdl) svprgmem.modules[mdl.description.id] = og } fs.writeFileSync(path, JSON.stringify(svprgmem, null, 2), 'utf8') console.log('PROGRAM SAVED AT', path) } function openProgram(path) { var program = {} // get the .json file as an object var prgRep = JSON.parse(fs.readFileSync(path, 'utf8')) console.log('OPENING THIS PRGRAM REP', prgRep) // copy-pasta the program descro, program.description = { name: prgRep.description.name, counter: 0, id: prgRep.description.name + 1, // in another world, we count path: path } // gonna get those modules from source program.modules = {} for (key in prgRep.modules) { var mdlRep = prgRep.modules[key] loadModuleFromSource(program, mdlRep.description.path, mdlRep.description.id) } // restore saved state and links for (modName in prgRep.modules) { // keys should be identical for rep and heap // this is the representation that we're opening up from var mdlRep = prgRep.modules[modName] // this is the actual object, having functions and whatnot var mdl = program.modules[modName] if (mdl == null) { console.log('NULL') console.log('prgRep modules', prgRep.modules) console.log('program modules', program.modules) } // hooking outputs -> inputs for (outName in mdlRep.outputs) { var outRep = mdlRep.outputs[outName] // each has some caller ids for (nestedInputRep in outRep.calls) { // conn from tl program -> this hookup var nIRParent = outRep.calls[nestedInputRep].parentId var nIRKey = outRep.calls[nestedInputRep].key var nI = program.modules[nIRParent].inputs[nIRKey] console.log("ATTACHING", nIRKey, 'to', outName) mdl.outputs[outName].attach(nI) } } // restoring state for (key in mdlRep.state) { if (isStateKey(key)) { // I think this is OK? // would prefer to do this before we write getters and setters // for now we walk-around to secret key ... mdl.state['_' + key] = mdlRep.state[key] } } console.log('mdlRep', mdlRep) console.log('mdl', mdl) // restore position / UI state if (mdlRep.description.position != null) { // ?? mdl.description.position = {} mdl.description.position.left = mdlRep.description.position.left mdl.description.position.top = mdlRep.description.position.top } } // once modules exist, link inputs / outputs / copy state ? return program } module.exports = { new: newProgram, open: openProgram, save: saveProgram, loadModuleFromSource: loadModuleFromSource, removeModule: removeModuleFromProgram, assignSocket: assignSocket }