From 5f0509e6b020c351f2809bbf0714167410d459a6 Mon Sep 17 00:00:00 2001 From: Sylvio Sell Date: Thu, 16 Apr 2020 06:04:42 +0200 Subject: [PATCH] fixing for full-dce and cleaning rpc-sample --- Source/peote/net/PeoteClient.hx | 10 +- Source/peote/net/PeoteClient.hx.bak | 258 +++++++++++++++++ Source/peote/net/Remote.hx | 7 + Source/peote/net/Remote.hx.bak | 427 ++++++++++++++++++++++++++++ haxelib.json | 4 +- samples/rpc/project.xml | 4 +- samples/rpc/src/MainOpenfl.hx | 32 ++- samples/rpc/src/ui/Button.hx | 56 ++++ samples/rpc/src/ui/InputText.hx | 68 +++++ 9 files changed, 851 insertions(+), 15 deletions(-) create mode 100644 Source/peote/net/PeoteClient.hx.bak create mode 100644 Source/peote/net/Remote.hx.bak create mode 100644 samples/rpc/src/ui/Button.hx create mode 100644 samples/rpc/src/ui/InputText.hx diff --git a/Source/peote/net/PeoteClient.hx b/Source/peote/net/PeoteClient.hx index c8d82d6..7234eaa 100644 --- a/Source/peote/net/PeoteClient.hx +++ b/Source/peote/net/PeoteClient.hx @@ -102,7 +102,8 @@ class PeoteClient public var last_delay:Int = 0; public var last_time:Float = 0; public function send(bytes:Bytes):Void - { + { + //trace("send:", bytes.toHex()); if (localPeoteServer == null) this.peoteJointSocket.sendDataToJointIn(this.jointNr, bytes ); else { var delay = Std.int(Math.max(0, last_delay - (Timer.stamp() - last_time) * 1000)) @@ -120,14 +121,15 @@ class PeoteClient if (bytes.length <= 0) throw("Error(sendChunk): can't send zero length chunk"); else if (bytes.length > maxChunkSize) throw("Error(sendChunk): max chunksize is 65536 Bytes"); // TODO: dynamic chunksize else { + //trace("sendChunkSize:", writeChunkSize(bytes.length).toHex()); send( writeChunkSize(bytes.length) ); + //trace("sendChunk:", bytes.toHex()); send( bytes ); } } function writeChunkSize(size:Int):Bytes { - //if (size <= 0) throw("Error(sendChunk): can't send zero length chunk"); var bytes = Bytes.alloc(maxBytesPerChunkSize); var bytecount:Int = 0; var b:Int; @@ -144,7 +146,7 @@ class PeoteClient size = size >> 8; } if (size > 0) b += 128; - bytes.set(bytecount-1, b); + bytes.set(bytecount - 1, b); } while (size > 0 && bytecount < maxBytesPerChunkSize); @@ -176,7 +178,7 @@ class PeoteClient public function _onData(jointNr:Int, bytes:Bytes):Void { - //trace("onData: " + bytes.length); + //trace("onData: ", bytes.toHex()); if (isChunks) { if (input_pos == input_end) { input_pos = input_end = 0; } diff --git a/Source/peote/net/PeoteClient.hx.bak b/Source/peote/net/PeoteClient.hx.bak new file mode 100644 index 0000000..c8d82d6 --- /dev/null +++ b/Source/peote/net/PeoteClient.hx.bak @@ -0,0 +1,258 @@ +package peote.net; + +import haxe.ds.Vector; +import haxe.io.Bytes; +import haxe.Timer; +import peote.io.PeoteBytesInput; + +/** + * by Sylvio Sell - rostock 2015 + */ + +class PeoteClient +{ + public var events:PeoteClientEvents; + + public var jointNr(default, null):Int; + public var jointId(default, null):String; + public var server(default, null):String = ""; + public var port(default, null):Int; + + public var isRemote(default, null):Bool=false; + public var isChunks(default, null):Bool=false; + + public var localPeoteServer:PeoteServer = null; + public var localUserNr:Int; + + // variable chunksize: + // max values for 1 byte -> max 256 + // for 2 byte -> max 32768 (32 KB), + // for 3 byte -> max 4194304 (4 MB) + // for 4 byte -> max 536870912 (512 MB) + var maxBytesPerChunkSize:Int = 2; + var maxChunkSize:Int = 32768; + + var peoteJointSocket:PeoteJointSocket; + + var input:Bytes; + var input_pos:Int = 0; + var input_end:Int = 0; + var chunk_size:Int = 0; + var chunkReady:Bool = false; + var chunkBytecount:Int = 0; + var byte:Int; + + public function new(events:PeoteClientEvents) + { + this.events = events; + + if (events.onDataChunk == null && events.onData == null) { + isRemote = true; + isChunks = true; + } else if (events.onDataChunk != null) { + if (events.onData != null) throw("Error: Use either 'onDataChunk' or 'onData' callback but not both."); + isChunks = true; + } + + if (isChunks) { + if (events.maxChunkSize != null) { + maxChunkSize = events.maxChunkSize; + maxBytesPerChunkSize = 1; + if (maxChunkSize > 256) maxBytesPerChunkSize++; + if (maxChunkSize > 32768) maxBytesPerChunkSize++; + if (maxChunkSize > 4194304) maxBytesPerChunkSize++; + //trace(maxChunkSize, maxBytesPerChunkSize); + } + input = Bytes.alloc((maxChunkSize+maxBytesPerChunkSize)*2); + } + + // TODO: only for remote-usage + remotes = new VectorVoid>>(256); + } + + // ----------------------------------------------------------------------------------- + // ENTER JOINT ----------------------------------------------------------------------- + + public function enter(server:String, port:Int, jointId:String):Void + { + if (this.server == "") + { + this.server = server; + this.port = port; + this.jointId = jointId; + PeoteNet.enterJoint(this, server, port, jointId); + } + else + { + throw("Error: This instance of PeoteClient already connected to a Joint (leave before enter again)"); + } + } + + // ----------------------------------------------------------------------------------- + // LEAVE JOINT ----------------------------------------------------------------------- + + public function leave():Void + { + PeoteNet.leaveJoint(this, this.server, this.port, this.jointNr); + this.server = ""; + } + + // ----------------------------------------------------------------------------------- + // SEND DATA ------------------------------------------------------------------------- + public var last_delay:Int = 0; + public var last_time:Float = 0; + public function send(bytes:Bytes):Void + { + if (localPeoteServer == null) this.peoteJointSocket.sendDataToJointIn(this.jointNr, bytes ); + else { + var delay = Std.int(Math.max(0, last_delay - (Timer.stamp() - last_time) * 1000)) + + Std.int(localPeoteServer.netLag + 1000 * bytes.length / localPeoteServer.netSpeed); + last_delay = delay; // TODO: for local testing put a LIMIT here for OVERFLOW!!!!! + last_time = Timer.stamp(); + Timer.delay(function() { + localPeoteServer._onData(localPeoteServer.jointNr, localUserNr , bytes); + }, delay); + } + } + + public function sendChunk(bytes:Bytes):Void + { + if (bytes.length <= 0) throw("Error(sendChunk): can't send zero length chunk"); + else if (bytes.length > maxChunkSize) throw("Error(sendChunk): max chunksize is 65536 Bytes"); // TODO: dynamic chunksize + else { + send( writeChunkSize(bytes.length) ); + send( bytes ); + } + } + + function writeChunkSize(size:Int):Bytes + { + //if (size <= 0) throw("Error(sendChunk): can't send zero length chunk"); + var bytes = Bytes.alloc(maxBytesPerChunkSize); + var bytecount:Int = 0; + var b:Int; + size--; + do + { + bytecount++; + if (bytecount < maxBytesPerChunkSize) { + b = size & 127; // get 7 bits + size = size >> 7; + } + else { + b = size & 255; // last get 8 bits + size = size >> 8; + } + if (size > 0) b += 128; + bytes.set(bytecount-1, b); + } + while (size > 0 && bytecount < maxBytesPerChunkSize); + + //if (size > 0) throw('chunksize to great for maxBytesPerChunkSize=$maxBytesPerChunkSize'); + return(bytes.sub(0, bytecount)); + } + + // ----------------------------------------------------------------------------------- + // CALLBACKS ------------------------------------------------------------------------- + + public function _onEnterJoint(peoteJointSocket:PeoteJointSocket, jointNr:Int):Void + { + this.peoteJointSocket = peoteJointSocket; + this.jointNr = jointNr; + events.onEnter(this); + } + + public function _onEnterJointError(errorNr:Int):Void // bei FEHLER + { + this.server = ""; + events.onError(this, errorNr ); + } + + public function _onDisconnect(jointNr:Int, reason:Int):Void + { + events.onDisconnect(this, reason); + } + + + public function _onData(jointNr:Int, bytes:Bytes):Void + { + //trace("onData: " + bytes.length); + if (isChunks) { + + if (input_pos == input_end) { input_pos = input_end = 0; } + + //var debugOut = "";for (i in 0...bytes.length) debugOut += bytes.get(i) + " ";trace("data:" + debugOut); + if (input_end + bytes.length > input.length) trace("ERROR Client: out of BOUNDS"); + input.blit(input_end, bytes, 0, bytes.length ); + + input_end += bytes.length; + + while (!chunkReady && input_end-input_pos >=1) { + + byte = input.get(input_pos++); + if (chunkBytecount == maxBytesPerChunkSize-1 || byte < 128) + { + if (byte == 0 && chunkBytecount != 0) trace("MALECIOUS ?"); + chunk_size = chunk_size | (byte << chunkBytecount*7); + chunkReady = true; chunkBytecount = 0; chunk_size++; + } + else // uppest bit is set and more bytes avail + { + chunk_size = chunk_size | ( (byte-128) << chunkBytecount*7); + chunkBytecount++; + } + } + + if ( chunkReady && input_end-input_pos >= chunk_size ) + { //trace("chunk_size:"+chunk_size); + var b:Bytes = Bytes.alloc(chunk_size); + //trace(" ---> onDataChunk: " + b.length + "Bytes ( start:"+input_pos+" end:"+input_end+ ")",b.get(0), b.get(1), b.get(2)); + b.blit(0, input, input_pos, chunk_size); + input_pos += chunk_size; + chunk_size = 0; chunkReady = false; + if (isRemote) remote(b); + else events.onDataChunk(this, b ); + } + } + else events.onData(this, bytes); + + } + + // ----------------------------------------------------------------------------------- + // RPC ------------------------------------------------------------------------- + var remotes:VectorVoid>>; // stores all remote functions for incomming data + + public function setRemote(f:Dynamic, remoteId:Int = 0):Void + { + if (!isRemote) throw("Error: Do not use 'onDataChunk' or 'onData' while using Remote-Objects in PeoteServer!"); + remotes[remoteId] = f.getRemotes(); + + var bytes = Bytes.alloc(1); // TODO: max-amount-of-remote-objects + bytes.set(0, remoteId); + sendChunk(bytes); + } + + public function remote(bytes:Bytes) + { + var input = new PeoteBytesInput(bytes); + var remoteId = input.readByte(); //trace("remoteId:"+remoteId); + + if (input.bytesLeft() == 0) events.onRemote(this, remoteId); + else + { + // check that remoteID exists + var remoteObject = remotes.get(remoteId); //trace("remoteObject:"+remoteObject); + if (remoteObject != null) + { + var procedureNr = input.readByte(); //trace("procedureNr:" + procedureNr); + // check max remotes + if (procedureNr < remoteObject.length) + try remoteObject[procedureNr](input) catch (m:Dynamic) events.onError(this, Reason.MALICIOUS); + else events.onError(this, Reason.MALICIOUS); + } else events.onError(this, Reason.MALICIOUS); // TODO: disconnect joint if malicous input from owner + } + + } + + +} \ No newline at end of file diff --git a/Source/peote/net/Remote.hx b/Source/peote/net/Remote.hx index f6d080a..fd43966 100644 --- a/Source/peote/net/Remote.hx +++ b/Source/peote/net/Remote.hx @@ -115,6 +115,7 @@ class RemoteImpl if (hasNoNew) fields.push({ name: "new", access: [APublic], + //meta: [{name:":keep", params:[], pos:Context.currentPos()}], pos: Context.currentPos(), kind: FFun({ args: [], @@ -145,6 +146,7 @@ class RemoteImpl } fields.push({ name: "getRemotes", + meta: [{name:":keep", params:[], pos:Context.currentPos()}], access: [APublic], pos: Context.currentPos(), kind: FieldType.FFun(getRemotes), @@ -168,11 +170,14 @@ class RemoteImpl } fields.push({ name: "getRemoteServer", + //meta: [{name:":keep", params:[], pos:Context.currentPos()}], access: [APublic,AStatic], pos: Context.currentPos(), kind: FieldType.FFun(getRemoteServer), }); + // ---------------------- + classnameRemote = classname+"RemoteClient"; //Context.defineType(generateRemoteCaller(classnameRemote, false, remoteNames, remoteParams)); Context.defineModule(classpackage.concat([classnameRemote]).join('.'),[generateRemoteCaller(classnameRemote, false, remoteNames, remoteParams)],Context.getLocalImports()); @@ -186,6 +191,7 @@ class RemoteImpl } fields.push({ name: "getRemoteClient", + //meta: [{name:":keep", params:[], pos:Context.currentPos()}], access: [APublic,AStatic], pos: Context.currentPos(), kind: FieldType.FFun(getRemoteClient), @@ -250,6 +256,7 @@ class RemoteImpl kind: FieldType.FFun(f), }); } + //c.meta= [{name:":keep", params:[], pos:Context.currentPos()}]; return(c); } diff --git a/Source/peote/net/Remote.hx.bak b/Source/peote/net/Remote.hx.bak new file mode 100644 index 0000000..f6d080a --- /dev/null +++ b/Source/peote/net/Remote.hx.bak @@ -0,0 +1,427 @@ +package peote.net; + +/** + * by Sylvio Sell - Rostock 2018 + */ + +import haxe.macro.Expr; +import haxe.macro.Context; +import haxe.macro.ExprTools; +//import peote.net.PeoteClient; // <----- HERE error: Class has no field lime_system_get_device_model + + +@:remove @:autoBuild(peote.net.Remote.RemoteImpl.build()) +extern interface Remote {} + +class RemoteImpl +{ + +#if macro + public static function getPackFromImports(name:String, imports:Array):Array + { + var pack = new Array(); + + for (imp in imports) { + if (imp.path[imp.path.length - 1].name == name) + for (i in 0 ... (imp.path.length - 1) ) + pack.push(imp.path[i].name); + } + return pack; + } + + public static function hasMeta(f:Field, s:String):Bool {for (m in f.meta) { if (m.name == s || m.name == ':$s') return true; } return false; } + + public static function build() + { + try { Context.resolvePath("org/msgpack/MsgPack.hx"); isMsgPack = true; } catch (e:Dynamic) {} + + var remoteNames = new Array(); + var remoteParams = new Array>(); + var hasNoNew:Bool = true; + + var classname = Context.getLocalClass().get().name; + var classpackage = Context.getLocalClass().get().pack; + + var fields = Context.getBuildFields(); + for (f in fields) + { + if (f.name == "new") { + hasNoNew = false; + } + else if ( hasMeta(f, "remote") ) + { + var remParams = new Array(); + switch (f.kind) + { + case FVar(TFunction(params,ret),_): + switch (ret) + { + case TPath(param): + if (param.name != "Void") { + throw Context.error('Remote function has no Void return type', f.pos); + } + default: + } + for (p in params) + { + switch (p) + { + case TPath(param): + switch (param.name) { + case "Void": + default: + if (param.pack.length == 0) param.pack = getPackFromImports(param.name, Context.getLocalImports()); + remParams.push(param); // add param type + } + default: + } + } + case FFun(func): + if (func.ret != null) + { + switch (func.ret) { + case TPath(param): + if (param.name != "Void") { + throw Context.error('Remote function has no Void return type', f.pos); + } + default: + } + } + for (p in func.args) + { + switch (p.type) + { + case TPath(param): + switch (param.name) { + case "Void": + default: + if (param.pack.length == 0) param.pack = getPackFromImports(param.name, Context.getLocalImports()); + remParams.push(param); // add param type + } + default: + } + } + default: + } + + // error if more then 256 remote-functions + if (remoteNames.length == 256) throw Context.error('To much @remote functions (max is 256)', f.pos); + remoteNames.push(f.name); + remoteParams.push(remParams); + } + } + + // add constructor ("new") if it is not there + if (hasNoNew) fields.push({ + name: "new", + access: [APublic], + pos: Context.currentPos(), + kind: FFun({ + args: [], + expr: macro {}, + params: [], + ret: null + }) + }); + + var exprs = []; + exprs.push(Context.parse('var v = new haxe.ds.VectorVoid>(${remoteNames.length})', Context.currentPos())); + for ( i in 0...remoteNames.length) + { + var fbody = ""; + for ( j in 0...remoteParams[i].length) fbody += generateInput(remoteParams[i][j],'p$j'); + + fbody += "if (input.bytesLeft() > 0) throw('flooded');"; + fbody += remoteNames[i] + "(" + [for (j in 0...remoteParams[i].length) 'p$j' ].join(",") + ");"; // remote function call + exprs.push(Context.parse('v.set($i, function(input:peote.io.PeoteBytesInput):Void { $fbody })', Context.currentPos())); + } + exprs.push(Context.parse("return v", Context.currentPos())); //trace( ExprTools.toString( macro $b{exprs} ) ); + + // add getRemotes function + var getRemotes:Function = { + args:[], + expr: macro $b{exprs}, + ret: macro:haxe.ds.VectorVoid>, // ret = return type + } + fields.push({ + name: "getRemotes", + access: [APublic], + pos: Context.currentPos(), + kind: FieldType.FFun(getRemotes), + }); + + + // ------------------------------------------------------------------------------------------------- + // ------------------------------------- generates new classs for remote-calling --------------------- + // ------------------------------------------------------------------------------------------------- + var classnameRemote = classname+"RemoteServer"; + //Context.defineType(generateRemoteCaller(classnameRemote, true, remoteNames, remoteParams)); + Context.defineModule(classpackage.concat([classnameRemote]).join('.'),[generateRemoteCaller(classnameRemote, true, remoteNames, remoteParams)],Context.getLocalImports()); + + var getRemoteServer:Function = { // add function to return an instanze of that class + args:[ {name:"server", type:macro:peote.net.PeoteServer, opt:false, value:null}, + {name:"user", type:macro:Int, opt:false, value:null}, + {name:"remoteId", type:macro:Int, opt:false, value:null} + ], + expr: Context.parse( 'return new $classnameRemote(server, user, remoteId)', Context.currentPos()), + ret: TPath({ name:classnameRemote, pack:[], params:[] }) // ret = return type + } + fields.push({ + name: "getRemoteServer", + access: [APublic,AStatic], + pos: Context.currentPos(), + kind: FieldType.FFun(getRemoteServer), + }); + + classnameRemote = classname+"RemoteClient"; + //Context.defineType(generateRemoteCaller(classnameRemote, false, remoteNames, remoteParams)); + Context.defineModule(classpackage.concat([classnameRemote]).join('.'),[generateRemoteCaller(classnameRemote, false, remoteNames, remoteParams)],Context.getLocalImports()); + + var getRemoteClient:Function = { // add function to return an instanze of that class + args:[ {name:"client", type:macro:peote.net.PeoteClient, opt:false, value:null}, + {name:"remoteId", type:macro:Int, opt:false, value:null} + ], + expr: Context.parse( 'return new $classnameRemote(client, remoteId)', Context.currentPos()) , + ret: TPath({ name:classnameRemote, pack:[], params:[] }) // ret = return type + } + fields.push({ + name: "getRemoteClient", + access: [APublic,AStatic], + pos: Context.currentPos(), + kind: FieldType.FFun(getRemoteClient), + }); + + return fields; + } + + // ------------------------------------------------------------------------------------------------- + public static function generateRemoteCaller(classname:String, isServer:Bool, remoteNames:Array, remoteParams:Array>):TypeDefinition + { + var c:TypeDefinition; + if (isServer) { + c = macro class $classname { + var server:peote.net.PeoteServer; + var user:Int; + var remoteId:Int; + public function new(server:peote.net.PeoteServer, user:Int, remoteId:Int) { this.server = server; this.user = user; this.remoteId = remoteId; } + } + } else { + c = macro class $classname { + var client:peote.net.PeoteClient; + var remoteId:Int; + public function new(client:peote.net.PeoteClient, remoteId:Int) { this.client = client; this.remoteId = remoteId; } + } + } + + for ( i in 0...remoteNames.length) + { + var fbody = "{var output = new peote.io.PeoteBytesOutput();"; + fbody += 'output.writeByte(remoteId);'; + fbody += 'output.writeByte($i);'; + + for ( j in 0...remoteParams[i].length) fbody += generateOutput(remoteParams[i][j],'p$j'); + + if (isServer) fbody += "server.sendChunk(user, output.getBytes());}"; + else fbody += "client.sendChunk(output.getBytes());}"; + + var f:Function = { + args:[for (j in 0...remoteParams[i].length) { + name:'p$j', + type:TPath({ + name: remoteParams[i][j].name, + /*pack: switch(remoteParams[i][j].name) { + case "Byte"|"UInt16"|"Int16"|"Int32"|"Double": ["peote", "io"]; + case "Bytes": ["haxe", "io"]; + case "Vector"|"IntMap"|"StringMap": ["haxe", "ds"]; + default:remoteParams[i][j].pack; + },*/ + pack: remoteParams[i][j].pack, + params:remoteParams[i][j].params + }), opt:false + }], + expr: Context.parse( fbody, Context.currentPos()), + ret: null, + } + //trace( ExprTools.toString( Context.parse( fbody, Context.currentPos()) ) ); + c.fields.push({ + name: remoteNames[i], + access: [APublic], + pos: Context.currentPos(), + kind: FieldType.FFun(f), + }); + } + return(c); + } + + public static function getFullType(tp:TypePath):String + { + if (tp.params.length == 0) return ""; + + return '<' + + [for (p in tp.params) switch(p) { + case TPType(TPath(t)): '${t.name}' + getFullType(t); + default: '';// throw Context.error('no valid subtype', f.pos); + }].join(',') + '>'; + } + + public static function generateInput(tp:TypePath, varname:String, depth:Int = 0):String + { + var subtypes:Array = []; + for (p in tp.params) + switch(p) { + case TPType(TPath(t)): subtypes.push(t); + default: + } + var moduleName = ((tp.pack.length != 0) ? tp.pack.join(".") + "." : "") + tp.name; + + return ((depth == 0) ? 'var ' : '') + varname + "=" + switch (tp.name) { + case "Array": 'new Array'+getFullType(tp)+'();' + + 'for (i$depth in 0...input.readChunkSize()) {'+ generateInput(subtypes[0], varname+'[i$depth]', depth+1) + '}'; + case "List": 'new List'+getFullType(tp)+'();' + + 'for (i$depth in 0...input.readChunkSize()) {' + + 'var ' + generateInput(subtypes[0], 'item$depth', depth + 1) + + '$varname.add(item$depth);}'; + case "Vector": 'new haxe.ds.Vector'+getFullType(tp)+'(input.readChunkSize());' + + 'for (i$depth in 0...$varname.length) {'+ generateInput(subtypes[0], varname+'[i$depth]', depth+1) + '}'; + case "Map": 'new Map'+getFullType(tp)+'();' + + 'for (i$depth in 0...input.readChunkSize()) {' + + 'var ' + generateInput(subtypes[0], 'k$depth', depth+1) + + 'var ' + generateInput(subtypes[1], 'v$depth', depth+1) + + '$varname.set(k$depth,v$depth);}'; + case "IntMap": 'new haxe.ds.IntMap'+getFullType(tp)+'();' + + 'for (i$depth in 0...input.readChunkSize()) {' + + 'var ' + generateInput({name:"Int", params:[], pack:[]}, 'k$depth', depth+1) + + 'var ' + generateInput(subtypes[0], 'v$depth', depth+1) + + '$varname.set(k$depth,v$depth);}'; + case "StringMap": 'new haxe.ds.StringMap'+getFullType(tp)+'();' + + 'for (i$depth in 0...input.readChunkSize()) {' + + 'var ' + generateInput({name:"String", params:[], pack:[]}, 'k$depth', depth+1) + + 'var ' + generateInput(subtypes[0], 'v$depth', depth+1) + + '$varname.set(k$depth,v$depth);}'; + + case "Bool": 'input.readBool();'; + case "Byte": 'input.readByte();'; + case "UInt16": 'input.readUInt16();'; + case "Int16": 'input.readInt16();'; + case "Int32": 'input.readInt32();'; + case "Int": 'input.readInt32();'; + case "Float": 'input.readFloat();'; + case "Double": 'input.readDouble();'; + case "String": 'input.readString();'; + case "Bytes": 'input.read();'; + + case "Dynamic": + if (isMsgPack) { + trace('Remote param "$moduleName" is using MsgPack Serialization.'); + 'org.msgpack.MsgPack.decode(input.read());'; + } else { + trace('Remote param "$moduleName" is using haxe.Serializer.'); + 'haxe.Unserializer.run(input.readString());'; + } + + case "Enum": 'haxe.Unserializer.run(input.readString());'; // TODO + + default: + if (isHxbit(tp, false)) '(new hxbit.Serializer()).unserialize(input.read(),$moduleName);'; + else if (isTypedef(tp, false)) 'org.msgpack.MsgPack.decode(input.read());'; + else 'haxe.Unserializer.run(input.readString());'; + } + } + + public static function generateOutput(tp:TypePath, varname:String, depth:Int = 0):String + { + var subtypes:Array = []; + for (p in tp.params) + switch(p) { + case TPType(TPath(t)): subtypes.push(t); + default: + } + + return switch (tp.name) { + case "Array"|"Vector": 'if ($varname==null) output.writeChunkSize(0) else {output.writeChunkSize($varname.length); for (i$depth in 0...$varname.length) {'+ generateOutput(subtypes[0], '$varname[i$depth]', depth+1) +'}}'; + case "List": 'if ($varname==null) output.writeChunkSize(0) else {output.writeChunkSize($varname.length); for (item$depth in $varname) {'+ generateOutput(subtypes[0], 'item$depth', depth+1) +'}}'; + case "Map": 'if ($varname==null) output.writeChunkSize(0) else {output.writeChunkSize(Lambda.count($varname)); for (k$depth in $varname.keys()) {' + + generateOutput(subtypes[0], 'k$depth', depth + 1) + + generateOutput(subtypes[1], '$varname.get(k$depth)', depth + 1) + + '}}'; + case "IntMap": 'if ($varname==null) output.writeChunkSize(0) else {output.writeChunkSize(Lambda.count($varname)); for (k$depth in $varname.keys()) {' + + generateOutput({name:"Int", params:[], pack:[]}, 'k$depth', depth + 1) + + generateOutput(subtypes[0], '$varname.get(k$depth)', depth + 1) + + '}}'; + case "StringMap": 'if ($varname==null) output.writeChunkSize(0) else {output.writeChunkSize(Lambda.count($varname)); for (k$depth in $varname.keys()) {' + + generateOutput({name:"String", params:[], pack:[]}, 'k$depth', depth + 1) + + generateOutput(subtypes[0], '$varname.get(k$depth)', depth + 1) + + '}}'; + + case "Bool": 'output.writeBool($varname);'; + case "Byte": 'output.writeByte($varname);'; + case "UInt16": 'output.writeUInt16($varname);'; + case "Int16": 'output.writeInt16($varname);'; + case "Int32": 'output.writeInt32($varname);'; + case "Int": 'output.writeInt32($varname);'; + case "Float": 'output.writeFloat($varname);'; + case "Double": 'output.writeDouble($varname);'; + case "String": 'output.writeString($varname);'; + case "Bytes": 'output.write($varname);'; + + case "Dynamic": + if (isMsgPack) 'output.write(org.msgpack.MsgPack.encode($varname));'; + else 'output.writeString(haxe.Serializer.run($varname));'; + + + case "Enum": 'output.writeString(haxe.Serializer.run($varname));'; // TODO + + default: + if (isHxbit(tp)) 'output.write((new hxbit.Serializer()).serialize($varname));'; + else if (isTypedef(tp)) 'output.write(org.msgpack.MsgPack.encode($varname));'; + else 'output.writeString(haxe.Serializer.run($varname));'; + } + } + + public static function isHxbit(t:TypePath, silent:Bool = true):Bool + { + #if hxbit + var path = t.name + ".hx"; + if (t.pack.length != 0) path = t.pack.join("/") +"/" + path; + //trace(path); + try { + var p = Context.resolvePath(path); + var s:String = sys.io.File.getContent(p); + var r = new EReg('class\\s+'+t.name+getFullType(t)+'\\s+[^{]*?implements\\s+hxbit.Serializable[\\s{]', ""); + if (r.match(s)) { + if (!silent) trace("Remote param '"+t.pack.join(".") + ((t.pack.length != 0) ? "." : "") + t.name + "' is using hxbit Serialization."); + return true; + } + } + catch(e:Dynamic) {} + #end + return false; + } + + public static var isMsgPack:Bool = false; + public static function isTypedef(t:TypePath, silent:Bool = true):Bool + { + if (isMsgPack) + { + var path = t.name + ".hx"; + if (t.pack.length != 0) path = t.pack.join("/") +"/" + path; + //trace(path); + try { + var p = Context.resolvePath(path); + var s:String = sys.io.File.getContent(p); + var r = new EReg('typedef\\s+'+t.name+getFullType(t)+'\\s*=\\s*{', ""); + if (r.match(s)) { + if (!silent) trace("Remote param '"+t.pack.join(".") + ((t.pack.length != 0) ? "." : "") + t.name + "' is using MsgPack Serialization."); + return true; + } + } + catch(e:Dynamic) {} + } + + if (!silent) trace("Remote param '"+t.pack.join(".") + ((t.pack.length != 0) ? "." : "") + t.name + "' is using haxe.Serializer."); + return false; + } + +#end + +} diff --git a/haxelib.json b/haxelib.json index 2774f5f..d6c5011 100644 --- a/haxelib.json +++ b/haxelib.json @@ -4,9 +4,9 @@ "license": "MIT", "tags": ["peote", "socket", "tcp", "networking", "rpc"], "description": "Library to write TCP-Networking Applications", - "version": "0.6.9", + "version": "0.7.0", "classPath": "Source/", - "releasenote": "hxbit and msgpack-haxe bindings", + "releasenote": "dce full and codecleaning rpc-sample", "contributors": ["maitag"], "dependencies": { "peote-socket": "" diff --git a/samples/rpc/project.xml b/samples/rpc/project.xml index ec2e719..fe42070 100644 --- a/samples/rpc/project.xml +++ b/samples/rpc/project.xml @@ -27,9 +27,9 @@ - + - + diff --git a/samples/rpc/src/MainOpenfl.hx b/samples/rpc/src/MainOpenfl.hx index a5c35ad..f8aef0b 100644 --- a/samples/rpc/src/MainOpenfl.hx +++ b/samples/rpc/src/MainOpenfl.hx @@ -28,10 +28,10 @@ class MainOpenfl extends Sprite var host:String = "localhost"; var port:Int = 7680; + var channelName:String = "testserver"; + var out:OutputText; - var channelName:String = "testserver"; - public function new () { super(); @@ -39,8 +39,10 @@ class MainOpenfl extends Sprite addChild(out); #if ((!server) && (!client)) + // local testing without socket-connections onLoadSocketBridge(); #else + // connecting to the dedicated perl-server PeoteSocketBridge.load( { onload: onLoadSocketBridge, preferWebsockets: true, @@ -51,10 +53,13 @@ class MainOpenfl extends Sprite public function onLoadSocketBridge():Void { + // ---------------------- SERVER ------------------------- + #if (server || (!client)) var peoteServer = new PeoteServer( { - #if (!server) + // bandwith simmulation if there is local testing + #if ((!server) && (!client)) offline:true, netLag:10, // results in 20 ms per chunk netSpeed:1024 * 1024 * 512, //[512KB] per second @@ -70,6 +75,9 @@ class MainOpenfl extends Sprite // server object where methods can be called by remote var serverFunctions = new ServerFunctions(); + serverFunctions.test = function() { + out.log('serverobject -> test()'); + }; serverFunctions.message = function(s:String, b:Bool) { out.log('serverobject -> message("$s", $b)'); }; @@ -123,10 +131,12 @@ class MainOpenfl extends Sprite } }); - trace("trying to connect to peote-server..."); - peoteServer.create("localhost", 7680, "testserver"); + trace("trying to create..."); + peoteServer.create(host, port, channelName); #end + // ---------------------- CLIENT ------------------------- + #if (client || (!server)) var peoteClient = new PeoteClient( { @@ -156,6 +166,9 @@ class MainOpenfl extends Sprite // call ServerFunctions serverFunctions.message("hello from client", true); + haxe.Timer.delay( function() { + serverFunctions.test(); + }, 3000); serverFunctions.numbers(255, 0xFFFF, 0x7FFF, 0x7FFFFFFF, 0x7FFFFFFF, 1.2345678901234, 1.2345678901234 ); var v = new Vector>(3); @@ -193,15 +206,19 @@ class MainOpenfl extends Sprite } }); - trace("trying to connect to peote-server..."); - peoteClient.enter("localhost", 7680, "testserver"); + trace("trying to enter..."); + peoteClient.enter(host, port, channelName); #end } } + // REMOTE-OBJECTS -------------------------------------- + +// functions that run on Server class ServerFunctions implements Remote { + @:remote public var test:Void->Void; @:remote public var message:String->Bool->Void; @:remote public var numbers:Byte->UInt16->Int16->Int32->Int->Float->Double->Void; @:remote public var complex:Bytes -> Vector> -> Void; @@ -217,6 +234,7 @@ class ServerFunctions implements Remote { @:remote public var msgpack: Dynamic -> Void; } +// functions that run on Client class FirstClientFunctions implements Remote { public inline static var remoteId = 0; @:remote public var message:String->Void; diff --git a/samples/rpc/src/ui/Button.hx b/samples/rpc/src/ui/Button.hx new file mode 100644 index 0000000..c37fe10 --- /dev/null +++ b/samples/rpc/src/ui/Button.hx @@ -0,0 +1,56 @@ +package ui; + +import flash.display.Sprite; +import flash.text.TextField; +import flash.text.TextFormat; +import flash.text.TextFormatAlign; +import flash.events.MouseEvent; + +/** + * ... + * @author Sylvio Sell + */ + +class Button extends Sprite +{ + var text:TextField; + + public function new(label:String, x:Int, y:Int, w:Int, h:Int, onClick:Dynamic->Void, selectable:Bool = false) + { + super(); + + this.x = x; + this.y = y; + + // drawRect ---------------------- + + graphics.beginFill( 0xcccccc ); + graphics.drawRoundRect( 0, 0, w, h, 16, 16 ); + graphics.endFill(); + + + // addTextField ------------------ + + var format = new TextFormat(); + format.align = TextFormatAlign.CENTER; + format.size = Math.floor(h/2); + + text = new TextField(); + text.defaultTextFormat = format; + + text.selectable = selectable; + text.y = Math.floor(h/8); + text.width = w; + text.height= h; + + text.text = label; + addChild (text); + + + // listen on Click ------------- + addEventListener(MouseEvent.CLICK, onClick ); + + } + + +} \ No newline at end of file diff --git a/samples/rpc/src/ui/InputText.hx b/samples/rpc/src/ui/InputText.hx new file mode 100644 index 0000000..f00bc9b --- /dev/null +++ b/samples/rpc/src/ui/InputText.hx @@ -0,0 +1,68 @@ +package ui; + +import openfl.display.Sprite; +import openfl.text.Font; +import openfl.text.TextField; +import openfl.text.TextFieldType; +import openfl.text.TextFormat; +import lime.ui.KeyCode; +import flash.events.KeyboardEvent; +import ui.Button; + +/** + * ... + * @author Sylvio Sell + */ +class InputText extends Sprite +{ + var input:TextField; + + public function new(label:String, x:Int, y:Int, w:Int, h:Int, callback:TextField->Void) + { + super(); + var enter_button:ui.Button = new ui.Button(label, x+w+2, y, 120, h, function(_) { + callback(input); + }); + addChild(enter_button); + + + + var textFormat = new TextFormat (); + textFormat.leftMargin = 5; + textFormat.rightMargin = 5; + textFormat.size = Math.floor(h/2); + + + input = new TextField (); + + input.defaultTextFormat = textFormat; + + //input.embedFonts = true; + input.type = TextFieldType.INPUT; + input.border = true; + input.multiline = false; + //input.wordWrap = true; + input.background = true; + input.backgroundColor = 0xeeeeee; + + input.x = x; + input.y = y; + input.width = w; + input.height= h-1; + + addChild (input); + + input.addEventListener(KeyboardEvent.KEY_UP, function(event:Dynamic):Void { + if (event.keyCode == KeyCode.RETURN) + { + callback(input); + } + }); + } + + public function focus():Void + { + stage.focus = input; + } + +} \ No newline at end of file