Przeglądaj źródła

Samples, readme

Fixed samples, added readme to samples and main
Luis Figueiredo 8 lat temu
commit
255840fdbb

+ 57 - 0
README.md

@@ -0,0 +1,57 @@
+WebSocket RPC
+============
+
+Usage:
+
+```go
+func MyCliFunc(cli *wsrpc.ClientCtx) {
+
+	cli.Define("btn1.click", func(param ...interface{}) interface{} { // Define a remote interface so javascript client can call
+		log.Println("Async button clicked")
+		return "ok"
+
+	})
+	ret := cli.Call("Hello", wsrpc.DataObj{
+		"couldbe": "interface",
+	})
+	t := <-ret
+
+	log.Println("Response:", t)
+}
+
+func main() {
+	var mux = http.NewServeMux()
+
+	wsrpc.RegisterTo(mux, MyCliFunc)  // Client websocket handler, this registers also the JS library
+	mux.HandleFunc("/", webGenerated.AssetHandleFunc)
+
+	log.Println("Listening :8080")
+	http.ListenAndServe(":8080", mux)
+
+}
+```
+
+```javascript
+	wsrpc = new WsRpc();
+	wsrpc.connect("ws://"+location.host +"/wsrpc");
+
+	obj = {
+		Hello: function(response,param) {
+			console.log("Hello world")
+			response({ok:"OK"})
+		}
+	}
+	wsrpc.export(obj)
+
+
+	btn = document.querySelector("#btn1")
+
+	btn.addEventListener("click",function(){
+		console.log("Button clicked")
+		wsrpc.call("btn1.click",{"hello":"ok"}, (res) =>{   // Will call server defined procedure
+			console.log("Answered:",res	);
+		})
+	})
+
+
+```

+ 110 - 0
client/wsrpc.js

@@ -0,0 +1,110 @@
+
+/*
+  Goal having something like:
+ 
+	wsrpc.Connect("/ws");
+	wsrpc.Connect("ws://127.0.0.1/ws");
+
+
+	obj = {
+		method1:function() {
+			//Hello
+		}
+	}
+	wsrpc.Export(obj);
+ */
+
+function WsRpc() {
+	var ctx = this
+	this._requests = {}
+	this.connect = function(loc) {
+			var ws = this.ws = new WebSocket(loc)
+			ws.onopen = function() {
+				ctx._requests = {}
+				ctx.connected = true;
+				if(ctx.onopen != undefined) {
+					ctx.onopen(ctx)
+				}
+			}	
+			ws.onmessage = function(evt) {
+				//console.log("RCV:",evt.data)
+				var obj = JSON.parse(evt.data)
+				var arrObj = [].concat(obj); // if object add, if array concat
+				for( var i = 0; i <arrObj.length; i++) {
+					ctx.process(arrObj[i]);
+				}
+			}
+			ws.onerror = function() { }
+			ws.onclose = function() {
+				if(ctx.connected == true) {
+					ctx.connected = false;
+					if(ctx.onclose != undefined) {
+						ctx.onclose(ctx)
+					}
+				}
+				
+				// Retry
+				setTimeout(function() {
+					ctx.connect(loc)
+				},3000) // 3 seconds reconnect
+			}
+		}
+	this.process =  function(obj) {
+		if (obj.op == "call") {
+			if(obj.method == undefined || this._exports[obj.method] == undefined) {
+				//TODO: Send objet to inform error
+				return
+			}
+			function response(data) {
+				var dataObj = {
+					"op":"response",
+					"reqId":obj.reqId,
+					"data":data
+				}
+				ctx.ws.send(JSON.stringify(dataObj));
+			}
+			var nparams = [].concat(response,obj.param)
+			this._exports[obj.method].apply(this._exports[obj.method],nparams)
+		}
+		if( obj.op == "response" ) {
+			//console.log("Receiving response for:", obj.reqId)
+			ctx._requests[obj.reqId](obj.data)
+			delete ctx._requests[obj.reqId]
+		}
+	}
+	this.call = function(method) {
+		var aparam = Array.prototype.slice.call(arguments,1)
+		return new Promise(function(resolve,reject) {
+			if(ctx.connected == false ) {
+				reject(Error("Not connected"))
+			}
+
+			var uuidStr = uuid();
+			var callObj = {
+				"reqId":uuidStr,
+				"op":"call",
+				"method":method,
+				"param":aparam
+			}
+			ctx._requests[uuidStr] = resolve;
+			//console.log("Sending a call:", method)
+			ctx.ws.send(JSON.stringify(callObj));
+		})
+	}
+	this.export = function(obj){
+		this._exports = obj;
+	}
+			
+	return this
+}
+
+
+function uuid() {
+	var ret = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+	    var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+	    return v.toString(16);
+	});
+	return ret;
+}
+
+

+ 106 - 0
clientctx.go

