浏览代码

Front end socket label background,

* Backend: Implementing gonum funcs
* Variable: func instead of operator
luis 7 年之前
父节点
当前提交
22c665cdfe

+ 2 - 2
browser/vue-flow/src/assets/dark-theme.css

@@ -26,8 +26,8 @@
   fill: transparent;
 }
 
-.dark .flow-node__socket-detail {
-  filter: url(#solid-dark);
+.dark .flow-node__socket-detail--background {
+  fill: rgba(30, 30, 30, 0.7);
 }
 
 .dark .flow-node--dragging .flow-node__body,

+ 4 - 1
browser/vue-flow/src/assets/default-theme.css

@@ -277,7 +277,10 @@ h3 {
   font-weight: 100;
   transition: all var(--transition-speed);
   fill: var(--normal);
-  filter: url(#solid-white);
+}
+
+.flow-node__socket-detail--background {
+  fill: rgba(230, 230, 230, 0.7);
 }
 
 .flow-node__trigger {

+ 44 - 2
browser/vue-flow/src/components/flow/node.vue

@@ -78,9 +78,15 @@
       :class="{'flow-node__socket--match': match.type == 'socket-in' && (inp.type == match.dtype || match.dtype == 'interface {}' || inp.type=='interface {}')}"
     >
       <circle r="5" />
+      <!--<rect :x="-16-inputLabel(i).length*7 " :y="-10" :width="inputLabel(i).length*7" :height="20" fill="red" stroke-width="0"/>-->
+      <rect
+        v-bind="inputLabelBGProps(i)"
+        class="flow-node__socket-detail--background"
+      />
       <text
+        ref="inputLabel"
         text-anchor="end"
-        :x="-18"
+        :x="-14"
         :y="4"
         class="flow-node__socket-detail"
       >{{ inputLabel(i) }}</text>
@@ -98,9 +104,15 @@
       :class="{ 'flow-node__socket--match': match.type =='socket-out' && (output.type == match.dtype || match.dtype == 'interface {}' || output.type == 'interface {}'), }"
     >
       <circle r="5" />
+
+      <rect
+        v-bind="outputLabelBGProps(0)"
+        class="flow-node__socket-detail--background"
+      />
       <text
+        ref="outputLabel"
         class="flow-node__socket-detail"
-        :x="18"
+        :x="14"
         :y="4">
         {{ outputLabel(0) }}
       </text>
@@ -239,6 +251,18 @@ export default {
         return input
       }
     },
+    inputLabelBGProps () {
+      return (i) => {
+        if (!this.$refs.inputLabel) {
+          this.$nextTick(this.$forceUpdate)
+          return {x: 0, y: 0, width: 0, height: 0}
+        }
+
+        let bbox = this.$refs.inputLabel[i].getBBox()
+
+        return {x: bbox.x - 5, y: bbox.y - 2, width: bbox.width + 10, height: bbox.height + 4}
+      }
+    },
     outputLabel () {
       return (i) => {
         var output = ''
@@ -249,7 +273,20 @@ export default {
 
         return output
       }
+    },
+    outputLabelBGProps () {
+      return (i) => {
+        if (!this.$refs.outputLabel) {
+          this.$nextTick(this.$forceUpdate)
+          return {x: 0, y: 0, width: 0, height: 0}
+        }
+
+        let bbox = this.$refs.outputLabel.getBBox()
+
+        return {x: bbox.x - 5, y: bbox.y - 2, width: bbox.width + 10, height: bbox.height + 4}
+      }
     }
+
   },
   watch: {
     // the only thing now that affects geometry
@@ -340,6 +377,11 @@ export default {
   transition: all var(--transition-speed);
 }
 
+.flow-node__socket-detail--background {
+  stroke-width:0;
+  transition:all var(--transition-speed);
+}
+
 .flow-node__socket--match {
   cursor:pointer;
   stroke-width:10;

+ 1 - 1
browser/vue-flow/src/components/panel-inspector.vue

@@ -43,7 +43,7 @@
             class="flow-inspector__param"
             v-for="(n,i) in registry[nodeInspect.src].inputs"
             :key="i">
-            <label>{{ i }}:{{ n.type }}</label>
+            <label>{{ n.name }}:{{ n.type }}</label>
             <input
               ref="inputs"
               type="text"

+ 0 - 6
browser/vue-flow/src/store/flow/default-registry.js

@@ -5,12 +5,6 @@ export default{
     style: { color: '#686', shape: 'circle' },
     props: {} // should be sent in the node
   },
-  'Variable': {
-    categories: ['core'],
-    output: {type: 'interface {}'},
-    style: { color: '#88a', shape: 'circle' },
-    props: {init: ''}
-  },
   'Const': {
     categories: ['core'],
     output: {type: 'interface {}'},

+ 6 - 6
go/src/flow/flow.go

@@ -23,7 +23,7 @@ type Flow struct {
 	registry   *registry.R
 	idGen      func() string
 	consts     map[string]Data
-	data       map[string]Data // Should be named, to fetch later
+	Data       map[string]Data // Should be named, to fetch later
 	operations sync.Map
 	runID      int
 
@@ -38,7 +38,7 @@ func New() *Flow {
 		idGen:    func() string { return RandString(8) },
 		// Data
 		consts:     map[string]Data{},
-		data:       map[string]Data{},
+		Data:       map[string]Data{},
 		operations: sync.Map{},
 		//map[string]opEntry{},
 	}
@@ -190,7 +190,7 @@ func (f *Flow) String() string {
 		fmt.Fprintf(ret, "  [%s] %v\n", i, v)
 	}
 	fmt.Fprintf(ret, "data:\n") // Or variable
-	for k, v := range f.data {
+	for k, v := range f.Data {
 		fmt.Fprintf(ret, "  [%v] %v\n", k, v)
 	}
 
@@ -234,7 +234,7 @@ func (f *Flow) MarshalJSON() ([]byte, error) {
 	})
 
 	data["operations"] = operations
-	data["data"] = f.data
+	data["data"] = f.Data
 	data["consts"] = f.consts
 
 	return json.Marshal(data)
@@ -272,7 +272,7 @@ func (f *Flow) allocID(fn func(id string) error) error {
 
 }
 
-func (f *Flow) addEntry(entry *operation) (string, error) {
+/*func (f *Flow) addEntry(entry *operation) (string, error) {
 	f.Lock()
 	defer f.Unlock()
 
@@ -287,7 +287,7 @@ func (f *Flow) addEntry(entry *operation) (string, error) {
 	}
 	return "", errors.New("ID exausted")
 
-}
+}*/
 
 // GetOp Return an existing operation or return notfound error
 func (f *Flow) GetOp(id string) *operation {

+ 55 - 11
go/src/flow/flow_test.go

@@ -61,8 +61,16 @@ func TestDefOp(t *testing.T) {
 	op, err = f.DefOp("123", "none")
 	a.NotEq(err, nil, "Error should not be nil")
 }
+func TestGetOp(t *testing.T) {
+	a := assert.A(t)
+	f := flow.New()
+
+	op := f.GetOp("1")
+	a.Eq(op, nil, "op should be nil")
 
-/*func TestIDGen(t *testing.T) {
+}
+
+func TestIDGen(t *testing.T) {
 	a := assert.A(t)
 	idTable := []string{"2", "1", "1"}
 
@@ -76,20 +84,22 @@ func TestDefOp(t *testing.T) {
 		return newID
 	})
 
-	o, err := f.Op("vecadd", f.In(0), f.In(1))
-	a.Eq(err, nil, "Should not nil")
-	a.Eq(o.ID(), "1", "id should be 1")
+	i1, err := f.In(0)
+	a.Eq(err, nil, "should be nil")
+	a.Eq(i1.ID(), "1", "id should be 1")
 
-	o, err = f.Op("vecadd", f.In(0), f.In(1))
+	i2, err := f.In(1)
+	a.Eq(err, nil, "should be nil")
+	a.Eq(i2.ID(), "2", "id should be 2")
+
+	o, err := f.Op("vecadd", i1, i2)
 	a.Eq(err, nil, "Should not nil")
-	a.Eq(o.ID(), "2", "id should be 2")
+	a.Eq(o.ID(), "0", "id should be 0")
 
-	c, _ := f.Const(1)
-	_, err = f.Const(1)
-	a.NotEq(err, nil, "Should not be nil,id generation exausted")
-	o, err = f.Op("vecadd", f.In(0), c)
+	o, err = f.Op("vecadd", i1, i2)
+	a.NotEq(err, nil, "Should not be nil, id generation exausted")
 
-}*/
+}
 
 func TestSerialize(t *testing.T) {
 	// Does not text yet
@@ -171,6 +181,7 @@ func TestVariable(t *testing.T) {
 	a.Eq(res, 2)
 }
 
+// Test context
 func TestCache(t *testing.T) {
 	a := assert.A(t)
 	f := flow.New()
@@ -223,6 +234,38 @@ func TestLocalRegistry(t *testing.T) {
 	op, err := f.Op("none")
 	a.NotEq(err, nil, "flow should contain an error")
 }
+func TestFlowParam(t *testing.T) {
+	a := assert.A(t)
+
+	r := registry.New()
+	f := flow.New()
+	f.SetRegistry(r)
+
+	r.Register("myflow", func(f *flow.Flow) flow.Data {
+		f.Data["test"] = "other test"
+		return f.Data["test"]
+	})
+
+	v := f.Var("test", "hello test")
+
+	vres, err := v.Process()
+	a.Eq(err, nil, "should not error reading var")
+	a.Eq(vres, "hello test", "should be the value setted by the var operation")
+
+	t.Log("The flow:", f)
+
+	op, err := f.Op("myflow")
+	a.Eq(err, nil, "should not error fetching operation")
+	r1, err := op.Process()
+	a.Eq(err, nil, "should not error executing operation")
+	a.Eq(r1, "other test", "should be equal")
+
+	vres, err = v.Process()
+	a.Eq(err, nil, "should not error reading var")
+	a.Eq(vres, "other test", "should be the value setted by the previous operation")
+	t.Log(f)
+
+}
 
 func init() {
 	registry.Register("vecmul", VecMul)
@@ -280,6 +323,7 @@ func VecDiv(a, b []float32) []float32 {
 
 // ScalarInt
 // Every time this operator is called we increase 1
+// Constructor
 func Inc() func() int {
 	i := 0
 	return func() int {

+ 115 - 72
go/src/flow/operation.go

@@ -8,6 +8,7 @@ package flow
 import (
 	"errors"
 	"fmt"
+	"log"
 	"reflect"
 	"sync"
 )
@@ -70,9 +71,13 @@ func (f *Flow) makeTrigger(id string, fn executorFunc) executorFunc {
 	}
 }
 
+// save makeExecutor with a bunch of type checks
+// we will create a less safer functions to get performance
 func (f *Flow) makeExecutor(id string, fn interface{}) executorFunc {
 	return func(ctx OpCtx, params ...Data) (Data, error) {
 		var err error
+
+		//panic recoverer, since nodes are not our functions
 		defer func() {
 			if r := recover(); r != nil {
 				err = fmt.Errorf("%v", r)
@@ -80,7 +85,6 @@ func (f *Flow) makeExecutor(id string, fn interface{}) executorFunc {
 			}
 		}()
 
-		// Should not need this
 		op := f.GetOp(id)
 		if op == nil {
 			err = fmt.Errorf("invalid operation '%s'", id)
@@ -89,80 +93,32 @@ func (f *Flow) makeExecutor(id string, fn interface{}) executorFunc {
 		}
 		op.Lock()
 		defer op.Unlock()
+
+		// Load from cache if any
 		if ctx != nil {
-			if v, ok := ctx.Load(id); ok { // Cache
+			if v, ok := ctx.Load(id); ok {
 				return v, nil
 			}
 		}
 
-		// Check inputs
-		fnval := reflect.ValueOf(fn)
-		if fnval.Type().NumIn() != len(op.inputs) {
-			err = fmt.Errorf("expect %d inputs got %d", fnval.Type().NumIn(), len(op.inputs))
-			f.hooks.error(id, err)
-			return nil, err
-		}
-		/////////////////////////////
-		// NEW PARALLEL PROCESSING
-		///////////
-		//f.hooks.wait(id)
+		// Wait for inputs
 		f.hooks.wait(id)
 
-		callParam := make([]reflect.Value, len(op.inputs))
-		callErrors := ""
-		paramMutex := sync.Mutex{}
-		// Parallel processing if inputs
-		wg := sync.WaitGroup{}
-		wg.Add(len(op.inputs))
-		for i, in := range op.inputs {
-			go func(i int, in *operation) {
-				defer wg.Done()
-				// Sub function with type checking
-				//Check the ctx
-				fr, err := in.executor(ctx, params...)
-				if err != nil {
-					paramMutex.Lock()
-					callErrors += err.Error() + "\n"
-					paramMutex.Unlock()
-					return
-				}
-				if fr == nil {
-					callParam[i] = reflect.Zero(fnval.Type().In(i))
-				}
-				res := reflect.ValueOf(fr)
-				if !res.IsValid() {
-					paramMutex.Lock()
-					callErrors += fmt.Sprintf("Input %d invalid\n", i)
-					paramMutex.Unlock()
-					return
-				} else if !res.Type().ConvertibleTo(fnval.Type().In(i)) {
-					if fnval.Type().In(i).Kind() == reflect.String {
-						callParam[i] = reflect.ValueOf(fmt.Sprint(res.Interface()))
-						return
-					}
-					paramMutex.Lock()
-					callErrors += fmt.Sprintf("Input %d type: %v(%v) cannot be converted to %v\n", i, res.Type(), res.Interface(), fnval.Type().In(i))
-					paramMutex.Unlock()
-					return
-				}
-				// can convert too?
-
-				// CheckError and safelly append
-				callParam[i] = res.Convert(fnval.Type().In(i))
-			}(i, in)
-		}
-		wg.Wait()
-		// Return type error checking
-		if len(callErrors) > 0 {
-			err := errors.New(callErrors)
+		fnval := reflect.ValueOf(fn)
+		callParam, err := f.processInputs(ctx, op, fnval, params...)
+		if err != nil {
+			log.Println("ERR:", err)
 			f.hooks.error(id, err)
 			return nil, err
 		}
 
 		// The actual operation process
-		f.hooks.start(id)
 
+		// Func returned starting the process
+		f.hooks.start(id)
+		// if entry is special we pass the Flow?
 		fnret := fnval.Call(callParam)
+		// Output erroring
 		if len(fnret) > 1 && (fnret[len(fnret)-1].Interface() != nil) {
 			err, ok := fnret[1].Interface().(error)
 			if !ok {
@@ -174,6 +130,8 @@ func (f *Flow) makeExecutor(id string, fn interface{}) executorFunc {
 
 		// THE RESULT
 		ret := fnret[0].Interface()
+
+		// Store in the cache
 		if ctx != nil {
 			ctx.Store(id, ret)
 		}
@@ -182,27 +140,112 @@ func (f *Flow) makeExecutor(id string, fn interface{}) executorFunc {
 	}
 }
 
+/////////////////////////////
+// NEW PARALLEL PROCESSING
+///////////
+func (f *Flow) processInputs(ctx OpCtx, op *operation, fnval reflect.Value, params ...Data) ([]reflect.Value, error) {
+	// Flow injector
+	nInputs := fnval.Type().NumIn()
+
+	// Total inputs
+	OcallParam := make([]reflect.Value, nInputs)
+	callParam := OcallParam
+	offs := 0
+
+	// Inject flow if the first param is of type Flow
+	if nInputs > 0 && fnval.Type().In(0) == reflect.TypeOf(f) {
+		OcallParam[0] = reflect.ValueOf(f)
+		offs = 1
+		nInputs--
+		//shift one to process inputs
+		callParam = OcallParam[1:]
+	}
+
+	if nInputs != len(op.inputs) {
+		return nil, fmt.Errorf("expect %d inputs got %d", nInputs, len(op.inputs))
+	} //Wait
+
+	callErrors := ""
+	paramMutex := sync.Mutex{}
+	// Parallel processing if inputs
+	wg := sync.WaitGroup{}
+	wg.Add(len(op.inputs))
+	for i, in := range op.inputs {
+		go func(i int, in *operation) {
+			defer wg.Done()
+			inTyp := fnval.Type().In(i + offs)
+			/////////////////
+			// Executor
+			fr, err := in.executor(ctx, params...)
+
+			paramMutex.Lock()
+			defer paramMutex.Unlock()
+			if err != nil {
+				callErrors += err.Error() + "\n"
+				return
+			}
+			if fr == nil {
+				callParam[i] = reflect.ValueOf(reflect.Zero(inTyp).Interface())
+			}
+
+			res := reflect.ValueOf(fr)
+			var cres reflect.Value
+
+			// Conversion effort
+			switch {
+			case !res.IsValid():
+				callErrors += fmt.Sprintf("Input %d invalid\n", i)
+				return
+			case !res.Type().ConvertibleTo(inTyp):
+				if inTyp.Kind() != reflect.String {
+					callErrors += fmt.Sprintf("Input %d type: %v(%v) cannot be converted to %v\n", i, res.Type(), res.Interface(), inTyp)
+					return
+				}
+				cres = reflect.ValueOf(fmt.Sprint(res.Interface()))
+			default:
+				cres = res.Convert(inTyp)
+			}
+			// CheckError and safelly append
+			callParam[i] = cres
+		}(i, in)
+	}
+	wg.Wait()
+	if callErrors != "" {
+		return nil, errors.New(callErrors)
+	}
+	/*offs := 0
+	if fnval.Type().NumIn() > 0 && fnval.Type().In(0) == reflect.TypeOf(f) {
+		nInputs--
+		offs = 1
+	}*/
+
+	return OcallParam, nil
+}
+
 // DefVar define var operation with optional initial
 func (f *Flow) DefVar(id string, name string, initial ...Data) Operation {
 	// Unique
-	if _, ok := f.data[name]; !ok {
+	if _, ok := f.Data[name]; !ok {
 		var v interface{}
 		if len(initial) > 0 {
 			v = initial[0]
 		}
-		f.data[name] = v
+		f.Data[name] = v
 	}
-	setter := func(v Data) { f.data[name] = v }
+	setter := func(v Data) { f.Data[name] = v }
 
 	opEntry := &operation{
-		Mutex:    sync.Mutex{},
-		id:       id,
-		flow:     f,
-		name:     fmt.Sprintf("(var)<%s>", name),
-		kind:     "var",
-		inputs:   nil,
-		setter:   setter,
-		executor: f.makeTrigger(id, func(OpCtx, ...Data) (Data, error) { return f.data[name], nil }),
+		Mutex:  sync.Mutex{},
+		id:     id,
+		flow:   f,
+		name:   fmt.Sprintf("(var)<%s>", name),
+		kind:   "var",
+		inputs: nil,
+		setter: setter,
+		executor: f.makeTrigger(id, func(OpCtx, ...Data) (Data, error) {
+			// if f.data == nil we set from the init operation
+			return f.Data[name], nil
+		}),
 	}
 	f.operations.Store(id, opEntry)
 	return opEntry

+ 17 - 4
go/src/flow/registry/entry.go

@@ -34,16 +34,29 @@ func NewEntry(r *R, fn interface{}) *Entry {
 		e.err = ErrOutput
 		return e
 	}
+	outTyp := fntyp.Out(0)
+	if outTyp.Kind() == reflect.Func {
+		outTyp.Out(0)
+	}
 
 	fnTyp := reflect.TypeOf(e.fn)
 	nInputs := fnTyp.NumIn()
+	// Experimental
+	offs := 0
+	if fnTyp.NumIn() > 0 && fnTyp.In(0).String() == "*flow.Flow" {
+		nInputs--
+		offs = 1
+	}
+	///
+
 	Inputs := make([]DescType, nInputs)
 	for i := 0; i < nInputs; i++ {
-		Inputs[i] = DescType{fmt.Sprint(fnTyp.In(i)), ""}
-		e.Inputs = append(e.Inputs, fnTyp.In(i)) // ?
+		inTyp := fnTyp.In(i + offs)
+		Inputs[i] = DescType{fmt.Sprint(inTyp), ""}
+		e.Inputs = append(e.Inputs, inTyp) // ?
 	}
-	Output := DescType{fmt.Sprint(fnTyp.Out(0)), ""}
-	e.Output = fnTyp.Out(0) // ?
+	Output := DescType{fmt.Sprint(outTyp), ""}
+	e.Output = outTyp // ?
 
 	e.Description = &Description{
 		Tags:   []string{"generic"},

+ 24 - 0
go/src/flow/registry/entry_test.go

@@ -1,11 +1,28 @@
 package registry_test
 
 import (
+	"flow"
 	"flow/internal/assert"
 	"flow/registry"
 	"testing"
 )
 
+func TestFlowFunc(t *testing.T) {
+	a := assert.A(t)
+	r := registry.New()
+	f := flow.New()
+	f.SetRegistry(r)
+
+	r.Register("flowfunc", func(paramFlow *flow.Flow) int {
+		a.Eq(paramFlow, f, "Flow should be equal")
+		return 0
+	})
+
+	op, err := f.Op("flowfunc")
+	a.Eq(err, nil, "should not error fetching operator")
+	op.Process()
+
+}
 func TestNewEntryInvalid(t *testing.T) {
 	a := assert.A(t)
 	r := registry.New()
@@ -46,6 +63,10 @@ func TestDescription(t *testing.T) {
 	e.Describer().Extra("test", 123)
 	a.Eq(e.Err(), nil, "should not fail setting extra doc")
 	a.Eq(e.Description.Extra["test"], 123, "extra text should be as expected")
+
+	e.Describer().Description("test")
+	a.Eq(e.Err(), nil, "should not fail setting description")
+
 }
 
 func TestDescriptionError(t *testing.T) {
@@ -63,6 +84,9 @@ func TestDescriptionError(t *testing.T) {
 	e.Describer().Extra("test", 123)
 	a.Eq(e.Err(), registry.ErrNotAFunc, "entry is not a function")
 
+	e.Describer().Description("test")
+	a.Eq(e.Err(), registry.ErrNotAFunc, "entry is not a function")
+
 }
 
 func TestEntryBatch(t *testing.T) {

+ 3 - 1
go/src/flow/registry/registry.go

@@ -25,7 +25,9 @@ type R struct {
 
 // New creates a new registry
 func New() *R {
-	return &R{map[string]*Entry{}}
+	r := &R{map[string]*Entry{}}
+	// create a base function here?
+	return r
 }
 
 // Clone an existing registry

+ 78 - 0
go/src/flowserver/cmd/demo1/gonumops/gonumops.go

@@ -0,0 +1,78 @@
+package gonumops
+
+import (
+	"flow"
+	"flow/registry"
+	"math"
+	"math/rand"
+
+	"gonum.org/v1/gonum/mat"
+)
+
+// Tensor wrapper
+type Tensor interface {
+	mat.Matrix
+}
+
+// New registry
+func New() *registry.R {
+
+	r := registry.New()
+
+	r.Add(
+		myVar,
+		normFloat,
+		tensorNew,
+		tensorMul,
+		tensorTranspose,
+		logistic,
+	).Description("gonum functions").
+		Tags("gonum").
+		Extra("style", registry.M{"color": "#953"})
+
+	return r
+}
+
+func myVar(f *flow.Flow, placeholder string, init flow.Data) flow.Data {
+
+	if _, ok := f.Data[placeholder]; !ok {
+		f.Data[placeholder] = init
+	}
+	return f.Data[placeholder]
+}
+
+func normFloat(n int) []float64 {
+	data := make([]float64, n)
+	for i := range data {
+		data[i] = rand.NormFloat64()
+	}
+	return data
+}
+
+func tensorNew(r, c int, data []float64) Tensor {
+	return mat.NewDense(r, c, data)
+}
+
+func tensorMul(a Tensor, b Tensor) Tensor {
+	var c mat.Dense
+	c.Mul(a, b)
+	return &c
+}
+
+func tensorTranspose(a Tensor) Tensor {
+	return a.T()
+}
+
+func logistic(a Tensor) Tensor {
+	v := a.(mat.Vector)
+	ret := mat.NewVecDense(v.Len(), nil)
+	// Should be a vector perhaps
+	for i := 0; i < v.Len(); i++ {
+		ret.SetVec(i, activator(v.AtVec(i)))
+	}
+	return ret
+}
+
+func activator(v float64) float64 {
+	return 1 / (1 + math.Exp(-v))
+}

+ 2 - 0
go/src/flowserver/cmd/demo1/main.go

@@ -5,6 +5,7 @@ import (
 	"flowserver/cmd/demo1/assets"
 	"flowserver/cmd/demo1/defaultops"
 	"flowserver/cmd/demo1/devops"
+	"flowserver/cmd/demo1/gonumops"
 	"flowserver/cmd/demo1/testops"
 	"log"
 	"mime"
@@ -33,6 +34,7 @@ func main() {
 	mux.HandleFunc("/", assetFunc)
 
 	mux.Handle("/default/", c.Build(flowserver.New(defaultops.New(), "default").ServeHTTP))
+	mux.Handle("/gonumops/", c.Build(flowserver.New(gonumops.New(), "gonumops").ServeHTTP))
 	mux.Handle("/devops/", c.Build(flowserver.New(devops.New(), "devops").ServeHTTP))
 	mux.Handle("/testops/", c.Build(flowserver.New(testops.New(), "tests").ServeHTTP))
 

+ 6 - 4
go/src/flowserver/cmd/demo1/static/index.html

@@ -11,16 +11,18 @@
   height:100%;
 }
 .app-selector >* { padding:10px; }
-.app-selector-items *:first-child { border-right: #000; }
+.app-selector-items:first-child { border-right: #000; }
 		</style>
 	</head>
 	<body>
 		<div class="app-selector">
+		<img src="sample.png">
     <h3> Sample ideas using flow: </h3>
     <div class="app-selector-items">
-			<a href="/default">Default thing</a>
-			<a href="/devops">Devops sample</a>
-			<a href="/testops">testing registry</a>
+			[<a href="/default">Default thing</a>]
+			[<a href="/gonum">Gonum</a>]
+			[<a href="/devops">Devops sample</a>]
+			[<a href="/testops">testing registry</a>]
     </div>
 	</div>
 	</body

二进制
go/src/flowserver/cmd/demo1/static/sample.png


+ 1 - 2
go/src/flowserver/flowbuilder/builder.go

@@ -206,9 +206,8 @@ func parseValue(typ reflect.Type, raw string) (flow.Data, error) {
 			if err != nil {
 				return nil, err
 			}
-			ret = refVal.Interface()
+			ret = refVal.Elem().Interface()
 		}
 	}
-	log.Printf("Returning %#v", ret)
 	return ret, nil
 }

+ 13 - 1
go/src/flowserver/flowserver.go

@@ -1,6 +1,7 @@
 package flowserver
 
 import (
+	"flow"
 	"flow/registry"
 	"log"
 	"net/http"
@@ -24,8 +25,19 @@ type FlowServer struct {
 // New creates a New flow server
 func New(r *registry.R, store string) *FlowServer {
 	if r == nil {
-		r = registry.Global
+		r = registry.Global.Clone()
 	}
+	// inject into registry
+	r.Register("Variable", func(f *flow.Flow, name string, initial flow.Data) flow.Data {
+		_, ok := f.Data[name]
+		if !ok {
+			f.Data[name] = initial
+		}
+		return f.Data[name]
+	}).Describer().
+		Inputs("name", "initial").
+		Tags("core").
+		Extra("style", registry.M{"color": "#88a"})
 
 	mux := http.NewServeMux()
 	mux.Handle("/conn", NewFlowSessionManager(r, store))

+ 8 - 1
go/src/flowserver/session.go

@@ -197,7 +197,14 @@ func (s *FlowSession) NodeRun(c *websocket.Conn, data []byte) error {
 		localR.Register("Log", func() io.Writer {
 			return s
 		})
-
+		// Special func
+		localR.Register("Variable", func(f *flow.Flow, name string, initial flow.Data) flow.Data {
+			_, ok := f.Data[name]
+			if !ok {
+				f.Data[name] = initial
+			}
+			return f.Data[name]
+		})
 		builder := flowbuilder.New(localR)
 		builder.Load(s.RawDoc).Build(ID)