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 { /// /// When this observer condition is placed on an object, a client must be within the same match to view the object. /// [CreateAssetMenu(menuName = "FishNet/Observers/Match Condition", fileName = "New Match Condition")] public class MatchCondition : ObserverCondition { #region Private. /// /// /// private static Dictionary> _matchConnections = new Dictionary>(); /// /// Matches and connections in each match. /// public static IReadOnlyDictionary> MatchConnections => _matchConnections; /// /// /// /// //todo this needs to hold hashset so conns can be in multiple matches. private static Dictionary _connectionMatch = new Dictionary(); /// /// Match a connection is in. /// public static IReadOnlyDictionary ConnectionMatch => _connectionMatch; /// /// /// private static Dictionary> _matchObjects = new Dictionary>(); /// /// Matches and connections in each match. /// public static IReadOnlyDictionary> MatchObjects => _matchObjects; /// /// /// /// //todo this needs to hold hashset so conns can be in multiple matches. private static Dictionary _objectMatch = new Dictionary(); /// /// Match a connection is in. /// public static IReadOnlyDictionary ObjectMatch => _objectMatch; #endregion public void ConditionConstructor() { } #region Add to match NetworkConnection. /// /// Adds a connection to a match. /// /// Match to add conn to. /// Connection to add to match. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. /// True to replace other matches with the new match. public static void AddToMatch(int match, NetworkConnection conn, NetworkManager manager = null, bool replaceMatch = false) { if (replaceMatch) RemoveFromMatchWithoutRebuild(conn, manager); HashSet results; if (!_matchConnections.TryGetValueIL2CPP(match, out results)) { results = new HashSet(); _matchConnections.Add(match, results); } bool r = results.Add(conn); _connectionMatch[conn] = match; if (r) FinalizeChange(match, results, manager); } /// /// Adds connections to a match. /// /// Match to add conns to. /// Connections to add to match. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. /// True to replace other matches with the new match. public static void AddToMatch(int match, NetworkConnection[] conns, NetworkManager manager = null, bool replaceMatch = false) { AddToMatch(match, conns.ToList(), manager, replaceMatch); } /// /// Adds connections to a match. /// /// Match to add conns to. /// Connections to add to match. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. /// True to replace other matches with the new match. public static void AddToMatch(int match, List conns, NetworkManager manager = null, bool replaceMatch = false) { if (replaceMatch) { foreach (NetworkConnection nc in conns) RemoveFromMatchWithoutRebuild(nc, manager); } HashSet results; if (!_matchConnections.TryGetValueIL2CPP(match, out results)) { results = new HashSet(); _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. /// /// Adds an object to a match. /// /// Match to add conn to. /// Connection to add to match. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. /// True to replace other matches with the new match. public static void AddToMatch(int match, NetworkObject nob, NetworkManager manager = null, bool replaceMatch = false) { if (replaceMatch) RemoveFromMatchWithoutRebuild(nob, manager); HashSet results; if (!_matchObjects.TryGetValueIL2CPP(match, out results)) { results = new HashSet(); _matchObjects.Add(match, results); } bool r = results.Add(nob); _objectMatch[nob] = match; if (r) FinalizeChange(match, results, nob, manager); } /// /// Adds objects to a match. /// /// Match to add conns to. /// Connections to add to match. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. /// True to replace other matches with the new match. public static void AddToMatch(int match, NetworkObject[] nobs, NetworkManager manager = null, bool replaceMatch = false) { AddToMatch(match, nobs.ToList(), manager, replaceMatch); } /// /// Adds objects to a match. /// /// Match to add conns to. /// Connections to add to match. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. /// True to replace other matches with the new match. public static void AddToMatch(int match, List nobs, NetworkManager manager = null, bool replaceMatch = false) { if (replaceMatch) { foreach (NetworkObject n in nobs) RemoveFromMatchWithoutRebuild(n, manager); } HashSet results; if (!_matchObjects.TryGetValueIL2CPP(match, out results)) { results = new HashSet(); _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. /// /// Removes a connection from any match without rebuilding observers. /// /// Connection to remove from matches. /// NetworkManager connection belongs to. This is not currently used. 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 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; } /// /// Removes a connection from all matches. /// /// NetworkConnection to remove. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. public static void RemoveFromMatch(NetworkConnection conn, NetworkManager manager) { bool removed = RemoveFromMatchWithoutRebuild(conn, manager); if (removed) GetServerObjects(manager).RebuildObservers(); } /// /// Removes a connection from a match. /// /// Match to remove conn from. /// Connection to remove from match. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void RemoveFromMatch(int match, NetworkConnection conn, NetworkManager manager) { if (_matchConnections.TryGetValueIL2CPP(match, out HashSet results)) { bool r = results.Remove(conn); _connectionMatch.Remove(conn); if (r) FinalizeChange(match, results, manager); } } /// /// Removes connections from a match. /// /// Match to remove conns from. /// Connections to remove from match. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void RemoveFromMatch(int match, NetworkConnection[] conns, NetworkManager manager) { if (_matchConnections.TryGetValueIL2CPP(match, out HashSet 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); } } /// /// Removes connections from a match. /// /// Match to remove conns from. /// Connections to remove from match. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void RemoveFromMatch(int match, List conns, NetworkManager manager) { if (_matchConnections.TryGetValueIL2CPP(match, out HashSet 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. /// /// Removes a network object from any match without rebuilding observers. /// /// NetworkObject to remove. /// Manager which the network object belongs to. This value is not yet used. 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 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; } /// /// Removes nob from all matches. /// /// NetworkObject to remove. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. public static void RemoveFromMatch(NetworkObject nob, NetworkManager manager = null) { bool removed = RemoveFromMatchWithoutRebuild(nob, manager); if (removed) GetServerObjects(manager).RebuildObservers(nob); } /// /// Removes a network object from all matches. /// /// NetworkObjects to remove. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. public static void RemoveFromMatch(NetworkObject[] nobs, NetworkManager manager = null) { RemoveFromMatch(nobs.ToList(), manager); } /// /// Removes network objects from all matches. /// /// NetworkObjects to remove. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. public static void RemoveFromMatch(List nobs, NetworkManager manager = null) { bool removed = false; foreach (NetworkObject n in nobs) removed |= RemoveFromMatchWithoutRebuild(n, manager); if (removed) GetServerObjects(manager).RebuildObservers(nobs); } /// /// Removes a network object from a match. /// /// Match to remove conn from. /// NetworkObject to remove from match. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void RemoveFromMatch(int match, NetworkObject nob, NetworkManager manager = null) { if (_matchObjects.TryGetValueIL2CPP(match, out HashSet results)) { bool r = results.Remove(nob); _objectMatch.Remove(nob); if (r) FinalizeChange(match, results, nob, manager); } } /// /// Removes network objects from a match. /// /// Match to remove conns from. /// NetworkObjects to remove from match. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void RemoveFromMatch(int match, NetworkObject[] nobs, NetworkManager manager = null) { if (_matchObjects.TryGetValueIL2CPP(match, out HashSet 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); } } /// /// Removes network objects from a match. /// /// Match to remove conns from. /// NetworkObjects to remove from match. /// NetworkManager to rebuild observers on. If null InstanceFinder.NetworkManager will be used. [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void RemoveFromMatch(int match, List nobs, NetworkManager manager = null) { if (_matchObjects.TryGetValueIL2CPP(match, out HashSet 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. /// /// Finalizes changes to observers. /// private static void FinalizeChange(int match, HashSet 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. /// /// Finalizes changes to observers. /// private static void FinalizeChange(int match, HashSet results, List nobs, NetworkManager manager) { ListCache cache = ListCaches.GetNetworkObjectCache(); cache.AddValues(nobs); FinalizeChange(match, results, cache, manager); ListCaches.StoreCache(cache); } /// /// Finalizes changes to observers. /// private static void FinalizeChange(int match, HashSet results, NetworkObject[] nobs, NetworkManager manager) { ListCache cache = ListCaches.GetNetworkObjectCache(); cache.AddValues(nobs); FinalizeChange(match, results, cache, manager); ListCaches.StoreCache(cache); } /// /// Finalizes changes to observers. /// private static void FinalizeChange(int match, HashSet results, NetworkObject nob, NetworkManager manager) { ListCache cache = ListCaches.GetNetworkObjectCache(); cache.AddValue(nob); FinalizeChange(match, results, cache, manager); ListCaches.StoreCache(cache); } /// /// Finalizes changes to observers. /// private static void FinalizeChange(int match, HashSet results, ListCache nobs, NetworkManager manager) { if (results.Count == 0) _matchConnections.Remove(match); GetServerObjects(manager).RebuildObservers(nobs); } #endregion /// /// Returns if the object which this condition resides should be visible to connection. /// /// Connection which the condition is being checked for. /// True if the connection currently has visibility of this object. /// 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. 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 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); } } /// /// Returns which ServerObjects to rebuild observers on. /// /// /// 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. */ /// /// Clears all match information without rebuilding. /// internal static void ClearMatchesWithoutRebuilding() { _connectionMatch.Clear(); _matchConnections.Clear(); _objectMatch.Clear(); _matchObjects.Clear(); } /// /// True if the condition requires regular updates. /// /// public override bool Timed() { return false; } /// /// Clones referenced ObserverCondition. This must be populated with your conditions settings. /// /// public override ObserverCondition Clone() { MatchCondition copy = ScriptableObject.CreateInstance(); copy.ConditionConstructor(); return copy; } } }