Ver código fonte

added network properties, added rpc call result

ncannasse 9 anos atrás
pai
commit
5cc7938c9b
3 arquivos alterados com 237 adições e 47 exclusões
  1. 144 31
      hxd/net/Macros.hx
  2. 78 15
      hxd/net/NetworkHost.hx
  3. 15 1
      hxd/net/NetworkSerializable.hx

+ 144 - 31
hxd/net/Macros.hx

@@ -4,6 +4,25 @@ import haxe.macro.Type;
 import haxe.macro.Context;
 using haxe.macro.TypeTools;
 
+enum RpcMode {
+	/*
+		When called on the client: will forward the call on the server, but not execute locally.
+		When called on the server: will forward the call to the clients (and force its execution), then execute.
+		This is the default behavior.
+	*/
+	All;
+	/*
+		When called on the server: will forward the call to the clients, but not execute locally.
+		When called on the client: will execute locally.
+	*/
+	Client;
+	/*
+		When called on the client: will forward the call the server, but not execute locally.
+		When called on the server: will execute locally.
+	*/
+	Server;
+}
+
 enum PropType {
 	PInt;
 	PFloat;
@@ -337,10 +356,9 @@ class Macros {
 		if( !Context.defined("display") )
 		for( f in fields ) {
 			if( f.meta == null ) continue;
-			var m = null;
 			for( meta in f.meta )
 				if( meta.name == ":s" ) {
-					toSerialize.push({ f : f, m : m });
+					toSerialize.push({ f : f, m : meta });
 					break;
 				}
 		}
@@ -570,6 +588,26 @@ class Macros {
 		return macro : hxd.net.NetworkSerializable.Proxy<$t>;
 	}
 
+
+	static var hasRetVal : Bool;
+	static function hasReturnVal( e : Expr ) {
+		hasRetVal = false;
+		checkRetVal(e);
+		return hasRetVal;
+	}
+
+	static function checkRetVal( e : Expr ) {
+		if( hasRetVal )
+			return;
+		switch( e.expr ) {
+		case EReturn(e):
+			if( e != null )
+				hasRetVal = true;
+		default:
+			haxe.macro.ExprTools.iter(e, checkRetVal);
+		}
+	}
+
 	public static function buildNetworkSerializable() {
 		var cl = Context.getLocalClass().get();
 		if( cl.isInterface )
@@ -581,14 +619,22 @@ class Macros {
 		if( !Context.defined("display") )
 		for( f in fields ) {
 			if( f.meta == null ) continue;
-			var m = null;
 			for( meta in f.meta ) {
 				if( meta.name == ":s" ) {
-					toSerialize.push({ f : f, m : m });
+					toSerialize.push({ f : f, m : meta });
 					break;
 				}
 				if( meta.name == ":rpc" ) {
-					rpc.push({ f : f, m:m });
+					var mode : RpcMode = All;
+					if( meta.params.length != 0 )
+						switch( meta.params[0].expr ) {
+						case EConst(CIdent("all")):
+						case EConst(CIdent("client")): mode = Client;
+						case EConst(CIdent("server")): mode = Server;
+						default:
+							Context.error("Unexpected Rpc mode : should be all|client|server", meta.params[0].pos);
+						}
+					rpc.push({ f : f, mode:mode });
 					break;
 				}
 			}
@@ -626,6 +672,9 @@ class Macros {
 					hxd.net.NetworkHost.enableReplication(this, b);
 					return b;
 				}
+				public inline function networkCancelProperty( props : hxd.net.NetworkSerializable.Property ) {
+					__bits &= ~props.toInt();
+				}
 			}).fields);
 		}
 
@@ -634,6 +683,7 @@ class Macros {
 		var noComplete : Metadata = [ { name : ":noCompletion", pos : pos } ];
 
 		for( f in toSerialize ) {
+			var pos = f.f.pos;
 			var fname = f.f.name;
 			var bitID = startFID++;
 			var ftype : ComplexType;
@@ -643,7 +693,7 @@ class Macros {
 				ftype = t;
 				proxy = needProxy(t, e);
 				if( proxy ) {
-					if( ftype == null ) Context.error("Type required", f.f.pos);
+					if( ftype == null ) Context.error("Type required", pos);
 					ftype = toProxy(ftype);
 				}
 				f.f.kind = FProp("default", "set", ftype, e);
@@ -651,13 +701,13 @@ class Macros {
 				ftype = t;
 				proxy = needProxy(t, e);
 				if( proxy ) {
-					if( ftype == null ) Context.error("Type required", f.f.pos);
+					if( ftype == null ) Context.error("Type required", pos);
 					ftype = toProxy(ftype);
 				}
 				if( set == "null" )
-					Context.warning("Null setter is not respected when using NetworkSerializable", f.f.pos);
+					Context.warning("Null setter is not respected when using NetworkSerializable", pos);
 				else if( set != "default" && set != "set" )
-					Context.error("Invalid setter", f.f.pos);
+					Context.error("Invalid setter", pos);
 				f.f.kind = FProp(get,"set", ftype, e);
 			default:
 				throw "assert";
@@ -701,7 +751,7 @@ class Macros {
 			if( !found )
 				fields.push({
 					name : "set_" + fname,
-					pos : f.f.pos,
+					pos : pos,
 					kind : FFun({
 						args : [ { name : "v", type : ftype } ],
 						expr : macro { $setExpr; return v; },
@@ -709,7 +759,25 @@ class Macros {
 					}),
 				});
 			flushExpr.push(macro if( b & (1 << $v{ bitID } ) != 0 ) hxd.net.Macros.serializeValue(ctx, this.$fname));
-			syncExpr.push(macro if( __bits & (1 << $v{ bitID } ) != 0 ) hxd.net.Macros.unserializeValue(ctx, this.$fname));
+			syncExpr.push(macro if( __bits & (1 << $v { bitID } ) != 0 ) hxd.net.Macros.unserializeValue(ctx, this.$fname));
+
+			var prop = "networkProp" + fname.charAt(0).toUpperCase() + fname.substr(1);
+			fields.push({
+				name : prop,
+				pos : pos,
+				kind : FProp("get", "never", macro : hxd.net.NetworkSerializable.Property),
+				access : [APublic],
+			});
+			fields.push({
+				name : "get_"+prop,
+				pos : pos,
+				kind : FFun( {
+					args : [],
+					expr : macro return new hxd.net.NetworkSerializable.Property(1 << $v{bitID}),
+					ret : null,
+				}),
+				access : [AInline],
+			});
 		}
 
 		// BUILD RPC
@@ -719,34 +787,79 @@ class Macros {
 			switch( r.f.kind ) {
 			case FFun(f):
 				var id = rpcID++;
-				if( f.ret != null && haxe.macro.ComplexTypeTools.toString(f.ret) != "Void" )
-					Context.error("RPC function cannot return a value", r.f.pos);
-				if( f.ret == null )
-					f.ret = macro : Void;
+				var ret = f.ret;
+				f.ret = macro : Void;
 				for( a in f.args )
 					if( a.type == null )
 						Context.error("Type required for rpc function argument " + r.f.name+"(" + a.name+")", r.f.pos);
+
+				var hasReturnVal = hasReturnVal(f.expr);
+
 				var expr = f.expr;
-				f.expr = macro {
-					inline function __dispatch() {
-						var __ctx = __host.beginRPC(this,$v{id});
-						$b{[
-							for( a in f.args )
-								macro hxd.net.Macros.serializeValue(__ctx,$i{a.name})
-						] };
+				var resultCall = macro null;
+
+				if( hasReturnVal ) {
+					expr = macro {
+						inline function __execute() ${f.expr};
+						onResult(__execute());
+					}
+					resultCall = macro function(__ctx) {
+						var v = cast null;
+						if( false ) onResult(v); // type
+						hxd.net.Macros.unserializeValue(__ctx, v);
+						onResult(v);
+					};
+				}
+
+				var forwardRPC = macro {
+					var __ctx = __host.beginRPC(this,$v{id},$resultCall);
+					$b{[
+						for( a in f.args )
+							macro hxd.net.Macros.serializeValue(__ctx,$i{a.name})
+					]};
+				};
+
+				f.expr = switch( r.mode ) {
+				case All:
+					macro {
+						if( __host != null ) {
+							$forwardRPC;
+							if( !__host.isAuth ) return;
+						}
+						$expr;
+					}
+				case Client:
+					macro {
+						if( __host != null && __host.isAuth ) {
+							$forwardRPC;
+							return;
+						}
+						$expr;
 					}
-					if( __host != null && !__host.isAuth ) {
-						__dispatch();
-						return;
+				case Server:
+					macro {
+						if( __host != null && !__host.isAuth ) {
+							$forwardRPC;
+							return;
+						}
+						$expr;
 					}
-					$expr;
-					if( __host != null ) __dispatch();
 				};
 				var p = r.f.pos;
 				var exprs = [{ expr : EVars([for( a in f.args ) { name : a.name, type : a.type, expr : null }]), pos : p }];
 				for( a in f.args )
-					exprs.push(macro hxd.net.Macros.unserializeValue(__ctx,$i{a.name}));
-				exprs.push( { expr : ECall( { expr : EField({ expr : EConst(CIdent("this")), pos:p }, r.f.name), pos : p }, [for( a in f.args ) { expr : EConst(CIdent(a.name)), pos : p } ]), pos : p } );
+					exprs.push(macro hxd.net.Macros.unserializeValue(__ctx, $i { a.name } ));
+
+				var cargs = [for( a in f.args ) { expr : EConst(CIdent(a.name)), pos : p } ];
+				if( hasReturnVal ) {
+					f.args.push( { name : "onResult", type : ret == null ? null : TFunction([ret], macro:Void) } );
+					cargs.push(macro function(result) {
+						@:privateAccess __clientResult.beginRPCResult();
+						hxd.net.Macros.serializeValue(__ctx, result);
+					});
+				}
+
+				exprs.push( { expr : ECall( { expr : EField({ expr : EConst(CIdent("this")), pos:p }, r.f.name), pos : p }, cargs), pos : p } );
 				rpcCases.push({ values : [{ expr : EConst(CInt(""+id)), pos : p }], guard : null, expr : { expr : EBlock(exprs), pos : p } });
 			default:
 				Context.error("Cannot use @:rpc on non function", r.f.pos);
@@ -800,9 +913,9 @@ class Macros {
 				access : access,
 				meta : noComplete,
 				kind : FFun({
-					args : [ { name : "__ctx", type : macro : hxd.net.Serializer }, { name : "__id", type : macro : Int } ],
+					args : [ { name : "__ctx", type : macro : hxd.net.Serializer }, { name : "__id", type : macro : Int }, { name : "__clientResult", type : macro : hxd.net.NetworkHost.NetworkClient } ],
 					ret : null,
-					expr : if( isSubSer && firstRPCID > 0 ) macro { if( __id < $v { firstRPCID } ) { super.networkRPC(__ctx, __id); return; } $swExpr; } else swExpr,
+					expr : if( isSubSer && firstRPCID > 0 ) macro { if( __id < $v { firstRPCID } ) { super.networkRPC(__ctx, __id, __clientResult); return; } $swExpr; } else swExpr,
 				}),
 			});
 		}

+ 78 - 15
hxd/net/NetworkHost.hx

@@ -3,12 +3,13 @@ package hxd.net;
 class NetworkClient {
 
 	var host : NetworkHost;
+	var resultID : Int;
 
 	public function new(h) {
 		this.host = h;
 	}
 
-	function send(bytes : haxe.io.Bytes) {
+	public function send(bytes : haxe.io.Bytes) {
 	}
 
 	public function fullSync( obj : Serializable ) {
@@ -62,16 +63,50 @@ class NetworkClient {
 			if( !o.__host.isAuth ) {
 				var old = o.__host;
 				o.__host = null;
-				o.networkRPC(ctx, fid);
+				o.networkRPC(ctx, fid, this);
 				o.__host = old;
 			} else
-				o.networkRPC(ctx, fid);
+				o.networkRPC(ctx, fid, this);
+		case NetworkHost.RPC_WITH_RESULT:
+
+			var old = resultID;
+			resultID = ctx.getInt();
+			var o : hxd.net.NetworkSerializable = cast ctx.refs[ctx.getInt()];
+			var fid = ctx.getByte();
+
+			if( !o.__host.isAuth ) {
+				var old = o.__host;
+				o.__host = null;
+				o.networkRPC(ctx, fid, this);
+				o.__host = old;
+			} else
+				o.networkRPC(ctx, fid, this);
+
+			host.doSend();
+			host.targetClient = null;
+			resultID = old;
+
+		case NetworkHost.RPC_RESULT:
+
+			var resultID = ctx.getInt();
+			var callb = host.rpcWaits.get(resultID);
+			host.rpcWaits.remove(resultID);
+			callb(ctx);
+
 		case x:
 			error("Unknown message code " + x);
 		}
 		return @:privateAccess ctx.inPos;
 	}
 
+	function beginRPCResult() {
+		var ctx = host.ctx;
+		host.flush();
+		host.targetClient = this;
+		ctx.addByte(NetworkHost.RPC_RESULT);
+		ctx.addInt(resultID);
+	}
+
 }
 
 @:allow(hxd.net.NetworkClient)
@@ -82,6 +117,8 @@ class NetworkHost {
 	static inline var UNREG 	= 3;
 	static inline var FULLSYNC 	= 4;
 	static inline var RPC 		= 5;
+	static inline var RPC_WITH_RESULT = 6;
+	static inline var RPC_RESULT = 7;
 
 	public static var current : NetworkHost = null;
 
@@ -92,6 +129,10 @@ class NetworkHost {
 	var isAlive = false;
 	var logger : String -> Void;
 	var hasData = false;
+	var rpcUID = Std.random(0x1000000);
+	var rpcWaits = new Map<Int,Serializer->Void>();
+
+	var targetClient : NetworkClient;
 
 	public function new() {
 		current = this;
@@ -106,19 +147,34 @@ class NetworkHost {
 		markHead = o;
 	}
 
-	public function beginRPC(o:NetworkSerializable, id:Int) {
+	public function beginRPC(o:NetworkSerializable, id:Int, onResult:Serializer->Void) {
 		flushProps();
 		hasData = true;
 		if( ctx.refs[o.__uid] == null )
 			throw "Can't call RPC on an object not previously transferred";
-		ctx.addByte(RPC);
+		if( onResult != null ) {
+			var id = rpcUID++;
+			ctx.addByte(RPC_WITH_RESULT);
+			ctx.addInt(id);
+			rpcWaits.set(id, onResult);
+		} else
+			ctx.addByte(RPC);
 		ctx.addInt(o.__uid);
 		ctx.addByte(id);
 		if( logger != null )
-			logger("RPC " + o +"#"+id);
+			logger("RPC " + o +"#"+id+(onResult == null ? "" : "->" + (rpcUID-1)));
 		return ctx;
 	}
 
+	public function defaultLogger( ?filter : String -> Bool ) {
+		setLogger(function(str) {
+			if( filter != null && !filter(str) ) return;
+			str = (isAuth ? "[S] " : "[C] ") + str;
+			str = haxe.Timer.stamp() + " " + str;
+			#if	sys Sys.println(str); #else trace(str); #end
+		});
+	}
+
 	public function makeAlive() {
 		isAlive = true;
 		for( o in ctx.refs ) {
@@ -152,9 +208,20 @@ class NetworkHost {
 		ctx.refs[o.__uid] = null;
 	}
 
+	function doSend() {
+		@:privateAccess {
+			var bytes = ctx.out.getBytes();
+			ctx.out = new haxe.io.BytesBuffer();
+			send(bytes);
+		}
+	}
+
 	function send( bytes : haxe.io.Bytes ) {
-		for( c in clients )
-			@:privateAccess c.send(bytes);
+		if( targetClient != null )
+			targetClient.send(bytes);
+		else
+			for( c in clients )
+				c.send(bytes);
 	}
 
 	public dynamic function onSync( obj : Serializable ) {
@@ -165,8 +232,8 @@ class NetworkHost {
 		var o = markHead;
 		while( o != null ) {
 			if( o.__bits != 0 ) {
-//				if( logger != null )
-//					logger("Sync " + o + "#" + o.__uid + " " + o.__bits);
+				if( logger != null )
+					logger("SYNC " + o + "#" + o.__uid + " " + o.__bits);
 				ctx.addByte(SYNC);
 				ctx.addInt(o.__uid);
 				o.networkFlush(ctx);
@@ -181,11 +248,7 @@ class NetworkHost {
 		flushProps();
 		if( !hasData )
 			return;
-		@:privateAccess {
-			var bytes = ctx.out.getBytes();
-			ctx.out = new haxe.io.BytesBuffer();
-			send(bytes);
-		}
+		doSend();
 		hasData = false;
 	}
 

+ 15 - 1
hxd/net/NetworkSerializable.hx

@@ -17,7 +17,7 @@ interface NetworkSerializable extends Serializable extends ProxyHost {
 	public var enableReplication(get, set) : Bool;
 	public function networkFlush( ctx : Serializer ) : Void;
 	public function networkSync( ctx : Serializer ) : Void;
-	public function networkRPC( ctx : Serializer, rpcID : Int ) : Void;
+	public function networkRPC( ctx : Serializer, rpcID : Int, clientResult : NetworkHost.NetworkClient ) : Void;
 	public function alive() : Void;
 }
 
@@ -42,3 +42,17 @@ class BaseProxy implements ProxyHost implements ProxyChild {
 		this.obj = null;
 	}
 }
+
+abstract Property(Int) {
+
+	public inline function new(x:Int) {
+		this = x;
+	}
+
+	public inline function toInt() {
+		return this;
+	}
+
+	@:op(a|b) inline function opOr(a:Property) return new Property(this | a.toInt());
+
+}