Add FishNet network library

This commit is contained in:
2026-09-17 20:29:57 +08:00
parent b0e708d54c
commit 668121e35c
2015 changed files with 213270 additions and 0 deletions
@@ -0,0 +1,13 @@
namespace FishNet.Utility
{
internal static class UtilityConstants
{
public const string CODEGEN_ASSEMBLY_NAME = "Unity.FishNet.CodeGen";
public const string GENERATED_ASSEMBLY_NAME = "FishNet.Generated";
public const string DEMOS_ASSEMBLY_NAME = "FishNet.Demos";
public const string TEST_ASSEMBLY_NAME = "FishNet.Test";
public const string RUNTIME_ASSEMBLY_NAME = "FishNet.Runtime";
public const string GeneratedWriterPrefix = "GWrite___";
public const string GeneratedReaderPrefix = "GRead___";
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 3f2a3c23b44e4ef4e9783ef53ec0d5da
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.7.3R
assetPath: Assets/FishNet/Runtime/Utility/Constants.cs
uploadId: 987062
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0d008bcb408806c45980331a665c65a7
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,25 @@
using FishNet.Documenting;
using FishNet.Managing;
using FishNet.Object;
namespace FishNet.Utility.Extension
{
[APIExclude]
public static class NetworksFN
{
/// <summary>
/// Returns if logic could have potentially called already on server side, and is calling a second time for clientHost side.
/// </summary>
public static bool DoubleLogic(this NetworkObject nob, bool asServer) => !asServer && nob.NetworkManager.IsServerStarted;
/// <summary>
/// Returns if logic could have potentially called already on server side, and is calling a second time for clientHost side.
/// </summary>
public static bool DoubleLogic(this NetworkManager manager, bool asServer) => !asServer && manager.IsServerStarted;
/// <summary>
/// Returns if logic could have potentially called already on server side, and is calling a second time for clientHost side.
/// </summary>
public static bool DoubleLogic(this NetworkBehaviour nb, bool asServer) => !asServer && nb.NetworkManager.IsServerStarted;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: cbd3adaae5d34a14ab9cf68726b3bd3e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.7.3R
assetPath: Assets/FishNet/Runtime/Utility/Extension/Networks.cs
uploadId: 987062
@@ -0,0 +1,122 @@
using FishNet.Managing;
using FishNet.Object;
using GameKit.Dependencies.Utilities;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
#if UNITY_6000_5_OR_NEWER
using SceneHandle = System.UInt64;
#else
using SceneHandle = System.Int32;
#endif
namespace FishNet.Utility.Extension
{
public static class Scenes
{
/// <summary>
/// Gets all NetworkObjects in a scene.
/// </summary>
/// <param name = "s">Scene to get objects in.</param>
/// <param name = "firstOnly">True to only return the first NetworkObject within an object chain. False will return nested NetworkObjects.</param>
/// <returns></returns>
public static void GetSceneNetworkObjects(Scene s, bool firstOnly, bool errorOnDuplicates, bool ignoreUnsetSceneIds, ref List<NetworkObject> result)
{
List<NetworkObject> nobCacheA = CollectionCaches<NetworkObject>.RetrieveList();
List<NetworkObject> nobCacheB = CollectionCaches<NetworkObject>.RetrieveList();
List<GameObject> gameObjectCache = CollectionCaches<GameObject>.RetrieveList();
Dictionary<ulong, NetworkObject> sceneIds = CollectionCaches<ulong, NetworkObject>.RetrieveDictionary();
// Iterate all root objects for the scene.
s.GetRootGameObjects(gameObjectCache);
foreach (GameObject go in gameObjectCache)
{
// Get NetworkObjects within children of each root.
go.GetComponentsInChildren(true, nobCacheA);
// If network objects are found.
if (nobCacheA.Count > 0)
{
// Add only the first networkobject
if (firstOnly)
{
/* The easiest way to see if a nob is nested is to
* get nobs in parent and if the count is greater than 1, then
* it is nested. The technique used here isn't exactly fast but
* it will only occur during scene loads, so I'm trading off speed
* for effort and readability. */
foreach (NetworkObject nob in nobCacheA)
{
if (ignoreUnsetSceneIds && !nob.IsSceneObject)
continue;
nob.GetComponentsInParent(true, nobCacheB);
// No extra nobs, only this one.
if (nobCacheB.Count == 1 && !TryDisplayDuplicateError(nob))
result.Add(nob);
}
}
// Not first only, add them all.
else
{
foreach (NetworkObject item in nobCacheA)
{
if (ignoreUnsetSceneIds && !item.IsSceneObject)
continue;
if (!TryDisplayDuplicateError(item))
result.Add(item);
}
}
}
}
CollectionCaches<ulong, NetworkObject>.Store(sceneIds);
bool TryDisplayDuplicateError(NetworkObject nob)
{
if (!errorOnDuplicates)
return false;
ulong id = nob.SceneId;
// There is a duplicate.
if (sceneIds.TryGetValue(id, out NetworkObject originalNob))
{
string err = $"Object {nob.name} and {originalNob.name} in scene {nob.gameObject.scene.name} have the same sceneId of {id}. This will result in spawning errors. Exit play mode and use the Fish-Networking menu to reserialize sceneIds for scene {nob.gameObject.scene.name}.";
NetworkManagerExtensions.LogError(err);
return true;
}
else
{
sceneIds[id] = nob;
return false;
}
}
}
/// <summary>
/// Returns the raw handle of a scene as a version-appropriate value.
/// Unity 6000.5 changed Scene.handle from an int to an EntityId struct exposing GetRawData(); earlier versions expose it directly as an int.
/// </summary>
/// <param name = "scene">Scene to return the handle of.</param>
public static SceneHandle GetRawHandle(this Scene scene)
{
#if UNITY_6000_5_OR_NEWER
return scene.handle.GetRawData();
#else
return scene.handle;
#endif
}
/// <summary>
/// Converts a legacy 32-bit scene handle into a version-appropriate raw handle.
/// </summary>
/// <param name = "legacyHandle">Legacy 32-bit scene handle.</param>
public static SceneHandle ToRawHandle(int legacyHandle)
{
#if UNITY_6000_5_OR_NEWER
return unchecked((uint)legacyHandle);
#else
return legacyHandle;
#endif
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: a02f3d03f737e304e9854278f4e9211d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.7.3R
assetPath: Assets/FishNet/Runtime/Utility/Extension/Scenes.cs
uploadId: 987062
@@ -0,0 +1,194 @@
using System;
using FishNet.Documenting;
using FishNet.Object;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace FishNet.Utility.Extension
{
[APIExclude]
public static class TransformFN
{
/// <summary>
/// Sets values of TransformProperties to a transforms world properties.
/// </summary>
public static TransformProperties GetWorldProperties(this Transform t)
{
TransformProperties tp = new(t.position, t.rotation, t.localScale);
return tp;
}
/// <summary>
/// Sets values of TransformProperties to a transforms world properties.
/// </summary>
public static TransformProperties GetWorldProperties(this Transform t, TransformProperties offset)
{
TransformProperties tp = new(t.position, t.rotation, t.localScale);
tp.Add(offset);
return tp;
}
/// <summary>
/// Sets values of TransformProperties to a transforms world properties.
/// </summary>
public static TransformPropertiesCls GetWorldPropertiesCls(this Transform t)
{
TransformPropertiesCls tp = new(t.position, t.rotation, t.localScale);
return tp;
}
/// <summary>
/// Sets values of TransformProperties to a transforms world properties.
/// </summary>
public static TransformProperties GetLocalProperties(this Transform t)
{
TransformProperties tp = new(t.localPosition, t.localRotation, t.localScale);
return tp;
}
/// <summary>
/// Sets values of TransformProperties to a transforms world properties.
/// </summary>
public static TransformPropertiesCls GetLocalPropertiesCls(this Transform t)
{
TransformPropertiesCls tp = new(t.localPosition, t.localRotation, t.localScale);
return tp;
}
/// <summary>
/// Sets values of TransformPropertiesCls to a transforms world properties.
/// </summary>
[Obsolete("Use TransformPropertiesExtensions.SetWorldProperties.")]
public static void SetWorldProperties(this TransformPropertiesCls tp, Transform t) => TransformPropertiesExtensions.SetWorldProperties(tp, t);
/// <summary>
/// Gets the offset values by subtracting this from target.
/// </summary>
/// <param name = "pos">Position offset result.</param>
/// <param name = "rot">Rotation offset result.</param>
public static void SetTransformOffsets(this Transform t, Transform target, ref Vector3 pos, ref Quaternion rot)
{
if (target == null)
return;
pos = target.position - t.position;
rot = target.rotation * Quaternion.Inverse(t.rotation);
}
/// <summary>
/// Gets the offset values by subtracting this from target.
/// </summary>
/// <param name = "zeroScale">True to set scale to Vector3.zero.</param>
public static TransformProperties GetTransformOffsets(this Transform t, Transform target)
{
if (target == null)
return default;
return new(target.position - t.position, target.rotation * Quaternion.Inverse(t.rotation), target.localScale - t.localScale);
}
/// <summary>
/// Sets a transform to local properties.
/// </summary>
public static void SetLocalProperties(this Transform t, TransformPropertiesCls tp)
{
t.localPosition = tp.Position;
t.localRotation = tp.Rotation;
t.localScale = tp.LocalScale;
}
/// <summary>
/// Sets a transform to local properties.
/// </summary>
public static void SetLocalProperties(this Transform t, TransformProperties tp)
{
t.localPosition = tp.Position;
t.localRotation = tp.Rotation;
t.localScale = tp.Scale;
}
/// <summary>
/// Sets a transform to world properties.
/// </summary>
public static void SetWorldProperties(this Transform t, TransformPropertiesCls tp)
{
t.position = tp.Position;
t.rotation = tp.Rotation;
t.localScale = tp.LocalScale;
}
/// <summary>
/// Sets a transform to world properties.
/// </summary>
public static void SetWorldProperties(this Transform t, TransformProperties tp)
{
t.position = tp.Position;
t.rotation = tp.Rotation;
t.localScale = tp.Scale;
}
/// <summary>
/// Sets local position and rotation for a transform.
/// </summary>
public static void SetLocalPositionAndRotation(this Transform t, Vector3 pos, Quaternion rot)
{
t.localPosition = pos;
t.localRotation = rot;
}
/// <summary>
/// Sets local position, rotation, and scale for a transform.
/// </summary>
public static void SetLocalPositionRotationAndScale(this Transform t, Vector3 pos, Quaternion rot, Vector3 scale)
{
t.localPosition = pos;
t.localRotation = rot;
t.localScale = scale;
}
/// <summary>
/// Sets local position, rotation, and scale using nullables for a transform. If a value is null then that property is skipped.
/// </summary>
public static void SetLocalPositionRotationAndScale(this Transform t, Vector3? nullablePos, Quaternion? nullableRot, Vector3? nullableScale)
{
if (nullablePos.HasValue)
t.localPosition = nullablePos.Value;
if (nullableRot.HasValue)
t.localRotation = nullableRot.Value;
if (nullableScale.HasValue)
t.localScale = nullableScale.Value;
}
/// <summary>
/// Sets world position, rotation, and scale using nullables for a transform. If a value is null then that property is skipped.
/// </summary>
public static void SetWorldPositionRotationAndScale(this Transform t, Vector3? nullablePos, Quaternion? nullableRot, Vector3? nullableScale)
{
if (nullablePos.HasValue)
t.position = nullablePos.Value;
if (nullableRot.HasValue)
t.rotation = nullableRot.Value;
if (nullableScale.HasValue)
t.localScale = nullableScale.Value;
}
/// <summary>
/// Oututs properties to use for a transform. When a nullable property has value that value is used, otherwise the transforms current property is used.
/// </summary>
public static void OutLocalPropertyValues(this Transform t, Vector3? nullablePos, Quaternion? nullableRot, Vector3? nullableScale, out Vector3 pos, out Quaternion rot, out Vector3 scale)
{
pos = nullablePos == null ? t.localPosition : nullablePos.Value;
rot = nullableRot == null ? t.localRotation : nullableRot.Value;
scale = nullableScale == null ? t.localScale : nullableScale.Value;
}
/// <summary>
/// Oututs properties to use for a transform. When a nullable property has value that value is used, otherwise the transforms current property is used.
/// </summary>
public static void OutWorldPropertyValues(this Transform t, Vector3? nullablePos, Quaternion? nullableRot, Vector3? nullableScale, out Vector3 pos, out Quaternion rot, out Vector3 scale)
{
pos = nullablePos == null ? t.position : nullablePos.Value;
rot = nullableRot == null ? t.rotation : nullableRot.Value;
scale = nullableScale == null ? t.localScale : nullableScale.Value;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 3d311fc1bf09b9e4fbc5a17a9c50ab0d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.7.3R
assetPath: Assets/FishNet/Runtime/Utility/Extension/Transforms.cs
uploadId: 987062
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7b315d9102b18b541b0b2346c9ad63ed
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,230 @@
// using System; // Remove on V5
// namespace FishNet.Utility.Performance
// {
// /// <summary>
// /// Unity 2022 has a bug where codegen will not compile when referencing a Queue type,
// /// while also targeting .Net as the framework API.
// /// As a work around this class is used for queues instead.
// /// </summary>
// public class BasicQueue<T>
// {
// /// <summary>
// /// Maximum size of the collection.
// /// </summary>
// public int Capacity => Collection.Length;
// /// <summary>
// /// Number of elements in the queue.
// /// </summary>
// public int Count => _written;
// /// <summary>
// /// Collection containing data.
// /// </summary>
// private T[] Collection = new T[4];
// /// <summary>
// /// Current write index of the collection.
// /// </summary>
// public int WriteIndex { get; private set; }
// /// <summary>
// /// Buffer for resizing.
// /// </summary>
// private T[] _resizeBuffer = new T[0];
// /// <summary>
// /// Read position of the next Dequeue.
// /// </summary>
// private int _read;
// /// <summary>
// /// Length of the queue.
// /// </summary>
// private int _written;
// /// <summary>
// /// Enqueues an entry.
// /// </summary>
// /// <param name="data"></param>
// public void Enqueue(T data)
// {
// if (_written == Collection.Length)
// Resize();
// if (WriteIndex >= Collection.Length)
// WriteIndex = 0;
// Collection[WriteIndex] = data;
// WriteIndex++;
// _written++;
// }
// /// <summary>
// /// Tries to dequeue the next entry.
// /// </summary>
// /// <param name="result">Dequeued entry.</param>
// /// <returns>True if an entry existed to dequeue.</returns>
// public bool TryDequeue(out T result)
// {
// if (_written == 0)
// {
// result = default;
// return false;
// }
// result = Dequeue();
// return true;
// }
// /// <summary>
// /// Dequeues the next entry.
// /// </summary>
// /// <returns></returns>
// public T Dequeue()
// {
// if (_written == 0)
// throw new Exception($"Queue of type {typeof(T).Name} is empty.");
// T result = Collection[_read];
// _written--;
// _read++;
// if (_read >= Collection.Length)
// _read = 0;
// return result;
// }
// /// <summary>
// /// Tries to peek the next entry.
// /// </summary>
// /// <param name="result">Peeked entry.</param>
// /// <returns>True if an entry existed to peek.</returns>
// public bool TryPeek(out T result)
// {
// if (_written == 0)
// {
// result = default;
// return false;
// }
// result = Peek();
// return true;
// }
// /// <summary>
// /// Peeks the next queue entry.
// /// </summary>
// /// <returns></returns>
// public T Peek()
// {
// if (_written == 0)
// throw new Exception($"Queue of type {typeof(T).Name} is empty.");
// return Collection[_read];
// }
// /// <summary>
// /// Clears the queue.
// /// </summary>
// public void Clear()
// {
// _read = 0;
// WriteIndex = 0;
// _written = 0;
// DefaultCollection(Collection);
// DefaultCollection(_resizeBuffer);
// void DefaultCollection(T[] array)
// {
// int count = array.Length;
// for (int i = 0; i < count; i++)
// array[i] = default;
// }
// }
// /// <summary>
// /// Doubles the queue size.
// /// </summary>
// private void Resize()
// {
// int length = _written;
// int doubleLength = (length * 2);
// int read = _read;
// /* Make sure copy array is the same size as current
// * and copy contents into it. */
// // Ensure large enough to fit contents.
// T[] resizeBuffer = _resizeBuffer;
// if (resizeBuffer.Length < doubleLength)
// Array.Resize(ref resizeBuffer, doubleLength);
// // Copy from the read of queue first.
// int copyLength = (length - read);
// Array.Copy(Collection, read, resizeBuffer, 0, copyLength);
// /* If read index was higher than 0
// * then copy remaining data as well from 0. */
// if (read > 0)
// Array.Copy(Collection, 0, resizeBuffer, copyLength, read);
// // Set _array to resize.
// Collection = resizeBuffer;
// // Reset positions.
// _read = 0;
// WriteIndex = length;
// }
// /// <summary>
// /// Returns value in actual index as it relates to simulated index.
// /// </summary>
// /// <param name="simulatedIndex">Simulated index to return. A value of 0 would return the first simulated index in the collection.</param>
// /// <returns></returns>
// public T this[int simulatedIndex]
// {
// get
// {
// int offset = GetRealIndex(simulatedIndex);
// return Collection[offset];
// }
// set
// {
// int offset = GetRealIndex(simulatedIndex);
// Collection[offset] = value;
// }
// }
// /// <summary>
// /// Returns the real index of the collection using a simulated index.
// /// </summary>
// /// <param name="allowUnusedBuffer">True to allow an index be returned from an unused portion of the buffer so long as it is within bounds.</param>
// private int GetRealIndex(int simulatedIndex, bool allowUnusedBuffer = false)
// {
// if (simulatedIndex >= Capacity)
// {
// return ReturnError();
// }
// else
// {
// int written = _written;
// // May be out of bounds if allowUnusedBuffer is false.
// if (simulatedIndex >= written)
// {
// if (!allowUnusedBuffer)
// return ReturnError();
// }
// int offset = (Capacity - written) + simulatedIndex + WriteIndex;
// if (offset >= Capacity)
// offset -= Capacity;
// return offset;
// }
// int ReturnError()
// {
// UnityEngine.Debug.LogError($"Index {simulatedIndex} is out of range. Collection count is {_written}, Capacity is {Capacity}");
// return -1;
// }
// }
// }
// }
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 7c0a46ad871780b45bfe317c8f904322
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.7.3R
assetPath: Assets/FishNet/Runtime/Utility/Performance/BasicQueue.cs
uploadId: 987062
@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
namespace FishNet.Utility.Performance
{
/// <summary>
/// Retrieves and stores byte arrays using a pooling system.
/// </summary>
public static class ByteArrayPool
{
/// <summary>
/// Stored byte arrays.
/// </summary>
private static Queue<byte[]> _byteArrays = new();
/// <summary>
/// Returns a byte array which will be of at lesat minimum length. The returned array must manually be stored.
/// </summary>
public static byte[] Retrieve(int minimumLength)
{
byte[] result = null;
if (_byteArrays.Count > 0)
result = _byteArrays.Dequeue();
int doubleMinimumLength = minimumLength * 2;
if (result == null)
result = new byte[doubleMinimumLength];
else if (result.Length < minimumLength)
Array.Resize(ref result, doubleMinimumLength);
return result;
}
/// <summary>
/// Stores a byte array for re-use.
/// </summary>
public static void Store(byte[] buffer)
{
/* Holy cow that's a lot of buffered
* buffers. This wouldn't happen under normal
* circumstances but if the user is stress
* testing connections in one executable perhaps. */
if (_byteArrays.Count > 300)
return;
_byteArrays.Enqueue(buffer);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c7620a5e6fedc18408f8f04821b35bbd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.7.3R
assetPath: Assets/FishNet/Runtime/Utility/Performance/ByteArrayPool.cs
uploadId: 987062
@@ -0,0 +1,326 @@
using FishNet.Managing;
using FishNet.Managing.Object;
using FishNet.Object;
using FishNet.Utility.Extension;
using GameKit.Dependencies.Utilities;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace FishNet.Utility.Performance
{
public class DefaultObjectPool : ObjectPool
{
#region Public.
/// <summary>
/// Cache for pooled NetworkObjects.
/// Key: CollectionId.
/// </summary>
public IReadOnlyList<Dictionary<int, Stack<NetworkObject>>> Cache => _cache;
private List<Dictionary<int, Stack<NetworkObject>>> _cache = new();
#endregion
#region Serialized.
/// <summary>
/// True if to use object pooling.
/// </summary>
[Tooltip("True if to use object pooling.")]
[SerializeField]
private bool _enabled = true;
#endregion
#region Private.
/// <summary>
/// Current count of the cache collection.
/// </summary>
private int _cacheCount = 0;
#endregion
#pragma warning disable CS0672 // Member overrides obsolete member
public override NetworkObject RetrieveObject(int prefabId, ushort collectionId, Transform parent = null, Vector3? nullablePosition = null, Quaternion? nullableRotation = null, Vector3? nullableScale = null, bool makeActive = true, bool asServer = true)
#pragma warning restore CS0672 // Member overrides obsolete member
{
ObjectPoolRetrieveOption options = ObjectPoolRetrieveOption.Unset;
if (makeActive)
options |= ObjectPoolRetrieveOption.MakeActive;
return RetrieveObject(prefabId, collectionId, options, parent, nullablePosition, nullableRotation, nullableScale, asServer);
}
/// <summary>
/// Returns an object that has been stored. A new object will be created if no stored objects are available.
/// </summary>
/// <param name = "prefabId">PrefabId of the object to return.</param>
/// <param name = "collectionId">CollectionId of the object to return.</param>
/// <param name = "asServer">True if being called on the server side.</param>
/// <returns></returns>
public override NetworkObject RetrieveObject(int prefabId, ushort collectionId, ObjectPoolRetrieveOption options, Transform parent = null, Vector3? nullablePosition = null, Quaternion? nullableRotation = null, Vector3? nullableScale = null, bool asServer = true)
{
bool makeActive = options.FastContains(ObjectPoolRetrieveOption.MakeActive);
bool localSpace = options.FastContains(ObjectPoolRetrieveOption.LocalSpace);
if (!_enabled)
return GetFromInstantiate();
Stack<NetworkObject> cache = GetCache(collectionId, prefabId, createIfMissing: true);
NetworkObject nob = null;
// Iterate until nob is populated just in case cache entries have been destroyed.
while (nob == null)
{
if (cache.TryPop(out nob))
{
if (nob != null)
{
nob.transform.SetParent(parent);
if (localSpace)
nob.transform.SetLocalPositionRotationAndScale(nullablePosition, nullableRotation, nullableScale);
else
nob.transform.SetWorldPositionRotationAndScale(nullablePosition, nullableRotation, nullableScale);
if (makeActive)
nob.gameObject.SetActive(true);
return nob;
}
}
// Nothing left in cache.
else
{
break;
}
}
// Fall through, nothing in cache.
return GetFromInstantiate();
// Returns a network object via instantation.
NetworkObject GetFromInstantiate()
{
NetworkObject prefab = GetPrefab(prefabId, collectionId, asServer);
if (prefab == null)
{
return null;
}
else
{
NetworkObject result;
Vector3 scale;
if (localSpace)
{
prefab.transform.OutLocalPropertyValues(nullablePosition, nullableRotation, nullableScale, out Vector3 pos, out Quaternion rot, out scale);
if (parent != null)
{
// Convert pos and rot to world values for the instantiate.
pos = parent.TransformPoint(pos);
rot = parent.rotation * rot;
}
result = Instantiate(prefab, pos, rot, parent);
}
else
{
prefab.transform.OutWorldPropertyValues(nullablePosition, nullableRotation, nullableScale, out Vector3 pos, out Quaternion rot, out scale);
result = Instantiate(prefab, pos, rot, parent);
}
result.transform.localScale = scale;
if (makeActive)
result.gameObject.SetActive(true);
return result;
}
}
}
/// <summary>
/// Returns a prefab for prefab and collectionId.
/// </summary>
public override NetworkObject GetPrefab(int prefabId, ushort collectionId, bool asServer)
{
PrefabObjects po = NetworkManager.GetPrefabObjects<PrefabObjects>(collectionId, false);
return po.GetObject(asServer, prefabId);
}
/// <summary>
/// Stores an object into the pool.
/// </summary>
/// <param name = "instantiated">Object to store.</param>
/// <param name = "asServer">True if being called on the server side.</param>
/// <returns></returns>
public override void StoreObject(NetworkObject instantiated, bool asServer)
{
// Pooling is not enabled.
if (!_enabled)
{
Destroy(instantiated.gameObject);
return;
}
/* This call occurs here rather than in the object pool
* to protect the user should they be using a
* custom pool. */
SetTransformToSerializedValues(instantiated);
// Get all children as well and reset state on them.
List<NetworkObject> nestedNobs = instantiated.GetNetworkObjects(GetNetworkObjectOption.All);
foreach (NetworkObject nob in nestedNobs)
nob.ResetState(asServer);
CollectionCaches<NetworkObject>.Store(nestedNobs);
// Set root inactive.
instantiated.gameObject.SetActive(false);
Stack<NetworkObject> cache = GetCache(instantiated.SpawnableCollectionId, instantiated.PrefabId, createIfMissing: true);
cache.Push(instantiated);
}
/// <summary>
/// Instantiates a number of objects and adds them to the pool.
/// </summary>
/// <param name = "prefab">Prefab to cache.</param>
/// <param name = "count">Quantity to spawn.</param>
/// <param name = "asServer">True if storing prefabs for the server collection. This is only applicable when using DualPrefabObjects.</param>
#pragma warning disable CS0672 // Member overrides obsolete member
public override void CacheObjects(NetworkObject prefab, int count, bool asServer) => StorePrefabObjects(prefab, count, asServer);
#pragma warning restore CS0672 // Member overrides obsolete member
/// <summary>
/// Instantiates a number of objects and adds them to the pool.
/// </summary>
/// <param name = "prefab">Prefab to cache.</param>
/// <param name = "count">Quantity to spawn.</param>
/// <param name = "asServer">True if storing prefabs for the server collection. This is only applicable when using DualPrefabObjects.</param>
/// <returns>Prefabs instantiated and added to cache.</returns>
public override List<NetworkObject> StorePrefabObjects(NetworkObject prefab, int count, bool asServer)
{
if (!_enabled)
return null;
if (count <= 0)
return null;
if (prefab == null)
return null;
if (prefab.PrefabId == NetworkObject.UNSET_PREFABID_VALUE)
{
NetworkManager.LogError($"Pefab {prefab.name} has an invalid prefabId and cannot be cached.");
return null;
}
List<NetworkObject> added = new();
Stack<NetworkObject> cache = GetCache(prefab.SpawnableCollectionId, prefab.PrefabId, createIfMissing: true);
for (int i = 0; i < count; i++)
{
NetworkObject nob = Instantiate(prefab);
nob.gameObject.SetActive(false);
cache.Push(nob);
added.Add(nob);
}
return added;
}
/// <summary>
/// Clears pooled objects for a specific NetworkObject.
/// </summary>
/// <param name = "nob">Prefab or Instantiated NetworkObject to clear pool for.</param>
/// <remarks>This will clear the entire pool for the specified object.</remarks>
public void ClearPool(NetworkObject nob)
{
if (!_enabled)
return;
if (nob == null)
return;
int spawnableCollectionId = nob.SpawnableCollectionId;
Stack<NetworkObject> stack = GetCache(spawnableCollectionId, nob.PrefabId, createIfMissing: false);
if (stack == null)
return;
DestroyStackNetworkObjectsAndClear(stack);
_cache[spawnableCollectionId].Clear();
}
/// <summary>
/// Clears all pooled objects.
/// </summary>
public void ClearPool()
{
int count = _cache.Count;
for (int i = 0; i < count; i++)
ClearPool(i);
}
/// <summary>
/// Clears a pool destroying objects for a SpawnableCollectionId.
/// </summary>
/// <param name = "spawnableCollectionId">CollectionId to clear for.</param>
public void ClearPool(int spawnableCollectionId)
{
if (spawnableCollectionId >= _cacheCount)
return;
Dictionary<int, Stack<NetworkObject>> dict = _cache[spawnableCollectionId];
foreach (Stack<NetworkObject> item in dict.Values)
DestroyStackNetworkObjectsAndClear(item);
dict.Clear();
}
/// <summary>
/// Gets a cache for an id or creates one if does not exist.
/// </summary>
/// <returns></returns>
public Stack<NetworkObject> GetCache(int collectionId, int prefabId, bool createIfMissing)
{
if (collectionId >= _cacheCount)
{
// Do not create if missing.
if (!createIfMissing)
return null;
// Add more to the cache.
while (_cache.Count <= collectionId)
{
Dictionary<int, Stack<NetworkObject>> dict = new();
_cache.Add(dict);
}
_cacheCount = _cache.Count;
}
Dictionary<int, Stack<NetworkObject>> dictionary = _cache[collectionId];
// No cache for prefabId yet, make one.
if (!dictionary.TryGetValueIL2CPP(prefabId, out Stack<NetworkObject> cache))
{
if (createIfMissing)
{
cache = new();
dictionary[prefabId] = cache;
}
}
return cache;
}
[Obsolete("Use GetCache(int, int, bool)")]
public Stack<NetworkObject> GetOrCreateCache(int collectionId, int prefabId) => GetCache(collectionId, prefabId, createIfMissing: true);
/// <summary>
/// Destroys all NetworkObjects within a stack and clears the stack.
/// </summary>
private void DestroyStackNetworkObjectsAndClear(Stack<NetworkObject> stack)
{
foreach (NetworkObject networkObject in stack)
{
if (networkObject != null)
Destroy(networkObject.gameObject);
}
stack.Clear();
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: cb13d174096685549b1d6a94d726ff7d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.7.3R
assetPath: Assets/FishNet/Runtime/Utility/Performance/DefaultObjectPool.cs
uploadId: 987062
@@ -0,0 +1,96 @@
using FishNet.Managing;
using FishNet.Managing.Object;
using FishNet.Object;
using System;
using System.Collections.Generic;
using FishNet.Utility.Extension;
using UnityEngine;
namespace FishNet.Utility.Performance
{
public abstract class ObjectPool : MonoBehaviour
{
/// <summary>
/// NetworkManager this ObjectPool belongs to.
/// </summary>
protected NetworkManager NetworkManager { get; private set; }
/// <summary>
/// Called at the end of every frame. This can be used to perform routine tasks.
/// </summary>
public virtual void LateUpdate() { }
/// <summary>
/// Initializes this script for use.
/// </summary>
public virtual void InitializeOnce(NetworkManager nm)
{
NetworkManager = nm;
}
/// <summary>
/// Returns an object that has been stored. A new object will be created if no stored objects are available.
/// </summary>
/// <param name = "prefabId">PrefabId of the object to return.</param>
/// <param name = "collectionId">CollectionId of the object to return.</param>
/// <param name = "asServer">True if being called on the server side.</param>
/// <returns></returns>
[Obsolete("Use RetrieveObject(int, ushort, RetrieveOption, parent, Vector3?, Quaternion? Vector3?, bool) instead.")] // Remove in V5
public virtual NetworkObject RetrieveObject(int prefabId, ushort collectionId, Transform parent = null, Vector3? position = null, Quaternion? rotation = null, Vector3? scale = null, bool makeActive = true, bool asServer = true) => null;
/// <summary>
/// Returns an object that has been stored. A new object will be created if no stored objects are available.
/// </summary>
/// <param name = "prefabId">PrefabId of the object to return.</param>
/// <param name = "collectionId">CollectionId of the object to return.</param>
/// <param name = "asServer">True if being called on the server side.</param>
/// <returns></returns>
public virtual NetworkObject RetrieveObject(int prefabId, ushort collectionId, ObjectPoolRetrieveOption options, Transform parent = null, Vector3? position = null, Quaternion? rotation = null, Vector3? scale = null, bool asServer = true) => null;
/// <summary>
/// Returns a prefab using specified values.
/// </summary>
/// <param name = "prefabId">PrefabId of the object to return.</param>
/// <param name = "collectionId">CollectionId of the object to return.</param>
/// <param name = "asServer">True if being called on the server side.</param>
/// <returns></returns>
public virtual NetworkObject GetPrefab(int prefabId, ushort collectionId, bool asServer)
{
PrefabObjects po = NetworkManager.GetPrefabObjects<PrefabObjects>(collectionId, false);
return po.GetObject(asServer, prefabId);
}
/// <summary>
/// Stores an object into the pool.
/// </summary>
/// <param name = "instantiated">Object to store.</param>
/// <param name = "asServer">True if being called on the server side.</param>
/// <returns></returns>
public abstract void StoreObject(NetworkObject instantiated, bool asServer);
/// <summary>
/// Instantiates a number of objects and adds them to the pool.
/// </summary>
/// <param name = "prefab">Prefab to cache.</param>
/// <param name = "count">Quantity to spawn.</param>
/// <param name = "asServer">True if storing prefabs for the server collection. This is only applicable when using DualPrefabObjects.</param>
public virtual void CacheObjects(NetworkObject prefab, int count, bool asServer) { }
/// <summary>
/// Instantiates a number of objects and adds them to the pool.
/// </summary>
/// <param name = "prefab">Prefab to cache.</param>
/// <param name = "count">Quantity to spawn.</param>
/// <param name = "asServer">True if storing prefabs for the server collection. This is only applicable when using DualPrefabObjects.</param>
/// <returns>Prefabs instantiated and added to cache.</returns>
public virtual List<NetworkObject> StorePrefabObjects(NetworkObject prefab, int count, bool asServer) => default;
/// <summary>
/// Sets the transform of a NetworkObject to it's values during serialization.
/// </summary>
public static void SetTransformToSerializedValues(NetworkObject nob)
{
nob.transform.SetLocalPositionRotationAndScale(nob.SerializedTransformProperties.Position, nob.SerializedTransformProperties.Rotation, nob.SerializedTransformProperties.Scale);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 6ec7d855ffa7afc45b619b84ddbda27c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.7.3R
assetPath: Assets/FishNet/Runtime/Utility/Performance/ObjectPool.cs
uploadId: 987062
@@ -0,0 +1,22 @@
namespace FishNet.Utility.Performance
{
public static class RetrieveOptionExtensions
{
public static bool FastContains(this ObjectPoolRetrieveOption whole, ObjectPoolRetrieveOption part) => (whole & part) == part;
}
[System.Flags]
public enum ObjectPoolRetrieveOption
{
Unset = 0,
/// <summary>
/// True to make the object active before returning.
/// </summary>
MakeActive = 1,
/// <summary>
/// True to treat supplied transform properties as local space.
/// False will treat the properties as world space.
/// </summary>
LocalSpace = 2
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 30dbc4b626ddb7844aac0189cc4c7487
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.7.3R
assetPath: Assets/FishNet/Runtime/Utility/Performance/ObjectPoolRetrieveOption.cs
uploadId: 987062
@@ -0,0 +1,25 @@
using FishNet.Object;
using System.Collections.Generic;
using UnityEngine;
namespace FishNet.Utility.Performance
{
public static class GetNonAlloc
{
/// <summary>
/// Gets all NetworkBehaviours on a transform.
/// </summary>
public static void GetNetworkBehavioursNonAlloc(this Transform t, ref List<NetworkBehaviour> results)
{
t.GetComponents(results);
}
/// <summary>
/// Gets all transforms on transform and it's children.
/// </summary>
public static void GetTransformsInChildrenNonAlloc(this Transform t, ref List<Transform> results, bool includeInactive = false)
{
t.GetComponentsInChildren(includeInactive, results);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 7d0740f919077254c8ffb131b9587407
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.7.3R
assetPath: Assets/FishNet/Runtime/Utility/Performance/Transforms.cs
uploadId: 987062
@@ -0,0 +1,45 @@
using System;
namespace FishNet.Utility
{
/// <summary>
/// Finds the next prime number on or after a value.
/// </summary>
public static class PrimeNumberFinder
{
public static uint GetNextPrime(uint number)
{
uint candidate = number < 2 ? 2 : number;
while (!IsPrime(candidate))
candidate++;
return candidate;
}
/// <summary>
/// Returns if a number is prime.
/// </summary>
private static bool IsPrime(uint number)
{
if (number < 2)
return false;
if (number == 2)
return true;
if (number % 2 == 0)
return false;
int limit = (int)Math.Sqrt(number);
for (int i = 3; i <= limit; i += 2)
{
if (number % i == 0)
return false;
}
return true;
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: e2c327a3a957646499f244ed651a7250
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.7.3R
assetPath: Assets/FishNet/Runtime/Utility/PrimeNumberFinder.cs
uploadId: 987062
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f0c2d33d2e9aea44195c265bd08ff498
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,115 @@
using FishNet.Managing.Timing;
using FishNet.Object;
using GameKit.Dependencies.Utilities;
using UnityEngine;
namespace FishNet.Utility.Template
{
/// <summary>
/// Subscribes to tick events making them available as virtual methods.
/// </summary>
public abstract class TickNetworkBehaviour : NetworkBehaviour
{
#region Types.
[System.Flags]
[System.Serializable]
public enum TickCallback : uint
{
None = 0,
PreTick = 1 << 0,
Tick = 1 << 1,
PostTick = 1 << 2,
Update = 1 << 3,
LateUpdate = 1 << 4,
Everything = Enums.SHIFT_EVERYTHING_UINT
}
#endregion
/// <summary>
/// Tick callbacks to use.
/// </summary>
[Tooltip("Tick callbacks to use.")]
[SerializeField]
private TickCallback _tickCallbacks = TickCallback.Tick | TickCallback.PostTick;
/// <summary>
/// Last subscription state.
/// </summary>
private bool _subscribed;
/// <summary>
/// TimeManager subscribed to.
/// </summary>
private TimeManager _timeManager;
internal override void OnStartNetwork_Internal()
{
_timeManager = TimeManager;
ChangeSubscriptions(subscribe: true);
base.OnStartNetwork_Internal();
}
internal override void OnStopNetwork_Internal()
{
ChangeSubscriptions(subscribe: false);
base.OnStopNetwork_Internal();
}
/// <summary>
/// Updates callbacks to use and changes subscriptions accordingly.
/// </summary>
/// <param name = "value">Next value.</param>
public void SetTickCallbacks(TickCallback value)
{
ChangeSubscriptions(subscribe: false);
_tickCallbacks = value;
if (value != TickCallback.None)
ChangeSubscriptions(subscribe: true);
}
private void ChangeSubscriptions(bool subscribe)
{
TimeManager tm = _timeManager;
if (tm == null)
return;
if (subscribe == _subscribed)
return;
_subscribed = subscribe;
if (subscribe)
{
if (TickCallbackFastContains(_tickCallbacks, TickCallback.PreTick))
tm.OnPreTick += TimeManager_OnPreTick;
if (TickCallbackFastContains(_tickCallbacks, TickCallback.Tick))
tm.OnTick += TimeManager_OnTick;
if (TickCallbackFastContains(_tickCallbacks, TickCallback.PostTick))
tm.OnPostTick += TimeManager_OnPostTick;
if (TickCallbackFastContains(_tickCallbacks, TickCallback.Update))
tm.OnUpdate += TimeManager_OnUpdate;
if (TickCallbackFastContains(_tickCallbacks, TickCallback.LateUpdate))
tm.OnLateUpdate += TimeManager_OnLateUpdate;
}
else
{
if (TickCallbackFastContains(_tickCallbacks, TickCallback.PreTick))
tm.OnPreTick -= TimeManager_OnPreTick;
if (TickCallbackFastContains(_tickCallbacks, TickCallback.Tick))
tm.OnTick -= TimeManager_OnTick;
if (TickCallbackFastContains(_tickCallbacks, TickCallback.PostTick))
tm.OnPostTick -= TimeManager_OnPostTick;
if (TickCallbackFastContains(_tickCallbacks, TickCallback.Update))
tm.OnUpdate -= TimeManager_OnUpdate;
if (TickCallbackFastContains(_tickCallbacks, TickCallback.LateUpdate))
tm.OnLateUpdate -= TimeManager_OnLateUpdate;
}
}
protected virtual void TimeManager_OnPreTick() { }
protected virtual void TimeManager_OnTick() { }
protected virtual void TimeManager_OnPostTick() { }
protected virtual void TimeManager_OnUpdate() { }
protected virtual void TimeManager_OnLateUpdate() { }
private bool TickCallbackFastContains(TickCallback whole, TickCallback part) => (whole & part) == part;
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 68553251b6087a342a59efd0b6433401
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 207815
packageName: 'FishNet: Networking Evolved'
packageVersion: 4.7.3R
assetPath: Assets/FishNet/Runtime/Utility/Template/TickNetworkBehaviour.cs
uploadId: 987062