Newer
Older
//
// Jake Read at the Center for Bits and Atoms
// (c) Massachusetts Institute of Technology 2018
//
// This work may be reproduced, modified, distributed, performed, and
// displayed for any purpose, but must acknowledge the mods
// project. Copyright is retained and must be preserved. The work is
// provided as is; no warranty is provided, and users accept all
// liability.
RULES:
modules have Ouputs() and Inputs() that connect to other software modules running on the same CPU. It's a directed graph.
modules can have ATKPort()s that use a Link() to get to remote hardware, per message-passing network. It's a physical graph of duplex connections.
this is nice because we might have two Link()s to double up on bandwidth.
alternately, Link()s could be TCP connections to other pieces of hardware, including something like us !
*/
/*
// file system to load / look at our available modules / edit them
const fs = require('fs')
// express serves files, http makes the connection
const app = require('express')()
const http = require('http').Server(app)
// websocket, to share program representations with the client (and back)
// serving this handful of static files
app.get('/', (req, res) => {
console.log('client req /')
res.sendFile(__dirname + '/client/index.html')
})
app.get('/:file', (req, res) => {
console.log('client req', req.params.file)
res.sendFile(__dirname + '/client/' + req.params.file)
})
// and listening for requests here
const wss = new WebSocket.Server({ port: 8081 })
wss.on('connection', (ws) => {
sckt = ws
// say hello
console.log('socket open on 8081')
ws.on('message', (evt) => {
socketRecv(evt)
})
})
// through this window
http.listen(8080, () => {
console.log('listening on 8080 for static files')
})
var sckt = null
if (sckt) {
var msg = {
type: type,
data: data
}
sckt.send(JSON.stringify(msg))
var recv = JSON.parse(evt)
var type = recv.type
var data = recv.data
console.log('RECV CONSOLE:', data)
break
case 'get menu':
var tree = buildMenu()
socketSend('put menu', tree)
break
case 'add module':
addModule(data)
break
putLink(data)
case 'put ui':
changeUi(data)
break
console.log('ERR server recv with non recognized type', recv)
------------------------------------------------------
------------------------------------------------------
// nodes are objects having inputs, outputs, and state
// nodes can be made up of other nodes
// a node originates somewhere,
// is it a JSON, or a txt .js file ? loadProgram and loadModule ...
// it wants to be an object that gets 'read' in ...
var gate = addModuleToProgram(program, './src/util/gate.js')
var delay = addModuleToProgram(program, './src/util/delay.js')
var log = addModuleToProgram(program, './src/util/log.js')
// hookup as usual
gate.outputs.out.attach(delay.inputs.thru)
delay.outputs.out.attach(log.inputs.thru)
gate.outputs.out.attach(log.inputs.thru)
console.log('saving onesave.json')
saveProgram(program, 'save/onesave.json')
program = null
delete gate
delete delay
delete log
console.log('opening onesave.json')
var prgmem = openProgram('save/onesave.json')
saveProgram(prgmem, 'save/twosave.json')
// working through:
/*
- add load-from-thing-connect-outputs-to-inputs
- add new state loading / structure?
- UI / letters
*/
/*
------------------------------------------------------
------------------------------------------------------
// source -> heap
// wants unique module id's
if(program.description.counter == null){
program.description.counter = 0
} else {
program.description.counter ++
}
mod.description.id = mod.description.id = mod.description.name + '-' + program.description.counter
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)
// also return it so that we can write programs without the UI
return mod
function writeStateObject(mod, key) {
Object.defineProperty(mod.state, key, {
set: function(x) {
this['_' + key] = x
//console.log('SET', key, this['_' + key])
// update server (or in some cases, a confirmation)
putState(mod)
get: function() {
//console.log('GET', key, this['_' + key])
return this['_' + key]
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
function makeRepFromModule(mdl) {
var rep = {
description: {
id: mdl.description.id,
name: mdl.description.name,
alt: mdl.description.alt,
path: mdl.description.path
}
}
// TODO: making rep. of input / output should be a f'n of that object ...
// input, outputs, state objs should be known /sysobjects
// everything else is free play
rep.inputs = {}
for (key in mdl.inputs) {
rep.inputs[key] = {}
rep.inputs[key].accepts = mdl.inputs[key].accepts
}
rep.outputs = {}
for (key in mdl.outputs) {
rep.outputs[key] = {}
rep.outputs[key].emits = mdl.outputs[key].emits
rep.outputs[key].calls = new Array()
mdl.outputs[key].calls.forEach(function(inp) {
var input = {
parentId: inp.parentId,
key: inp.key
}
rep.outputs[key].calls.push(input)
})
}
rep.state = {}
for(key in mdl.state){
if(isStateKey(key)){
rep.state[key] = mdl.state[key]
}
}
return rep
}
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
for(key in mdls){
var mdl = mdls[key]
svprgmem.modules[mdl.description.id] = og
}
fs.writeFileSync(path, JSON.stringify(svprgmem, null, 2), 'utf8')
}
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,
counter: 0,
id: prgRep.description.name + 1, // in another world, we count
// gonna get those modules from source
program.modules = {}
for(key in prgRep.modules){
var mdlRep = prgRep.modules[key]
addModuleToProgram(program, mdlRep.description.path)
}
// gonna hook 'em up
for(modName in prgRep.modules){
// keys should be identical for rep and heap
var mdlRep = prgRep.modules[modName]
var mdl = program.modules[modName]
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", nI, nIRKey, 'to', mdl.outputs[outName])
mdl.outputs[outName].attach(nI)
}
}
}
// once modules exist, link inputs / outputs / copy state ?
return program
}
//console.log('modules at prgmem start', modules)
function putReps() {
reps.push(makeRep(modules[mod]))
// scrape for only things we'll want to represent
var rep = {
id: mod.id,
description: mod.description,
inputs: mod.inputs,
outputs: mod.outputs,
state: {}
}
// holds UI information - to load mods at location
for (var key in mod.state) {
if (isStateKey(key)) {
return rep
}
function putRep(mod) {
socketSend('add rep', makeRep(mod))
}
socketSend('change rep', makeRep(mod))
}
// update state from UI to server
function changeState(data) {
// of one module
// should just recv all state, walk tree for differences
var oldState = modules[data.id].state
var newState = data.state
// rep only holds proper key-values w/o emitters, _values etc
if (oldState[key].isButton) {
if (oldState[key].isPressed != newState[key].isPressed) {
console.log('CHANGE BUTTON STATE', key, 'to', newState[key], 'in', data.id)
// this will trigger some 'onChange' f'ns
// that might change some state
// to prevent quickly changing it back, we'll exit now (one state change from UI per trx)
} else if (oldState[key].isMultiLine) {
if (oldState[key].value != newState[key].value) {
console.log('CHANGE MULTILINE STATE', key, 'to', newState[key].value, 'in', data.id)
oldState[key].value = newState[key].value
return true
console.log('CHANGE STATE', key, 'to', newState[key], 'in', data.id)
oldState[key] = newState[key]
return true
console.log("NO UI")
mod.ui = {}
}
mod.ui = data.ui
console.log('CHANGE UI', mod.id, mod.ui)
}
function setUiPos(module, left, top) {
if (module.ui == null) {
module.ui = {}
}
module.ui.left = left
module.ui.top = top
}
function isStateKey(key) {
if (key.indexOf('_') == 0 || key == 'emitters' || key == 'onChange' || key == 'emitChange') {
return false
} else {
return true
}
}
// push new state from server to UI
function putState(mod) {
// push just the state to the individual mod
for (var key in mod.state) {
if (isStateKey(key)) {
socketSend('change state', data)
function putLink(data) {
var fromModule = modules.find((module) => {
return module.id === data.from.id
})
var toModule = modules.find((module) => {
return module.id === data.to.id
})
if (fromModule.outputs[data.from.output].isLinked(toModule.inputs[data.to.input])) {
console.log("HOOKDOWN")
fromModule.outputs[data.from.output].remove(toModule.inputs[data.to.input])
fromModule.outputs[data.from.output].attach(toModule.inputs[data.to.input])
// replace it
changeRep(fromModule)
tree[dir[i]] = {}
var subdir = fs.readdirSync('./src/' + dir[i])
var obj = {}
obj.path = './src/' + dir[i] + '/' + subdir[j]
tree[dir[i]][subdir[j].slice(0, -3)] = obj
}
}
}