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. The Network Proximity Checker component controls the visibility of game objects for network clients, based on proximity to players.
- [Network Scene Checker](NetworkSceneChecker.md) - [Network Scene Checker](NetworkSceneChecker.md)
The Network Scene Checker component controls visibility of networked objects between scenes. 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) - [Network Room Manager](NetworkRoomManager.md)
The Network Room Manager is an extension component of Network Manager that provides a basic functional room. The Network Room Manager is an extension component of Network Manager that provides a basic functional room.
- [Network Room Player](NetworkRoomPlayer.md) - [Network Room Player](NetworkRoomPlayer.md)