Skip to content

Commit

Permalink
perf: NetworkProximityChecker checks Server.connections instead of do…
Browse files Browse the repository at this point in the history
…ing 10k sphere casts for 10k monsters. 2k NetworkTransforms demo is significantly faster. Stable 80fps instead of 500ms freezes in between. (#1852)
  • Loading branch information
miwarnec committed May 5, 2020
1 parent 201411d commit 2d89f05
Showing 1 changed file with 17 additions and 80 deletions.
97 changes: 17 additions & 80 deletions Assets/Mirror/Components/NetworkProximityChecker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,6 @@ namespace Mirror
[HelpURL("https://mirror-networking.com/docs/Components/NetworkProximityChecker.html")]
public class NetworkProximityChecker : NetworkVisibility
{
/// <summary>
/// Enumeration of methods to use to check proximity.
/// </summary>
public enum CheckMethod
{
Physics3D,
Physics2D
}

/// <summary>
/// The maximim range that objects will be visible at.
/// </summary>
Expand All @@ -33,39 +24,15 @@ public enum CheckMethod
[Tooltip("How often (in seconds) that this object should update the list of observers that can see it.")]
public float visUpdateInterval = 1;

/// <summary>
/// Which method to use for checking proximity of players.
/// <para>Physics3D uses 3D physics to determine proximity.</para>
/// <para>Physics2D uses 2D physics to determine proximity.</para>
/// </summary>
[Tooltip("Which method to use for checking proximity of players.\n\nPhysics3D uses 3D physics to determine proximity.\nPhysics2D uses 2D physics to determine proximity.")]
public CheckMethod checkMethod = CheckMethod.Physics3D;

/// <summary>
/// Flag to force this object to be hidden for players.
/// <para>If this object is a player object, it will not be hidden for that player.</para>
/// </summary>
[Tooltip("Enable to force this object to be hidden from players.")]
public bool forceHidden;

// Layers are used anyway, might as well expose them to the user.
/// <summary>
/// Select only the Player's layer to avoid unnecessary SphereCasts against the Terrain, etc.
/// <para>~0 means 'Everything'.</para>
/// </summary>
[Tooltip("Select only the Player's layer to avoid unnecessary SphereCasts against the Terrain, etc.")]
public LayerMask castLayers = ~0;

float lastUpdateTime;

// OverlapSphereNonAlloc array to avoid allocations.
// -> static so we don't create one per component
// -> this is worth it because proximity checking happens for just about
// every entity on the server!
// -> should be big enough to work in just about all cases
static Collider[] hitsBuffer3D = new Collider[10000];
static Collider2D[] hitsBuffer2D = new Collider2D[10000];

void Update()
{
if (!NetworkServer.active)
Expand Down Expand Up @@ -104,55 +71,25 @@ public override void OnRebuildObservers(HashSet<NetworkConnection> observers, bo
if (forceHidden)
return;

// find players within range
switch (checkMethod)
{
case CheckMethod.Physics3D:
Add3DHits(observers);
break;

case CheckMethod.Physics2D:
Add2DHits(observers);
break;
}
}

void Add3DHits(HashSet<NetworkConnection> observers)
{
// cast without allocating GC for maximum performance
int hitCount = Physics.OverlapSphereNonAlloc(transform.position, visRange, hitsBuffer3D, castLayers);
if (hitCount == hitsBuffer3D.Length) Debug.LogWarning("NetworkProximityChecker's OverlapSphere test for " + name + " has filled the whole buffer(" + hitsBuffer3D.Length + "). Some results might have been omitted. Consider increasing buffer size.");

for (int i = 0; i < hitCount; i++)
{
Collider hit = hitsBuffer3D[i];
// collider might be on pelvis, often the NetworkIdentity is in a parent
// (looks in the object itself and then parents)
NetworkIdentity identity = hit.GetComponentInParent<NetworkIdentity>();
// (if an object has a connectionToClient, it is a player)
if (identity != null && identity.connectionToClient != null)
{
observers.Add(identity.connectionToClient);
}
}
}

void Add2DHits(HashSet<NetworkConnection> observers)
{
// cast without allocating GC for maximum performance
int hitCount = Physics2D.OverlapCircleNonAlloc(transform.position, visRange, hitsBuffer2D, castLayers);
if (hitCount == hitsBuffer2D.Length) Debug.LogWarning("NetworkProximityChecker's OverlapCircle test for " + name + " has filled the whole buffer(" + hitsBuffer2D.Length + "). Some results might have been omitted. Consider increasing buffer size.");

for (int i = 0; i < hitCount; i++)
// 'transform.' calls GetComponent, only do it once
Vector3 position = transform.position;

// brute force distance check
// -> only player connections can be observers, so it's enough if we
// go through all connections instead of all spawned identities.
// -> compared to UNET's sphere cast checking, this one is orders of
// magnitude faster. if we have 10k monsters and run a sphere
// cast 10k times, we will see a noticeable lag even with physics
// layers. but checking to every connection is fast.
foreach (NetworkConnectionToClient conn in NetworkServer.connections.Values)
{
Collider2D hit = hitsBuffer2D[i];
// collider might be on pelvis, often the NetworkIdentity is in a parent
// (looks in the object itself and then parents)
NetworkIdentity identity = hit.GetComponentInParent<NetworkIdentity>();
// (if an object has a connectionToClient, it is a player)
if (identity != null && identity.connectionToClient != null)
if (conn != null && conn.identity != null)
{
observers.Add(identity.connectionToClient);
// check distance
if (Vector3.Distance(conn.identity.transform.position, position) < visRange)
{
observers.Add(conn);
}
}
}
}
Expand Down

0 comments on commit 2d89f05

Please sign in to comment.