@@ -0,0 +1,106 @@
+// Client handler
+package wsrpc
+
+import (
+	"sync"
+
+	"github.com/google/uuid"
+
+	"golang.org/x/net/websocket"
+)
+
+// DataObj common structure for dymanic json object
+type DataObj map[string]interface{}
+
+// ListenerFunc function type to handle browser events
+type ListenerFunc func(...interface{}) interface{}
+
+// Request request type for handling requests channels
+
+// ClientCtx main client struct
+type ClientCtx struct {
+	locker sync.Mutex
+
+	WS *websocket.Conn
+	// stuff
+	listeners map[string]ListenerFunc
+	requests  map[string]chan interface{}
+}
+
+//NewHandler creates a new handler
+func NewHandler(id string, ws *websocket.Conn) *ClientCtx {
+	var c = ClientCtx{}
+	c.WS = ws
+	c.listeners = map[string]ListenerFunc{}
+	c.requests = map[string]chan interface{}{}
+	return &c
+}
+
+// Process messages
+func (c *ClientCtx) Process(data DataObj) {
+	if data["op"].(string) == "call" {
+		params := data["param"].([]interface{})
+		var idn = data["method"].(string)
+		var reqId = data["reqId"].(string)
+		var fn, ok = c.listeners[idn]
+		if !ok {
+			return
+		}
+		go func() {
+			ret := fn(params...)
+			var responseObj = DataObj{
+				"op":    "response",
+				"reqId": reqId,
+				"data":  ret,
+			}
+			//log.Println("Sending response")
+			websocket.JSON.Send(c.WS, responseObj)
+		}()
+	}
+
+	if data["op"].(string) == "response" {
+		c.locker.Lock()
+		mchan, ok := c.requests[data["reqId"].(string)]
+		delete(c.requests, data["reqId"].(string))
+		c.locker.Unlock()
+
+		if ok {
+			mchan <- data["data"]
+		}
+	}
+}
+
+// Call a client method and estabilishes a request id
+func (c *ClientCtx) Call(method string, params ...interface{}) chan interface{} {
+	u := uuid.New()
+	uuidStr := u.String()
+
+	var callObj = DataObj{
+		"op":     "call",
+		"reqId":  uuidStr,
+		"method": method,
+		"param":  params,
+	}
+	res := make(chan interface{}, 1)
+	c.locker.Lock()
+	c.requests[uuidStr] = res
+	c.locker.Unlock()
+
+	websocket.JSON.Send(c.WS, &callObj)
+	return res
+}
+
+//Emit emit event listener
+/*func (c *ClientCtx) Emit(name string, data DataObj) (interface{}, error) {
+	fn, ok := c.listeners[name]
+	if !ok {
+		return nil, fmt.Errorf("Not found") // Dont execute
+	}
+	return fn(data), nil
+
+}*/
+
+//On add a event listener on browser
+func (c *ClientCtx) Define(name string, listener ListenerFunc) {
+	c.listeners[name] = listener
+}

+ 12 - 0
doc/readme.md

@@ -0,0 +1,12 @@
+Create optional return, if the function someone is void
+we do not return or we do a different call
+
+... 
+each wsrpc.Call() creates a request identifier so when  it comes back
+we trigger the callback/request
+...
+if we create a different call or any thing to identify a non returning function
+we wouldn't store the req
+
+as for if we call a non existent method we should return an error
+

+ 4 - 0
sample/basic/README.md

@@ -0,0 +1,4 @@
+Basic example of wsrpc
+==================
+
+Simple button click will send a realtime message to server 

+ 36 - 0
sample/basic/test.go

@@ -0,0 +1,36 @@
+package main
+
+//go:generate folder2go web webGenerated
+import (
+	"log"
+	"net/http"
+
+	"dev.hexasoftware.com/stdio/wsrpc"
+	"dev.hexasoftware.com/stdio/wsrpc/sample/basic/webGenerated"
+)
+
+func MyCliFunc(cli *wsrpc.ClientCtx) {
+
+	cli.Define("btn1.click", func(param ...interface{}) interface{} {
+		log.Println("Async button clicked")
+		return "ok"
+
+	})
+	ret := cli.Call("Hello", wsrpc.DataObj{
+		"couldbe": "interface",
+	})
+	t := <-ret
+
+	log.Println("Response:", t)
+}
+
+func main() {
+	var mux = http.NewServeMux()
+
+	wsrpc.RegisterTo(mux, MyCliFunc)
+	mux.HandleFunc("/", webGenerated.AssetHandleFunc)
+
+	log.Println("Listening :8080")
+	http.ListenAndServe(":8080", mux)
+
+}

+ 9 - 0
sample/basic/web/index.html

@@ -0,0 +1,9 @@
+<html>
+	<head>
+		<script src='wsrpc/client.js'></script>	
+		<script src='js/main.js'></script>	
+	</head>
+	<body>
+		<button id="btn1">The button</button>	
+	</body>
+</html>

+ 37 - 0
sample/basic/web/js/main.js

@@ -0,0 +1,37 @@
+
+// Simple func to attach to element
+/*
+ * LiveUI('#main','ws://host/ws');
+ */
+
+// http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
+
+window.onload = function() {
+
+	wsrpc = new WsRpc();
+	wsrpc.connect("ws://"+location.host +"/wsrpc");
+
+	console.log("Trying to connect")
+
+
+	obj = {
+		Hello: function(response,param) {
+			console.log("Hello world")
+			response({ok:"OK"})
+		}
+	}
+	wsrpc.export(obj)
+
+
+	btn = document.querySelector("#btn1")
+
+	btn.addEventListener("click",function(){
+		console.log("Button clicked")
+		wsrpc.call("btn1.click",{"hello":"ok"}, (res) =>{
+			console.log("Answered:",res	);
+		})
+	})
+}
+	
+	
+

Plik diff jest za duży
+ 27 - 0
sample/basic/webGenerated/webGenerated.go


+ 4 - 0
sample/canvas/README.md

@@ -0,0 +1,4 @@
+Canvas wsrpc example
+==========================
+
+It reads the mouse position from screen, and draws a line on the received mouse history from server

+ 44 - 0
sample/canvas/canvas/canvas.go

@@ -0,0 +1,44 @@
+package canvas
+
+import (
+	//"hexasoftware/lib/vnode/vfunc"
+
+	"hexasoftware/lib/vnode/vfunc"
+
+	"dev.hexasoftware.com/stdio/wsrpc"
+)
+
+type Canvas struct {
+	ctx         *wsrpc.ClientCtx
+	OnResize    func(width, height int)
+	OnMouseMove func(x, y int)
+}
+
+func New(ctx *wsrpc.ClientCtx) *Canvas {
+	c := &Canvas{ctx, nil, nil}
+
+	ctx.Define("OnResize", vfunc.NCastFunc(func(width, height float64) interface{} {
+		if c.OnResize != nil {
+			c.OnResize(int(width), int(height))
+		}
+		return nil
+	}))
+	ctx.Define("OnMouseMove", vfunc.NCastFunc(func(x, y float64) interface{} {
+		if c.OnMouseMove != nil {
+			c.OnMouseMove(int(x), int(y))
+		}
+		return nil
+	}))
+
+	return c
+}
+
+func (c *Canvas) Arc(x, y, radius, startAngle, endAngle int) {
+	c.ctx.Call("DrawArc", x, y, radius, startAngle, endAngle)
+}
+func (c *Canvas) Line(x1, y1, x2, y2 int) {
+	c.ctx.Call("Line", x1, y1, x2, y2)
+}
+func (c *Canvas) Clear() {
+	c.ctx.Call("Clear")
+}

+ 54 - 0
sample/canvas/main.go

@@ -0,0 +1,54 @@
+package main
+
+import (
+	"log"
+	"net/http"
+	"sync"
+
+	"dev.hexasoftware.com/stdio/wsrpc"
+
+	_ "dev.hexasoftware.com/stdio/prettylog"
+
+	"dev.hexasoftware.com/stdio/wsrpc/sample/canvas/canvas"
+)
+
+var objlock sync.Mutex
+
+type Point struct {
+	x, y int
+}
+
+func ClientHandler(ctx *wsrpc.ClientCtx) {
+	log.Println("Client connected", ctx.WS.RemoteAddr())
+	c := canvas.New(ctx)
+	points := []Point{}
+	c.OnMouseMove = func(x, y int) {
+		objlock.Lock()
+		defer objlock.Unlock()
+		c.Clear()
+
+		points = append(points, Point{x, y})
+		lenPoints := len(points)
+		// Test this
+		if lenPoints > 2 {
+			for i := 1; i < lenPoints; i++ {
+				c.Line(points[i-1].x, points[i-1].y,
+					points[i].x, points[i].y)
+
+			}
+		}
+		if lenPoints > 15 { // try 100 drawCalls
+			points = points[1:] // remove first
+		}
+	}
+
+}
+
+func main() {
+	var mux = http.NewServeMux()
+	wsrpc.RegisterTo(mux, ClientHandler)
+
+	mux.Handle("/", http.FileServer(http.Dir("web")))
+	log.Println("Listening :8080")
+	http.ListenAndServe(":8080", mux)
+}

+ 4 - 0
sample/canvas/vendor/hexasoftware/lib/vnode/doc/readme.md

@@ -0,0 +1,4 @@
+vnode1
+
+#### back propagation theory
+Construct dynamic functions by back propagate nodes

+ 1 - 0
sample/canvas/vendor/hexasoftware/lib/vnode/test/build-chain/main.go

@@ -0,0 +1 @@
+

+ 66 - 0
sample/canvas/vendor/hexasoftware/lib/vnode/test/complex/main.go

@@ -0,0 +1,66 @@
+package main
+
+// #cgo CFLAGS: -O3
+// #cgo LDFLAGS: -O3
+import (
+	"C"
+	"hexasoftware/lib/vnode"
+	"strings"
+)
+import (
+	"hexasoftware/lib/bench"
+	"reflect"
+)
+
+func NCastFunc(fn interface{}) func(...interface{}) interface{} {
+	fnRefl := reflect.ValueOf(fn)
+	ret := func(args ...interface{}) interface{} {
+		nargs := []reflect.Value{}
+		for _, a := range args {
+			nargs = append(nargs, reflect.ValueOf(a))
+		}
+		return fnRefl.Call(nargs)[0].Interface()
+	}
+
+	return ret
+}
+
+var srsplitNode = vnode.New(NCastFunc(strings.Fields), 1)
+var srjoinNode = vnode.New(NCastFunc(strings.Join), 2)
+
+var rsplitNode = vnode.New(strings.Fields)
+var rjoinNode = vnode.New(strings.Join)
+
+var splitNode = vnode.New(func(args ...interface{}) interface{} {
+	return strings.Fields(args[0].(string))
+}, 1) // Inform number of inputs
+var joinNode = vnode.New(func(args ...interface{}) interface{} {
+	return strings.Join(args[0].([]string), args[1].(string))
+}, 2)
+
+func main() {
+	s := "This is a test"
+
+	splitNode.Source(s)
+	joinNode.Source(splitNode, ",")
+
+	rsplitNode.Source(s)
+	rjoinNode.Source(rsplitNode, ",")
+
+	srsplitNode.Source(s)
+	srjoinNode.Source(srsplitNode, ",")
+
+	// Exec full strenght
+	bench.Exec("node iface", func() interface{} {
+		return joinNode.Exec()
+	})
+	bench.Exec("node refl", func() interface{} {
+		return rjoinNode.Exec()
+	})
+	bench.Exec("node Smart refl", func() interface{} {
+		return srjoinNode.Exec()
+	})
+	bench.Exec("node native", func() interface{} {
+		return strings.Join(strings.Fields(s), ",")
+	})
+}

+ 63 - 0
sample/canvas/vendor/hexasoftware/lib/vnode/test/dev/main.go

@@ -0,0 +1,63 @@
+package main
+
+import (
+	"bytes"
+	"hexasoftware/lib/bench"
+	_ "hexasoftware/lib/prettylog/global"
+	"hexasoftware/lib/vnode"
+	"log"
+)
+
+// Test 1 Sum 2 numbers
+// The goal:
+/*
+
+	GetInput(1).Link(othervnode)
+*/
+func sumFunc(a, b int) int {
+	return a + b
+}
+
+func sumFuncV(args ...interface{}) interface{} {
+	return args[0].(int) + args[1].(int)
+}
+
+func ret10() int {
+	return 10
+}
+
+func main() {
+	reverse := vnode.New(func(s string) string {
+		var b bytes.Buffer
+		for i := len(s) - 1; i >= 0; i-- {
+			b.WriteByte(s[i])
+		}
+		return b.String()
+	})
+	reverse.Source("Hello from the other side")
+
+	count := vnode.New(func(s string) int {
+		return len(s)
+	})
+	count.Source("This is a big string")
+
+	log.Println(reverse.Exec())
+
+	sumNode1 := vnode.New(sumFunc, 2)
+	var err error
+	err = sumNode1.Source(count, ret10)
+	log.Println("Error check", err)
+
+	sumNode := vnode.New(sumFunc, 2)
+
+	err = sumNode.Source(sumNode1, 2)
+	log.Println("Error check", err)
+
+	log.Println("Node:", sumNode.Exec())
+	log.Printf("Func done")
+
+	bench.Exec("main", func() interface{} {
+		return sumNode.Exec()
+	})
+
+}

