Skip to content
Snippets Groups Projects
server.js 10.3 KiB
Newer Older
  • Learn to ignore specific revisions
  • Jake Read's avatar
    Jake Read committed
    //
    // server.js
    //
    // 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 ! 
    
    */
    
    /*
    
    
    SYSTEM REQUIRES
    
    */
    
    
    Jake Read's avatar
    Jake Read committed
    // file system to load / look at our available modules / edit them 
    const fs = require('fs')
    
    Jake Read's avatar
    Jake Read committed
    // express serves files, http makes the connection
    const app = require('express')()
    const http = require('http').Server(app)
    
    Jake Read's avatar
    Jake Read committed
    // websocket does websocket
    const WebSocket = require('ws')
    
    /*
    
    SERVER AND WS SETUP
    
    */
    
    
    Jake Read's avatar
    Jake Read committed
    // 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)
    })
    
    
    Jake Read's avatar
    Jake Read committed
    // and listening for requests here
    const wss = new WebSocket.Server({ port: 8081 })
    
    wss.on('connection', (ws) => {
        sckt = ws
    
    Jake Read's avatar
    Jake Read committed
        socketSend('console', 'hello client')
    
    Jake Read's avatar
    Jake Read committed
        // send current config as list of all modules
        putReps()
    
    Jake Read's avatar
    Jake Read committed
        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')
    })
    
    
    Jake Read's avatar
    Jake Read committed
    // server globals 
    
    
    Jake Read's avatar
    Jake Read committed
    function socketSend(type, data) {
    
        if (sckt) {
            var msg = {
                type: type,
                data: data
            }
    
            //console.log('SEND', msg)
    
            sckt.send(JSON.stringify(msg))
    
    Jake Read's avatar
    Jake Read committed
    function socketRecv(evt) {
    
    Jake Read's avatar
    Jake Read committed
        var recv = JSON.parse(evt)
        var type = recv.type
        var data = recv.data
    
        //console.log('RECV', recv)
    
    Jake Read's avatar
    Jake Read committed
        // bang thru
    
    Jake Read's avatar
    Jake Read committed
        switch (type) {
    
    Jake Read's avatar
    Jake Read committed
            case 'console':
    
                console.log('RECV CONSOLE:', data)
    
    Jake Read's avatar
    Jake Read committed
                break
            case 'get menu':
                var tree = buildMenu()
    
    Jake Read's avatar
    Jake Read committed
                socketSend('put menu', tree)
                break
            case 'add module':
                addModule(data)
                break
    
    Jake Read's avatar
    Jake Read committed
            case 'put state':
                changeState(data)
    
    Jake Read's avatar
    Jake Read committed
                break
            case 'put link':
    
    Jake Read's avatar
    Jake Read committed
                // id:output > id:input
                break
    
            case 'put ui':
                changeUi(data)
                break
    
    Jake Read's avatar
    Jake Read committed
            case 'rm link':
                // id:output > id:input
    
    Jake Read's avatar
    Jake Read committed
                break
            default:
    
                console.log('ERR server recv with non recognized type', recv)
    
    Jake Read's avatar
    Jake Read committed
                break
    
    Jake Read's avatar
    Jake Read committed
    ------------------------------------------------------
    
    PROGRAM AS API
    
    Jake Read's avatar
    Jake Read committed
    ------------------------------------------------------
    
    */
    
    var modules = new Array()
    
    
    Jake Read's avatar
    Jake Read committed
    // a program is a list of modules, 
    // modules have internal lists of modules that they call 
    
    var prgmem = {
        // we want to describe all of the nodes first,
        // then we can add connections once we have hooks 
        // to each of them 
        modules: {
            gate: {
                path: './src/util/gate.js',
                conn: {
                    
                }
                ui:{
                    pos: [100, 100]
                }
            },
            delay: {
                path: './src/util/delay.js',
                state: {
                    ms: 500
                }
            },
            log: {
                path: './src/util.log.js'
            }
        },
        connections: ['gate.outputs.out', 'delay.inputs.thru']
    }
    
    Jake Read's avatar
    Jake Read committed
    
    
    Jake Read's avatar
    Jake Read committed
    function loadPrgmem(desc) {
        console.log(desc)
    }
    
    Jake Read's avatar
    Jake Read committed
    loadPrgmem(prgmem)
    
    Jake Read's avatar
    Jake Read committed
    
    
    /*
    ------------------------------------------------------
    LOST FUNCTION
    ------------------------------------------------------
    */
    
    function setUiPos(module, left, top) {
        if (module.ui == null) {
    
            module.ui = {}
        }
        module.ui.left = left
        module.ui.top = top
    }
    
    Jake Read's avatar
    Jake Read committed
    /*
    ------------------------------------------------------
    PROGRAM WRITING 
    ------------------------------------------------------
    
    Jake Read's avatar
    Jake Read committed
    function addModule(path) {
        // get and add to server system
        if (fs.existsSync(path)) {
            var src = require(path) // get and return 
    
            var mod = new src() // make a new one
            modules.push(mod) // add to the system
    
            // assign and id and remember from whence it came
    
    Jake Read's avatar
    Jake Read committed
            mod.id = modules.length - 1
    
            // we need to add some top-level info to the inputs so that we can draw them 
    
    Jake Read's avatar
    Jake Read committed
            for (item in mod.inputs) {
    
                mod.inputs[item].parentId = mod.id
                mod.inputs[item].key = item
            }
    
    
            for (item in mod.state) {
                if (item == 'onChange' | item == 'emitChange' | item == 'emitters') {
                    //console.log('rolling past change fn')
                } else {
                    mod.state['_' + item] = mod.state[item]
                    mod.state[item] = {}
    
    Jake Read's avatar
    Jake Read committed
                    writeStateObject(mod, item)
    
            console.log('ADDING MODULE', mod.description.name)
    
    Jake Read's avatar
    Jake Read committed
            // now roll and return representable object 
    
    Jake Read's avatar
    Jake Read committed
            putRep(mod)
    
            // also to fn call, in case writing program ? 
            return mod
    
    Jake Read's avatar
    Jake Read committed
        } else {
    
            console.log('ERR no module found at', path)
    
    Jake Read's avatar
    Jake Read committed
    function writeStateObject(mod, item) {
        Object.defineProperty(mod.state, item, {
    
    Jake Read's avatar
    Jake Read committed
                // update internal value 
    
    Jake Read's avatar
    Jake Read committed
                //console.log('SET', item, this['_' + item])
    
    Jake Read's avatar
    Jake Read committed
                // push to internal state change handler
    
    Jake Read's avatar
    Jake Read committed
                // update server (or in some cases, a confirmation)
                putState(mod)
    
    Jake Read's avatar
    Jake Read committed
        Object.defineProperty(mod.state, item, {
    
    Jake Read's avatar
    Jake Read committed
                //console.log('GET', item, this['_' + item])
    
    Jake Read's avatar
    Jake Read committed
    /*
    
    PROGRAM UPDATING 
    
    */
    
    //console.log('modules at prgmem start', modules)
    function putReps() {
    
        var reps = new Array()
    
    Jake Read's avatar
    Jake Read committed
        for (mod in modules) {
    
            reps.push(makeRep(modules[mod]))
    
    Jake Read's avatar
    Jake Read committed
        }
    
        socketSend('add reps', reps)
    
    Jake Read's avatar
    Jake Read committed
    }
    
    
    Jake Read's avatar
    Jake Read committed
    function makeRep(mod) {
    
    Jake Read's avatar
    Jake Read committed
        // 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
    
    Jake Read's avatar
    Jake Read committed
        if (mod.ui != null) {
    
            rep.ui = mod.ui
    
    Jake Read's avatar
    Jake Read committed
        }
    
    
    Jake Read's avatar
    Jake Read committed
        for (var key in mod.state) {
            if (isStateKey(key)) {
    
    Jake Read's avatar
    Jake Read committed
                rep.state[key] = mod.state[key]
            }
        }
    
    
        return rep
    }
    
    function putRep(mod) {
        socketSend('add rep', makeRep(mod))
    }
    
    
    Jake Read's avatar
    Jake Read committed
    function changeRep(mod) {
    
        socketSend('change rep', makeRep(mod))
    
    Jake Read's avatar
    Jake Read committed
    }
    
    // update state from UI to server 
    function changeState(data) {
    
    Jake Read's avatar
    Jake Read committed
        // should just recv all state, walk tree for differences
    
    Jake Read's avatar
    Jake Read committed
        var oldState = modules[data.id].state
        var newState = data.state
        // rep only holds proper key-values w/o emitters, _values etc
    
    Jake Read's avatar
    Jake Read committed
        for (var key in newState) {
    
    Jake Read's avatar
    Jake Read committed
            if (isStateKey(key)) {
    
    Jake Read's avatar
    Jake Read committed
                if (oldState[key].isButton) {
                    if (oldState[key].isPressed != newState[key].isPressed) {
    
                        console.log('CHANGE BUTTON STATE', key, 'to', newState[key], 'in', data.id)
    
    Jake Read's avatar
    Jake Read committed
                        // this will trigger some 'onChange' f'ns
                        // that might change some state
    
    Jake Read's avatar
    Jake Read committed
                        oldState[key] = newState[key]
    
    Jake Read's avatar
    Jake Read committed
                        // 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
    
    Jake Read's avatar
    Jake Read committed
                } else if (oldState[key] !== newState[key]) {
    
    Jake Read's avatar
    Jake Read committed
                    console.log('CHANGE STATE', key, 'to', newState[key], 'in', data.id)
    
                    oldState[key] = newState[key]
                    return true
    
    Jake Read's avatar
    Jake Read committed
                }
    
    Jake Read's avatar
    Jake Read committed
            }
    
    Jake Read's avatar
    Jake Read committed
    function changeUi(data) {
    
        var mod = modules[data.id]
    
    Jake Read's avatar
    Jake Read committed
        if (mod.ui == null) {
    
            console.log("NO UI")
            mod.ui = {}
        }
        mod.ui = data.ui
        console.log('CHANGE UI', mod.id, mod.ui)
    }
    
    
    Jake Read's avatar
    Jake Read committed
    function isStateKey(key) {
        if (key.indexOf('_') == 0 || key == 'emitters' || key == 'onChange' || key == 'emitChange') {
    
    Jake Read's avatar
    Jake Read committed
            return false
        } else {
            return true
        }
    }
    
    // push new state from server to UI
    function putState(mod) {
        // push just the state to the individual mod
    
    Jake Read's avatar
    Jake Read committed
        // copy only the keys we want to see 
        var state = {}
    
    Jake Read's avatar
    Jake Read committed
        for (var key in mod.state) {
            if (isStateKey(key)) {
    
    Jake Read's avatar
    Jake Read committed
                state[key] = mod.state[key]
            }
        }
    
    Jake Read's avatar
    Jake Read committed
        var data = {
            id: mod.id,
    
    Jake Read's avatar
    Jake Read committed
            state: state
    
    Jake Read's avatar
    Jake Read committed
        }
    
        socketSend('change state', data)
    
    Jake Read's avatar
    Jake Read committed
    }
    
    
    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])
    
        } else {
    
            fromModule.outputs[data.from.output].attach(toModule.inputs[data.to.input])
    
        // replace it 
        changeRep(fromModule)
    
    Jake Read's avatar
    Jake Read committed
    function buildMenu() {
        var tree = {}
    
    Jake Read's avatar
    Jake Read committed
        var dir = fs.readdirSync('./src')
    
    Jake Read's avatar
    Jake Read committed
        for (i in dir) {
    
    Jake Read's avatar
    Jake Read committed
            tree[dir[i]] = {}
            var subdir = fs.readdirSync('./src/' + dir[i])
    
    Jake Read's avatar
    Jake Read committed
            for (j in subdir) {
    
    Jake Read's avatar
    Jake Read committed
                // find js files
    
    Jake Read's avatar
    Jake Read committed
                if (subdir[j].slice(-3) === '.js') {
    
    Jake Read's avatar
    Jake Read committed
                    var obj = {}
                    obj.path = './src/' + dir[i] + '/' + subdir[j]
                    tree[dir[i]][subdir[j].slice(0, -3)] = obj
                }
            }
        }
    
    Jake Read's avatar
    Jake Read committed
        return tree
    }
    
    Jake Read's avatar
    Jake Read committed
    // put