using FishNet.Connection; using FishNet.Managing; using FishNet.Managing.Logging; using FishNet.Managing.Server; using FishNet.Object; using FishNet.Observing; using FishNet.Utility.Extension; using FishNet.Utility.Performance; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using UnityEngine; namespace FishNet.Component.Observing { /// <summary> /// When this observer condition is placed on an object, a client must be within the same match to view the object. /// </summary> [CreateAssetMenu(menuName = "FishNet/Observers/Match Condition", fileName = "New Match Condition")] public class MatchCondition : ObserverCondition { #region Private. /// <summary> /// /// </summary> private static Dictionary<int, HashSet<NetworkConnection>> _matchConnections = new Dictionary<int, HashSet<NetworkConnection>>(); /// <summary> /// Matches and connections in each match. /// </summary> public static IReadOnlyDictionary<int, HashSet<NetworkConnection>> MatchConnections => _matchConnections; /// <summary> /// /// </summary> /// //todo this needs to hold hashset so conns can be in multiple matches. private static Dictionary<NetworkConnection, int> _connectionMatch = new Dictionary<NetworkConnection, int>(); /// <summary> /// Match a connection is in. /// </summary> public static IReadOnlyDictionary<NetworkConnection, int> ConnectionMatch => _connectionMatch; /// <summary> /// /// </summary> private static Dictionary<int, HashSet<NetworkObject>> _matchObjects = new Dictionary<int, HashSet<NetworkObject>>(); /// <summary> /// Matches and connections in each match. /// </summary> public static IReadOnlyDictionary<int, HashSet<NetworkObject>> MatchObjects => _matchObjects; /// <summary> /// /// </summary> /// //todo this needs to hold hashset so conns can be in multiple matches. private static Dictionary<NetworkObject, int> _objectMatch = new Dictionary<NetworkObject, int>(); /// <summary> /// Match a connection is in. /// </summary> public static IReadOnlyDictionary<NetworkObject, int> ObjectMatch => _objectMatch; #endregion public void ConditionConstructor() { } #region Add to match NetworkConnection. /// <summary> /// Adds a connection to a match. /// </summary> /// <param name="match">Match to add conn to.</param> /// <param name="conn">Connection to add to match.</param> /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param> /// <param name="replaceMatch">True to replace other matches with the new match.</param> public static void AddToMatch(int match, NetworkConnection conn, NetworkManager manager = null, bool replaceMatch = false) { if (replaceMatch) RemoveFromMatchWithoutRebuild(conn, manager); HashSet<NetworkConnection> results; if (!_matchConnections.TryGetValueIL2CPP(match, out results)) { results = new HashSet<NetworkConnection>(); _matchConnections.Add(match, results); } bool r = results.Add(conn); _connectionMatch[conn] = match; if (r) FinalizeChange(match, results, manager); } /// <summary> /// Adds connections to a match. /// </summary> /// <param name="match">Match to add conns to.</param> /// <param name="conns">Connections to add to match.</param> /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param> /// <param name="replaceMatch">True to replace other matches with the new match.</param> public static void AddToMatch(int match, NetworkConnection[] conns, NetworkManager manager = null, bool replaceMatch = false) { AddToMatch(match, conns.ToList(), manager, replaceMatch); } /// <summary> /// Adds connections to a match. /// </summary> /// <param name="match">Match to add conns to.</param> /// <param name="conns">Connections to add to match.</param> /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param> /// <param name="replaceMatch">True to replace other matches with the new match.</param> public static void AddToMatch(int match, List<NetworkConnection> conns, NetworkManager manager = null, bool replaceMatch = false) { if (replaceMatch) { foreach (NetworkConnection nc in conns) RemoveFromMatchWithoutRebuild(nc, manager); } HashSet<NetworkConnection> results; if (!_matchConnections.TryGetValueIL2CPP(match, out results)) { results = new HashSet<NetworkConnection>(); _matchConnections.Add(match, results); } bool r = false; for (int i = 0; i < conns.Count; i++) { NetworkConnection c = conns[i]; r |= results.Add(c); _connectionMatch[c] = match; } if (r) FinalizeChange(match, results, manager); } #endregion #region Add to match NetworkObject. /// <summary> /// Adds an object to a match. /// </summary> /// <param name="match">Match to add conn to.</param> /// <param name="nob">Connection to add to match.</param> /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param> /// <param name="replaceMatch">True to replace other matches with the new match.</param> public static void AddToMatch(int match, NetworkObject nob, NetworkManager manager = null, bool replaceMatch = false) { if (replaceMatch) RemoveFromMatchWithoutRebuild(nob, manager); HashSet<NetworkObject> results; if (!_matchObjects.TryGetValueIL2CPP(match, out results)) { results = new HashSet<NetworkObject>(); _matchObjects.Add(match, results); } bool r = results.Add(nob); _objectMatch[nob] = match; if (r) FinalizeChange(match, results, nob, manager); } /// <summary> /// Adds objects to a match. /// </summary> /// <param name="match">Match to add conns to.</param> /// <param name="nobs">Connections to add to match.</param> /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param> /// <param name="replaceMatch">True to replace other matches with the new match.</param> public static void AddToMatch(int match, NetworkObject[] nobs, NetworkManager manager = null, bool replaceMatch = false) { AddToMatch(match, nobs.ToList(), manager, replaceMatch); } /// <summary> /// Adds objects to a match. /// </summary> /// <param name="match">Match to add conns to.</param> /// <param name="nobs">Connections to add to match.</param> /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param> /// <param name="replaceMatch">True to replace other matches with the new match.</param> public static void AddToMatch(int match, List<NetworkObject> nobs, NetworkManager manager = null, bool replaceMatch = false) { if (replaceMatch) { foreach (NetworkObject n in nobs) RemoveFromMatchWithoutRebuild(n, manager); } HashSet<NetworkObject> results; if (!_matchObjects.TryGetValueIL2CPP(match, out results)) { results = new HashSet<NetworkObject>(); _matchObjects.Add(match, results); } bool r = false; for (int i = 0; i < nobs.Count; i++) { NetworkObject n = nobs[i]; r |= results.Add(n); _objectMatch[n] = match; } if (r) FinalizeChange(match, results, nobs, manager); } #endregion #region Remove from match NetworkConnection. /// <summary> /// Removes a connection from any match without rebuilding observers. /// </summary> /// <param name="conn">Connection to remove from matches.</param> /// <param name="manager">NetworkManager connection belongs to. This is not currently used.</param> internal static bool RemoveFromMatchWithoutRebuild(NetworkConnection conn, NetworkManager manager) { bool removed = false; //If found to be in a match. if (_connectionMatch.TryGetValueIL2CPP(conn, out int match)) { //If match is found. if (_matchConnections.TryGetValue(match, out HashSet<NetworkConnection> conns)) { removed |= conns.Remove(conn); //If no more in hashset remove match. if (conns.Count == 0) _matchConnections.Remove(match); } } //Remove from connectionMatch. _connectionMatch.Remove(conn); return removed; } /// <summary> /// Removes a connection from all matches. /// </summary> /// <param name="conn">NetworkConnection to remove.</param> /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param> public static void RemoveFromMatch(NetworkConnection conn, NetworkManager manager) { bool removed = RemoveFromMatchWithoutRebuild(conn, manager); if (removed) GetServerObjects(manager).RebuildObservers(); } /// <summary> /// Removes a connection from a match. /// </summary> /// <param name="match">Match to remove conn from.</param> /// <param name="conn">Connection to remove from match.</param> /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void RemoveFromMatch(int match, NetworkConnection conn, NetworkManager manager) { if (_matchConnections.TryGetValueIL2CPP(match, out HashSet<NetworkConnection> results)) { bool r = results.Remove(conn); _connectionMatch.Remove(conn); if (r) FinalizeChange(match, results, manager); } } /// <summary> /// Removes connections from a match. /// </summary> /// <param name="match">Match to remove conns from.</param> /// <param name="conns">Connections to remove from match.</param> /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void RemoveFromMatch(int match, NetworkConnection[] conns, NetworkManager manager) { if (_matchConnections.TryGetValueIL2CPP(match, out HashSet<NetworkConnection> results)) { bool r = false; for (int i = 0; i < conns.Length; i++) { NetworkConnection c = conns[i]; r |= results.Remove(c); _connectionMatch.Remove(c); } if (r) FinalizeChange(match, results, manager); } } /// <summary> /// Removes connections from a match. /// </summary> /// <param name="match">Match to remove conns from.</param> /// <param name="conns">Connections to remove from match.</param> /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void RemoveFromMatch(int match, List<NetworkConnection> conns, NetworkManager manager) { if (_matchConnections.TryGetValueIL2CPP(match, out HashSet<NetworkConnection> results)) { bool r = false; for (int i = 0; i < conns.Count; i++) { NetworkConnection c = conns[i]; r |= results.Remove(c); _connectionMatch.Remove(c); } if (r) FinalizeChange(match, results, manager); } } #endregion #region Remove from match NetworkObject. /// <summary> /// Removes a network object from any match without rebuilding observers. /// </summary> /// <param name="nob">NetworkObject to remove.</param> /// <param name="manager">Manager which the network object belongs to. This value is not yet used.</param> internal static bool RemoveFromMatchWithoutRebuild(NetworkObject nob, NetworkManager manager = null) { bool removed = false; //If found to be in a match. if (_objectMatch.TryGetValueIL2CPP(nob, out int match)) { //If match is found. if (_matchObjects.TryGetValue(match, out HashSet<NetworkObject> nobs)) { removed |= nobs.Remove(nob); //If no more in hashset remove match. if (nobs.Count == 0) _matchObjects.Remove(match); } } //Remove from connectionMatch. _objectMatch.Remove(nob); return removed; } /// <summary> /// Removes nob from all matches. /// </summary> /// <param name="nob">NetworkObject to remove.</param> /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param> public static void RemoveFromMatch(NetworkObject nob, NetworkManager manager = null) { bool removed = RemoveFromMatchWithoutRebuild(nob, manager); if (removed) GetServerObjects(manager).RebuildObservers(nob); } /// <summary> /// Removes a network object from all matches. /// </summary> /// <param name="nobs">NetworkObjects to remove.</param> /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param> public static void RemoveFromMatch(NetworkObject[] nobs, NetworkManager manager = null) { RemoveFromMatch(nobs.ToList(), manager); } /// <summary> /// Removes network objects from all matches. /// </summary> /// <param name="nobs">NetworkObjects to remove.</param> /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param> public static void RemoveFromMatch(List<NetworkObject> nobs, NetworkManager manager = null) { bool removed = false; foreach (NetworkObject n in nobs) removed |= RemoveFromMatchWithoutRebuild(n, manager); if (removed) GetServerObjects(manager).RebuildObservers(nobs); } /// <summary> /// Removes a network object from a match. /// </summary> /// <param name="match">Match to remove conn from.</param> /// <param name="nob">NetworkObject to remove from match.</param> /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void RemoveFromMatch(int match, NetworkObject nob, NetworkManager manager = null) { if (_matchObjects.TryGetValueIL2CPP(match, out HashSet<NetworkObject> results)) { bool r = results.Remove(nob); _objectMatch.Remove(nob); if (r) FinalizeChange(match, results, nob, manager); } } /// <summary> /// Removes network objects from a match. /// </summary> /// <param name="match">Match to remove conns from.</param> /// <param name="nobs">NetworkObjects to remove from match.</param> /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void RemoveFromMatch(int match, NetworkObject[] nobs, NetworkManager manager = null) { if (_matchObjects.TryGetValueIL2CPP(match, out HashSet<NetworkObject> results)) { bool r = false; for (int i = 0; i < nobs.Length; i++) { NetworkObject n = nobs[i]; r |= results.Remove(n); _objectMatch.Remove(n); } if (r) FinalizeChange(match, results, nobs, manager); } } /// <summary> /// Removes network objects from a match. /// </summary> /// <param name="match">Match to remove conns from.</param> /// <param name="nobs">NetworkObjects to remove from match.</param> /// <param name="manager">NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used.</param> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void RemoveFromMatch(int match, List<NetworkObject> nobs, NetworkManager manager = null) { if (_matchObjects.TryGetValueIL2CPP(match, out HashSet<NetworkObject> results)) { bool r = false; for (int i = 0; i < nobs.Count; i++) { NetworkObject n = nobs[i]; r |= results.Remove(n); _objectMatch.Remove(n); } if (r) FinalizeChange(match, results, nobs, manager); } } #endregion #region FinalizeChange NetworkConnection. /// <summary> /// Finalizes changes to observers. /// </summary> private static void FinalizeChange(int match, HashSet<NetworkConnection> remainingConnsInMatch, NetworkManager manager) { if (remainingConnsInMatch.Count == 0) _matchConnections.Remove(match); /* Observers on all objects and all conditions have to be rebuilt. * This is because the connection changing matches could * require the connection to be visible for other players in the match, * as well make other connections in the same match visible. * But also make all the objects not associated with connections * of that match visible. In result to tick all of those boxes * all objects need to be rebuilt for all connections. */ GetServerObjects(manager).RebuildObservers(); } #endregion #region FinalizeChange NetworkObject. /// <summary> /// Finalizes changes to observers. /// </summary> private static void FinalizeChange(int match, HashSet<NetworkObject> results, List<NetworkObject> nobs, NetworkManager manager) { ListCache<NetworkObject> cache = ListCaches.GetNetworkObjectCache(); cache.AddValues(nobs); FinalizeChange(match, results, cache, manager); ListCaches.StoreCache(cache); } /// <summary> /// Finalizes changes to observers. /// </summary> private static void FinalizeChange(int match, HashSet<NetworkObject> results, NetworkObject[] nobs, NetworkManager manager) { ListCache<NetworkObject> cache = ListCaches.GetNetworkObjectCache(); cache.AddValues(nobs); FinalizeChange(match, results, cache, manager); ListCaches.StoreCache(cache); } /// <summary> /// Finalizes changes to observers. /// </summary> private static void FinalizeChange(int match, HashSet<NetworkObject> results, NetworkObject nob, NetworkManager manager) { ListCache<NetworkObject> cache = ListCaches.GetNetworkObjectCache(); cache.AddValue(nob); FinalizeChange(match, results, cache, manager); ListCaches.StoreCache(cache); } /// <summary> /// Finalizes changes to observers. /// </summary> private static void FinalizeChange(int match, HashSet<NetworkObject> results, ListCache<NetworkObject> nobs, NetworkManager manager) { if (results.Count == 0) _matchConnections.Remove(match); GetServerObjects(manager).RebuildObservers(nobs); } #endregion /// <summary> /// Returns if the object which this condition resides should be visible to connection. /// </summary> /// <param name="connection">Connection which the condition is being checked for.</param> /// <param name="currentlyAdded">True if the connection currently has visibility of this object.</param> /// <param name="notProcessed">True if the condition was not processed. This can be used to skip processing for performance. While output as true this condition result assumes the previous ConditionMet value.</param> public override bool ConditionMet(NetworkConnection connection, bool alreadyAdded, out bool notProcessed) { //If here then checks are being processed. notProcessed = false; NetworkConnection owner = base.NetworkObject.Owner; /* If object is owned then check if owner * and connection share a match. */ if (owner.IsValid) { //Connection isn't in a match. if (!_connectionMatch.TryGetValueIL2CPP(connection, out int match)) { //Return if this owner is also not in a match. return !_connectionMatch.TryGetValueIL2CPP(owner, out int _); } //Match isn't found. if (!_matchConnections.TryGetValueIL2CPP(match, out HashSet<NetworkConnection> conns)) return false; //If owner is in same match return true. return conns.Contains(owner); } /* If no owner see if the object is in a match and if so * then compare that. */ else { //Object isn't in a match. if (!_objectMatch.TryGetValueIL2CPP(base.NetworkObject, out int objectMatch)) return true; /* See if connection is in the same match as the object. * If connection isn't in a match then it fails. */ if (!_connectionMatch.TryGetValueIL2CPP(connection, out int connectionMatch)) return false; return (connectionMatch == objectMatch); } } /// <summary> /// Returns which ServerObjects to rebuild observers on. /// </summary> /// <param name="nm"></param> /// <returns></returns> private static ServerObjects GetServerObjects(NetworkManager manager) { return (manager == null) ? InstanceFinder.ServerManager.Objects : manager.ServerManager.Objects; } /* //todo this needs to be passing in the network manager to clear on, * otherwise only a single instance of NM is supported. * Users are already forced to specify which NM to add * matches for but the functionality separating different NMs in relation * to such isn't done yet. */ /// <summary> /// Clears all match information without rebuilding. /// </summary> internal static void ClearMatchesWithoutRebuilding() { _connectionMatch.Clear(); _matchConnections.Clear(); _objectMatch.Clear(); _matchObjects.Clear(); } /// <summary> /// True if the condition requires regular updates. /// </summary> /// <returns></returns> public override bool Timed() { return false; } /// <summary> /// Clones referenced ObserverCondition. This must be populated with your conditions settings. /// </summary> /// <returns></returns> public override ObserverCondition Clone() { MatchCondition copy = ScriptableObject.CreateInstance<MatchCondition>(); copy.ConditionConstructor(); return copy; } } }