Skip to content
Snippets Groups Projects
README.md 8.02 KiB
Newer Older
  • Learn to ignore specific revisions
  • Jake Read's avatar
    Jake Read committed
    # Cuttlefish
    
    Jake Read's avatar
    Jake Read committed
    
    
    Jake Read's avatar
    Jake Read committed
    Cuttlefish is a modular browser computing system and a member of [the squidworks project](https://gitlab.cba.mit.edu/squidworks/squidworks) to develop a distributed dataflow computing protocol. It is modular code wrapped in dataflow descriptors and handles that allow rapid assembly of computing applications (programs!). Cuttlefish also serves as the eyes into remote dataflow sytems - meaning we can use it as a tool to assemble big networks of computers, all using the same dataflow assumptions - to build, use, update, and discover distributed programs.
    
    Jake Read's avatar
    Jake Read committed
    
    
    Jake Read's avatar
    Jake Read committed
    ![img](doc/2019-07-21-l1.png)
    
    Jake Read's avatar
    Jake Read committed
    
    
    Jake Read's avatar
    Jake Read committed
    # How it Works
    
    Jake Read's avatar
    Jake Read committed
    
    
    Jake Read's avatar
    Jake Read committed
    ### Hunks: Modular Code Blocks
    
    Cuttlefish runs programs that are made of 'hunks' - each hunk is a small block of code. In the browser, these are [ECMAScript Modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) that are loaded from the server and dropped into the client at runtime.
    
    ### Typed Data Interfaces
    
    Hunks interface with the rest of the world through data ports - inputs and outputs - and their state variables. Each of these interfaces *is typed* - this helps us when we serialize messages (Cuttlefish is made for networks) - and helps everyone understand what hunks are meant to do.
    
    
    Jake Read's avatar
    Jake Read committed
    Typing is rendered in `/typeset.js` - we can only connect outputs to inputs when a function exists here to copy data from the output-type to the input-type. These functions are directional!
    
    Jake Read's avatar
    Jake Read committed
    
    
    Jake Read's avatar
    Jake Read committed
    ### Flowcontrolled Runtime
    
    When Cuttlefish is running, it ferries data between hunk outputs and outputs, and gives each hunk some time to operate on those inputs, producing outputs. By connecting different hunks in graphs, we can develop programs.
    
    Data ferrying takes place according to a set of flowcontrol rules: data must be explicitly taken off of an input port before an output can push more data towards it. This means that processes which take time - and messages that have to traverse a network - don't overrun one another.
    
    ### Polymorphic Hunks
    
    To handle some complexity - particularely with typing - we can write hunks whose inputs and outputs change based on some of their state variables.
    
    Jake Read's avatar
    Jake Read committed
    
    
    Jake Read's avatar
    Jake Read committed
    # Usage
    
    
    Jake Read's avatar
    Jake Read committed
    To use Cuttlefish, we can serve it locally [as described here](#running-locally) and load it from the browser. This makes local development easier.
    
    Jake Read's avatar
    Jake Read committed
    
    
    Jake Read's avatar
    Jake Read committed
    A menu is accessible on right-click. Here, we can add new hunks, and save / load programs - these are just lists of hunks, their state, and their connections. For a program to appear in the 'restore context' menu, just drop that .json in the ```/save/contexts/cuttlefish``` folder. TBD is a server-side architecture to make this all sing and dance.
    
    Jake Read's avatar
    Jake Read committed
    
    
    Jake Read's avatar
    Jake Read committed
    To connect an output to an input, we just drag that output onto an input. To disconnect them, we can right-click on the wire between them.
    
    Jake Read's avatar
    Jake Read committed
    
    
    Jake Read's avatar
    Jake Read committed
    To delete hunks, right click on their title bar. We can also copy hunks, or reload them from source (which works *pretty well* but isn't bulletproof: careful of leftover DOM elements).
    
    Jake Read's avatar
    Jake Read committed
    
    
    Jake Read's avatar
    Jake Read committed
    # Development
    
    Jake Read's avatar
    Jake Read committed
    
    
    Jake Read's avatar
    Jake Read committed
    Example hunks are below, commented. To add a new component, simply copy one of these files into a new location and modify it - (hunks are named by their file location) - the system should automatically pick it up when you hit 'add a hunk' in the environment.
    
    In the browser, the development tools are a great friend. Uncaught exceptions can shutdown the runtime loop, so keep an eye on the console.
    
    
    Jake Read's avatar
    Jake Read committed
    ```javascript
    
    Jake Read's avatar
    Jake Read committed
    /*
    
    hunk template
    
    */
    
    // these are ES6 modules
    import {
      Hunkify,
      Input,
      Output,
      State
    } from './hunks.js'
    
    function Name() {
      // this fn attaches handles to our function-object,
      Hunkify(this)
    
      // inputs, outputs, and state are objects. they have a type (string identifier)
      // see 'typeset.js'
      // a name (doesn't have to be unique), and we pass them a handle to ourselves...
      let inA = new Input('number', 'name', this)
      // inputs, outputs and state are all kept locally in these arrays,
      // if we don't include them here, the manager will have a hard time finding them ...
      this.inputs.push(inA)
    
      let outB = new Output('number', 'name', this)
      this.outputs.push(outB)
    
      let stateItem = new State('string', 'name', 'startupValue')
      this.states.push(stateItem)
    
      // State items also have change handlers,
      stateItem.onChange = (value) => {
        // at this point, something external (probably a human)
        // has requested that we change this state variable,
        console.log('requests:', value)
        // we can reject that, by doing nothing here, or we can
        stateItem.set(value)
      }
    
      // hunks can choose to- or not- have init code.
      // at init, the module has been loaded and state variables have been
      // recalled from any program save - so this is a good point
      // to check any of those, and setup accordingly ...
      this.init = () => {
        this.log('hello template world')
      }
    
      let internalVariable = 'local globals'
    
      function internalFunc(data) {
        // scoped function, not accessible externally
        // do work,
        return (data)
      }
    
      // to divide time between hunks, each has a loop function
      // this is the hunks' runtime: a manager calls this once-per-round
      // here is where we check inputs, put to outputs, do work, etc
      this.loop = () => {
        // typically we check inputs and outputs first,
        // making sure we are clear to run,
        if (inA.io() && !outB.io()) {
          // an input is occupied, and the exit path is empty
    
          let output = internalFunc(inA.get())
    
    Jake Read's avatar
    Jake Read committed
          // put 'er there
          outB.put(output)
        }
      }
    }
    
    // the hunk is also an ES6 module, this is how we export those:
    export default Name
    ```
    
    Hunks with DOM elements (to render into the browser's window) are a *tad* more complicated, here's an example:
    
    
    Jake Read's avatar
    Jake Read committed
    ```javascript
    
    Jake Read's avatar
    Jake Read committed
    /*
    
    debugger ! log anything !
    
    */
    
    import { Hunkify, Input, Output, State } from '../hunks.js'
    
    function Logger() {
        Hunkify(this, 'Logger')
    
        let tolog = new Input('any', 'tolog', this)
        this.inputs.push(tolog)
    
        let prefix = new State('string', 'prefix', 'LOG:')
        let logToConsole = new State('boolean', 'console', true)
        this.states.push(prefix, logToConsole)
    
        this.dom = {}
    
        this.init = () => {
            // manager calls this once
            // it is loaded and state is updated (from program)
            this.log('HELLO LOGGER')
            this.dom = $('<div>').get(0)
        }
    
        this.onload = () => {
          //error here
          let text = $('<div>').addClass('txt').append('- >').get(0)
          $(this.dom).append(text)
        }
    
        this.loop = () => {
            // this will be called once every round turn
            // typically we check flow control first
            if (tolog.io()) {
                // an input is occupied, and the exit path is empty
                let val = tolog.get()
                if(Array.isArray(val)){
                  val = val.join(', ')
                } else if (typeof val === "boolean"){
                  val = val.toString()
                }
                $(this.dom).children('.txt').html(val)
                if (logToConsole.value === true) {
                    console.log(this.ind, prefix.value, val)
                }
            }
        }
    }
    
    export default Logger
    ```
    
    For a more involved example, see [the linechart](hunks/data/linechart.js).
    
    Jake Read's avatar
    Jake Read committed
    
    # Running Locally
    
    
    Jake Read's avatar
    Jake Read committed
    Cuttlefish emerges from a small node.js server, and the best way to develop / use it is still to run a local copy of this on your machine.
    
    Jake Read's avatar
    Jake Read committed
    
    
    Jake Read's avatar
    Jake Read committed
    First, you should [install node.js](https://nodejs.org), if you're on a Mac / Unix (Raspberry Pi!), I would highly recommend [NVM](https://github.com/nvm-sh/nvm), and windows has [an installer](https://nodejs.org) that works brilliantly.
    
    Jake Read's avatar
    Jake Read committed
    
    
    Jake Read's avatar
    Jake Read committed
    Then, we need to install a few packages. Clone this repo wherever you'd like in your machine, ``cd`` to that directory, and run:
    
    Jake Read's avatar
    Jake Read committed
    
    
    Jake Read's avatar
    Jake Read committed
    ``npm install express``
    
    Jake Read's avatar
    Jake Read committed
    
    
    Jake Read's avatar
    Jake Read committed
    If you just want to run the browser tools, you're now done. If you want to use CF to connect to hardware, do:
    
    Jake Read's avatar
    Jake Read committed
    
    
    Jake Read's avatar
    Jake Read committed
    ``npm install ws``
    
    Jake Read's avatar
    Jake Read committed
    and
    
    Jake Read's avatar
    Jake Read committed
    
    
    Jake Read's avatar
    Jake Read committed
    ``npm install serialport``
    
    Jake Read's avatar
    Jake Read committed
    You can now run Cuttlefish by doing:
    
    ``node cf``
    
    
    It should then report an IP and port. One is your local address, if the browser you are using is on the same machine as the node process, and the other is the machine's outward facing IP. You should be able to point the browser to one of these IPs, and you're off and cuttling.