[Add] FishNet

This commit is contained in:
2026-03-30 20:11:57 +07:00
parent ee793a3361
commit c22c08753a
1797 changed files with 197950 additions and 1 deletions
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b3bebf2a796db5c4c8c98b891492e895
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,106 @@
using FishNet.Connection;
using FishNet.Object;
using FishNet.Observing;
using System;
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 specified distance to view the object.
/// </summary>
[CreateAssetMenu(menuName = "FishNet/Observers/Distance Condition", fileName = "New Distance Condition")]
public class DistanceCondition : ObserverCondition
{
#region Serialized.
/// <summary>
/// </summary>
[Tooltip("Maximum distance a client must be within this object to see it.")]
[SerializeField]
private float _maximumDistance = 100f;
/// <summary>
/// Maximum distance a client must be within this object to see it.
/// </summary>
[Obsolete("Use Get/SetMaximumDistance.")]
public float MaximumDistance
{
get => GetMaximumDistance();
set => SetMaximumDistance(value);
}
/// <summary>
/// Maximum distance a client must be within this object to see it.
/// </summary>
/// <returns></returns>
public float GetMaximumDistance() => _maximumDistance;
/// <summary>
/// Sets the maximum distance value.
/// </summary>
/// <param name = "value">New value.</param>
public void SetMaximumDistance(float value)
{
_maximumDistance = value;
_sqrMaximumDistance = _maximumDistance * _maximumDistance;
float maxDistanceHide = _maximumDistance * (1f + _hideDistancePercent);
_sqrHideMaximumDistance = maxDistanceHide * maxDistanceHide;
}
/// <summary>
/// Additional percent of distance client must be until this object is hidden. For example, if distance was 100f and percent was 0.5f the client must be 150f units away before this object is hidden again. This can be useful for keeping objects from regularly appearing and disappearing.
/// </summary>
[Tooltip("Additional percent of distance client must be until this object is hidden. For example, if distance was 100f and percent was 0.5f the client must be 150f units away before this object is hidden again. This can be useful for keeping objects from regularly appearing and disappearing.")]
[Range(0f, 1f)]
[SerializeField]
private float _hideDistancePercent = 0.1f;
#endregion
#region Private.
/// <summary>
/// MaximumDistance squared for faster checks.
/// </summary>
private float _sqrMaximumDistance;
/// <summary>
/// Distance to hide object at.
/// </summary>
private float _sqrHideMaximumDistance;
#endregion
private void Awake()
{
SetMaximumDistance(_maximumDistance);
}
/// <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 currentlyAdded, out bool notProcessed)
{
// If here then checks are being processed.
notProcessed = false;
float sqrMaximumDistance = currentlyAdded ? _sqrHideMaximumDistance : _sqrMaximumDistance;
Vector3 thisPosition = NetworkObject.transform.position;
foreach (NetworkObject nob in connection.Objects)
{
// If within distance.
if (Vector3.SqrMagnitude(nob.transform.position - thisPosition) <= sqrMaximumDistance)
return true;
}
/* If here no client objects are within distance. */
return false;
}
/// <summary>
/// Type of condition this is. Certain types are handled different, such as Timed which are checked for changes at timed intervals.
/// </summary>
/// <returns></returns>
public override ObserverConditionType GetConditionType() => ObserverConditionType.Timed;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 7c3e28fa2e37d1d41b4f63c8a0cc2553
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Observing/Conditions/DistanceCondition.cs
uploadId: 866910
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8e63bfe98ed333f4d9b0e8c81c98c513
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 731898d70776e2341a64ea1f9494299a, type: 3}
m_Name: GridCondition
m_EditorClassIdentifier:
NetworkObject: {fileID: 0}
@@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: cc503f7541ebd424c94541e6a767efee
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Observing/Conditions/GridCondition/GridCondition.asset
uploadId: 866910
@@ -0,0 +1,34 @@
using FishNet.Connection;
using FishNet.Observing;
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 specified grid accuracy to view the object.
/// </summary>
[CreateAssetMenu(menuName = "FishNet/Observers/Grid Condition", fileName = "New Grid Condition")]
public class GridCondition : ObserverCondition
{
/// <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 currentlyAdded, out bool notProcessed)
{
// If here then checks are being processed.
notProcessed = false;
return connection.HashGridEntry.NearbyEntries.Contains(NetworkObject.HashGridEntry);
}
/// <summary>
/// How a condition is handled.
/// </summary>
/// <returns></returns>
public override ObserverConditionType GetConditionType() => ObserverConditionType.Timed;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 731898d70776e2341a64ea1f9494299a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Observing/Conditions/GridCondition/GridCondition.cs
uploadId: 866910
@@ -0,0 +1,265 @@
using FishNet.Managing;
using FishNet.Object;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace FishNet.Component.Observing
{
public class GridEntry
{
/// <summary>
/// Position on the grid.
/// </summary>
public Vector2Int Position;
/// <summary>
/// This grid entry as well those neighboring it.
/// </summary>
public HashSet<GridEntry> NearbyEntries;
public GridEntry() { }
public GridEntry(HashSet<GridEntry> nearby)
{
NearbyEntries = nearby;
}
public void SetValues(Vector2Int position, HashSet<GridEntry> nearby)
{
Position = position;
NearbyEntries = nearby;
}
public void SetValues(HashSet<GridEntry> nearby)
{
NearbyEntries = nearby;
}
public void SetValues(Vector2Int position)
{
Position = position;
}
public void Reset()
{
Position = Vector2Int.zero;
NearbyEntries.Clear();
}
}
public class HashGrid : MonoBehaviour
{
#region Types.
public enum GridAxes : byte
{
XY = 0,
YZ = 1,
XZ = 2
}
#endregion
#region Internal.
/// <summary>
/// Value for when grid position is not set.
/// </summary>
internal static Vector2Int UnsetGridPosition = Vector2Int.one * int.MaxValue;
/// <summary>
/// An empty grid entry.
/// </summary>
internal static GridEntry EmptyGridEntry = new(new());
#endregion
#region Serialized.
/// <summary>
/// Axes of world space to base the grid on.
/// </summary>
[Tooltip("Axes of world space to base the grid on.")]
[SerializeField]
private GridAxes _gridAxes = GridAxes.XY;
/// <summary>
/// Accuracy of the grid. Objects will be considered nearby if they are within this number of units. Lower values may be more expensive.
/// </summary>
[Tooltip("Accuracy of the grid. Objects will be considered nearby if they are within this number of units. Lower values may be more expensive.")]
[Range(1, ushort.MaxValue)]
[SerializeField]
private ushort _accuracy = 10;
#endregion
/// <summary>
/// Half of accuracy.
/// </summary>
private int _halfAccuracy;
/// <summary>
/// Cache of List<GridEntry>.
/// </summary>
private Stack<HashSet<GridEntry>> _gridEntryHashSetCache = new();
/// <summary>
/// Cache of GridEntrys.
/// </summary>
private Stack<GridEntry> _gridEntryCache = new();
/// <summary>
/// All grid entries.
/// </summary>
private Dictionary<Vector2Int, GridEntry> _gridEntries = new();
/// <summary>
/// NetworkManager this is used with.
/// </summary>
private NetworkManager _networkManager;
private void Awake()
{
_networkManager = GetComponentInParent<NetworkManager>();
if (_networkManager == null)
{
_networkManager.LogError($"NetworkManager not found on object or within parent of {gameObject.name}. The {GetType().Name} must be placed on or beneath a NetworkManager.");
return;
}
// Make sure there is only one per networkmanager.
if (!_networkManager.HasInstance<HashGrid>())
{
_halfAccuracy = Mathf.CeilToInt((float)_accuracy / 2f);
_networkManager.RegisterInstance(this);
}
else
{
Destroy(this);
}
}
/// <summary>
/// Sets out values to be used when creating a new GridEntry.
/// </summary>
private void OutputNewGridCollections(out GridEntry gridEntry, out HashSet<GridEntry> gridEntries)
{
const int cacheCount = 100;
if (!_gridEntryHashSetCache.TryPop(out gridEntries))
{
BuildGridEntryHashSetCache();
gridEntries = new();
}
if (!_gridEntryCache.TryPop(out gridEntry))
{
BuildGridEntryCache();
gridEntry = new();
}
void BuildGridEntryHashSetCache()
{
for (int i = 0; i < cacheCount; i++)
_gridEntryHashSetCache.Push(new());
}
void BuildGridEntryCache()
{
for (int i = 0; i < cacheCount; i++)
_gridEntryCache.Push(new());
}
}
/// <summary>
/// Creates a GridEntry for position and inserts it into GridEntries.
/// </summary>
private GridEntry CreateGridEntry(Vector2Int position)
{
// Make this into a stack that populates a number of entries when empty. also populate with some in awake.
GridEntry newEntry;
HashSet<GridEntry> nearby;
OutputNewGridCollections(out newEntry, out nearby);
newEntry.SetValues(position, nearby);
// Add to grid.
_gridEntries[position] = newEntry;
// Get neighbors.
int endX = position.x + 1;
int endY = position.y + 1;
int iterations = 0;
for (int x = position.x - 1; x <= endX; x++)
{
for (int y = position.y - 1; y <= endY; y++)
{
iterations++;
if (_gridEntries.TryGetValue(new(x, y), out GridEntry foundEntry))
{
nearby.Add(foundEntry);
foundEntry.NearbyEntries.Add(newEntry);
}
}
}
return newEntry;
}
/// <summary>
/// Gets grid positions and neighbors for a NetworkObject.
/// </summary>
internal void GetNearbyHashGridPositions(NetworkObject nob, ref HashSet<Vector2Int> collection)
{
Vector2Int position = GetHashGridPosition(nob);
// Get neighbors.
int endX = position.x + 1;
int endY = position.y + 1;
for (int x = position.x - 1; x < endX; x++)
{
for (int y = position.y - 1; y < endY; y++)
collection.Add(new(x, y));
}
}
/// <summary>
/// Gets the grid position to use for a NetworkObjects current position.
/// </summary>
internal Vector2Int GetHashGridPosition(NetworkObject nob)
{
Vector3 position = nob.transform.position;
float fX;
float fY;
if (_gridAxes == GridAxes.XY)
{
fX = position.x;
fY = position.y;
}
else if (_gridAxes == GridAxes.XZ)
{
fX = position.x;
fY = position.z;
}
else if (_gridAxes == GridAxes.YZ)
{
fX = position.y;
fY = position.z;
}
else
{
_networkManager.LogError($"GridAxes of {_gridAxes.ToString()} is not handled.");
return default;
}
return new((int)fX / _halfAccuracy, (int)fY / _halfAccuracy);
}
/// <summary>
/// Gets a GridEntry for a NetworkObject, creating the entry if needed.
/// </summary>
internal GridEntry GetGridEntry(NetworkObject nob)
{
Vector2Int pos = GetHashGridPosition(nob);
return GetGridEntry(pos);
}
/// <summary>
/// Gets a GridEntry for position, creating the entry if needed.
/// </summary>
internal GridEntry GetGridEntry(Vector2Int position)
{
GridEntry result;
if (!_gridEntries.TryGetValue(position, out result))
result = CreateGridEntry(position);
return result;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 0a9fc3aafb02eb74fb571c300f846bf2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Observing/Conditions/GridCondition/HashGrid.cs
uploadId: 866910
@@ -0,0 +1,25 @@
using FishNet.Connection;
using FishNet.Observing;
using UnityEngine;
namespace FishNet.Component.Observing
{
[CreateAssetMenu(menuName = "FishNet/Observers/Host Only Condition", fileName = "New Host Only Condition")]
public class HostOnlyCondition : ObserverCondition
{
public override bool ConditionMet(NetworkConnection connection, bool currentlyAdded, out bool notProcessed)
{
notProcessed = false;
/* Only return true if connection is the local client.
* This check only runs on the server, so if local client
* is true then they must also be the server (clientHost). */
return NetworkObject.ClientManager.Connection == connection;
}
/// <summary>
/// How a condition is handled.
/// </summary>
/// <returns></returns>
public override ObserverConditionType GetConditionType() => ObserverConditionType.Normal;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: aa62c0af0c0a4da46b03309dcd3858c3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Observing/Conditions/HostOnlyCondition.cs
uploadId: 866910
@@ -0,0 +1,785 @@
using FishNet.Connection;
using FishNet.Managing;
using FishNet.Managing.Logging;
using FishNet.Managing.Server;
using FishNet.Object;
using FishNet.Observing;
using GameKit.Dependencies.Utilities;
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 Types.
/// <summary>
/// MatchCondition collections used.
/// </summary>
public class ConditionCollections
{
public Dictionary<int, HashSet<NetworkConnection>> MatchConnections = new();
public Dictionary<NetworkConnection, HashSet<int>> ConnectionMatches = new();
public Dictionary<int, HashSet<NetworkObject>> MatchObjects = new();
public Dictionary<NetworkObject, HashSet<int>> ObjectMatches = new();
}
#endregion
#region Private.
/// <summary>
/// Collections for each NetworkManager instance.
/// </summary>
private static Dictionary<NetworkManager, ConditionCollections> _collections = new();
#endregion
#region Collections.
/// <summary>
/// Stores collections for a manager.
/// </summary>
/// <param name = "manager"></param>
internal static void StoreCollections(NetworkManager manager)
{
ConditionCollections cc;
if (!_collections.TryGetValue(manager, out cc))
return;
foreach (HashSet<int> item in cc.ObjectMatches.Values)
CollectionCaches<int>.Store(item);
foreach (HashSet<NetworkConnection> item in cc.MatchConnections.Values)
CollectionCaches<NetworkConnection>.Store(item);
foreach (HashSet<NetworkObject> item in cc.MatchObjects.Values)
CollectionCaches<NetworkObject>.Store(item);
foreach (HashSet<int> item in cc.ConnectionMatches.Values)
CollectionCaches<int>.Store(item);
_collections.Remove(manager);
}
/// <summary>
/// Gets condition collections for a NetowrkManager.
/// </summary>
private static ConditionCollections GetCollections(NetworkManager manager = null)
{
if (manager == null)
manager = InstanceFinder.NetworkManager;
ConditionCollections cc;
if (!_collections.TryGetValue(manager, out cc))
{
cc = new();
_collections[manager] = cc;
}
return cc;
}
/// <summary>
/// Returns matches and connections in each match.
/// </summary>
/// <param name = "manager">NetworkManager to use.</param>
/// <returns></returns>
public static Dictionary<int, HashSet<NetworkConnection>> GetMatchConnections(NetworkManager manager = null)
{
ConditionCollections cc = GetCollections(manager);
return cc.MatchConnections;
}
/// <summary>
/// Returns connections and the matches they are in.
/// </summary>
/// <param name = "manager">NetworkManager to use.</param>
/// <returns></returns>
public static Dictionary<NetworkConnection, HashSet<int>> GetConnectionMatches(NetworkManager manager = null)
{
ConditionCollections cc = GetCollections(manager);
return cc.ConnectionMatches;
}
/// <summary>
/// Returns matches and objects within each match.
/// </summary>
/// <param name = "manager">NetworkManager to use.</param>
/// <returns></returns>
public static Dictionary<int, HashSet<NetworkObject>> GetMatchObjects(NetworkManager manager = null)
{
ConditionCollections cc = GetCollections(manager);
return cc.MatchObjects;
}
/// <summary>
/// Returns objects and the matches they are in.
/// </summary>
/// <param name = "manager">NetworkManager to use.</param>
/// <returns></returns>
public static Dictionary<NetworkObject, HashSet<int>> GetObjectMatches(NetworkManager manager = null)
{
ConditionCollections cc = GetCollections(manager);
return cc.ObjectMatches;
}
#endregion
#region Add to match NetworkConnection.
/// <summary>
/// Adds a connection to a match.
/// </summary>
private static bool AddToMatch(int match, NetworkConnection conn, NetworkManager manager, bool replaceMatch, bool rebuild)
{
Dictionary<int, HashSet<NetworkConnection>> matchConnections = GetMatchConnections(manager);
if (replaceMatch)
RemoveFromMatchesWithoutRebuild(conn, manager);
/* Get current connections in match. This is where the conn
* will be added to. If does not exist then make new
* collection. */
HashSet<NetworkConnection> matchConnValues;
if (!matchConnections.TryGetValueIL2CPP(match, out matchConnValues))
{
matchConnValues = CollectionCaches<NetworkConnection>.RetrieveHashSet();
matchConnections.Add(match, matchConnValues);
}
bool r = matchConnValues.Add(conn);
AddToConnectionMatches(conn, match, manager);
if (r && rebuild)
GetServerObjects(manager).RebuildObservers();
return r;
}
/// <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)
{
AddToMatch(match, conn, manager, replaceMatch, true);
}
/// <summary>
/// Updates a connection within ConnectionMatches to contain match.
/// </summary>
private static void AddToConnectionMatches(NetworkConnection conn, int match, NetworkManager manager)
{
Dictionary<NetworkConnection, HashSet<int>> connectionMatches = GetConnectionMatches(manager);
HashSet<int> matches;
if (!connectionMatches.TryGetValueIL2CPP(conn, out matches))
{
matches = CollectionCaches<int>.RetrieveHashSet();
connectionMatches[conn] = matches;
}
matches.Add(match);
}
/// <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)
{
bool added = false;
foreach (NetworkConnection c in conns)
added |= AddToMatch(match, c, manager, replaceMatch, false);
if (added)
GetServerObjects(manager).RebuildObservers();
}
#endregion
#region Add to match NetworkObject.
/// <summary>
/// Adds an object to a match.
/// </summary>
private static bool AddToMatch(int match, NetworkObject nob, NetworkManager manager, bool replaceMatch, bool rebuild)
{
Dictionary<int, HashSet<NetworkObject>> matchObjects = GetMatchObjects(manager);
Dictionary<NetworkObject, HashSet<int>> objectMatches = GetObjectMatches(manager);
if (replaceMatch)
RemoveFromMatchWithoutRebuild(nob, manager);
HashSet<NetworkObject> matchObjectsValues;
if (!matchObjects.TryGetValueIL2CPP(match, out matchObjectsValues))
{
matchObjectsValues = CollectionCaches<NetworkObject>.RetrieveHashSet();
matchObjects.Add(match, matchObjectsValues);
}
bool added = matchObjectsValues.Add(nob);
/* Also add to reverse dictionary. */
HashSet<int> objectMatchesValues;
if (!objectMatches.TryGetValueIL2CPP(nob, out objectMatchesValues))
{
objectMatchesValues = CollectionCaches<int>.RetrieveHashSet();
objectMatches.Add(nob, objectMatchesValues);
}
objectMatchesValues.Add(match);
if (added && rebuild)
GetServerObjects(manager).RebuildObservers();
return added;
}
/// <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)
{
AddToMatch(match, nob, manager, replaceMatch, true);
}
/// <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)
{
// Remove from current matches.
if (replaceMatch)
{
foreach (NetworkObject n in nobs)
RemoveFromMatchWithoutRebuild(n, manager);
}
bool added = false;
// Add to matches.
foreach (NetworkObject n in nobs)
added |= AddToMatch(match, n, manager, replaceMatch, false);
if (added)
GetServerObjects(manager).RebuildObservers();
}
#endregion
#region TryRemoveKey.
/// <summary>
/// Removes a key if values are empty, and caches values.
/// </summary>
private static void TryRemoveKey(Dictionary<int, HashSet<NetworkObject>> dict, int key, HashSet<NetworkObject> value)
{
bool isEmpty = true;
if (value != null)
{
isEmpty = value.Count == 0;
if (isEmpty)
CollectionCaches<NetworkObject>.Store(value);
}
if (isEmpty)
dict.Remove(key);
}
/// <summary>
/// Removes a key if values are empty, and caches values.
/// </summary>
private static void TryRemoveKey(Dictionary<int, HashSet<NetworkObject>> dict, int key)
{
HashSet<NetworkObject> value;
dict.TryGetValue(key, out value);
TryRemoveKey(dict, key, value);
}
/// <summary>
/// Removes a key if values are empty, and caches values.
/// </summary>
private static void TryRemoveKey(Dictionary<NetworkObject, HashSet<int>> dict, NetworkObject key, HashSet<int> value)
{
bool isEmpty = true;
if (value != null)
{
isEmpty = value.Count == 0;
if (isEmpty)
CollectionCaches<int>.Store(value);
}
if (isEmpty)
dict.Remove(key);
}
/// <summary>
/// Removes a key if values are empty, and caches values.
/// </summary>
private static void TryRemoveKey(Dictionary<NetworkObject, HashSet<int>> dict, NetworkObject key)
{
HashSet<int> value;
dict.TryGetValueIL2CPP(key, out value);
TryRemoveKey(dict, key, value);
}
/// <summary>
/// Removes a key if values are empty, and caches values.
/// </summary>
private static void TryRemoveKey(Dictionary<int, HashSet<NetworkConnection>> dict, int key, HashSet<NetworkConnection> value)
{
bool isEmpty = true;
if (value != null)
{
isEmpty = value.Count == 0;
if (isEmpty)
CollectionCaches<NetworkConnection>.Store(value);
}
if (isEmpty)
dict.Remove(key);
}
/// <summary>
/// Removes a key if values are empty, and caches values.
/// </summary>
private static void TryRemoveKey(Dictionary<int, HashSet<NetworkConnection>> dict, int key)
{
HashSet<NetworkConnection> value;
dict.TryGetValueIL2CPP(key, out value);
TryRemoveKey(dict, key, value);
}
/// <summary>
/// Removes a key if values are empty, and caches values.
/// </summary>
private static void TryRemoveKey(Dictionary<NetworkConnection, HashSet<int>> dict, NetworkConnection key, HashSet<int> value)
{
bool isEmpty = true;
if (value != null)
{
isEmpty = value.Count == 0;
if (isEmpty)
CollectionCaches<int>.Store(value);
}
if (isEmpty)
dict.Remove(key);
}
/// <summary>
/// Removes a key and caches collections where needed.
/// </summary>
private static void TryRemoveKey(Dictionary<NetworkConnection, HashSet<int>> dict, NetworkConnection key)
{
HashSet<int> value;
dict.TryGetValueIL2CPP(key, out value);
TryRemoveKey(dict, key, value);
}
#endregion
#region Remove from match NetworkConnection.
/// <summary>
/// Removes a connection from all matches 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 RemoveFromMatchesWithoutRebuild(NetworkConnection conn, NetworkManager manager)
{
Dictionary<NetworkConnection, HashSet<int>> connectionMatches = GetConnectionMatches(manager);
Dictionary<int, HashSet<NetworkConnection>> matchConnections = GetMatchConnections(manager);
bool removed = false;
// If found to be in a match.
if (connectionMatches.TryGetValueIL2CPP(conn, out HashSet<int> connectionMatchesValues))
{
removed = connectionMatchesValues.Count > 0;
foreach (int m in connectionMatchesValues)
{
HashSet<NetworkConnection> matchConnsValues;
// If match is found.
if (matchConnections.TryGetValue(m, out matchConnsValues))
{
matchConnsValues.Remove(conn);
TryRemoveKey(matchConnections, m, matchConnsValues);
}
}
// Clear matches connection is in.
connectionMatchesValues.Clear();
// Remove from connectionMatches.
TryRemoveKey(connectionMatches, conn, connectionMatchesValues);
}
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 = RemoveFromMatchesWithoutRebuild(conn, manager);
if (removed)
GetServerObjects(manager).RebuildObservers();
}
/// <summary>
/// Removes a connection from a match.
/// </summary>
private static bool RemoveFromMatch(int match, NetworkConnection conn, NetworkManager manager, bool rebuild)
{
Dictionary<NetworkConnection, HashSet<int>> connectionMatches = GetConnectionMatches(manager);
Dictionary<int, HashSet<NetworkConnection>> matchConnections = GetMatchConnections(manager);
bool removed = false;
HashSet<NetworkConnection> matchConnsValues;
if (matchConnections.TryGetValueIL2CPP(match, out matchConnsValues))
{
removed |= matchConnsValues.Remove(conn);
HashSet<int> connectionMatchesValues;
if (connectionMatches.TryGetValueIL2CPP(conn, out connectionMatchesValues))
{
connectionMatchesValues.Remove(match);
TryRemoveKey(connectionMatches, conn, connectionMatchesValues);
}
if (removed && rebuild)
{
TryRemoveKey(matchConnections, match, matchConnsValues);
GetServerObjects(manager).RebuildObservers();
}
}
return removed;
}
/// <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>
public static bool RemoveFromMatch(int match, NetworkConnection conn, NetworkManager manager = null)
{
return RemoveFromMatch(match, conn, manager, true);
}
/// <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>
public static void RemoveFromMatch(int match, NetworkConnection[] conns, NetworkManager manager)
{
RemoveFromMatch(match, conns.ToList(), 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>
public static void RemoveFromMatch(int match, List<NetworkConnection> conns, NetworkManager manager)
{
bool removed = false;
foreach (NetworkConnection c in conns)
removed |= RemoveFromMatch(match, c, manager, false);
if (removed)
GetServerObjects(manager).RebuildObservers();
}
#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)
{
Dictionary<NetworkObject, HashSet<int>> objectMatches = GetObjectMatches(manager);
Dictionary<int, HashSet<NetworkObject>> matchObjects = GetMatchObjects(manager);
HashSet<int> objectMatchesValues;
bool removed = false;
// If found to be in a match.
if (objectMatches.TryGetValueIL2CPP(nob, out objectMatchesValues))
{
removed = objectMatchesValues.Count > 0;
foreach (int m in objectMatchesValues)
{
// If match is found.
if (matchObjects.TryGetValue(m, out HashSet<NetworkObject> matchObjectsValues))
{
matchObjectsValues.Remove(nob);
TryRemoveKey(matchObjects, m, matchObjectsValues);
}
}
// Since object is being removed from all matches this can be cleared.
objectMatchesValues.Clear();
TryRemoveKey(objectMatches, nob, objectMatchesValues);
}
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 bool RemoveFromMatch(NetworkObject nob, NetworkManager manager = null)
{
bool removed = RemoveFromMatchWithoutRebuild(nob, manager);
if (removed)
GetServerObjects(manager).RebuildObservers(nob);
return removed;
}
/// <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>
public static void RemoveFromMatch(int match, NetworkObject nob, NetworkManager manager = null)
{
Dictionary<int, HashSet<NetworkObject>> matchObjects = GetMatchObjects(manager);
Dictionary<NetworkObject, HashSet<int>> objectMatches = GetObjectMatches(manager);
HashSet<NetworkObject> matchObjectsValues;
if (matchObjects.TryGetValueIL2CPP(match, out matchObjectsValues))
{
bool removed = matchObjectsValues.Remove(nob);
if (removed)
{
/* Check if nob is still in matches. If not then remove
* nob from ObjectMatches. */
HashSet<int> objectMatchesValues;
if (objectMatches.TryGetValueIL2CPP(nob, out objectMatchesValues))
{
objectMatchesValues.Remove(match);
TryRemoveKey(objectMatches, nob, objectMatchesValues);
}
TryRemoveKey(matchObjects, match, matchObjectsValues);
GetServerObjects(manager).RebuildObservers(nob);
}
}
}
/// <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>
public static void RemoveFromMatch(int match, NetworkObject[] nobs, NetworkManager manager = null)
{
Dictionary<int, HashSet<NetworkObject>> matchObjects = GetMatchObjects(manager);
Dictionary<NetworkObject, HashSet<int>> objectMatches = GetObjectMatches(manager);
if (matchObjects.TryGetValueIL2CPP(match, out HashSet<NetworkObject> matchObjectsValues))
{
bool removed = false;
for (int i = 0; i < nobs.Length; i++)
{
NetworkObject n = nobs[i];
removed |= matchObjectsValues.Remove(n);
objectMatches.Remove(n);
}
if (removed)
{
TryRemoveKey(matchObjects, match, matchObjectsValues);
GetServerObjects(manager).RebuildObservers(nobs);
}
}
}
/// <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>
public static void RemoveFromMatch(int match, List<NetworkObject> nobs, NetworkManager manager = null)
{
Dictionary<int, HashSet<NetworkObject>> matchObjects = GetMatchObjects(manager);
Dictionary<NetworkObject, HashSet<int>> objectMatches = GetObjectMatches(manager);
if (matchObjects.TryGetValueIL2CPP(match, out HashSet<NetworkObject> matchObjectsValues))
{
bool removed = false;
for (int i = 0; i < nobs.Count; i++)
{
NetworkObject n = nobs[i];
removed |= matchObjectsValues.Remove(n);
objectMatches.Remove(n);
}
if (removed)
{
TryRemoveKey(matchObjects, match, matchObjectsValues);
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 currentlyAdded, out bool notProcessed)
{
// If here then checks are being processed.
notProcessed = false;
NetworkConnection owner = NetworkObject.Owner;
/* If object is owned then check if owner
* and connection share a match. */
if (owner.IsValid)
{
Dictionary<NetworkConnection, HashSet<int>> connectionMatches = GetConnectionMatches(NetworkObject.NetworkManager);
// Output owner matches.
HashSet<int> ownerMatches;
/* This objects owner is not in a match so treat it like
* a networkobject without an owner. Objects not in matches
* are visible to everyone. */
if (!connectionMatches.TryGetValueIL2CPP(owner, out ownerMatches))
{
return true;
}
/* Owner is in a match. See if connection is in any of
* the same matches. */
else
{
// If conn is not in any matches then they cannot see this object, as it is.
if (!connectionMatches.TryGetValue(connection, out HashSet<int> connMatches))
{
return false;
}
// See if conn is in any of the same matches.
else
{
foreach (int m in connMatches)
{
if (ownerMatches.Contains(m))
return true;
}
}
// Fall through, not found.
return false;
}
}
/* If no owner see if the object is in a match and if so
* then compare that. */
else
{
Dictionary<NetworkObject, HashSet<int>> objectMatches = GetObjectMatches(NetworkObject.NetworkManager);
Dictionary<NetworkConnection, HashSet<int>> connectionMatches = GetConnectionMatches(NetworkObject.NetworkManager);
// Object isn't in a match. Is visible with no owner.
HashSet<int> objectMatchesValues;
if (!objectMatches.TryGetValueIL2CPP(NetworkObject, out objectMatchesValues))
return true;
/* See if connection is in any of same matches as the object.
* If connection isn't in a match then it fails as at this point
* object would be, but not conn. */
if (!connectionMatches.TryGetValueIL2CPP(connection, out HashSet<int> connectionMatchesValues))
return false;
// Compare for same matches.
foreach (int cM in connectionMatchesValues)
{
if (objectMatchesValues.Contains(cM))
return true;
}
// Fall through, not in any of the matches.
return false;
}
}
/// <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;
}
/// <summary>
/// How a condition is handled.
/// </summary>
/// <returns></returns>
public override ObserverConditionType GetConditionType() => ObserverConditionType.Normal;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 5afdd6c2de1c76f4faa6840cc29fda8a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Observing/Conditions/MatchCondition.cs
uploadId: 866910
@@ -0,0 +1,33 @@
using FishNet.Connection;
using FishNet.Observing;
using UnityEngine;
namespace FishNet.Component.Observing
{
/// <summary>
/// This condition makes an object only visible to the owner.
/// </summary>
[CreateAssetMenu(menuName = "FishNet/Observers/Owner Only Condition", fileName = "New Owner Only Condition")]
public class OwnerOnlyCondition : ObserverCondition
{
/// <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 currentlyAdded, out bool notProcessed)
{
notProcessed = false;
/* Returning false immediately indicates no connection will
* meet this condition. */
return false;
}
/// <summary>
/// How a condition is handled.
/// </summary>
/// <returns></returns>
public override ObserverConditionType GetConditionType() => ObserverConditionType.Normal;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 1ca3d8a36a10fd344806a2df999f3eda
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Observing/Conditions/OwnerOnlyCondition.cs
uploadId: 866910
@@ -0,0 +1,37 @@
using FishNet.Connection;
using FishNet.Observing;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace FishNet.Component.Observing
{
/// <summary>
/// When this observer condition is placed on an object, a client must be within the same scene to view the object.
/// </summary>
[CreateAssetMenu(menuName = "FishNet/Observers/Scene Condition", fileName = "New Scene Condition")]
public class SceneCondition : ObserverCondition
{
/// <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 currentlyAdded, out bool notProcessed)
{
notProcessed = false;
if (NetworkObject == null || connection == null)
return false;
/* When there is no owner only then is the gameobject
* scene checked. That's the only way to know at this point. */
return connection.Scenes.Contains(NetworkObject.gameObject.scene);
}
/// <summary>
/// How a condition is handled.
/// </summary>
/// <returns></returns>
public override ObserverConditionType GetConditionType() => ObserverConditionType.Normal;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: fab85d1c51ee2c344b7dd914dc262ec4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Observing/Conditions/SceneCondition.cs
uploadId: 866910
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1f8371074a46da248ba15efa18140ff3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,18 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 7c3e28fa2e37d1d41b4f63c8a0cc2553, type: 3}
m_Name: DistanceCondition
m_EditorClassIdentifier:
NetworkObject: {fileID: 0}
_maximumDistance: 10
_hideDistancePercent: 0.1
UpdateFrequency: 0
@@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: 5f33eb0e5b83b5546822cfe42a305657
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Observing/Conditions/ScriptableObjects/DistanceCondition.asset
uploadId: 866910
@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: aa62c0af0c0a4da46b03309dcd3858c3, type: 3}
m_Name: HostOnlyCondition
m_EditorClassIdentifier:
NetworkObject: {fileID: 0}
@@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: 9ff842b44ec59314d9efcecbcdbaac04
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Observing/Conditions/ScriptableObjects/HostOnlyCondition.asset
uploadId: 866910
@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5afdd6c2de1c76f4faa6840cc29fda8a, type: 3}
m_Name: MatchCondition
m_EditorClassIdentifier:
NetworkObject: {fileID: 0}
@@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: efcd7f0dfd341ed4e8671079e91e0544
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Observing/Conditions/ScriptableObjects/MatchCondition.asset
uploadId: 866910
@@ -0,0 +1,15 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 1ca3d8a36a10fd344806a2df999f3eda, type: 3}
m_Name: OwnerOnlyCondition
m_EditorClassIdentifier:
NetworkObject: {fileID: 0}
@@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: bcf92670d91dbb74dad77c56b9b8712e
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Observing/Conditions/ScriptableObjects/OwnerOnlyCondition.asset
uploadId: 866910
@@ -0,0 +1,17 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fab85d1c51ee2c344b7dd914dc262ec4, type: 3}
m_Name: SceneCondition
m_EditorClassIdentifier:
NetworkObject: {fileID: 0}
_synchronizeScene: 1
_timed: 0
@@ -0,0 +1,15 @@
fileFormatVersion: 2
guid: 2033f54fd2794464bae08fa5a55c8996
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Observing/Conditions/ScriptableObjects/SceneCondition.asset
uploadId: 866910
@@ -0,0 +1,15 @@
namespace FishNet.Observing
{
[System.Flags]
public enum HostVisibilityUpdateTypes : byte
{
/// <summary>
/// Include this flag to update manager.
/// </summary>
Manager = 1,
/// <summary>
/// Include this flag to update spawned.
/// </summary>
Spawned = 2
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: dc90636a96cf14d47812768a9ce3a4d8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Observing/HostVisibilityUpdateTypes.cs
uploadId: 866910
@@ -0,0 +1,621 @@
// using FishNet.Connection;
// using FishNet.Documenting;
// using FishNet.Managing.Server;
// using FishNet.Object;
// using FishNet.Transporting;
// using FishNet.Utility.Performance;
// using GameKit.Dependencies.Utilities;
// using System.Collections.Generic;
// using System.Runtime.CompilerServices;
// using FishNet.Managing.Observing;
// using UnityEngine;
// using UnityEngine.Serialization;
//
// namespace FishNet.Observing
// {
// /// <summary>
// /// Controls which clients can see and get messages for an object.
// /// </summary>
// [DisallowMultipleComponent]
// [RequireComponent(typeof(NetworkObject))]
// [AddComponentMenu("FishNet/Component/NetworkObserver")]
// public sealed class NetworkObserver : MonoBehaviour
// {
// #region Types.
// /// <summary>
// /// How ObserverManager conditions are used.
// /// </summary>
// public enum ConditionOverrideType
// {
// /// <summary>
// /// Keep current conditions, add new conditions from manager.
// /// </summary>
// AddMissing = 1,
// /// <summary>
// /// Replace current conditions with manager conditions.
// /// </summary>
// UseManager = 2,
// /// <summary>
// /// Keep current conditions, ignore manager conditions.
// /// </summary>
// IgnoreManager = 3,
// }
// #endregion
//
// #region Serialized.
// /// <summary>
// ///
// /// </summary>
// [Tooltip("How ObserverManager conditions are used.")]
// [SerializeField]
// private ConditionOverrideType _overrideType = ConditionOverrideType.IgnoreManager;
// /// <summary>
// /// How ObserverManager conditions are used.
// /// </summary>
// public ConditionOverrideType OverrideType
// {
// get => _overrideType;
// internal set => _overrideType = value;
// }
//
// /// <summary>
// ///
// /// </summary>
// [Tooltip("True to update visibility for clientHost based on if they are an observer or not.")]
// [SerializeField]
// private bool _updateHostVisibility = true;
// /// <summary>
// /// True to update visibility for clientHost based on if they are an observer or not.
// /// </summary>
// public bool UpdateHostVisibility
// {
// get => _updateHostVisibility;
// private set => _updateHostVisibility = value;
// }
// /// <summary>
// ///
// /// </summary>
// [Tooltip("Conditions connections must met to be added as an observer. Multiple conditions may be used.")]
// [SerializeField]
// internal List<ObserverCondition> _observerConditions = new();
// /// <summary>
// /// Conditions connections must met to be added as an observer. Multiple conditions may be used.
// /// </summary>
// public IReadOnlyList<ObserverCondition> ObserverConditions => _observerConditions;
// [APIExclude]
// #if MIRROR
// public List<ObserverCondition> ObserverConditionsInternal
// #else
// internal List<ObserverCondition> ObserverConditionsInternal
// #endif
// {
// get => _observerConditions;
// set => _observerConditions = value;
// }
// #endregion
//
// #region Private.
// /// <summary>
// /// Becomes true if there are timed conditions.
// /// </summary>
// private bool _hasTimedConditions;
// /// <summary>
// /// Connections which have all non-timed conditions met.
// /// </summary>
// private HashSet<NetworkConnection> _nonTimedMet;
// /// <summary>
// /// NetworkObject this belongs to.
// /// </summary>
// private NetworkObject _networkObject;
// /// <summary>
// /// Becomes true when registered with ServerObjects as Timed observers.
// /// </summary>
// private bool _registeredAsTimed;
// /// <summary>
// /// True if was initialized previously.
// /// </summary>
// private bool _initializedPreviously;
// /// <summary>
// /// True if currently initialized.
// /// </summary>
// private bool _initialized;
// /// <summary>
// /// Last ObserverManager hash which initialized conditions.
// /// </summary>
// private uint _initializingHash = ObserverManager.UNSET_INITIALIZING_HASH
// /// <summary>
// /// True if ParentNetworkObject was visible last iteration.
// /// This value will also be true if there is no ParentNetworkObject.
// /// </summary>
// private bool _lastParentVisible;
// /// <summary>
// /// ServerManager for this script.
// /// </summary>
// private ServerManager _serverManager;
// /// <summary>
// /// Becomes true if there are non-timed, normal conditions.
// /// </summary>
// private bool _hasNormalConditions;
// /// <summary>
// /// ObserverConditions which are referenced or instantiated from ObserverConditions.
// /// </summary>
// private List<ObserverCondition> _runtimeObserverConditions;
// #endregion
//
// /// <summary>
// /// Deinitializes for reuse or clean up.
// /// </summary>
// /// <param name="destroyed"></param>
// internal void Deinitialize(bool destroyed)
// {
// if (!_initialized)
// return;
//
// Debug.Log($"Deinit called on {GetInstanceID()}. Destroyed? {destroyed}");
//
// _lastParentVisible = false;
// if (_nonTimedMet != null)
// _nonTimedMet.Clear();
// UnregisterTimedConditions();
//
// if (_serverManager != null)
// _serverManager.OnRemoteConnectionState -= ServerManager_OnRemoteConnectionState;
//
// if (_initializedPreviously)
// {
// _hasNormalConditions = false;
//
// foreach (ObserverCondition item in _observerConditions)
// {
// item.Deinitialize(destroyed);
// /* Use GetInstanceId to ensure the object is actually
// * instantiated. If Id is negative, then it's instantiated
// * and not a reference to the original object. */
// if (destroyed && item.GetInstanceID() < 0)
// Destroy(item);
// }
//
// // Clean up lists.
// if (destroyed)
// CollectionCaches<NetworkConnection>.Store(_nonTimedMet);
// }
//
// _serverManager = null;
// _networkObject = null;
// _initialized = false;
// }
//
// /// <summary>
// /// Initializes this script for use.
// /// </summary>
// internal void Initialize(NetworkObject networkObject)
// {
// if (_initialized)
// return;
//
// Debug.Log($"Init called on {GetInstanceID()}. Initialized previously? {_initializedPreviously}");
// _networkObject = networkObject;
// _serverManager = _networkObject.ServerManager;
// _serverManager.OnRemoteConnectionState += ServerManager_OnRemoteConnectionState;
//
// if (!_initializedPreviously)
// {
// _initializedPreviously = true;
// bool ignoringManager = (OverrideType == ConditionOverrideType.IgnoreManager);
//
// //Check to override SetHostVisibility.
// if (!ignoringManager)
// UpdateHostVisibility = networkObject.ObserverManager.UpdateHostVisibility;
//
// /* Sort the conditions so that normal conditions are first.
// * This prevents normal conditions from being skipped if a timed
// * condition fails before the normal passed.
// *
// * Example: Let's say an object has a distance and scene condition, with
// * the distance condition being first. Normal conditions are only checked
// * as the change occurs, such as when the scene was loaded. So if the client
// * loaded into the scene and they were not within the distance the condition
// * iterations would skip remaining, which would be the scene condition. As
// * result normal conditions (non timed) would never be met since they are only
// * checked as-needed, in this case during a scene change.
// *
// * By moving normal conditions to the front they will always be checked first
// * and timed can update at intervals per expectancy. This could also be resolved
// * by simply not exiting early when a condition fails but that's going to
// * cost hotpath performance where sorting is only done once. */
//
// //Initialize collections.
// _nonTimedMet = CollectionCaches<NetworkConnection>.RetrieveHashSet();
// //Caches for ordering.
// List<ObserverCondition> nonTimedConditions = CollectionCaches<ObserverCondition>.RetrieveList();
// List<ObserverCondition> timedConditions = CollectionCaches<ObserverCondition>.RetrieveList();
//
// bool observerFound = false;
// foreach (ObserverCondition condition in _observerConditions)
// {
// if (condition == null)
// continue;
//
// observerFound = true;
//
// /* Make an instance of each condition so values are
// * not overwritten when the condition exist more than
// * once in the scene. Double-edged sword of using scriptable
// * objects for conditions. */
// ObserverCondition ocCopy = Instantiate(condition);
//
// //Condition type.
// ObserverConditionType oct = ocCopy.GetConditionType();
// if (oct == ObserverConditionType.Timed)
// {
// timedConditions.AddOrdered(ocCopy);
// }
// else
// {
// _hasNormalConditions = true;
// nonTimedConditions.AddOrdered(ocCopy);
// }
// }
//
// //Add to condition collection as ordered now.
// _observerConditions.Clear();
// //Non timed.
// for (int i = 0; i < nonTimedConditions.Count; i++)
// _observerConditions.Add(nonTimedConditions[i]);
//
// //Timed.
// _timedConditions = CollectionCaches<ObserverCondition>.RetrieveList();
// foreach (ObserverCondition timedCondition in timedConditions)
// {
// _observerConditions.Add(timedCondition);
// _timedConditions.Add(timedCondition);
// }
//
// //Store caches.
// CollectionCaches<ObserverCondition>.Store(nonTimedConditions);
// CollectionCaches<ObserverCondition>.Store(timedConditions);
//
// //No observers specified, do not need to take further action.
// if (!observerFound)
// return;
// }
//
// //Initialize conditions.
// for (int i = 0; i < _observerConditions.Count; i++)
// _observerConditions[i].Initialize(_networkObject);
//
// _initialized = true;
//
// RegisterTimedConditions();
// }
//
// /// <summary>
// /// Returns a condition if found within Conditions.
// /// </summary>
// /// <returns></returns>
// public ObserverCondition GetObserverCondition<T>() where T : ObserverCondition
// {
// /* Do not bother setting local variables,
// * condition collections aren't going to be long
// * enough to make doing so worth while. */
//
// System.Type conditionType = typeof(T);
// for (int i = 0; i < _observerConditions.Count; i++)
// {
// if (_observerConditions[i].GetType() == conditionType)
// return _observerConditions[i];
// }
//
// //Fall through, not found.
// return null;
// }
//
// /// <summary>
// /// Returns ObserverStateChange by comparing conditions for a connection.
// /// </summary>
// /// <returns>True if added to Observers.</returns>
// internal ObserverStateChange RebuildObservers(NetworkConnection connection, bool timedOnly)
// {
// bool currentlyAdded = (_networkObject.Observers.Contains(connection));
//
// //True if all conditions are met.
// bool allConditionsMet = true;
// /* If cnnection is owner then they can see the object. */
// bool notOwner = (connection != _networkObject.Owner);
// /* Only check conditions if not owner. Owner will always
// * have visibility. */
// if (notOwner)
// {
// bool parentVisible = true;
// if (_networkObject.CurrentParentNetworkBehaviour != null)
// parentVisible = _networkObject.CurrentParentNetworkBehaviour.NetworkObject.Observers.Contains(connection);
//
// /* If parent is visible but was not previously
// * then unset timedOnly to make sure all conditions
// * are checked again. This ensures that the _nonTimedMet
// * collection is updated. */
// if (parentVisible && !_lastParentVisible)
// timedOnly = false;
// _lastParentVisible = parentVisible;
//
// //If parent is not visible no further checks are required.
// if (!parentVisible)
// {
// allConditionsMet = false;
// }
// //Parent is visible, perform checks.
// else
// {
// //Only need to check beyond this if conditions exist.
// if (_observerConditions.Count > 0)
// {
// /* True if all conditions are timed or
// * if connection has met non timed. */
// bool startNonTimedMet = (!_hasNormalConditions || _nonTimedMet.Contains(connection));
// /* If a timed update an1d nonTimed
// * have not been met then there's
// * no reason to check timed. */
// if (timedOnly && !startNonTimedMet)
// {
// allConditionsMet = false;
// }
// else
// {
// //Becomes true if a non-timed condition fails.
// bool nonTimedMet = true;
//
// List<ObserverCondition> collection = _runtimeObserverConditions;
// for (int i = 0; i < collection.Count; i++)
// {
// ObserverCondition condition = collection[i];
//
// if (timedOnly && condition.GetConditionType() != ObserverConditionType.Timed)
// continue;
// /* If any observer returns removed then break
// * from loop and return removed. If one observer has
// * removed then there's no reason to iterate
// * the rest.
// *
// * A condition is automatically met if it's not enabled. */
// bool notProcessed = false;
// Debug.LogWarning($"Condition check {GetInstanceID()} {condition.GetInstanceID()}. Type {condition.GetType()}");
// bool conditionMet = (!condition.GetIsEnabled() || condition.ConditionMet(connection, currentlyAdded, out notProcessed));
//
// if (notProcessed)
// conditionMet = currentlyAdded;
//
// //Condition not met.
// if (!conditionMet)
// {
// allConditionsMet = false;
// if (condition.GetConditionType() != ObserverConditionType.Timed)
// nonTimedMet = false;
// break;
// }
// }
//
// //If nonTimedMet changed.
// if (startNonTimedMet != nonTimedMet)
// {
// /* If the collection was iterated without breaks
// * then add to nontimed met. */
// if (nonTimedMet)
// _nonTimedMet.Add(connection);
// //If there were breaks not all conditions were checked.
// else
// _nonTimedMet.Remove(connection);
// }
// }
// }
// }
// }
//
// //If all conditions met.
// if (allConditionsMet)
// return ReturnPassedConditions(currentlyAdded);
// else
// return ReturnFailedCondition(currentlyAdded);
// }
//
// /// <summary>
// /// Registers timed observer conditions.
// /// </summary>
// private void RegisterTimedConditions()
// {
// if (!_hasTimedConditions)
// return;
// if (_registeredAsTimed)
// return;
//
// _registeredAsTimed = true;
//
// if (_serverManager == null)
// return;
// _serverManager.Objects.AddTimedNetworkObserver(_networkObject);
// }
//
// /// <summary>
// /// Unregisters timed conditions.
// /// </summary>
// private void UnregisterTimedConditions()
// {
// if (!_hasTimedConditions)
// return;
// if (!_registeredAsTimed)
// return;
// _registeredAsTimed = false;
//
// if (_serverManager == null)
// return;
// _serverManager.Objects.RemoveTimedNetworkObserver(_networkObject);
// }
//
// /// <summary>
// /// Returns an ObserverStateChange when a condition fails.
// /// </summary>
// /// <param name="currentlyAdded"></param>
// /// <returns></returns>
// private ObserverStateChange ReturnFailedCondition(bool currentlyAdded)
// {
// if (currentlyAdded)
// return ObserverStateChange.Removed;
// else
// return ObserverStateChange.Unchanged;
// }
//
// /// <summary>
// /// Returns an ObserverStateChange when all conditions pass.
// /// </summary>
// /// <param name="currentlyAdded"></param>
// /// <returns></returns>
// private ObserverStateChange ReturnPassedConditions(bool currentlyAdded)
// {
// if (currentlyAdded)
// return ObserverStateChange.Unchanged;
// else
// return ObserverStateChange.Added;
// }
//
// /// <summary>
// /// Called when a remote client state changes with the server.
// /// </summary>
// private void ServerManager_OnRemoteConnectionState(NetworkConnection conn, RemoteConnectionStateArgs arg2)
// {
// if (arg2.ConnectionState == RemoteConnectionState.Stopped)
// _nonTimedMet.Remove(conn);
// }
//
// /// <summary>
// /// Updates current conditions to supplied values. Conditions are updated if forced or if the manager hash provided differs from what is stored.
// /// </summary>
// internal void SetObserverConditions(List<ObserverCondition> conditions, uint observerManagerHash, bool force = false)
// {
// //Already initialized for the observerManager!
// if (observerManagerHash == _initializingHash && !force)
// return;
// if (_overrideType == ConditionOverrideType.IgnoreManager)
// return;
//
// DisposeOfRuntimeConditions();
//
// //Caches for ordering.
// List<ObserverCondition> nonTimedConditions = CollectionCaches<ObserverCondition>.RetrieveList();
// List<ObserverCondition> timedConditions = CollectionCaches<ObserverCondition>.RetrieveList();
//
// //Set new conditions.
// foreach (ObserverCondition oc in conditions)
// {
// if (oc == null)
// continue;
//
// /* Make an instance of each condition so values are
// * not overwritten when the condition exist more than
// * once in the scene. Double-edged sword of using scriptable
// * objects for conditions. */
// ObserverCondition ocCopy = (oc.IsConstant) ? oc : Instantiate(oc);
//
// //Condition type.
// ObserverConditionType oct = ocCopy.GetConditionType();
// if (oct == ObserverConditionType.Timed)
// {
// timedConditions.AddOrdered(ocCopy);
// }
// else
// {
// _hasNormalConditions = true;
// nonTimedConditions.AddOrdered(ocCopy);
// }
// }
//
// CollectionCaches<ObserverCondition>.StoreAndDefault(ref _runtimeObserverConditions);
// _runtimeObserverConditions = CollectionCaches<ObserverCondition>.RetrieveList();
//
// /* Add nonTimed first, as they're always checked first for performance. */
// for (int i = 0; i < nonTimedConditions.Count; i++)
// _runtimeObserverConditions.Add(nonTimedConditions[i]);
//
// /* Add timed into their own collection as well runtime collection.
// * There are separate collections as timed are checked regularly so
// * this prevents iterating over all conditions in a timed check. */
// CollectionCaches<ObserverCondition>.StoreAndDefault(ref _timedConditions);
// _timedConditions = CollectionCaches<ObserverCondition>.RetrieveList();
// foreach (ObserverCondition timedCondition in timedConditions)
// {
// _observerConditions.Add(timedCondition);
// _timedConditions.Add(timedCondition);
// }
//
// //Store caches.
// CollectionCaches<ObserverCondition>.Store(nonTimedConditions);
// CollectionCaches<ObserverCondition>.Store(timedConditions);
// }
//
// /// <summary>
// /// Updates current conditions to supplied values. Conditions are updated if forced or if the manager hash provided differs from what is stored.
// /// </summary>
// public void UpdateObserverConditions(List<ObserverCondition> conditions, uint observerManagerHash, bool force = false)
// {
// //Already initialized for the observerManager!
// if (observerManagerHash == _initializingHash && !force)
// return;
//
// //Dispose of current instantiated conditions.
// foreach (ObserverCondition oc in _observerConditions)
// {
// //Constant are never initialized.
// if (oc.IsConstant)
// continue;
// /* Not constant, but isn't in instance.
// * Unity tells us only negative Ids are instantiated. */
// if (oc.GetInstanceID() >= 0)
// continue;
//
// oc.Deinitialize(destroyed: true);
// Destroy(oc);
// }
//
// _observerConditions.Clear();
// }
//
// /// <summary>
// /// Destroys runtime ObserverConditions as needed and clears the collection.
// /// </summary>
// private void DisposeOfRuntimeConditions()
// {
// if (_runtimeObserverConditions == null)
// return;
//
// foreach (ObserverCondition oc in _runtimeObserverConditions)
// {
// //Constant are never initialized.
// if (oc.IsConstant)
// continue;
// /* Not constant, but isn't in instance.
// * Unity tells us only negative Ids are instantiated. */
// if (oc.GetInstanceID() >= 0)
// continue;
//
// oc.Deinitialize(destroyed: true);
// Destroy(oc);
// }
//
// _runtimeObserverConditions.Clear();
// }
//
// /// <summary>
// /// Sets a new value for UpdateHostVisibility.
// /// This does not immediately update renderers.
// /// You may need to combine with NetworkObject.SetRenderersVisible(bool).
// /// </summary>
// /// <param name="value">New value.</param>
// public void SetUpdateHostVisibility(bool value)
// {
// //Unchanged.
// if (value == UpdateHostVisibility)
// return;
//
// UpdateHostVisibility = value;
// }
// }
// }
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c8dfd3072b1d3414d8833438b8c2b258
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Observing/NetworkObserver.Rework.cs
uploadId: 866910
@@ -0,0 +1,498 @@
using FishNet.Connection;
using FishNet.Documenting;
using FishNet.Managing.Server;
using FishNet.Object;
using FishNet.Transporting;
using GameKit.Dependencies.Utilities;
using System.Collections.Generic;
using FishNet.Managing;
using UnityEngine;
namespace FishNet.Observing
{
/// <summary>
/// Controls which clients can see and get messages for an object.
/// </summary>
[DisallowMultipleComponent]
[RequireComponent(typeof(NetworkObject))]
[AddComponentMenu("FishNet/Component/NetworkObserver")]
public sealed class NetworkObserver : MonoBehaviour
{
#region Types.
/// <summary>
/// How ObserverManager conditions are used.
/// </summary>
public enum ConditionOverrideType
{
/// <summary>
/// Keep current conditions, add new conditions from manager.
/// </summary>
AddMissing = 1,
/// <summary>
/// Replace current conditions with manager conditions.
/// </summary>
UseManager = 2,
/// <summary>
/// Keep current conditions, ignore manager conditions.
/// </summary>
IgnoreManager = 3
}
#endregion
#region Internal.
/// <summary>
/// True if the ObserverManager had already added conditions for this component.
/// </summary>
internal bool ConditionsSetByObserverManager;
#endregion
#region Serialized.
/// <summary>
/// </summary>
[Tooltip("How ObserverManager conditions are used.")]
[SerializeField]
private ConditionOverrideType _overrideType = ConditionOverrideType.IgnoreManager;
/// <summary>
/// How ObserverManager conditions are used.
/// </summary>
public ConditionOverrideType OverrideType
{
get => _overrideType;
internal set => _overrideType = value;
}
/// <summary>
/// </summary>
[Tooltip("True to update visibility for clientHost based on if they are an observer or not.")]
[SerializeField]
private bool _updateHostVisibility = true;
/// <summary>
/// True to update visibility for clientHost based on if they are an observer or not.
/// </summary>
public bool UpdateHostVisibility
{
get => _updateHostVisibility;
private set => _updateHostVisibility = value;
}
/// <summary>
/// </summary>
[Tooltip("Conditions connections must met to be added as an observer. Multiple conditions may be used.")]
[SerializeField]
internal List<ObserverCondition> _observerConditions = new();
/// <summary>
/// Conditions connections must met to be added as an observer. Multiple conditions may be used.
/// </summary>
public IReadOnlyList<ObserverCondition> ObserverConditions => _observerConditions;
[APIExclude]
#if MIRROR
public List<ObserverCondition> ObserverConditionsInternal
#else
internal List<ObserverCondition> ObserverConditionsInternal
#endif
{
get => _observerConditions;
set => _observerConditions = value;
}
#endregion
#region Private.
/// <summary>
/// Conditions under this component which are timed.
/// </summary>
private List<ObserverCondition> _timedConditions;
/// <summary>
/// Connections which have all non-timed conditions met.
/// </summary>
private HashSet<NetworkConnection> _nonTimedMet;
/// <summary>
/// NetworkObject this belongs to.
/// </summary>
private NetworkObject _networkObject;
/// <summary>
/// Becomes true when registered with ServerObjects as Timed observers.
/// </summary>
private bool _registeredAsTimed;
/// <summary>
/// True if was initialized previously.
/// </summary>
private bool _conditionsInitializedPreviously;
/// <summary>
/// True if currently initialized.
/// </summary>
private bool _initialized;
/// <summary>
/// True if ParentNetworkObject was visible last iteration.
/// This value will also be true if there is no ParentNetworkObject.
/// </summary>
private bool _lastParentVisible;
/// <summary>
/// ServerManager for this script.
/// </summary>
private ServerManager _serverManager;
/// <summary>
/// Becomes true if there are non-timed, normal conditions.
/// </summary>
private bool _hasNormalConditions;
#endregion
/// <summary>
/// Deinitializes for reuse or clean up.
/// </summary>
/// <param name = "destroyed"></param>
internal void Deinitialize(bool destroyed)
{
_lastParentVisible = false;
if (_nonTimedMet != null)
_nonTimedMet.Clear();
UnregisterTimedConditions();
if (_serverManager != null)
_serverManager.OnRemoteConnectionState -= ServerManager_OnRemoteConnectionState;
if (_conditionsInitializedPreviously)
{
_hasNormalConditions = false;
foreach (ObserverCondition item in _observerConditions)
{
item.Deinitialize(destroyed);
/* Use GetInstanceId to ensure the object is actually
* instantiated. If Id is negative, then it's instantiated
* and not a reference to the original object. */
if (destroyed && item.GetInstanceID() < 0)
Destroy(item);
}
// Clean up lists.
if (destroyed)
{
_observerConditions.Clear();
CollectionCaches<ObserverCondition>.Store(_timedConditions);
CollectionCaches<NetworkConnection>.Store(_nonTimedMet);
}
}
_serverManager = null;
_networkObject = null;
_initialized = false;
}
/// <summary>
/// Initializes this script for use.
/// </summary>
internal void Initialize(NetworkObject networkObject)
{
if (_initialized)
return;
_networkObject = networkObject;
_serverManager = _networkObject.ServerManager;
_serverManager.OnRemoteConnectionState += ServerManager_OnRemoteConnectionState;
bool observerFound = _conditionsInitializedPreviously;
if (!_conditionsInitializedPreviously)
{
_conditionsInitializedPreviously = true;
bool ignoringManager = OverrideType == ConditionOverrideType.IgnoreManager;
// Check to override SetHostVisibility.
if (!ignoringManager)
UpdateHostVisibility = networkObject.ObserverManager.UpdateHostVisibility;
/* Sort the conditions so that normal conditions are first.
* This prevents normal conditions from being skipped if a timed
* condition fails before the normal passed.
*
* Example: Let's say an object has a distance and scene condition, with
* the distance condition being first. Normal conditions are only checked
* as the change occurs, such as when the scene was loaded. So if the client
* loaded into the scene and they were not within the distance the condition
* iterations would skip remaining, which would be the scene condition. As
* result normal conditions (non timed) would never be met since they are only
* checked as-needed, in this case during a scene change.
*
* By moving normal conditions to the front they will always be checked first
* and timed can update at intervals per expectancy. This could also be resolved
* by simply not exiting early when a condition fails but that's going to
* cost hotpath performance where sorting is only done once. */
// Initialize collections.
_nonTimedMet = CollectionCaches<NetworkConnection>.RetrieveHashSet();
//Caches for ordering.
List<ObserverCondition> nonTimedConditions = CollectionCaches<ObserverCondition>.RetrieveList();
List<ObserverCondition> timedConditions = CollectionCaches<ObserverCondition>.RetrieveList();
foreach (ObserverCondition condition in _observerConditions)
{
if (condition == null)
continue;
observerFound = true;
/* Make an instance of each condition so values are
* not overwritten when the condition exist more than
* once in the scene. Double-edged sword of using scriptable
* objects for conditions. */
ObserverCondition ocCopy = Instantiate(condition);
//Condition type.
ObserverConditionType oct = ocCopy.GetConditionType();
if (oct == ObserverConditionType.Timed)
{
timedConditions.AddOrdered(ocCopy);
}
else
{
_hasNormalConditions = true;
nonTimedConditions.AddOrdered(ocCopy);
}
}
//Add to condition collection as ordered now.
_observerConditions.Clear();
//Non timed.
for (int i = 0; i < nonTimedConditions.Count; i++)
_observerConditions.Add(nonTimedConditions[i]);
//Timed.
_timedConditions = CollectionCaches<ObserverCondition>.RetrieveList();
foreach (ObserverCondition timedCondition in timedConditions)
{
_observerConditions.Add(timedCondition);
_timedConditions.Add(timedCondition);
}
//Store caches.
CollectionCaches<ObserverCondition>.Store(nonTimedConditions);
CollectionCaches<ObserverCondition>.Store(timedConditions);
}
if (observerFound)
{
//Initialize conditions.
for (int i = 0; i < _observerConditions.Count; i++)
_observerConditions[i].Initialize(_networkObject);
RegisterTimedConditions();
}
_initialized = true;
}
/// <summary>
/// Returns a condition if found within Conditions.
/// </summary>
/// <returns></returns>
public ObserverCondition GetObserverCondition<T>() where T : ObserverCondition
{
/* Do not bother setting local variables,
* condition collections aren't going to be long
* enough to make doing so worth while. */
System.Type conditionType = typeof(T);
for (int i = 0; i < _observerConditions.Count; i++)
{
if (_observerConditions[i].GetType() == conditionType)
return _observerConditions[i];
}
//Fall through, not found.
return null;
}
/// <summary>
/// Returns ObserverStateChange by comparing conditions for a connection.
/// </summary>
/// <returns>True if added to Observers.</returns>
internal ObserverStateChange RebuildObservers(NetworkConnection connection, bool timedOnly)
{
if (!_initialized)
{
string goName = gameObject == null ? "Empty" : gameObject.name;
NetworkManager nm = _networkObject == null ? null : _networkObject.NetworkManager;
nm.LogError($"{GetType().Name} is not initialized on NetworkObject [{goName}]. RebuildObservers should not be called. If you are able to reproduce this error consistently please report this issue.");
return ObserverStateChange.Unchanged;
}
bool currentlyAdded = _networkObject.Observers.Contains(connection);
//True if all conditions are met.
bool allConditionsMet = true;
/* If cnnection is owner then they can see the object. */
bool notOwner = connection != _networkObject.Owner;
/* Only check conditions if not owner. Owner will always
* have visibility. */
if (notOwner)
{
bool parentVisible = true;
if (_networkObject.CurrentParentNetworkBehaviour != null)
parentVisible = _networkObject.CurrentParentNetworkBehaviour.NetworkObject.Observers.Contains(connection);
/* If parent is visible but was not previously
* then unset timedOnly to make sure all conditions
* are checked again. This ensures that the _nonTimedMet
* collection is updated. */
if (parentVisible && !_lastParentVisible)
timedOnly = false;
_lastParentVisible = parentVisible;
//If parent is not visible no further checks are required.
if (!parentVisible)
{
allConditionsMet = false;
}
//Parent is visible, perform checks.
else
{
//Only need to check beyond this if conditions exist.
if (_observerConditions.Count > 0)
{
/* True if all conditions are timed or
* if connection has met non timed. */
bool startNonTimedMet = !_hasNormalConditions || _nonTimedMet.Contains(connection);
/* If a timed update an1d nonTimed
* have not been met then there's
* no reason to check timed. */
if (timedOnly && !startNonTimedMet)
{
allConditionsMet = false;
}
else
{
//Becomes true if a non-timed condition fails.
bool nonTimedMet = true;
List<ObserverCondition> collection = timedOnly ? _timedConditions : _observerConditions;
for (int i = 0; i < collection.Count; i++)
{
ObserverCondition condition = collection[i];
/* If any observer returns removed then break
* from loop and return removed. If one observer has
* removed then there's no reason to iterate
* the rest.
*
* A condition is automatically met if it's not enabled. */
bool notProcessed = false;
bool conditionMet = !condition.GetIsEnabled() || condition.ConditionMet(connection, currentlyAdded, out notProcessed);
if (notProcessed)
conditionMet = currentlyAdded;
//Condition not met.
if (!conditionMet)
{
allConditionsMet = false;
if (condition.GetConditionType() != ObserverConditionType.Timed)
nonTimedMet = false;
break;
}
}
//If nonTimedMet changed.
if (startNonTimedMet != nonTimedMet)
{
/* If the collection was iterated without breaks
* then add to nontimed met. */
if (nonTimedMet)
_nonTimedMet.Add(connection);
//If there were breaks not all conditions were checked.
else
_nonTimedMet.Remove(connection);
}
}
}
}
}
//If all conditions met.
if (allConditionsMet)
return ReturnPassedConditions(currentlyAdded);
else
return ReturnFailedCondition(currentlyAdded);
}
/// <summary>
/// Registers timed observer conditions.
/// </summary>
private void RegisterTimedConditions()
{
if (_timedConditions == null || _timedConditions.Count == 0)
return;
if (_registeredAsTimed)
return;
_registeredAsTimed = true;
if (_serverManager == null)
return;
_serverManager.Objects.AddTimedNetworkObserver(_networkObject);
}
/// <summary>
/// Unregisters timed conditions.
/// </summary>
private void UnregisterTimedConditions()
{
if (_timedConditions == null || _timedConditions.Count == 0)
return;
if (!_registeredAsTimed)
return;
_registeredAsTimed = false;
if (_serverManager == null)
return;
_serverManager.Objects.RemoveTimedNetworkObserver(_networkObject);
}
/// <summary>
/// Returns an ObserverStateChange when a condition fails.
/// </summary>
/// <param name = "currentlyAdded"></param>
/// <returns></returns>
private ObserverStateChange ReturnFailedCondition(bool currentlyAdded)
{
if (currentlyAdded)
return ObserverStateChange.Removed;
else
return ObserverStateChange.Unchanged;
}
/// <summary>
/// Returns an ObserverStateChange when all conditions pass.
/// </summary>
/// <param name = "currentlyAdded"></param>
/// <returns></returns>
private ObserverStateChange ReturnPassedConditions(bool currentlyAdded)
{
if (currentlyAdded)
return ObserverStateChange.Unchanged;
else
return ObserverStateChange.Added;
}
/// <summary>
/// Called when a remote client state changes with the server.
/// </summary>
private void ServerManager_OnRemoteConnectionState(NetworkConnection conn, RemoteConnectionStateArgs arg2)
{
if (arg2.ConnectionState == RemoteConnectionState.Stopped)
_nonTimedMet.Remove(conn);
}
/// <summary>
/// Sets a new value for UpdateHostVisibility.
/// This does not immediately update renderers.
/// You may need to combine with NetworkObject.SetRenderersVisible(bool).
/// </summary>
/// <param name = "value">New value.</param>
public void SetUpdateHostVisibility(bool value)
{
//Unchanged.
if (value == UpdateHostVisibility)
return;
UpdateHostVisibility = value;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c71fd7f855ec523429999fc4e14a1928
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Observing/NetworkObserver.cs
uploadId: 866910
@@ -0,0 +1,109 @@
using FishNet.Connection;
using FishNet.Managing.Server;
using FishNet.Object;
using System;
using GameKit.Dependencies.Utilities.Types;
using UnityEngine;
namespace FishNet.Observing
{
/// <summary>
/// Condition a connection must meet to be added as an observer.
/// This class can be inherited from for custom conditions.
/// </summary>
public abstract class ObserverCondition : ScriptableObject, IOrderable
{
#region Public.
/// <summary>
/// NetworkObject this condition is for.
/// </summary>
[HideInInspector]
public NetworkObject NetworkObject;
#endregion
#region Serialized.
/// <summary>
/// Order in which conditions are added to the NetworkObserver. Lower values will added first, resulting in the condition being checked first. Timed conditions will never check before non-timed conditions.
/// </summary>
public int Order => _addOrder;
[Tooltip("Order in which conditions are added to the NetworkObserver. Lower values will added first, resulting in the condition being checked first. Timed conditions will never check before non-timed conditions.")]
[SerializeField]
[Range(sbyte.MinValue, sbyte.MaxValue)]
private sbyte _addOrder;
/// <summary>
/// Setting this to true can save performance on conditions which do change settings or store data at runtime.
/// This feature does not function yet, but you may set values now for future implementation.
/// </summary>
public bool IsConstant => _isConstant;
[Tooltip("Setting this to true can save performance on conditions which do change settings or store data at runtime. This feature does not function yet but you may set values now for future implementation.")]
[SerializeField]
private bool _isConstant;
#endregion
#region Private.
/// <summary>
/// True if this condition is enabled.
/// </summary>
private bool _isEnabled = true;
/// <summary>
/// Gets the enabled state of this condition.
/// </summary>
/// <returns></returns>
public bool GetIsEnabled() => _isEnabled;
/// <summary>
/// Sets the enabled state of this condition.
/// If the state has changed observers will be rebuilt
/// for this object.
/// </summary>
/// <param name = "value"></param>
public void SetIsEnabled(bool value)
{
if (value == GetIsEnabled())
return;
_isEnabled = value;
// No object to rebuild for.
if (NetworkObject == null)
return;
ServerObjects so = NetworkObject?.ServerManager?.Objects;
if (so != null)
so.RebuildObservers(NetworkObject);
}
#endregion
/// <summary>
/// Initializes this script for use.
/// </summary>
/// <param name = "networkObject">NetworkObject this condition is initializing for.</param>
public virtual void Initialize(NetworkObject networkObject)
{
NetworkObject = networkObject;
}
/// <summary>
/// Deinitializes this script.
/// </summary>
/// <param name = "destroyed">True if the object is being destroyed, false if being despawned. An object may deinitialize for despawn, then destroy after.</param>
public virtual void Deinitialize(bool destroyed)
{
NetworkObject = null;
}
/// <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 abstract bool ConditionMet(NetworkConnection connection, bool currentlyAdded, out bool notProcessed);
/// <summary>
/// Type of condition this is. Certain types are handled different, such as Timed which are checked for changes at timed intervals.
/// </summary>
/// <returns></returns>
public abstract ObserverConditionType GetConditionType();
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 7d496d4febcb07f4abbdc081eaa99234
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Observing/ObserverCondition.cs
uploadId: 866910
@@ -0,0 +1,18 @@
namespace FishNet.Observing
{
/// <summary>
/// How a condition is handled.
/// This is intentionally not set as flags.
/// </summary>
public enum ObserverConditionType : byte
{
/// <summary>
/// Condition is checked only when changed.
/// </summary>
Normal = 1,
/// <summary>
/// Condition requires checks at regular intervals. The intervals are handled internally.
/// </summary>
Timed = 2
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: a0a36dd46b1f5bd4993d5f5b615bb2dd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Observing/ObserverConditionType.cs
uploadId: 866910
@@ -0,0 +1,12 @@
namespace FishNet.Observing
{
/// <summary>
/// States which observer(s) can change to.
/// </summary>
internal enum ObserverStateChange : byte
{
Unchanged = 0,
Added = 1,
Removed = 2
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: a4a8dca28b7d84548a918c5c32f684ad
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.6.22R
assetPath: Assets/FishNet/Runtime/Observing/ObserverStateChange.cs
uploadId: 866910