namespace Fusion.Addons.InterestManagement
{
using System;
using System.Collections.Generic;
using UnityEngine;
///
/// Global interest manager registered as a singleton on NetworkRunner.
/// Provides access to PlayerInterestManager and PlayerInterestView instances and methods for filtering.
/// Tracks global interest providers.
/// There is always only one instance of GlobalInterestManager.
///
[DefaultExecutionOrder(GlobalInterestManager.EXECUTION_ORDER)]
public class GlobalInterestManager : SimulationBehaviour
{
// CONSTANTS
public const int EXECUTION_ORDER = InterestProvider.EXECUTION_ORDER + 500;
// PRIVATE MEMBERS
[SerializeField][Tooltip("Size of each cell in the interest grid. Use only for Client/Server architecture.")]
private EInterestCellSize _interestCellSize = EInterestCellSize.Default;
private List _runtimeProviders = new List();
private List _playerInterestViews = new List();
private Dictionary _playerInterestManagers = new Dictionary();
private static Stack _runtimeProvidersPool = new Stack();
// PUBLIC METHODS
///
/// Register interest provider to self.
///
/// Reference to interest provider.
/// How long the interest provider stays registered.
public bool RegisterProvider(IInterestProvider interestProvider, float duration = 0.0f)
{
int version = interestProvider.Version;
for (int i = 0, count = _runtimeProviders.Count; i < count; ++i)
{
RuntimeProvider runtimeProvider = _runtimeProviders[i];
if (ReferenceEquals(runtimeProvider.Provider, interestProvider) == true)
{
bool hasChanged = false;
if (duration > runtimeProvider.Duration)
{
runtimeProvider.Duration = duration;
hasChanged = true;
}
if (version != runtimeProvider.Version)
{
runtimeProvider.Version = version;
hasChanged = true;
}
return hasChanged;
}
}
if (_runtimeProvidersPool.TryPop(out RuntimeProvider newProvider) == false)
{
newProvider = new RuntimeProvider();
}
newProvider.Provider = interestProvider;
newProvider.Duration = duration;
newProvider.Version = version;
_runtimeProviders.Add(newProvider);
return true;
}
///
/// Unregister interest provider from self.
///
/// Reference to interest provider.
/// If true, the interest provider is unregistered recursively from all registered interest providers.
public bool UnregisterProvider(IInterestProvider interestProvider, bool recursive)
{
bool unregistered = false;
if (recursive == true)
{
for (int i = 0, count = _runtimeProviders.Count; i < count; ++i)
{
unregistered |= _runtimeProviders[i].Provider.UnregisterProvider(interestProvider, true);
}
}
for (int i = 0, count = _runtimeProviders.Count; i < count; ++i)
{
RuntimeProvider runtimeProvider = _runtimeProviders[i];
if (ReferenceEquals(runtimeProvider.Provider, interestProvider) == true)
{
_runtimeProviders.RemoveAt(i);
runtimeProvider.Clear();
_runtimeProvidersPool.Push(runtimeProvider);
unregistered = true;
break;
}
}
return unregistered;
}
///
/// Get all registered interest providers.
/// The set is cleared on the beginning and the result is sorted.
///
/// Set of interest providers that will be filled.
/// If true, registered interest providers are processed recursively.
public void GetProviders(InterestProviderSet interestProviders, bool recursive)
{
interestProviders.Clear();
for (int i = 0, count = _runtimeProviders.Count; i < count; ++i)
{
RuntimeProvider runtimeProvider = _runtimeProviders[i];
IInterestProvider interestProvider = runtimeProvider.Provider;
if (interestProvider.Version == runtimeProvider.Version)
{
interestProviders.Add(interestProvider);
if (recursive == true)
{
interestProvider.GetProviders(interestProviders, true);
}
}
}
interestProviders.Sort();
}
///
/// Get all interest providers that are relevant to the player.
/// This processes active PlayerInterestView (PlayerInterestManager.InterestView) and all registered interest providers.
///
/// Player reference.
/// Interest providers set that will be filled.
public bool GetProvidersForPlayer(PlayerRef player, InterestProviderSet interestProviders)
{
return GetProvidersForPlayer(player, interestProviders, out _);
}
///
/// Get all interest providers that are relevant to the player.
/// This processes active PlayerInterestView (PlayerInterestManager.InterestView) and all registered interest providers.
///
/// Player reference.
/// Interest providers set that will be filled.
/// Returned reference to active PlayerInterestView.
public bool GetProvidersForPlayer(PlayerRef player, InterestProviderSet interestProviders, out PlayerInterestView playerView)
{
interestProviders.Clear();
playerView = null;
if (_playerInterestManagers.TryGetValue(player, out PlayerInterestManager playerInterestManager) == false)
return false;
if (playerInterestManager.Player != player)
return false;
playerView = playerInterestManager.InterestView;
if (playerView == null)
return false;
IInterestProvider playerInterestProvider = playerView;
if (playerInterestProvider.IsPlayerInterested(playerView) == true)
{
interestProviders.Add(playerInterestProvider);
playerInterestProvider.GetProvidersForPlayer(playerView, interestProviders, true);
}
for (int i = 0, count = _runtimeProviders.Count; i < count; ++i)
{
RuntimeProvider runtimeProvider = _runtimeProviders[i];
IInterestProvider interestProvider = runtimeProvider.Provider;
if (interestProvider.Version == runtimeProvider.Version && interestProvider.IsPlayerInterested(playerView) == true)
{
interestProviders.Add(interestProvider);
interestProvider.GetProvidersForPlayer(playerView, interestProviders, true);
}
}
interestProviders.Sort();
return true;
}
///
/// Try getting registered PlayerInterestManager for a given player.
///
/// Player reference.
/// Returned reference to registered PlayerInterestManager.
public bool TryGetPlayerManager(PlayerRef player, out PlayerInterestManager playerInterestManager)
{
if (_playerInterestManagers.TryGetValue(player, out playerInterestManager) == true && playerInterestManager != null && playerInterestManager.Player == player)
return true;
playerInterestManager = default;
return false;
}
///
/// Registers PlayerInterestManager for a given player.
///
/// Player reference.
/// Reference to PlayerInterestManager instance that will be registered.
public bool RegisterPlayerManager(PlayerRef player, PlayerInterestManager playerInterestManager)
{
if (player.IsRealPlayer == false)
return false;
_playerInterestManagers[player] = playerInterestManager;
return true;
}
///
/// Unregisters PlayerInterestManager for a given player.
/// If the manager reference doesn't match, the currently registered manager stays untouched.
///
/// Player reference.
/// Reference to PlayerInterestManager instance that will be unregistered.
public bool UnregisterPlayerManager(PlayerRef player, PlayerInterestManager playerInterestManager)
{
if (_playerInterestManagers.TryGetValue(player, out PlayerInterestManager registeredManager) == true && ReferenceEquals(registeredManager, playerInterestManager) == true)
{
_playerInterestManagers.Remove(player);
return true;
}
return false;
}
///
/// Fills the list with all valid player interest managers.
///
public void GetPlayerManagers(List playerInterestManagers)
{
playerInterestManagers.Clear();
foreach (var playerInterestManagerKVP in _playerInterestManagers)
{
PlayerInterestManager playerInterestManager = playerInterestManagerKVP.Value;
playerInterestManagers.Add(playerInterestManager);
}
}
///
/// Fills the list with all valid player interest managers using a filter function.
///
public void GetPlayerManagers(List playerInterestManagers, Func filter)
{
playerInterestManagers.Clear();
foreach (var playerInterestManagerKVP in _playerInterestManagers)
{
PlayerInterestManager playerInterestManager = playerInterestManagerKVP.Value;
if (filter(playerInterestManager) == true)
{
playerInterestManagers.Add(playerInterestManager);
}
}
}
/// Fills the list with all valid player interest managers using a filter function.
public void GetPlayerManagers (List playerInterestManagers, Func filter, T1 arg1) { playerInterestManagers.Clear(); foreach (var playerInterestProviderItem in _playerInterestManagers) { PlayerInterestManager playerInterestProvider = playerInterestProviderItem.Value; if (filter(playerInterestProvider, arg1) == true) { playerInterestManagers.Add(playerInterestProvider); } } }
/// Fills the list with all valid player interest managers using a filter function.
public void GetPlayerManagers (List playerInterestManagers, Func filter, T1 arg1, T2 arg2) { playerInterestManagers.Clear(); foreach (var playerInterestProviderItem in _playerInterestManagers) { PlayerInterestManager playerInterestProvider = playerInterestProviderItem.Value; if (filter(playerInterestProvider, arg1, arg2) == true) { playerInterestManagers.Add(playerInterestProvider); } } }
/// Fills the list with all valid player interest managers using a filter function.
public void GetPlayerManagers (List playerInterestManagers, Func filter, T1 arg1, T2 arg2, T3 arg3) { playerInterestManagers.Clear(); foreach (var playerInterestProviderItem in _playerInterestManagers) { PlayerInterestManager playerInterestProvider = playerInterestProviderItem.Value; if (filter(playerInterestProvider, arg1, arg2, arg3) == true) { playerInterestManagers.Add(playerInterestProvider); } } }
/// Fills the list with all valid player interest managers using a filter function.
public void GetPlayerManagers (List playerInterestManagers, Func filter, T1 arg1, T2 arg2, T3 arg3, T4 arg4) { playerInterestManagers.Clear(); foreach (var playerInterestProviderItem in _playerInterestManagers) { PlayerInterestManager playerInterestProvider = playerInterestProviderItem.Value; if (filter(playerInterestProvider, arg1, arg2, arg3, arg4) == true) { playerInterestManagers.Add(playerInterestProvider); } } }
/// Fills the list with all valid player interest managers using a filter function.
public void GetPlayerManagers (List playerInterestManagers, Func filter, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { playerInterestManagers.Clear(); foreach (var playerInterestProviderItem in _playerInterestManagers) { PlayerInterestManager playerInterestProvider = playerInterestProviderItem.Value; if (filter(playerInterestProvider, arg1, arg2, arg3, arg4, arg5) == true) { playerInterestManagers.Add(playerInterestProvider); } } }
/// Fills the list with all valid player interest managers using a filter function.
public void GetPlayerManagers(List playerInterestManagers, Func filter, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) { playerInterestManagers.Clear(); foreach (var playerInterestProviderItem in _playerInterestManagers) { PlayerInterestManager playerInterestProvider = playerInterestProviderItem.Value; if (filter(playerInterestProvider, arg1, arg2, arg3, arg4, arg5, arg6) == true) { playerInterestManagers.Add(playerInterestProvider); } } }
///
/// Try getting active PlayerInterestView for a given player.
/// There might be more PlayerInterestView instances registered, but only one is active (PlayerInterestManager.InterestView).
///
/// Player reference.
/// Returned reference to active PlayerInterestView.
public bool TryGetPlayerView(PlayerRef player, out PlayerInterestView playerView)
{
if (_playerInterestManagers.TryGetValue(player, out PlayerInterestManager playerInterestManager) == true && playerInterestManager != null && playerInterestManager.Player == player)
{
playerView = playerInterestManager.InterestView;
return true;
}
playerView = default;
return false;
}
///
/// Registers PlayerInterestView. One player can have multiple views registered at the same time.
///
/// Reference to PlayerInterestView instance that will be registered.
public bool RegisterPlayerView(PlayerInterestView playerView)
{
for (int i = 0, count = _playerInterestViews.Count; i < count; ++i)
{
PlayerInterestView registeredView = _playerInterestViews[i];
if (ReferenceEquals(registeredView, playerView) == true)
return false;
}
_playerInterestViews.Add(playerView);
return true;
}
///
/// Unregisters PlayerInterestView.
///
/// Reference to PlayerInterestView instance that will be unregistered.
public bool UnregisterPlayerView(PlayerInterestView playerView)
{
for (int i = 0, count = _playerInterestViews.Count; i < count; ++i)
{
PlayerInterestView registeredView = _playerInterestViews[i];
if (ReferenceEquals(registeredView, playerView) == true)
{
_playerInterestViews.RemoveAt(i);
return true;
}
}
return false;
}
///
/// Fills the list with all valid player interest views.
///
public void GetPlayerViews(List playerViews)
{
playerViews.Clear();
playerViews.AddRange(_playerInterestViews);
}
///
/// Fills the list with all valid player interest views using a filter function.
///
public void GetPlayerViews(List playerViews, Func filter)
{
playerViews.Clear();
foreach (PlayerInterestView playerView in _playerInterestViews)
{
if (filter(playerView) == true)
{
playerViews.Add(playerView);
}
}
}
/// Fills the list with all valid player interest views using a filter function.
public void GetPlayerViews (List playerViews, Func filter, T1 arg1) { playerViews.Clear(); foreach (PlayerInterestView playerView in _playerInterestViews) { if (filter(playerView, arg1) == true) { playerViews.Add(playerView); } } }
/// Fills the list with all valid player interest views using a filter function.
public void GetPlayerViews (List playerViews, Func filter, T1 arg1, T2 arg2) { playerViews.Clear(); foreach (PlayerInterestView playerView in _playerInterestViews) { if (filter(playerView, arg1, arg2) == true) { playerViews.Add(playerView); } } }
/// Fills the list with all valid player interest views using a filter function.
public void GetPlayerViews (List playerViews, Func filter, T1 arg1, T2 arg2, T3 arg3) { playerViews.Clear(); foreach (PlayerInterestView playerView in _playerInterestViews) { if (filter(playerView, arg1, arg2, arg3) == true) { playerViews.Add(playerView); } } }
/// Fills the list with all valid player interest views using a filter function.
public void GetPlayerViews (List playerViews, Func filter, T1 arg1, T2 arg2, T3 arg3, T4 arg4) { playerViews.Clear(); foreach (PlayerInterestView playerView in _playerInterestViews) { if (filter(playerView, arg1, arg2, arg3, arg4) == true) { playerViews.Add(playerView); } } }
/// Fills the list with all valid player interest views using a filter function.
public void GetPlayerViews (List playerViews, Func filter, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { playerViews.Clear(); foreach (PlayerInterestView playerView in _playerInterestViews) { if (filter(playerView, arg1, arg2, arg3, arg4, arg5) == true) { playerViews.Add(playerView); } } }
/// Fills the list with all valid player interest views using a filter function.
public void GetPlayerViews(List playerViews, Func filter, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) { playerViews.Clear(); foreach (PlayerInterestView playerView in _playerInterestViews) { if (filter(playerView, arg1, arg2, arg3, arg4, arg5, arg6) == true) { playerViews.Add(playerView); } } }
///
/// Fills the list with all valid player interest views using a filter function.
///
public void GetPlayerViews(List playerViews, Func filter) where T : PlayerInterestView
{
playerViews.Clear();
foreach (PlayerInterestView playerView in _playerInterestViews)
{
if (playerView is T playerViewAsT && filter(playerViewAsT) == true)
{
playerViews.Add(playerViewAsT);
}
}
}
/// Fills the list with all valid player interest views using a filter function.
public void GetPlayerViews (List playerViews, Func filter, T1 arg1) where T : PlayerInterestView { playerViews.Clear(); foreach (PlayerInterestView playerView in _playerInterestViews) { if (playerView is T playerViewAsT && filter(playerViewAsT, arg1) == true) { playerViews.Add(playerViewAsT); } } }
/// Fills the list with all valid player interest views using a filter function. where T : PlayerInterestView
public void GetPlayerViews (List playerViews, Func filter, T1 arg1, T2 arg2) where T : PlayerInterestView { playerViews.Clear(); foreach (PlayerInterestView playerView in _playerInterestViews) { if (playerView is T playerViewAsT && filter(playerViewAsT, arg1, arg2) == true) { playerViews.Add(playerViewAsT); } } }
/// Fills the list with all valid player interest views using a filter function. where T : PlayerInterestView
public void GetPlayerViews (List playerViews, Func filter, T1 arg1, T2 arg2, T3 arg3) where T : PlayerInterestView { playerViews.Clear(); foreach (PlayerInterestView playerView in _playerInterestViews) { if (playerView is T playerViewAsT && filter(playerViewAsT, arg1, arg2, arg3) == true) { playerViews.Add(playerViewAsT); } } }
/// Fills the list with all valid player interest views using a filter function. where T : PlayerInterestView
public void GetPlayerViews (List playerViews, Func filter, T1 arg1, T2 arg2, T3 arg3, T4 arg4) where T : PlayerInterestView { playerViews.Clear(); foreach (PlayerInterestView playerView in _playerInterestViews) { if (playerView is T playerViewAsT && filter(playerViewAsT, arg1, arg2, arg3, arg4) == true) { playerViews.Add(playerViewAsT); } } }
/// Fills the list with all valid player interest views using a filter function. where T : PlayerInterestView
public void GetPlayerViews (List playerViews, Func filter, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) where T : PlayerInterestView { playerViews.Clear(); foreach (PlayerInterestView playerView in _playerInterestViews) { if (playerView is T playerViewAsT && filter(playerViewAsT, arg1, arg2, arg3, arg4, arg5) == true) { playerViews.Add(playerViewAsT); } } }
/// Fills the list with all valid player interest views using a filter function. where T : PlayerInterestView
public void GetPlayerViews(List playerViews, Func filter, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) where T : PlayerInterestView { playerViews.Clear(); foreach (PlayerInterestView playerView in _playerInterestViews) { if (playerView is T playerViewAsT && filter(playerViewAsT, arg1, arg2, arg3, arg4, arg5, arg6) == true) { playerViews.Add(playerViewAsT); } } }
// GlobalInterestManager INTERFACE
///
/// Called on the end of Awake(), after updating internal providers list.
///
protected void OnAwake() {}
///
/// Called on the end of Update(), after updating internal providers list.
///
protected void OnUpdate() {}
// MonoBehaviour INTERFACE
private void Awake()
{
NetworkRunner runner = GetComponent();
if (runner == null)
{
Debug.LogError($"{GetType().Name} should be instantiated on a game object with NetworkRunner component.");
}
if (_interestCellSize != EInterestCellSize.Default)
{
Simulation.AreaOfInterest.CELL_SIZE = (int)_interestCellSize;
Debug.LogWarning($"[{GetType().Name}] Set interest cell size to {Simulation.AreaOfInterest.CELL_SIZE}m.");
}
OnAwake();
}
private void Update()
{
if (_runtimeProviders.Count > 0)
{
float deltaTime = Time.unscaledDeltaTime;
for (int i = _runtimeProviders.Count - 1; i >= 0; --i)
{
RuntimeProvider runtimeProvider = _runtimeProviders[i];
IInterestProvider interestProvider = runtimeProvider.Provider;
bool isValid = interestProvider.Version == runtimeProvider.Version;
if (isValid == true)
{
if (runtimeProvider.Duration > 0.0f)
{
runtimeProvider.Duration -= deltaTime;
if (runtimeProvider.Duration <= 0.0f)
{
isValid = false;
}
}
}
if (isValid == false)
{
_runtimeProviders.RemoveAt(i);
runtimeProvider.Clear();
_runtimeProvidersPool.Push(runtimeProvider);
}
}
}
OnUpdate();
}
// DATA STRUCTURES
private enum EInterestCellSize
{
Default = 0,
_8 = 8,
_16 = 16,
_24 = 24,
_32 = 32,
_48 = 48,
_64 = 64,
}
private sealed class RuntimeProvider
{
public IInterestProvider Provider;
public float Duration;
public int Version;
public void Clear()
{
Provider = default;
Duration = default;
Version = default;
}
}
}
}