Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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>
- Loading branch information
1 parent
cbc2a47
commit 21acf66
Showing
7 changed files
with
364 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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)); | ||
} | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
Assets/Mirror/Tests/Editor/NetworkMatchCheckerTest.cs.meta
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters