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; } } } }