UNPKG

msgflo

Version:

Polyglot FBP runtime based on message queues

255 lines (216 loc) 9.03 kB
# Implementation of the FBP protocol # http://noflojs.org/documentation/protocol debug = require('debug')('msgflo:fbp') EventEmitter = require('events').EventEmitter async = require 'async' fbpPort = (port) -> m = id: port.id type: port.type or "any" description: port.description or "" addressable: false required: false # TODO: implement return m fbpComponentFromMsgflo = (name, component) -> if component.definition # full info available info = name: name description: component.definition.label or component.cmd or "" icon: component.definition.icon subgraph: false inPorts: component.definition.inports.map fbpPort outPorts: component.definition.outports.map fbpPort else # just inifial info info = name: name description: component.cmd icon: null subgraph: false inPorts: [] outPorts: [] return info # JSON serialization of Error objects is empty serializeErr = (err) -> return { message: err.message } defaultGraph = 'default/main' handleMessage = (proto, sub, cmd, payload, ctx) -> debug 'RECV:', sub, cmd, payload if sub == 'runtime' and cmd == 'getruntime' runtime = type: 'msgflo' version: '0.4' capabilities: [ 'protocol:component' 'protocol:graph' 'protocol:network' 'component:getsource' 'component:setsource' ] graph: defaultGraph options = proto.coordinator.library.options runtime.namespace = options.config.namespace if options.config?.namespace? runtime.repository = options.config.repository if options.config?.repository? proto.transport.send 'runtime', 'runtime', runtime, ctx else if sub == 'runtime' and cmd == 'packet' proto.coordinator.sendToExportedPort payload.port, payload.payload, (err) -> return proto.transport.send 'runtime', 'error', serializeErr(err), ctx if err # No ACK in this case apparently, as it is interpreted as output # Component else if sub == 'component' and cmd == 'list' debug 'attempting to list components' components = [] for name, component of proto.coordinator.library.components info = fbpComponentFromMsgflo name, component components.push info for info in components proto.transport.send 'component', 'component', info, ctx proto.transport.send 'component', 'componentsready', components.length, ctx debug 'sent components', components.length else if sub == 'component' and cmd == 'getsource' sendMainGraphSource = () -> graph = proto.coordinator.serializeGraph 'main' resp = code: JSON.stringify graph name: 'main' library: 'default' language: 'json' proto.transport.send 'component', 'source', resp, ctx if payload.name == defaultGraph # Main graph. Ref https://github.com/noflo/noflo-ui/issues/390 setTimeout sendMainGraphSource, 0 else # Regular component proto.coordinator.getComponentSource payload.name, (err, source) -> if err proto.transport.send 'component', 'error', { name: payload.name, error: err.message }, ctx return proto.transport.send 'component', 'source', source, ctx else if sub == 'component' and cmd == 'source' p = payload proto.coordinator.addComponent p.name, p.language, p.code, (err) -> return proto.transport.send 'component', 'error', err, ctx if err return proto.transport.sendAll 'component', 'source', payload # Network else if sub == 'network' and cmd == 'start' proto.coordinator.startNetwork payload.graph, (err) -> return proto.transport.sendAll 'network', 'error', err if err proto.transport.sendAll 'network', 'started', running: true started: true graph: payload.graph time: new Date() else if sub == 'network' and cmd == 'stop' proto.coordinator.stopNetwork payload.graph, (err) -> return proto.transport.sendAll 'network', 'error', err if err proto.transport.sendAll 'network', 'stopped', running: false started: true graph: payload.graph time: new Date() else if sub == 'network' and cmd == 'getstatus' proto.transport.sendAll 'network', 'status', running: proto.coordinator.started started: proto.coordinator.started graph: payload.graph time: new Date() else if sub == 'network' and cmd == 'edges' debug 'network:edges', payload.edges.length subscribeEdge = (edge, cb) -> proto.coordinator.subscribeConnection edge.src.node, edge.src.port, edge.tgt.node, edge.tgt.port, (err) -> return cb err proto.coordinator.clearSubscriptions (err) -> if err return proto.transport.sendAll 'network', 'error', serializeErr(err) async.map payload.edges, subscribeEdge, (err) -> if err return proto.transport.sendAll 'network', 'error', serializeErr(err) proto.transport.sendAll 'network', 'edges', payload # Graph else if sub == 'graph' handleGraphMessage proto, cmd, payload, ctx else debug 'Unhandled FBP protocol message: ', sub, cmd handleGraphMessage = (proto, cmd, payload, ctx) -> graph = payload.graph if cmd == 'clear' # FIXME: support multiple graphs proto.coordinator.clearGraph payload.id, (err) -> return proto.transport.send 'graph', 'error', serializeErr(err), ctx if err proto.transport.sendAll 'graph', 'clear', payload else if cmd == 'addnode' proto.coordinator.startParticipant payload.id, payload.component, (err) -> return proto.transport.send 'graph', 'error', serializeErr(err), ctx if err proto.transport.sendAll 'graph', 'addnode', payload else if cmd == 'removenode' proto.coordinator.stopParticipant payload.id, payload.component, (err) -> return proto.transport.send 'graph', 'error', serializeErr(err), ctx if err proto.transport.sendAll 'graph', 'removenode', payload # Connections else if cmd == 'addedge' debug 'addedge', payload p = payload proto.coordinator.connect p.src.node, p.src.port, p.tgt.node, p.tgt.port, (err) -> return proto.transport.send 'graph', 'error', serializeErr(err), ctx if err proto.transport.sendAll 'graph', 'addedge', payload else if cmd == 'removeedge' p = payload proto.coordinator.disconnect p.src.node, p.src.port, p.tgt.node, p.tgt.port, (err) -> return proto.transport.send 'graph', 'error', serializeErr(err), ctx if err proto.transport.sendAll 'graph', 'removeedge', payload # IIPs else if cmd == 'addinitial' proto.coordinator.addInitial payload.tgt.node, payload.tgt.port, payload.src.data, (err) -> return proto.transport.send 'graph', 'error', serializeErr(err), ctx if err proto.transport.sendAll 'graph', 'addinitial', payload else if cmd == 'removeinitial' proto.coordinator.removeInitial payload.tgt.node, payload.tgt.port proto.transport.sendAll 'graph', 'removeinitial', payload # exported ports else if cmd == 'addinport' proto.coordinator.exportPort 'inport', payload.public, payload.node, payload.port, (err) -> return proto.transport.send 'graph', 'error', serializeErr(err), ctx if err proto.transport.sendAll 'graph', 'addinport', payload else if cmd == 'addoutport' proto.coordinator.exportPort 'outport', payload.public, payload.node, payload.port, (err) -> return proto.transport.send 'graph', 'error', serializeErr(err), ctx if err proto.transport.sendAll 'graph', 'addoutport', payload else debug 'Unhandled FBP protocol message: ', 'graph', cmd class Protocol constructor: (@transport, @coordinator) -> throw Error 'Protocol' if not @coordinator @coordinator.on 'exported-port-data', (port, data, graph) => @transport.sendAll 'runtime', 'packet', port: port event: 'data' payload: data graph: graph @transport.on 'message', (protocol, command, payload, ctx) => handleMessage @, protocol, command, payload, ctx @coordinator.library.on 'components-changed', (names, allComponents) => debug 'components-changed', names for name in names component = allComponents[name] info = fbpComponentFromMsgflo name, component @transport.sendAll 'component', 'component', info @coordinator.on 'connection-data', (conn, data) => from = conn.src.node fromPort = conn.src.port to = conn.tgt.node toPort = conn.tgt.port debug 'on data', from, fromPort, data id = "#{from}() #{fromPort.toUpperCase()} -> #{toPort.toUpperCase()} #{to}()" msg = id: id # FIXME: https://github.com/noflo/noflo-ui/issues/293 graph: conn.graph or defaultGraph src: node: from port: fromPort tgt: node: to port: toPort data: data @transport.sendAll 'network', 'data', msg exports.Protocol = Protocol