feat: NetworkMatchChecker Component (#1688)

* feat: NetworkMatchChecker Component

* Added docs

* feat: Use logger framework for NetworkClient (#1685)

* Use logger framework for NetworkClient

* Update Assets/Mirror/Runtime/NetworkClient.cs

Co-authored-by: vis2k <info@noobtuts.com>

* breaking: NetworkVisbility component (#1681)

* backup

* breaking: NetworkProximityCheck abstract class. Simplifies code, reduces complexity, improves performance because if 10k identities have 10 components each, we don't have to iterate 100k components each time we rebuild observers.

* update tests and checkers

* DisallowMultipleComponents

* fix tests

* split OnCheckObserver check

* fix tests

* syntax

* update comment

* renamed to NetworkVisibility

* forgot to remove comment

* breaking: Network Visibility Component

* changing namespaces of performance tests (#1689)

* Updated to use NetworkVisibility

* Updated comments

* Updated OnCheckObserver and removed OnSetHostVisibility

* tests for OnCheckObserver

* adding check for empty guid

* tests for changing matchId

* RebuildObservers if player left a match

* Refactored to make it simpler

Co-authored-by: Paul Pacheco <paulpach@gmail.com>
Co-authored-by: vis2k <info@noobtuts.com>
Co-authored-by: James Frowen <jamesfrowendev@gmail.com>
This commit is contained in:
MrGadget 2020-04-17 04:35:08 -04:00 committed by GitHub
parent cbc2a47729
commit 21acf66190
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 364 additions and 0 deletions

View File

@ -0,0 +1,131 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Mirror
{
/// <summary>
/// Component that controls visibility of networked objects based on match id.
/// <para>Any object with this component on it will only be visible to other objects in the same match.</para>
/// <para>This would be used to isolate players to their respective matches within a single game server instance. </para>
/// </summary>
[AddComponentMenu("Network/NetworkMatchChecker")]
[RequireComponent(typeof(NetworkIdentity))]
[HelpURL("https://mirror-networking.com/docs/Components/NetworkMatchChecker.html")]
public class NetworkMatchChecker : NetworkVisibility
{
static readonly Dictionary<Guid, HashSet<NetworkIdentity>> matchPlayers = new Dictionary<Guid, HashSet<NetworkIdentity>>();
Guid currentMatch = Guid.Empty;
[Header("Diagnostics")]
[SyncVar]
public string currentMatchDebug;
/// <summary>
/// Set this to the same value on all networked objects that belong to a given match
/// </summary>
public Guid matchId
{
get { return currentMatch; }
set
{
if (currentMatch == value) return;
// cache previous match so observers in that match can be rebuilt
Guid previousMatch = currentMatch;
// Set this to the new match this object just entered ...
currentMatch = value;
// ... and copy the string for the inspector because Unity can't show Guid directly
currentMatchDebug = currentMatch.ToString();
if (previousMatch != Guid.Empty)
{
// Remove this object from the hashset of the match it just left
matchPlayers[previousMatch].Remove(netIdentity);
// RebuildObservers of all NetworkIdentity's in the match this object just left
RebuildMatchObservers(previousMatch);
}
if (currentMatch != Guid.Empty)
{
// Make sure this new match is in the dictionary
if (!matchPlayers.ContainsKey(currentMatch))
matchPlayers.Add(currentMatch, new HashSet<NetworkIdentity>());
// Add this object to the hashset of the new match
matchPlayers[currentMatch].Add(netIdentity);
// RebuildObservers of all NetworkIdentity's in the match this object just entered
RebuildMatchObservers(currentMatch);
}
else
{
// Not in any match now...RebuildObservers will clear and add self
netIdentity.RebuildObservers(false);
}
}
}
public override void OnStartServer()
{
if (currentMatch == Guid.Empty) return;
if (!matchPlayers.ContainsKey(currentMatch))
matchPlayers.Add(currentMatch, new HashSet<NetworkIdentity>());
matchPlayers[currentMatch].Add(netIdentity);
// No need to rebuild anything here.
// identity.RebuildObservers is called right after this from NetworkServer.SpawnObject
}
void RebuildMatchObservers(Guid specificMatch)
{
foreach (NetworkIdentity networkIdentity in matchPlayers[specificMatch])
if (networkIdentity != null)
networkIdentity.RebuildObservers(false);
}
#region Observers
/// <summary>
/// Callback used by the visibility system to determine if an observer (player) can see this object.
/// <para>If this function returns true, the network connection will be added as an observer.</para>
/// </summary>
/// <param name="conn">Network connection of a player.</param>
/// <returns>True if the player can see this object.</returns>
public override bool OnCheckObserver(NetworkConnection conn)
{
// Not Visible if not in a match
if (matchId == Guid.Empty)
return false;
NetworkMatchChecker networkMatchChecker = conn.identity.GetComponent<NetworkMatchChecker>();
if (networkMatchChecker == null)
return false;
return networkMatchChecker.matchId == matchId;
}
/// <summary>
/// Callback used by the visibility system to (re)construct the set of observers that can see this object.
/// <para>Implementations of this callback should add network connections of players that can see this object to the observers set.</para>
/// </summary>
/// <param name="observers">The new set of observers for this object.</param>
/// <param name="initialize">True if the set of observers is being built for the first time.</param>
public override void OnRebuildObservers(HashSet<NetworkConnection> observers, bool initialize)
{
if (currentMatch == Guid.Empty) return;
foreach (NetworkIdentity networkIdentity in matchPlayers[currentMatch])
if (networkIdentity != null && networkIdentity.connectionToClient != null)
observers.Add(networkIdentity.connectionToClient);
}
#endregion
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1020a74962faada4b807ac5dc053a4cf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,196 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using NUnit.Framework;
using UnityEngine;
namespace Mirror.Tests
{
public class NetworkMatchCheckerTest
{
private GameObject player1;
private GameObject player2;
private GameObject player3;
private NetworkMatchChecker player1MatchChecker;
private NetworkMatchChecker player2MatchChecker;
private NetworkConnection player1Connection;
private NetworkConnection player2Connection;
private NetworkConnection player3Connection;
private GameObject transportGO;
static int nextConnectionId;
private Dictionary<Guid, HashSet<NetworkIdentity>> matchPlayers;
[SetUp]
public void Setup()
{
transportGO = new GameObject("transportGO");
Transport.activeTransport = transportGO.AddComponent<TelepathyTransport>();
player1 = new GameObject("TestPlayer1", typeof(NetworkIdentity), typeof(NetworkMatchChecker));
player2 = new GameObject("TestPlayer2", typeof(NetworkIdentity), typeof(NetworkMatchChecker));
player3 = new GameObject("TestPlayer3", typeof(NetworkIdentity));
player1MatchChecker = player1.GetComponent<NetworkMatchChecker>();
player2MatchChecker = player2.GetComponent<NetworkMatchChecker>();
player1Connection = CreateNetworkConnection(player1);
player2Connection = CreateNetworkConnection(player2);
player3Connection = CreateNetworkConnection(player3);
Dictionary<Guid, HashSet<NetworkIdentity>> g = GetMatchPlayersDictionary();
matchPlayers = g;
}
private static Dictionary<Guid, HashSet<NetworkIdentity>> GetMatchPlayersDictionary()
{
Type type = typeof(NetworkMatchChecker);
FieldInfo fieldInfo = type.GetField("matchPlayers", BindingFlags.Static | BindingFlags.NonPublic);
return (Dictionary<Guid, HashSet<NetworkIdentity>>)fieldInfo.GetValue(null);
}
static NetworkConnection CreateNetworkConnection(GameObject player)
{
NetworkConnectionToClient connection = new NetworkConnectionToClient(++nextConnectionId);
connection.identity = player.GetComponent<NetworkIdentity>();
connection.identity.connectionToClient = connection;
connection.identity.observers = new Dictionary<int, NetworkConnection>();
connection.isReady = true;
return connection;
}
[TearDown]
public void TearDown()
{
UnityEngine.Object.DestroyImmediate(player1);
UnityEngine.Object.DestroyImmediate(player2);
UnityEngine.Object.DestroyImmediate(player3);
UnityEngine.Object.DestroyImmediate(transportGO);
matchPlayers.Clear();
matchPlayers = null;
}
static void SetMatchId(NetworkMatchChecker target, Guid guid)
{
// set using reflection so bypass property
FieldInfo field = typeof(NetworkMatchChecker).GetField("currentMatch", BindingFlags.Instance | BindingFlags.NonPublic);
field.SetValue(target, guid);
}
[Test]
public void OnCheckObserverShouldBeTrueForSameMatchId()
{
string guid = Guid.NewGuid().ToString();
SetMatchId(player1MatchChecker, new Guid(guid));
SetMatchId(player2MatchChecker, new Guid(guid));
bool player1Visable = player1MatchChecker.OnCheckObserver(player1Connection);
Assert.IsTrue(player1Visable);
bool player2Visable = player1MatchChecker.OnCheckObserver(player2Connection);
Assert.IsTrue(player2Visable);
}
[Test]
public void OnCheckObserverShouldBeFalseForDifferentMatchId()
{
string guid1 = Guid.NewGuid().ToString();
string guid2 = Guid.NewGuid().ToString();
SetMatchId(player1MatchChecker, new Guid(guid1));
SetMatchId(player2MatchChecker, new Guid(guid2));
bool player1VisableToPlayer1 = player1MatchChecker.OnCheckObserver(player1Connection);
Assert.IsTrue(player1VisableToPlayer1);
bool player2VisableToPlayer1 = player1MatchChecker.OnCheckObserver(player2Connection);
Assert.IsFalse(player2VisableToPlayer1);
bool player1VisableToPlayer2 = player2MatchChecker.OnCheckObserver(player1Connection);
Assert.IsFalse(player1VisableToPlayer2);
bool player2VisableToPlayer2 = player2MatchChecker.OnCheckObserver(player2Connection);
Assert.IsTrue(player2VisableToPlayer2);
}
[Test]
public void OnCheckObserverShouldBeFalseIfObjectDoesNotHaveNetworkMatchChecker()
{
string guid = Guid.NewGuid().ToString();
SetMatchId(player1MatchChecker, new Guid(guid));
bool player3Visable = player1MatchChecker.OnCheckObserver(player3Connection);
Assert.IsFalse(player3Visable);
}
[Test]
public void OnCheckObserverShouldBeFalseForEmptyGuid()
{
string guid = Guid.Empty.ToString();
SetMatchId(player1MatchChecker, new Guid(guid));
SetMatchId(player2MatchChecker, new Guid(guid));
bool player1Visable = player1MatchChecker.OnCheckObserver(player1Connection);
Assert.IsFalse(player1Visable);
bool player2Visable = player1MatchChecker.OnCheckObserver(player2Connection);
Assert.IsFalse(player2Visable);
}
[Test]
public void SettingMatchIdShouldRebuildObservers()
{
string guidMatch1 = Guid.NewGuid().ToString();
// make players join same match
player1MatchChecker.matchId = new Guid(guidMatch1);
player2MatchChecker.matchId = new Guid(guidMatch1);
// check player1's observers contains player 2
Assert.IsTrue(player1MatchChecker.netIdentity.observers.ContainsValue(player2MatchChecker.connectionToClient));
// check player2's observers contains player 1
Assert.IsTrue(player2MatchChecker.netIdentity.observers.ContainsValue(player1MatchChecker.connectionToClient));
}
[Test]
public void ChangingMatchIdShouldRebuildObservers()
{
string guidMatch1 = Guid.NewGuid().ToString();
string guidMatch2 = Guid.NewGuid().ToString();
// make players join same match
player1MatchChecker.matchId = new Guid(guidMatch1);
player2MatchChecker.matchId = new Guid(guidMatch1);
// make player2 join different match
player2MatchChecker.matchId = new Guid(guidMatch2);
// check player1's observers does NOT contain player 2
Assert.IsFalse(player1MatchChecker.netIdentity.observers.ContainsValue(player2MatchChecker.connectionToClient));
// check player2's observers does NOT contain player 1
Assert.IsFalse(player2MatchChecker.netIdentity.observers.ContainsValue(player1MatchChecker.connectionToClient));
}
[Test]
public void ClearingMatchIdShouldRebuildObservers()
{
string guidMatch1 = Guid.NewGuid().ToString();
// make players join same match
player1MatchChecker.matchId = new Guid(guidMatch1);
player2MatchChecker.matchId = new Guid(guidMatch1);
// make player 2 leave match
player2MatchChecker.matchId = Guid.Empty;
// check player1's observers does NOT contain player 2
Assert.IsFalse(player1MatchChecker.netIdentity.observers.ContainsValue(player2MatchChecker.connectionToClient));
// check player2's observers does NOT contain player 1
Assert.IsFalse(player2MatchChecker.netIdentity.observers.ContainsValue(player1MatchChecker.connectionToClient));
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2fa0a455ab9b4cf47b9eab0f2b03ce0c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,13 @@
# Network Scene Checker
The Network Match Checker component controls visibility of networked objects based on match id.
![Network Scene Checker component](NetworkMatchChecker.png)
Any object with this component on it will only be visible to other objects in the same match.
This would be used to isolate players to their respective matches within a single game server instance.
When you create a match, generate and store, in a List for example, a new match id with `System.Guid.NewGuid();` and assign the same match id to the Network Scene Checker via `GetComponent<NetworkMatchChecker>().matchId`.
Mirror's built-in Observers system will isolate SyncVar's and ClientRpc's on networked objects to only send updates to clients with the same match id.

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -16,6 +16,8 @@ These core components are included in Mirror:
The Network Proximity Checker component controls the visibility of game objects for network clients, based on proximity to players.
- [Network Scene Checker](NetworkSceneChecker.md)
The Network Scene Checker component controls visibility of networked objects between scenes.
- [Network Match Checker](NetworkMatchChecker.md)
The Network Match Checker component controls visibility of networked objects based on match id.
- [Network Room Manager](NetworkRoomManager.md)
The Network Room Manager is an extension component of Network Manager that provides a basic functional room.
- [Network Room Player](NetworkRoomPlayer.md)