Skip to content

Commit

Permalink
feat: LowLevelInterestManagement
Browse files Browse the repository at this point in the history
A low level interest management base class that allows more advanced interest management by bypassing the built-in HashSet checks
  • Loading branch information
imerr committed Feb 11, 2023
1 parent 6bea6e2 commit ed5b77a
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 155 deletions.
146 changes: 96 additions & 50 deletions Assets/Mirror/Core/InterestManagement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,11 @@ namespace Mirror
{
[DisallowMultipleComponent]
[HelpURL("https://mirror-networking.gitbook.io/docs/guides/interest-management")]
public abstract class InterestManagement : MonoBehaviour
public abstract class InterestManagement : LowLevelInterestManagement
{
// Awake configures InterestManagement in NetworkServer/Client
// Do NOT check for active server or client here.
// Awake must always set the static aoi references.
// make sure to call base.Awake when overwriting!
protected virtual void Awake()
{
if (NetworkServer.aoi == null)
{
NetworkServer.aoi = this;
}
else Debug.LogError($"Only one InterestManagement component allowed. {NetworkServer.aoi.GetType()} has been set up already.");

if (NetworkClient.aoi == null)
{
NetworkClient.aoi = this;
}
else Debug.LogError($"Only one InterestManagement component allowed. {NetworkClient.aoi.GetType()} has been set up already.");
}

[ServerCallback]
public virtual void Reset() {}

// Callback used by the visibility system to determine if an observer
// (player) can see the NetworkIdentity. If this function returns true,
// the network connection will be added as an observer.
// conn: Network connection of a player.
// returns True if the player can see this object.
public abstract bool OnCheckObserver(NetworkIdentity identity, NetworkConnectionToClient newObserver);
// allocate newObservers helper HashSet only once
readonly HashSet<NetworkConnectionToClient> newObservers =
new HashSet<NetworkConnectionToClient>();

// rebuild observers for the given NetworkIdentity.
// Server will automatically spawn/despawn added/removed ones.
Expand Down Expand Up @@ -72,29 +47,100 @@ protected void RebuildAll()
}
}

// Callback used by the visibility system for objects on a host.
// Objects on a host (with a local client) cannot be disabled or
// destroyed when they are not visible to the local client. So this
// function is called to allow custom code to hide these objects. A
// typical implementation will disable renderer components on the
// object. This is only called on local clients on a host.
// => need the function in here and virtual so people can overwrite!
// => not everyone wants to hide renderers!
[ServerCallback]
public virtual void SetHostVisibility(NetworkIdentity identity, bool visible)
// rebuild observers via interest management system
public override void OnRequestRebuild(NetworkIdentity identity, bool initialize)
{
foreach (Renderer rend in identity.GetComponentsInChildren<Renderer>())
rend.enabled = visible;
}
// clear newObservers hashset before using it
newObservers.Clear();

/// <summary>Called on the server when a new networked object is spawned.</summary>
// (useful for 'only rebuild if changed' interest management algorithms)
[ServerCallback]
public virtual void OnSpawned(NetworkIdentity identity) {}
// not force hidden?
if (identity.visible != Visibility.ForceHidden)
{
OnRebuildObservers(identity, newObservers);
}

/// <summary>Called on the server when a networked object is destroyed.</summary>
// (useful for 'only rebuild if changed' interest management algorithms)
[ServerCallback]
public virtual void OnDestroyed(NetworkIdentity identity) {}
// IMPORTANT: AFTER rebuilding add own player connection in any case
// to ensure player always sees himself no matter what.
// -> OnRebuildObservers might clear observers, so we need to add
// the player's own connection AFTER. 100% fail safe.
// -> fixes https://github.com/vis2k/Mirror/issues/692 where a
// player might teleport out of the ProximityChecker's cast,
// losing the own connection as observer.
if (identity.connectionToClient != null)
{
newObservers.Add(identity.connectionToClient);
}

bool changed = false;

// add all newObservers that aren't in .observers yet
foreach (NetworkConnectionToClient conn in newObservers)
{
// only add ready connections.
// otherwise the player might not be in the world yet or anymore
if (conn != null && conn.isReady)
{
if (initialize || !identity.observers.ContainsKey(conn.connectionId))
{
// new observer
conn.AddToObserving(identity);
// Debug.Log($"New Observer for {gameObject} {conn}");
changed = true;
}
}
}

// remove all old .observers that aren't in newObservers anymore
foreach (NetworkConnectionToClient conn in identity.observers.Values)
{
if (!newObservers.Contains(conn))
{
// removed observer
conn.RemoveFromObserving(identity, false);
// Debug.Log($"Removed Observer for {gameObject} {conn}");
changed = true;
}
}

// copy new observers to observers
if (changed)
{
identity.observers.Clear();
foreach (NetworkConnectionToClient conn in newObservers)
{
if (conn != null && conn.isReady)
identity.observers.Add(conn.connectionId, conn);
}
}