+ 61 - 0
sample/canvas/vendor/hexasoftware/lib/vnode/test/dev/main_test.go

@@ -0,0 +1,61 @@
+package main
+
+import (
+	"hexasoftware/lib/vnode"
+	"testing"
+)
+
+func BenchmarkNative(b *testing.B) {
+	sumFunc := func(a, b int) int {
+		return a + b
+	}
+	ret10 := func() int {
+		return 10
+	}
+	test := func() int {
+		return sumFunc(sumFunc(1, ret10()), 2)
+	}
+
+	for i := 0; i < b.N; i++ {
+		test()
+	}
+
+}
+
+func BenchmarkNodeReflection(b *testing.B) {
+
+	sumFunc := func(a, b int) int {
+		return a + b
+	}
+	ret10 := func() int {
+		return 10
+	}
+
+	main := vnode.New(sumFunc)
+	vnode2 := vnode.New(sumFunc)
+	vnode2.Source(ret10, 4)
+	main.Source(1, vnode2)
+
+	for i := 0; i < b.N; i++ {
+		main.Exec()
+	}
+
+}
+
+func BenchmarkNodeIface(b *testing.B) {
+	sumFunc := func(p ...interface{}) interface{} {
+		return p[0].(int) + p[1].(int)
+	}
+	ret10 := func() interface{} {
+		return 10
+	}
+
+	main := vnode.New(sumFunc, 2)
+	vnode2 := vnode.New(sumFunc, 2)
+	vnode2.Source(ret10, 4)
+	main.Source(1, vnode2)
+	for i := 0; i < b.N; i++ {
+		main.Exec()
+	}
+
+}

+ 45 - 0
sample/canvas/vendor/hexasoftware/lib/vnode/vfunc/vfunc.go

@@ -0,0 +1,45 @@
+package vfunc
+
+import (
+	"reflect"
+	"unsafe"
+)
+
+// UCastFunc create function caller by unsafe pointer
+func UCastFunc(fn interface{}) func(...interface{}) interface{} {
+	f := fn
+	fRefl := reflect.ValueOf(f)
+	return *(*func(...interface{}) interface{})(unsafe.Pointer(fRefl.Pointer()))
+}
+
+// NCastFunc creates a function caller by Reflection
+func NCastFunc(fn interface{}) func(...interface{}) interface{} {
+	fnRefl := reflect.ValueOf(fn)
+	ret := func(args ...interface{}) interface{} {
+		nargs := []reflect.Value{}
+		for _, a := range args {
+			nargs = append(nargs, reflect.ValueOf(a))
+		}
+		return fnRefl.Call(nargs)[0].Interface()
+	}
+
+	return ret
+}
+
+//CastFunc creates a function caller by MakeFunc
+func RCastFunc(fn interface{}) func(...interface{}) interface{} {
+	var dummy func(...interface{}) interface{}
+	fnRefl := reflect.ValueOf(fn)
+	myFunci := reflect.MakeFunc(reflect.TypeOf(dummy), func(args []reflect.Value) (results []reflect.Value) {
+		// Expect variadic so
+		var nargs []reflect.Value
+		for _, v := range args[0].Interface().([]interface{}) {
+			nargs = append(nargs, reflect.ValueOf(v))
+		}
+		ret := fnRefl.Call(nargs)[0].Interface()
+
+		return []reflect.Value{reflect.ValueOf(&ret).Elem()}
+		// Convert the fucker"
+	}).Interface().(func(...interface{}) interface{})
+	return myFunci
+}

+ 207 - 0
sample/canvas/vendor/hexasoftware/lib/vnode/vnode.go

@@ -0,0 +1,207 @@
+package vnode
+
+import (
+	"fmt"
+	"log"
+	"reflect"
+)
+
+type IVNode interface {
+	Result() interface{}
+}
+
+const (
+	OPT_VAR = iota
+	OPT_FUNC
+	OPT_FUNCI
+	OPT_SFUNC
+)
+
+type Result interface{}
+
+type VNodeFunc func(*VNode) Result
+
+type SourceEntry struct {
+	fnInputType       reflect.Type
+	srcInputInterface interface{}
+	srcInputValue     reflect.Value //
+	srcInputFn        func() interface{}
+	//iface         interface{}
+	Value func() reflect.Value
+	//fnHandler func() reflect.Value
+	opType int
+}
+type VNode struct {
+	sourceList    []SourceEntry
+	mainFuncValue reflect.Value
+	specialFn     func(...interface{}) interface{}
+}
+
+func New(fns ...interface{}) *VNode {
+	var vn VNode
+
+	fn := fns[0]
+	nInputs := 0
+
+	if len(fns) > 1 {
+		nInputs = fns[1].(int)
+	}
+
+	rfn := reflect.ValueOf(fn)
+
+	// Or Pointer
+
+	// Check type func
+	if rfn.Kind() != reflect.Func {
+		log.Println("Argument should be a function")
+		return nil
+	}
+	vn.mainFuncValue = rfn
+	// could this reduce complexity??
+	/*var Func func(...interface{}) interface{}
+	if rfn.Type() != reflect.TypeOf(Func) {
+		nInputs = rfn.Type().NumIn()
+		madeFn := reflect.MakeFunc(reflect.TypeOf(Func), func(in []reflect.Value) []reflect.Value {
+			nargs := []reflect.Value{}
+			for i := 0; i < in[0].Len(); i++ {
+				narg := in[0].Index(i).Elem()
+				nargs = append(nargs, narg)
+			}
+			res := rfn.Call(nargs)
+			v := res[0].Interface()
+
+			return []reflect.Value{reflect.ValueOf(&v).Elem()}
+		})
+		reflect.ValueOf(&Func).Elem().Set(madeFn)
+		fn = Func
+	}*/
+
+	switch fn.(type) {
+	case func(...interface{}) interface{}:
+		vn.specialFn = fn.(func(...interface{}) interface{})
+		vn.sourceList = make([]SourceEntry, nInputs)
+		for i := 0; i < nInputs; i++ {
+			vn.sourceList[i].fnInputType = nil
+		}
+	default:
+		nInputs = rfn.Type().NumIn()
+		vn.sourceList = make([]SourceEntry, nInputs)
+		for i := 0; i < nInputs; i++ {
+			vn.sourceList[i].fnInputType = rfn.Type().In(i)
+		}
+	}
+
+	return &vn
+}
+
+func (vn *VNode) SourceAt(i int, val interface{}) error {
+	se := &vn.sourceList[i]
+	// If fninputType is interface we can accept anything
+	// Do some type checks here
+	se.srcInputValue = reflect.ValueOf(val)
+	if se.srcInputValue.Type() == reflect.TypeOf(vn) {
+		se.srcInputValue = reflect.ValueOf(func() interface{} {
+			return val.(*VNode).Exec()
+		})
+	}
+
+	var t func() interface{}
+	if se.srcInputValue.Type() == reflect.TypeOf(t) {
+		se.Value = func() reflect.Value {
+			return reflect.ValueOf(se.srcInputValue.Interface().(func() interface{})())
+		}
+		se.srcInputFn = se.srcInputValue.Interface().(func() interface{})
+		se.opType = OPT_SFUNC
+		return nil
+	}
+
+	// Especial case:
+	if se.srcInputValue.Kind() == reflect.Func {
+		if se.srcInputValue.Type().NumOut() != 1 { // we could ignore this
+			return fmt.Errorf("Input: %d Invalid number of returns", i)
+		}
+		out := se.srcInputValue.Type().Out(0)
+		// check output type if not interface or same type
+		/*if out.Kind() != reflect.Interface && out != se.fnInputType {
+			return fmt.Errorf("Input: %d Type mismatch", i)
+		}*/
+		if out.Kind() == reflect.Interface {
+			se.Value = func() reflect.Value {
+				return se.srcInputValue.Call([]reflect.Value{})[0].Elem()
+			}
+			se.opType = OPT_FUNCI
+		} else {
+			se.Value = func() reflect.Value {
+				return se.srcInputValue.Call([]reflect.Value{})[0]
+			}
+			se.opType = OPT_FUNC
+		}
+	} else if se.srcInputValue.Type() == se.fnInputType || se.fnInputType == nil {
+		se.Value = func() reflect.Value {
+			return se.srcInputValue
+		}
+		se.srcInputInterface = se.srcInputValue.Interface()
+		se.opType = OPT_VAR
+	} else {
+		return fmt.Errorf("Input: %d Didn't match", i)
+	}
+
+	//vn.sourceList[i] = se
+	return nil
+}
+func (vn *VNode) Source(vnodes ...interface{}) error {
+	for i, vnode := range vnodes {
+		err := vn.SourceAt(i, vnode)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// Change to execute
+func (vn *VNode) Exec() Result {
+	//special case
+	if vn.specialFn != nil {
+		iargs := make([]interface{}, len(vn.sourceList))
+		for i, se := range vn.sourceList {
+			switch se.opType {
+			case OPT_VAR: // Direct value input
+				iargs[i] = se.srcInputInterface
+			case OPT_SFUNC: // Function returning interface , optimizations
+				iargs[i] = se.srcInputFn()
+			default: // Function call
+				iargs[i] = se.srcInputValue.Call([]reflect.Value{})[0].Interface()
+				//iargs[i] = se.Value().Interface()
+			}
+		}
+		return vn.specialFn(iargs...)
+	}
+
+	rargs := make([]reflect.Value, len(vn.sourceList))
+	for i, se := range vn.sourceList {
+		switch se.opType {
+		case OPT_VAR: // Direct value input
+			rargs[i] = se.srcInputValue
+		case OPT_FUNC:
+			rargs[i] = se.srcInputValue.Call([]reflect.Value{})[0]
+		case OPT_FUNCI:
+			rargs[i] = se.srcInputValue.Call([]reflect.Value{})[0].Elem()
+		case OPT_SFUNC: // Function returning interface , optimizations
+			rargs[i] = se.Value()
+		default: // Function call
+			rargs[i] = se.Value()
+			//iargs[i] = se.srcInputValue.Call([]reflect.Value{})[0].Interface()
+			//iargs[i] = se.Value().Interface()
+		}
+
+	}
+	return vn.mainFuncValue.Call(rargs)[0].Interface()
+}
+
+// Exec input here
+/*func (vn *VNode) input(i int) interface{} {
+	in := vn.sourceList[i]
+
+	return in.fnHandler().Interface()
+}*/

+ 5 - 0
sample/canvas/web/css/style.css

@@ -0,0 +1,5 @@
+body,html {
+	margin:0px;
+	padding:0px;
+	height:100vh;
+}

+ 19 - 0
sample/canvas/web/index.html

@@ -0,0 +1,19 @@
+<html>
+	<head>
+		<link rel="stylesheet" href="/css/style.css">
+		<link rel="stylesheet" href="/vendor/hLayout/hLayout.css">
+		<script src="/vendor/hLayout/hLayout.js"></script>
+		<script src="/wsrpc/client.js"></script>
+		<script src="/js/main.js"></script>
+	</head>
+	<body>
+		<div class="layout-container">
+			<div class="hpane">
+				<header>Canvas</header>
+				<div class="hcontent">
+					<canvas id="screen" width="100%" height="100%"></canvas>
+				</div>
+			</div>
+		</div>
+	</body>
+</html>

+ 74 - 0
sample/canvas/web/js/main.js

@@ -0,0 +1,74 @@
+
+
+function start(wsrpc){
+	// attach on windows resize
+	var canvas = document.getElementById("screen")
+	var ctx = canvas.getContext("2d");
+	ctx.lineWidth = 2;
+
+	function handleResize(evt){
+		if (ctx.canvas.width != window.innerWidth ||
+			ctx.canvas.height != window.innerHeight) {
+		
+			console.log("Readapt canvas");
+			ctx.canvas.width = window.innerWidth;
+			ctx.canvas.height = window.innerHeight;
+		}
+		wsrpc.call("OnResize",ctx.canvas.width,ctx.canvas.height);
+	}
+	window.onresize = handleResize
+	handleResize();
+
+	// Get Canvas ctx
+	console.log("Connected");
+	wsrpc.export({
+		ping: function() {
+			console.log("Client sent ping");
+		},
+		DrawArc: function(response, x,y,radius,startAngle,endAngle) { 
+			//console.log("Drawing arc",x,y,radius,startAngle,endAngle);
+			x = x - canvas.offsetLeft
+			y = y - canvas.offsetTop
+			ctx.beginPath();
+			ctx.arc(x,y,radius,startAngle,endAngle,false);
+			ctx.stroke();
+		},
+		Line: function(response, x1,y1,x2,y2) {
+			x1 = x1 - canvas.offsetLeft
+			y1 = y1 - canvas.offsetTop
+			x2 = x2 - canvas.offsetLeft
+			y2 = y2 - canvas.offsetTop
+			ctx.beginPath();
+			ctx.moveTo(x1,y1);
+			ctx.lineTo(x2,y2);
+			ctx.stroke();
+		},
+		Clear: function(response) {
+			ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
+		}
+		// Canvas stuff
+		//
+		
+	})
+
+	// Events
+	window.onmousemove = function(evt) {
+		wsrpc.call("OnMouseMove",evt.pageX,evt.pageY);
+	}
+
+}
+
+
+
+
+
+
+
+
+
+window.onload = function() {
+	wsrpc = new WsRpc();
+	wsrpc.onopen = start;
+	wsrpc.connect("ws://" + location.host + "/wsrpc");
+
+}

BIN
sample/canvas/web/vendor/hLayout/OpenSans-Regular.ttf


+ 185 - 0
sample/canvas/web/vendor/hLayout/hLayout.css

@@ -0,0 +1,185 @@
+@font-face {
+  font-family: 'Open Sans';
+  src: url('OpenSans-Regular.ttf')  format('truetype') /* Safari, Android, iOS */
+}
+
+body,html {
+	position:relative;
+	width:100%;
+	height:100%;
+	margin:0px;
+	padding:0px;
+	
+}
+* {
+	box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+}
+
+.layout-container {
+	box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+	
+	
+	
+	padding:5px;
+	position:absolute;
+	top:0px;
+	bottom:0px;
+	left:0px;
+	right:0px;
+	
+	
+	
+	display:flex;
+	background: #333;
+	
+}
+
+
+section {
+	
+	display:flex;
+	flex:1 0 auto;
+	background:transparent;
+	
+}
+
+section.vertical {
+	flex-direction: row;
+}
+section.horizontal {
+	flex-direction: column;
+}
+
+
+section.horizontal > .splitter {
+	cursor:s-resize;
+}
+section.vertical > .splitter {
+	cursor:e-resize;
+}
+
+.splitter {
+	background: transparent;
+	flex-basis:6px;
+	flex-grow: 0;
+	min-width:6px;
+	min-height:6px;
+
+}
+
+
+/* AKA PAne */
+
+.hpane {
+	/*box-shadow: 0px 0px 10px #000;*/
+	position:relative;
+	overflow:hidden;
+	flex: 1 1 auto;
+	flex-direction: column;
+	/* Theme part */
+	
+	background: #EEE;
+	color: #333;
+	
+	transition: border 1s ease;
+	opacity:1;
+}
+
+.hpane.discover {
+	box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+	
+	gborder: solid 40px #999;
+	transition: border 1s ease;
+}
+
+.hpane.detached {
+	position:Fixed;
+	/*width:20%;*/
+	/*height:50%;*/
+	z-index:99;
+	 
+	box-shadow: 0px 0px 10px #000;
+	
+	
+}
+
+.hpane.detached.moving {
+	opacity: 0.5;
+	pointer-events: none;
+	
+}
+
+
+
+.hpane.preview {
+	background: #555;
+	border: dashed 1px #FFF;
+}
+
+.hpane >header {
+	
+	padding:10px;
+	background: #555;
+	color:#FFF;
+	font-family: 'Open Sans';
+	text-transform:uppercase;
+	font-weight: 700;
+	
+	-webkit-touch-callout: none; /* iOS Safari */
+	-webkit-user-select: none;   /* Chrome/Safari/Opera */
+	-khtml-user-select: none;    /* Konqueror */
+	-moz-user-select: none;      /* Firefox */
+	-ms-user-select: none;       /* IE/Edge */
+	user-select: none;
+	
+}
+.hpane .hcontent {
+	overflow:auto;
+	/*position:absolute;*/
+	top:40px;
+	bottom:0px;
+	left:0px;
+	right:0px;
+	
+	background: #EEE;
+	color:#333;
+	font-family: 'Open Sans';
+	-webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
+	-moz-box-sizing: border-box;    /* Firefox, other Gecko */
+	box-sizing: border-box;         /* Opera/IE 8+ */
+}
+
+
+.attach-preview {
+	position:fixed;
+	background: rgba(0,0,0,0.2);
+	border: dashed 1px #777;
+	z-index:90;
+	pointer-events:none;
+	transition: all 0.2s;
+}
+
+section.horizontal {
+	cursor:s-resize;
+}
+section.vertical {
+	cursor:e-resize;
+}
+section.horizontal > *:first-child {
+	rborder: solid 1px #F00;
+	margin-bottom:7px;
+}
+section.vertical > *:first-child {
+	rborder: solid 1px #F00;
+	margin-right:7px;
+}
+	section > * {
+		cursor:initial;
+	}
+

+ 278 - 0
sample/canvas/web/vendor/hLayout/hLayout.js

@@ -0,0 +1,278 @@
+
+var md;
+
+function startHLayoutEvents() {
+	document.addEventListener("mousemove",function(e) {
+		if(md == undefined) md = {x:e.clientX,y:e.clientY};
+		md.deltaX = e.clientX - md.x;
+		md.deltaY = e.clientY - md.y;
+		md.x = e.clientX;
+		md.y = e.clientY;
+
+	})
+	// Global mouse event;
+	/*$(document).on('mousemove',function(e) {
+		if(md == undefined) md = {x:e.clientX,y:e.clientY};
+		md.deltaX = e.clientX - md.x;
+		md.deltaY = e.clientY - md.y;
+		md.x = e.clientX;
+		md.y = e.clientY;
+	});*/
+	
+	var $grab = undefined;
+	document.addEventListener("mousedown",function(e) {
+		console.log("Target",e.target);
+	})
+	/*$(document).on('mousedown','.hpane > header',function(e){
+		if(e.which != 1) return;
+		if(!$(e.target).is(".hpane > header")) return;
+		
+		e.preventDefault();
+		e.stopPropagation();
+		
+		$grab = $(this).parent();
+		$parent = $grab.parent();
+		var next = $grab.next();
+		var origW = $grab.width();
+		var origH = $grab.height();
+		
+		// Check if we are root?
+		
+		var dim = {w: $grab.width(), h: $grab.height()};
+		var pos = $grab.position();
+	
+		$($grab).detach().appendTo('body').addClass("detached").addClass("moving").css({width:dim.w,height:dim.h});
+		
+		$('.hpane:not(.detached)').addClass("discover");
+		
+		// All childs to pointer event none
+		if($parent.is('section')) {
+			// We aren't
+
+			$child = $parent.children('.hpane, section');
+			$child.attr('style','');
+			$child.css('flex-basis',$parent.css('flex-basis'));
+			$child.detach();
+			$parent.replaceWith($child);
+		}
+	})*/
+	
+	/*var $sectionResize = null;
+	$(document).on('mousedown','section',function(e) {
+		
+		var $target = $(e.target);
+		if(!$target.is("section"))return;
+		e.preventDefault();
+		$sectionResize = $target;
+		$sectionResize.addClass("resizing");
+	});
+	$(document).on('mousemove',function(e) {
+		if($sectionResize == null) return;
+		var $first = $sectionResize.children(':nth-child(1)');
+		var $second = $sectionResize.children(':nth-child(2)');
+		
+		
+		var common;
+		var common2;
+		if( $sectionResize.is(".horizontal")) {common = "height",common2 = "deltaY"};
+		if( $sectionResize.is(".vertical")) {common = "width", common2 = "deltaX"};
+
+		var parentValue = $sectionResize[common]();
+		var hFirst = $first[common]();
+		var hSec = $second[common]();
+	
+		var newVal1 = (hFirst + md[common2]) / parentValue * 100;
+		var newVal2 = (hSec -md[common2]) / parentValue * 100;
+			
+		$first.css('flex-basis',newVal1 + "%");
+		$second.css('flex-basis',newVal2 + "%");
+			
+	});
+	$(document).on('mouseup',function(e) {
+		if($sectionResize) {
+			$sectionResize.removeClass("resizing");
+		}
+
+		$sectionResize = null;
+	});
+	
+	
+	$(document).on('mousemove',function(e) {
+		if($grab == undefined) return;
+		var pos = $grab.position();
+		
+		var nx = pos.left + md.deltaX;
+		var ny = pos.top + md.deltaY;
+		var offX = $grab.width() / 2;
+		var offY = 0;
+		
+		$grab.css({left: e.clientX - offX, top: e.clientY -offY});
+		
+		// Check target
+		var $target = $(e.target);
+		
+		if(!$target.is(".hpane")) {
+			$target = $target.parents(".hpane");
+		}
+		
+		
+		if($target.is(".hpane:not(.preview)")){
+			var placement = checkAttach($target,e);
+			if(placement != 0 ) {
+				previewPanel($grab,$target,placement);
+			} else {
+				removePreviews();
+			}
+		
+		} else {
+			removePreviews();
+			
+		}
+		
+	});
+	
+	$(document).on('mouseup',function(e) {
+		if($grab == undefined) return;
+		
+		removePreviews();
+
+		$grab.removeClass("moving");
+		$('.hpane').removeClass("discover");
+
+		$target = $(e.target);
+		if(!$target.is(".hpane")) $target = $target.parents('.hpane');
+		
+		if($target.is(".hpane")){
+			var placement = checkAttach($target,e);
+			if(placement != 0 ) {
+				$grab.removeClass("detached");
+				$grab.attr('style','');
+				attachPanel($grab,$target,placement);
+			}
+		}
+		
+		$grab = undefined;
+		return;
+	});*/
+	
+}
+
+
+	
+function recalcFlex() {
+	console.log("Recalculating flex");
+	var elems;
+	elems = document.querySelectorAll("section.horizontal > section, section.horizontal > .hpane");
+	for( var i = 0 ; i < elems.length; i++ ) {
+		var elem = elems[i];
+		var parentH = elem.parentElem.innerHeight;
+		var curH = elem.innerHeight;
+		var flex = 100 * curH / parentH;
+		elem.style["flex-basis"] = flex +"%"
+	}
+	elems = document.querySelectorAll("section.vertical > section, section.vertical > .hpane");
+	for( var i = 0 ; i < elems.length; i++ ) {
+		var elem = elems[i];
+		var parentW = elem.parentElem.innerWidth;
+		var curW = elem.innerWidth;
+		var flex = 100 * curH / parentH;
+		elem.style["flex-basis"] = flex +"%"
+	}
+}	
+recalcFlex();
+	
+function checkAttach($target,e) {
+		
+	var tW = $target.width()/3;
+	var tH = $target.height()/3;
+		
+	var targetPos = $target.offset();
+	var dim = { w: $target.width(), h: $target.height()};
+	var rPos = { x: e.pageX - targetPos.left,  y: e.pageY - targetPos.top};
+		//Calc dists and check the closest one
+		
+		
+	if(rPos.x > tW && rPos.x < dim.w - tW && rPos.y < tH) {  					// 1 Top
+		return 1;
+	} else if(rPos.x > tW && rPos.x < dim.w - tW && rPos.y > dim.h - tH){ 		// 3 Bottom
+		return 3;
+	} else if(rPos.x < tW) {													// 2 Left
+		return 2;
+	} else if( rPos.x > dim.w - tW){											// 4 Right
+		return 4;
+	}
+		
+	return 0;
+}
+function removePreviews() {
+	$('.attach-preview').hide();
+}
+function previewPanel($grab,$target,attach) {
+		
+	$previewDiv = $(".attach-preview");
+	if($previewDiv.length == 0 ) {
+		$previewDiv = $('<div class="attach-preview" style="display:none;position:fixed"></div>').appendTo('body');
+	}
+		
+	$previewDiv.show();
+	var dim = {w:$target.width(),h:$target.height()};
+	var pos = $target.offset();
+		
+	if((attach % 2) == 1) {
+		$previewDiv.css('height',dim.h/3);
+		$previewDiv.css("width",dim.w);
+		$previewDiv.css("left",pos.left);
+		if(attach == 1) {
+			$previewDiv.css('top',pos.top);
+		} else {
+			$previewDiv.css('top',pos.top  + dim.h - dim.h/3);
+		}
+	} else if((attach % 2) == 0) {
+		$previewDiv.css("height",dim.h);
+		$previewDiv.css('width',dim.w/3);
+		$previewDiv.css("top",pos.top);
+		if(attach == 2) {
+			$previewDiv.css('left',pos.left);
+		} else {
+			$previewDiv.css('left',pos.left + dim.w - dim.w/3);
+		}
+
+	}
+			
+}
+function attachPanel(what,target, attach) {
+	if(attach){
+		var type
+		if((attach % 2) == 1)type = "horizontal";
+		if((attach % 2) == 0)type = "vertical";
+		$target = $(target);
+		$parent = $(target).parent();
+			
+		$sib = $(target);
+		if($sib.next().is("section, .hpane,.splitter")) {
+			$section = $("<section class='" + type +"' style='flex-basis:" + $target.css('flex-basis') + "'></section>").prependTo($parent);
+		} else {
+			$section = $("<section class='" + type + "' style='flex-basis:" + $target.css('flex-basis') +" '></section>").appendTo($parent);
+		}
+		$sib.detach();
+		what.css('flex-basis','33%');
+		$sib.css('flex-basis','66%');
+		if(attach < 3) {
+			$section.append(what);
+			//$section.append("<div class='splitter'></div>");
+			$section.append($sib);
+		} else {
+			$section.append($sib);
+			//$section.append("<div class='splitter'></div>");
+			$section.append(what);
+		}
+			
+	}
+	
+}
+	
+
+window.onload = function() {
+	startHLayoutEvents();
+	
+};

+ 58 - 0
wsrpc.go

@@ -0,0 +1,58 @@
+package wsrpc
+
+//go:generate folder2go client wsrpcAssets
+
+import (
+	"log"
+	"net/http"
+
+	"dev.hexasoftware.com/stdio/wsrpc/wsrpcAssets" // embed assets
+
+	"golang.org/x/net/websocket"
+)
+
+// Return a specific handler
+type ServerContext interface {
+	Handle(string, http.Handler)
+	HandleFunc(string, func(http.ResponseWriter, *http.Request))
+}
+type HandleFunc func(*ClientCtx)
+
+// Write wsrpc.js
+func wsrpcjsFunc(w http.ResponseWriter, r *http.Request) {
+	w.Header().Set("Content-type", "application/javascript")
+	w.WriteHeader(200)
+	w.Write(wsrpcAssets.Data["/wsrpc.js"])
+}
+
+// Create a client websocket handler on connection
+func createCliHandler(handler HandleFunc) websocket.Handler {
+	return func(ws *websocket.Conn) {
+		ch := NewHandler("main", ws)
+		go handler(ch)
+
+		for {
+			var data = map[string]interface{}{}
+			err := websocket.JSON.Receive(ws, &data)
+			if err != nil {
+				log.Println("Error: ", err)
+				return
+			}
+			ch.Process(data)
+		}
+	}
+}
+
+// RegisterTo register both js and websocket in the servermux
+func RegisterTo(server ServerContext, handler HandleFunc) {
+	server.Handle("/wsrpc", websocket.Handler(createCliHandler(handler)))
+	server.HandleFunc("/wsrpc/client.js", wsrpcjsFunc)
+}
+
+// Handler we can attatch this to http.Server
+func Handler(handler HandleFunc) http.Handler {
+	mux := http.NewServeMux()
+	RegisterTo(mux, handler)
+	// When we receive a connection
+	return mux
+}

Plik diff jest za duży
+ 26 - 0
wsrpcAssets/wsrpcAssets.go