From 6f5c47c7da14213e831824a5c8f2d35eb9592630 Mon Sep 17 00:00:00 2001 From: jefvel Date: Tue, 21 Jan 2025 17:07:31 +0100 Subject: [PATCH 1/5] made it possible to set @:visibility on RPC calls can be used like this: @:rpc(all) @:visible(group) public function groupMethod() { trace('hello'); } It will only be forwarded to clients with visibility set to true --- hxbit/Macros.hx | 9 +++++++-- hxbit/NetworkHost.hx | 10 +++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/hxbit/Macros.hx b/hxbit/Macros.hx index 53c649c..53e0d1c 100644 --- a/hxbit/Macros.hx +++ b/hxbit/Macros.hx @@ -2047,9 +2047,13 @@ class Macros { var conds = new haxe.EnumFlags(); conds.set(PreventCDB); - for( m in r.f.meta ) + var visibility : Null = null; + for( m in r.f.meta ) { if( m.name == ":allowCDB" ) conds.unset(PreventCDB); + if( m.name == ":visible" ) + visibility = getVisibility(m); + } if( returnVal.value || returnVal.call ) { var typeValue; @@ -2085,7 +2089,7 @@ class Macros { } var forwardRPC = macro { - @:privateAccess __host.doRPC(this,$v{id},$resultCall, function(__ctx) { + @:privateAccess __host.doRPC(this,$v{id}, $v{visibility},$resultCall, function(__ctx) { $b{[ for( a in funArgs ) withPos(macro hxbit.Macros.serializeValue(__ctx, $i{a.name}), f.expr.pos) @@ -2158,6 +2162,7 @@ class Macros { } case Immediate: macro { + var vs = $v{visibility}; if( __host != null ) { if( !__host.isAuth && !networkAllow(Ownership, $v{id}, __host.self.ownerObject) ) { var fieldName = networkGetName($v{id}, true); diff --git a/hxbit/NetworkHost.hx b/hxbit/NetworkHost.hx index 7649a8d..e790677 100644 --- a/hxbit/NetworkHost.hx +++ b/hxbit/NetworkHost.hx @@ -638,12 +638,20 @@ class NetworkHost { return targetClient != null; // owner not connected } - inline function doRPC(o:NetworkSerializable, id:Int, onResult:NetworkSerializer->Void, serialize:NetworkSerializer->Void) { + inline function doRPC(o:NetworkSerializable, id:Int, visibilityGroup: Null, onResult:NetworkSerializer->Void, serialize:NetworkSerializer->Void) { beforeRPC(o,id); #if hxbit_visibility for( c in clients ) { var ctx = c.ctx; if( targetClient != null && targetClient != c ) continue; + + if ( isAuth && visibilityGroup != null && c.ownerObject?.__cachedVisibility != null ) { + var visibility = c.ownerObject.__cachedVisibility.get(o); + var mask = 1 << visibilityGroup; + if ( visibility & mask == 0 ) { + continue; + } + } #end var rpcPosition = beginRPC(ctx, o, id, onResult); #if hxbit_visibility From 7ebb62ea06bc524efc56fdfa8b526dbf85ec99c9 Mon Sep 17 00:00:00 2001 From: jefvel Date: Tue, 21 Jan 2025 17:09:23 +0100 Subject: [PATCH 2/5] removed unused value --- hxbit/Macros.hx | 1 - 1 file changed, 1 deletion(-) diff --git a/hxbit/Macros.hx b/hxbit/Macros.hx index 53e0d1c..b4431a1 100644 --- a/hxbit/Macros.hx +++ b/hxbit/Macros.hx @@ -2162,7 +2162,6 @@ class Macros { } case Immediate: macro { - var vs = $v{visibility}; if( __host != null ) { if( !__host.isAuth && !networkAllow(Ownership, $v{id}, __host.self.ownerObject) ) { var fieldName = networkGetName($v{id}, true); From b9a472f0da31aef59c97a4a32230f88e256ce3dd Mon Sep 17 00:00:00 2001 From: jefvel Date: Tue, 21 Jan 2025 17:11:06 +0100 Subject: [PATCH 3/5] formatting --- hxbit/Macros.hx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hxbit/Macros.hx b/hxbit/Macros.hx index b4431a1..0e5f99e 100644 --- a/hxbit/Macros.hx +++ b/hxbit/Macros.hx @@ -2089,7 +2089,7 @@ class Macros { } var forwardRPC = macro { - @:privateAccess __host.doRPC(this,$v{id}, $v{visibility},$resultCall, function(__ctx) { + @:privateAccess __host.doRPC(this,$v{id}, $v{visibility}, $resultCall, function(__ctx) { $b{[ for( a in funArgs ) withPos(macro hxbit.Macros.serializeValue(__ctx, $i{a.name}), f.expr.pos) From 63aaaf4a4d7f190cca1a789e8c740e5cf4502b4e Mon Sep 17 00:00:00 2001 From: jefvel Date: Wed, 22 Jan 2025 21:53:38 +0100 Subject: [PATCH 4/5] Allow multiple @:visible tags on RPC functions, call RPC if one of the groups is visible --- hxbit/Macros.hx | 6 +++--- hxbit/NetworkHost.hx | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/hxbit/Macros.hx b/hxbit/Macros.hx index 0e5f99e..3973fe9 100644 --- a/hxbit/Macros.hx +++ b/hxbit/Macros.hx @@ -2047,12 +2047,12 @@ class Macros { var conds = new haxe.EnumFlags(); conds.set(PreventCDB); - var visibility : Null = null; + var visibilityMask = 0; for( m in r.f.meta ) { if( m.name == ":allowCDB" ) conds.unset(PreventCDB); if( m.name == ":visible" ) - visibility = getVisibility(m); + visibilityMask |= 1 << getVisibility(m); } if( returnVal.value || returnVal.call ) { @@ -2089,7 +2089,7 @@ class Macros { } var forwardRPC = macro { - @:privateAccess __host.doRPC(this,$v{id}, $v{visibility}, $resultCall, function(__ctx) { + @:privateAccess __host.doRPC(this,$v{id}, $v{visibilityMask}, $resultCall, function(__ctx) { $b{[ for( a in funArgs ) withPos(macro hxbit.Macros.serializeValue(__ctx, $i{a.name}), f.expr.pos) diff --git a/hxbit/NetworkHost.hx b/hxbit/NetworkHost.hx index e790677..ac6fbb5 100644 --- a/hxbit/NetworkHost.hx +++ b/hxbit/NetworkHost.hx @@ -638,17 +638,16 @@ class NetworkHost { return targetClient != null; // owner not connected } - inline function doRPC(o:NetworkSerializable, id:Int, visibilityGroup: Null, onResult:NetworkSerializer->Void, serialize:NetworkSerializer->Void) { + inline function doRPC(o:NetworkSerializable, id:Int, visibilityMask: Int, onResult:NetworkSerializer->Void, serialize:NetworkSerializer->Void) { beforeRPC(o,id); #if hxbit_visibility for( c in clients ) { var ctx = c.ctx; if( targetClient != null && targetClient != c ) continue; - if ( isAuth && visibilityGroup != null && c.ownerObject?.__cachedVisibility != null ) { + if ( isAuth && visibilityMask != 0 && c.ownerObject?.__cachedVisibility != null ) { var visibility = c.ownerObject.__cachedVisibility.get(o); - var mask = 1 << visibilityGroup; - if ( visibility & mask == 0 ) { + if ( visibility & visibilityMask == 0 ) { continue; } } From 162eb6000ed3b467476bb288cedc7f83cf376758 Mon Sep 17 00:00:00 2001 From: jefvel Date: Thu, 30 Jan 2025 18:54:34 +0100 Subject: [PATCH 5/5] Updated readme with visibility info --- README.md | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/README.md b/README.md index 8bcf59f..ea06b2e 100644 --- a/README.md +++ b/README.md @@ -237,3 +237,79 @@ You can also cancel the change of a property can calling `networkCancelProperty( The whole set of currently shared network objects can be saved using host.saveState() and loaded using host.loadState(bytes). It uses the versionning decribed previously. Once loaded, call host.makeAlive() to make sure all alive() calls are made to object. + +### Visibility + +It is possible to filter which clients an object's properties will be synced to. To enable this, compile with `-D hxbit_visibility`, and define the available groups by adding this macro to your hxml file: + +```haxe +--macro hxbit.Macros.initVisibility(["owner", "sameRoom"]) // The group names can be anything you want +``` + +We also need to implement a `rootObject`, which will reference all objects (objects not part of it wont be synced to clients): +```haxe +// Example player class +class Player extends hxbit.NetworkSerializable { + public function new(client: hxbit.NetworkHost.NetworkClient) { + client.ownerObject = this; + enableReplication = true; + } +} + +class RootObject extends hxbit.NetworkSerializable { + @:s public var players: Array = []; + public function new(client: hxbit.NetworkHost.NetworkClient) { + client.ownerObject = this; + enableReplication = true; + } +} +``` + +Then set this as the root object on the server, and enable `lateRegistration`: + +```haxe +var host = new NetworkHost(); +var rootObject:RootObject; + +... + +host.lateRegistration = true; // Also has to be set to true on clients + +host.wait('0.0.0.0', 9999, (client) -> { + trace('player connected'); + rootObject.players.push(new Player(client)); +}); + +rootObject = new RootObject(host.self); +host.rootObject = rootObject; +``` + +Now, for example, if we want a certain property to only be synced between the host and the client that owns the object, we add the following attribute to it: + +```haxe +@:s @:visible(owner) public var ownProperty: Int; +``` + +And then add the an `evalVisibility` method to the class: +```haxe +public override function evalVisibility(group: hxbit.VisibilityGroup, from: hxbit.NetworkSerializable): Bool { + if (group == Owner) { // The group owner was declared using the macro above + return from == this; + } + + // Example for syncing properties only if clients are in the same room + if (group == SameRoom) { + return this.roomId == from.roomId; + } + + return true; +} +``` + +If `ownProperty` is modified, it will only be synced between the host and owner, on every other client it will be `null`. + +The visibility can be revalidated by calling the method `setVisibilityDirty(GroupName);`. +An example of this would be if a property is only visible for clients in the same room, or a property that is only +available to nearby players. + +Note: make sure to call `host.makeAlive()` frequently on the clients, otherwise new objects wont appear until a RPC is called on them.