// special case for host mode: we use SetHostVisibility to hide
// NetworkIdentities that aren't in observer range from host.
// this is what games like Dota/Counter-Strike do too, where a host
// does NOT see all players by default. they are in memory, but
// hidden to the host player.
//
// this code is from UNET, it's a bit strange but it works:
// * it hides newly connected identities in host mode
// => that part was the intended behaviour
// * it hides ALL NetworkIdentities in host mode when the host
// connects but hasn't selected a character yet
// => this only works because we have no .localConnection != null
// check. at this stage, localConnection is null because
// StartHost starts the server first, then calls this code,
// then starts the client and sets .localConnection. so we can
// NOT add a null check without breaking host visibility here.
// * it hides ALL NetworkIdentities in server-only mode because
// observers never contain the 'null' .localConnection
// => that was not intended, but let's keep it as it is so we
// don't break anything in host mode. it's way easier than
// iterating all identities in a special function in StartHost.
if (initialize)
{
if (!newObservers.Contains(NetworkServer.localConnection))
{
SetHostVisibility(identity, false);
}
}
}
}
}
91 changes: 91 additions & 0 deletions Assets/Mirror/Core/LowLevelInterestManagement.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// interest management component for custom solutions like
// distance based, spatial hashing, raycast based, etc.
using System.Collections.Generic;
using UnityEngine;

namespace Mirror
{
[DisallowMultipleComponent]
[HelpURL("https://mirror-networking.gitbook.io/docs/guides/interest-management")]
public abstract class LowLevelInterestManagement : MonoBehaviour
{
// Awake configures LowLevelInterestManagement in NetworkServer/Client
// Do NOT check for active server or client here.
// Awake must always set the static aoi references.
// make sure to call base.Awake when overwriting!
protected virtual void Awake()
{
if (NetworkServer.aoi == null)
{
NetworkServer.aoi = this;
}
else Debug.LogError($"Only one InterestManagement component allowed. {NetworkServer.aoi.GetType()} has been set up already.");

if (NetworkClient.aoi == null)
{
NetworkClient.aoi = this;
}
else Debug.LogError($"Only one InterestManagement component allowed. {NetworkClient.aoi.GetType()} has been set up already.");
}

[ServerCallback]
public virtual void Reset() {}

// Callback used by the visibility system to determine if an observer
// (player) can see the NetworkIdentity. If this function returns true,
// the network connection will be added as an observer.
// conn: Network connection of a player.
// returns True if the player can see this object.
public abstract bool OnCheckObserver(NetworkIdentity identity, NetworkConnectionToClient newObserver);


// Callback used by the visibility system for objects on a host.
// Objects on a host (with a local client) cannot be disabled or
// destroyed when they are not visible to the local client. So this
// function is called to allow custom code to hide these objects. A
// typical implementation will disable renderer components on the
// object. This is only called on local clients on a host.
// => need the function in here and virtual so people can overwrite!
// => not everyone wants to hide renderers!
[ServerCallback]
public virtual void SetHostVisibility(NetworkIdentity identity, bool visible)
{
foreach (Renderer rend in identity.GetComponentsInChildren<Renderer>())
rend.enabled = visible;
}

/// <summary>Called on the server when a new networked object is spawned.</summary>
// (useful for 'only rebuild if changed' interest management algorithms)
[ServerCallback]
public virtual void OnSpawned(NetworkIdentity identity) {}

/// <summary>Called on the server when a networked object is destroyed.</summary>
// (useful for 'only rebuild if changed' interest management algorithms)
[ServerCallback]
public virtual void OnDestroyed(NetworkIdentity identity) {}

public abstract void OnRequestRebuild(NetworkIdentity identity, bool initialize);

/// <summary>
/// Adds the specified connection to the observers of identity
/// </summary>
/// <param name="identity"></param>
/// <param name="connection"></param>
protected void AddObserver(NetworkConnectionToClient connection, NetworkIdentity identity)
{
connection.AddToObserving(identity);
identity.observers.Add(connection.connectionId, connection);
}

/// <summary>
/// Removes the specified connection from the observers of identity
/// </summary>
/// <param name="identity"></param>
/// <param name="connection"></param>
protected void RemoveObserver(NetworkConnectionToClient connection, NetworkIdentity identity)
{
connection.RemoveFromObserving(identity, false);
identity.observers.Remove(connection.connectionId);
}
}
}
3 changes: 3 additions & 0 deletions Assets/Mirror/Core/LowLevelInterestManagement.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Assets/Mirror/Core/NetworkClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ public static partial class NetworkClient

// interest management component (optional)
// only needed for SetHostVisibility
public static InterestManagement aoi;
public static LowLevelInterestManagement aoi;

// scene loading
public static bool isLoadingScene;
Expand Down
Loading

0 comments on commit ed5b77a

Please sign in to comment.