fishnet installed
This commit is contained in:
170
Assets/FishNet/Runtime/Object/Attributes.cs
Normal file
170
Assets/FishNet/Runtime/Object/Attributes.cs
Normal file
@ -0,0 +1,170 @@
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Transporting;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// ServerRpc methods will send messages to the server.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
|
||||
public class ServerRpcAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// True to only allow the owning client to call this RPC.
|
||||
/// </summary>
|
||||
public bool RequireOwnership = true;
|
||||
/// <summary>
|
||||
/// True to also run the RPC logic locally.
|
||||
/// </summary>
|
||||
public bool RunLocally = false;
|
||||
/// <summary>
|
||||
/// Estimated length of data being sent.
|
||||
/// When a value other than -1 the minimum length of the used serializer will be this value.
|
||||
/// This is useful for writing large packets which otherwise resize the serializer.
|
||||
/// </summary>
|
||||
public int DataLength = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ObserversRpc methods will send messages to all observers.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
|
||||
public class ObserversRpcAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// True to exclude the owner from receiving this RPC.
|
||||
/// </summary>
|
||||
public bool ExcludeOwner = false;
|
||||
/// <summary>
|
||||
/// True to prevent the connection from receiving this Rpc if they are also server.
|
||||
/// </summary>
|
||||
public bool ExcludeServer = false;
|
||||
/// <summary>
|
||||
/// True to buffer the last value and send it to new players when the object is spawned for them.
|
||||
/// RPC will be sent on the same channel as the original RPC, and immediately before the OnSpawnServer override.
|
||||
/// </summary>
|
||||
public bool BufferLast = false;
|
||||
/// <summary>
|
||||
/// True to also run the RPC logic locally.
|
||||
/// </summary>
|
||||
public bool RunLocally = false;
|
||||
/// <summary>
|
||||
/// Estimated length of data being sent.
|
||||
/// When a value other than -1 the minimum length of the used serializer will be this value.
|
||||
/// This is useful for writing large packets which otherwise resize the serializer.
|
||||
/// </summary>
|
||||
public int DataLength = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TargetRpc methods will send messages to a single client.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
|
||||
public class TargetRpcAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// True to prevent the connection from receiving this Rpc if they are also server.
|
||||
/// </summary>
|
||||
public bool ExcludeServer = false;
|
||||
/// <summary>
|
||||
/// True to also run the RPC logic locally.
|
||||
/// </summary>
|
||||
public bool RunLocally = false;
|
||||
/// <summary>
|
||||
/// True to validate the target is possible and output debug when not.
|
||||
/// Use this field with caution as it may create undesired results when set to false.
|
||||
/// </summary>
|
||||
public bool ValidateTarget = true;
|
||||
/// <summary>
|
||||
/// Estimated length of data being sent.
|
||||
/// When a value other than -1 the minimum length of the used serializer will be this value.
|
||||
/// This is useful for writing large packets which otherwise resize the serializer.
|
||||
/// </summary>
|
||||
public int DataLength = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prevents a method from running if server is not active.
|
||||
/// <para>Can only be used inside a NetworkBehaviour</para>
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
|
||||
public class ServerAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of logging to use when the IsServer check fails.
|
||||
/// </summary>
|
||||
public LoggingType Logging = LoggingType.Warning;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prevents this method from running if client is not active.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
|
||||
public class ClientAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Type of logging to use when the IsClient check fails.
|
||||
/// </summary>
|
||||
public LoggingType Logging = LoggingType.Warning;
|
||||
/// <summary>
|
||||
/// True to only allow a client to run the method if they are owner of the object.
|
||||
/// </summary>
|
||||
public bool RequireOwnership = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace FishNet.Object.Synchronizing
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizes collections or objects from the server to clients. Can be used with custom SyncObjects.
|
||||
/// Value must be changed on server.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)]
|
||||
public class SyncObjectAttribute : PropertyAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// How often values may update over the network.
|
||||
/// </summary>
|
||||
public float SendRate = 0.1f;
|
||||
/// <summary>
|
||||
/// Clients which may receive value updates.
|
||||
/// </summary>
|
||||
public ReadPermission ReadPermissions = ReadPermission.Observers;
|
||||
/// <summary>
|
||||
/// True if to require the readonly attribute.
|
||||
/// Setting to false will allow inspector serialization of this object, but you must never manually initialize this object.
|
||||
/// </summary>
|
||||
public bool RequireReadOnly = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizes a variable from server to clients automatically.
|
||||
/// Value must be changed on server.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)]
|
||||
public class SyncVarAttribute : PropertyAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// How often values may update over the network.
|
||||
/// </summary>
|
||||
public float SendRate = 0.1f;
|
||||
/// <summary>
|
||||
/// Clients which may receive value updates.
|
||||
/// </summary>
|
||||
public ReadPermission ReadPermissions = ReadPermission.Observers;
|
||||
/// <summary>
|
||||
/// Channel to use. Unreliable SyncVars will use eventual consistency.
|
||||
/// </summary>
|
||||
public Channel Channel;
|
||||
///<summary>
|
||||
/// Method which will be called on the server and clients when the value changes.
|
||||
///</summary>
|
||||
public string OnChange;
|
||||
}
|
||||
|
||||
}
|
11
Assets/FishNet/Runtime/Object/Attributes.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/Attributes.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2c79ec60813585469c43b4539e3d0c5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
35
Assets/FishNet/Runtime/Object/ChangedTransformProperties.cs
Normal file
35
Assets/FishNet/Runtime/Object/ChangedTransformProperties.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using FishNet.Documenting;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
/// <summary>
|
||||
/// Properties which have changed on a transform.
|
||||
/// </summary>
|
||||
[System.Flags]
|
||||
[APIExclude]
|
||||
internal enum ChangedTransformProperties : byte
|
||||
{
|
||||
Unset = 0,
|
||||
LocalPosition = 1,
|
||||
LocalRotation = 2,
|
||||
LocalScale = 4,
|
||||
}
|
||||
|
||||
[APIExclude]
|
||||
internal static partial class ChangedTransformPropertiesEnum
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns if whole contains part.
|
||||
/// </summary>
|
||||
/// <param name="whole"></param>
|
||||
/// <param name="part"></param>
|
||||
/// <returns></returns>
|
||||
public static bool Contains(ChangedTransformProperties whole, ChangedTransformProperties part)
|
||||
{
|
||||
return (whole & part) == part;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1870202c019b99348aaedbe2029caf33
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
13
Assets/FishNet/Runtime/Object/Delegates.cs
Normal file
13
Assets/FishNet/Runtime/Object/Delegates.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Utility.Constant;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo(UtilityConstants.CODEGEN_ASSEMBLY_NAME)]
|
||||
namespace FishNet.Object.Delegating
|
||||
{
|
||||
public delegate void ServerRpcDelegate(PooledReader reader, Channel channel, NetworkConnection sender);
|
||||
public delegate void ClientRpcDelegate(PooledReader reader, Channel channel);
|
||||
|
||||
}
|
11
Assets/FishNet/Runtime/Object/Delegates.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/Delegates.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5dbd9cdda4843f34ab416273d80f83c8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
13
Assets/FishNet/Runtime/Object/DespawnType.cs
Normal file
13
Assets/FishNet/Runtime/Object/DespawnType.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using FishNet.Object.Helping;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
|
||||
public enum DespawnType : byte
|
||||
{
|
||||
Destroy = 0,
|
||||
Pool = 1,
|
||||
}
|
||||
|
||||
|
||||
}
|
11
Assets/FishNet/Runtime/Object/DespawnType.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/DespawnType.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 205c3ddfc86fbaa44aa0b92ecef58474
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
13
Assets/FishNet/Runtime/Object/EmptyNetworkBehaviour.cs
Normal file
13
Assets/FishNet/Runtime/Object/EmptyNetworkBehaviour.cs
Normal file
@ -0,0 +1,13 @@
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
/// <summary>
|
||||
/// This may be added at runtime to find objects without any network scripts, beneath a NetworkObject.
|
||||
/// </summary>
|
||||
public class EmptyNetworkBehaviour : NetworkBehaviour
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
11
Assets/FishNet/Runtime/Object/EmptyNetworkBehaviour.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/EmptyNetworkBehaviour.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8a6a39c46bf52104ba8efe3100bce3f7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/FishNet/Runtime/Object/Helping.meta
Normal file
8
Assets/FishNet/Runtime/Object/Helping.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0684bc52d23dfb743a5d2fab1278d8f7
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
153
Assets/FishNet/Runtime/Object/Helping/Hashing.cs
Normal file
153
Assets/FishNet/Runtime/Object/Helping/Hashing.cs
Normal file
@ -0,0 +1,153 @@
|
||||
namespace FishNet.Object.Helping
|
||||
{
|
||||
|
||||
public static class Hashing
|
||||
{
|
||||
|
||||
private const uint FNV_offset_basis32 = 2166136261;
|
||||
private const uint FNV_prime32 = 16777619;
|
||||
private const ulong FNV_offset_basis64 = 14695981039346656037;
|
||||
private const ulong FNV_prime64 = 1099511628211;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// non cryptographic stable hash code,
|
||||
/// it will always return the same hash for the same
|
||||
/// string.
|
||||
///
|
||||
/// This is simply an implementation of FNV-1 32 bit xor folded to 16 bit
|
||||
/// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
|
||||
/// </summary>
|
||||
/// <returns>The stable hash32.</returns>
|
||||
/// <param name="txt">Text.</param>
|
||||
internal static ushort GetStableHash16(this string txt)
|
||||
{
|
||||
uint hash32 = txt.GetStableHash32();
|
||||
|
||||
return (ushort)((hash32 >> 16) ^ hash32);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// non cryptographic stable hash code,
|
||||
/// it will always return the same hash for the same
|
||||
/// string.
|
||||
///
|
||||
/// This is simply an implementation of FNV-1 32 bit
|
||||
/// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
|
||||
/// </summary>
|
||||
/// <returns>The stable hash32.</returns>
|
||||
/// <param name="txt">Text.</param>
|
||||
public static uint GetStableHash32(this string txt)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
uint hash = FNV_offset_basis32;
|
||||
for (int i = 0; i < txt.Length; i++)
|
||||
{
|
||||
uint ch = txt[i];
|
||||
hash = hash * FNV_prime32;
|
||||
hash = hash ^ ch;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// non cryptographic stable hash code,
|
||||
/// it will always return the same hash for the same
|
||||
/// string.
|
||||
///
|
||||
/// This is simply an implementation of FNV-1 64 bit
|
||||
/// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
|
||||
/// </summary>
|
||||
/// <returns>The stable hash32.</returns>
|
||||
/// <param name="txt">Text.</param>
|
||||
internal static ulong GetStableHash64(this string txt)
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
ulong hash = FNV_offset_basis64;
|
||||
for (int i = 0; i < txt.Length; i++)
|
||||
{
|
||||
ulong ch = txt[i];
|
||||
hash = hash * FNV_prime64;
|
||||
hash = hash ^ ch;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
///// <summary>
|
||||
///// non cryptographic stable hash code,
|
||||
///// it will always return the same hash for the same
|
||||
///// string.
|
||||
/////
|
||||
///// This is simply an implementation of FNV-1 32 bit xor folded to 16 bit
|
||||
///// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
|
||||
///// </summary>
|
||||
///// <returns>The stable hash32.</returns>
|
||||
///// <param name="bytes">Text.</param>
|
||||
//internal static ushort GetStableHash16(this byte[] bytes)
|
||||
//{
|
||||
// uint hash32 = bytes.GetStableHash32();
|
||||
|
||||
// return (ushort)((hash32 >> 16) ^ hash32);
|
||||
//}
|
||||
|
||||
///// <summary>
|
||||
///// non cryptographic stable hash code,
|
||||
///// it will always return the same hash for the same
|
||||
///// string.
|
||||
/////
|
||||
///// This is simply an implementation of FNV-1 32 bit
|
||||
///// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
|
||||
///// </summary>
|
||||
///// <returns>The stable hash32.</returns>
|
||||
///// <param name="bytes">Text.</param>
|
||||
//internal static uint GetStableHash32(this byte[] bytes)
|
||||
//{
|
||||
// unchecked
|
||||
// {
|
||||
// uint hash = FNV_offset_basis32;
|
||||
// for (int i = 0; i < bytes.Length; i++)
|
||||
// {
|
||||
// uint bt = bytes[i];
|
||||
// hash = hash * FNV_prime32;
|
||||
// hash = hash ^ bt;
|
||||
// }
|
||||
// return hash;
|
||||
// }
|
||||
//}
|
||||
|
||||
///// <summary>
|
||||
///// non cryptographic stable hash code,
|
||||
///// it will always return the same hash for the same
|
||||
///// string.
|
||||
/////
|
||||
///// This is simply an implementation of FNV-1 64 bit
|
||||
///// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
|
||||
///// </summary>
|
||||
///// <returns>The stable hash32.</returns>
|
||||
///// <param name="bytes">Text.</param>
|
||||
//internal static ulong GetStableHash64(this byte[] bytes)
|
||||
//{
|
||||
// unchecked
|
||||
// {
|
||||
// ulong hash = FNV_offset_basis64;
|
||||
// for (int i = 0; i < bytes.Length; i++)
|
||||
// {
|
||||
// ulong bt = bytes[i];
|
||||
// hash = hash * FNV_prime64;
|
||||
// hash = hash ^ bt;
|
||||
// }
|
||||
// return hash;
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
11
Assets/FishNet/Runtime/Object/Helping/Hashing.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/Helping/Hashing.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c55dc5a22646764aa5dbfab2062669e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
39
Assets/FishNet/Runtime/Object/Helping/RpcLink.cs
Normal file
39
Assets/FishNet/Runtime/Object/Helping/RpcLink.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using FishNet.Object.Helping;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
|
||||
#region Types.
|
||||
/// <summary>
|
||||
/// Lookup data for a RPC Link.
|
||||
/// </summary>
|
||||
internal struct RpcLink
|
||||
{
|
||||
/// <summary>
|
||||
/// ObjectId for link.
|
||||
/// </summary>
|
||||
public int ObjectId;
|
||||
/// <summary>
|
||||
/// NetworkBehaviour component index on ObjectId.
|
||||
/// </summary>
|
||||
public byte ComponentIndex;
|
||||
/// <summary>
|
||||
/// RpcHash for link.
|
||||
/// </summary>
|
||||
public uint RpcHash;
|
||||
/// <summary>
|
||||
/// Type of Rpc link is for.
|
||||
/// </summary>
|
||||
public RpcType RpcType;
|
||||
|
||||
public RpcLink(int objectId, byte componentIndex, uint rpcHash, RpcType rpcType)
|
||||
{
|
||||
ObjectId = objectId;
|
||||
ComponentIndex = componentIndex;
|
||||
RpcHash = rpcHash;
|
||||
RpcType = rpcType;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
}
|
11
Assets/FishNet/Runtime/Object/Helping/RpcLink.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/Helping/RpcLink.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05a91745dd829d043aadf391ac7b233e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
13
Assets/FishNet/Runtime/Object/Helping/RpcType.cs
Normal file
13
Assets/FishNet/Runtime/Object/Helping/RpcType.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace FishNet.Object.Helping
|
||||
{
|
||||
public enum RpcType : int
|
||||
{
|
||||
None = 0,
|
||||
Server = 1,
|
||||
Observers = 2,
|
||||
Target = 4,
|
||||
Replicate = 8,
|
||||
Reconcile = 16
|
||||
}
|
||||
|
||||
}
|
11
Assets/FishNet/Runtime/Object/Helping/RpcType.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/Helping/RpcType.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09f9e7236f988c64fad54645f4cc7f8b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
49
Assets/FishNet/Runtime/Object/Helping/StaticShortcuts.cs
Normal file
49
Assets/FishNet/Runtime/Object/Helping/StaticShortcuts.cs
Normal file
@ -0,0 +1,49 @@
|
||||
|
||||
namespace FishNet.Object.Helping
|
||||
{
|
||||
|
||||
public static class CodegenHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns if a NetworkObject is deinitializing.
|
||||
/// </summary>
|
||||
/// <param name="nb"></param>
|
||||
/// <returns></returns>
|
||||
public static bool NetworkObject_Deinitializing(NetworkBehaviour nb)
|
||||
{
|
||||
if (nb == null)
|
||||
return true;
|
||||
|
||||
return nb.IsDeinitializing;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if running as server.
|
||||
/// </summary>
|
||||
/// <param name="nb"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsServer(NetworkBehaviour nb)
|
||||
{
|
||||
if (nb == null)
|
||||
return false;
|
||||
|
||||
return nb.IsServer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if running as client.
|
||||
/// </summary>
|
||||
/// <param name="nb"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsClient(NetworkBehaviour nb)
|
||||
{
|
||||
if (nb == null)
|
||||
return false;
|
||||
|
||||
return nb.IsClient;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 149cde0042627604d810c2c7fc0f9176
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
184
Assets/FishNet/Runtime/Object/NetworkBehaviour.Callbacks.cs
Normal file
184
Assets/FishNet/Runtime/Object/NetworkBehaviour.Callbacks.cs
Normal file
@ -0,0 +1,184 @@
|
||||
#if UNITY_2020_3_OR_NEWER && UNITY_EDITOR_WIN
|
||||
using FishNet.CodeAnalysis.Annotations;
|
||||
#endif
|
||||
using FishNet.Connection;
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Object.Synchronizing.Internal;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
|
||||
public abstract partial class NetworkBehaviour : MonoBehaviour
|
||||
{
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// True if OnStartServer has been called.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
public bool OnStartServerCalled { get; private set; }
|
||||
/// <summary>
|
||||
/// True if OnStartClient has been called.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
public bool OnStartClientCalled { get; private set; }
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// True if OnStartNetwork has been called.
|
||||
/// </summary>
|
||||
private bool _onStartNetworkCalled;
|
||||
/// <summary>
|
||||
/// True if OnStopNetwork has been called.
|
||||
/// </summary>
|
||||
private bool _onStopNetworkCalled;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Invokes cached callbacks on SyncTypes which were held until OnStartXXXXX was called.
|
||||
/// </summary>
|
||||
/// <param name="asServer"></param>
|
||||
internal void InvokeSyncTypeCallbacks(bool asServer)
|
||||
{
|
||||
foreach (SyncBase item in _syncVars.Values)
|
||||
item.OnStartCallback(asServer);
|
||||
foreach (SyncBase item in _syncObjects.Values)
|
||||
item.OnStartCallback(asServer);
|
||||
}
|
||||
/// <summary>
|
||||
/// Invokes the OnStart/StopNetwork.
|
||||
/// </summary>
|
||||
/// <param name="start"></param>
|
||||
internal void InvokeOnNetwork(bool start)
|
||||
{
|
||||
if (start)
|
||||
{
|
||||
if (_onStartNetworkCalled)
|
||||
return;
|
||||
OnStartNetwork();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_onStopNetworkCalled)
|
||||
return;
|
||||
OnStopNetwork();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the network has initialized this object. May be called for server or client but will only be called once.
|
||||
/// When as host or server this method will run before OnStartServer.
|
||||
/// When as client only the method will run before OnStartClient.
|
||||
/// </summary>
|
||||
#if UNITY_2020_3_OR_NEWER && UNITY_EDITOR_WIN
|
||||
[OverrideMustCallBase(BaseCallMustBeFirstStatement = true)]
|
||||
#endif
|
||||
public virtual void OnStartNetwork()
|
||||
{
|
||||
_onStartNetworkCalled = true;
|
||||
_onStopNetworkCalled = false;
|
||||
}
|
||||
/// <summary>
|
||||
/// Called when the network is deinitializing this object. May be called for server or client but will only be called once.
|
||||
/// When as host or server this method will run after OnStopServer.
|
||||
/// When as client only this method will run after OnStopClient.
|
||||
/// </summary>
|
||||
#if UNITY_2020_3_OR_NEWER && UNITY_EDITOR_WIN
|
||||
[OverrideMustCallBase(BaseCallMustBeFirstStatement = true)]
|
||||
#endif
|
||||
public virtual void OnStopNetwork()
|
||||
{
|
||||
_onStopNetworkCalled = true;
|
||||
_onStartNetworkCalled = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on the server after initializing this object.
|
||||
/// SyncTypes modified before or during this method will be sent to clients in the spawn message.
|
||||
/// </summary>
|
||||
#if UNITY_2020_3_OR_NEWER && UNITY_EDITOR_WIN
|
||||
[OverrideMustCallBase(BaseCallMustBeFirstStatement = true)]
|
||||
#endif
|
||||
public virtual void OnStartServer()
|
||||
{
|
||||
OnStartServerCalled = true;
|
||||
}
|
||||
/// <summary>
|
||||
/// Called on the server before deinitializing this object.
|
||||
/// </summary>
|
||||
#if UNITY_2020_3_OR_NEWER && UNITY_EDITOR_WIN
|
||||
[OverrideMustCallBase(BaseCallMustBeFirstStatement = true)]
|
||||
#endif
|
||||
public virtual void OnStopServer()
|
||||
{
|
||||
OnStartServerCalled = false;
|
||||
ReturnRpcLinks();
|
||||
}
|
||||
/// <summary>
|
||||
/// Called on the server after ownership has changed.
|
||||
/// </summary>
|
||||
/// <param name="prevOwner">Previous owner of this object.</param>
|
||||
#if UNITY_2020_3_OR_NEWER && UNITY_EDITOR_WIN
|
||||
[OverrideMustCallBase(BaseCallMustBeFirstStatement = true)]
|
||||
#endif
|
||||
public virtual void OnOwnershipServer(NetworkConnection prevOwner)
|
||||
{
|
||||
//When switching ownership always clear replicate cache on server.
|
||||
ClearReplicateCache_Internal(true);
|
||||
}
|
||||
/// <summary>
|
||||
/// Called on the server after a spawn message for this object has been sent to clients.
|
||||
/// Useful for sending remote calls or data to clients.
|
||||
/// </summary>
|
||||
/// <param name="connection">Connection the object is being spawned for.</param>
|
||||
#if UNITY_2020_3_OR_NEWER && UNITY_EDITOR_WIN
|
||||
[OverrideMustCallBase(BaseCallMustBeFirstStatement = true)]
|
||||
#endif
|
||||
public virtual void OnSpawnServer(NetworkConnection connection) { }
|
||||
/// <summary>
|
||||
/// Called on the server before a despawn message for this object has been sent to connection.
|
||||
/// Useful for sending remote calls or actions to clients.
|
||||
/// </summary>
|
||||
#if UNITY_2020_3_OR_NEWER && UNITY_EDITOR_WIN
|
||||
[OverrideMustCallBase(BaseCallMustBeFirstStatement = true)]
|
||||
#endif
|
||||
public virtual void OnDespawnServer(NetworkConnection connection) { }
|
||||
/// <summary>
|
||||
/// Called on the client after initializing this object.
|
||||
/// </summary>
|
||||
#if UNITY_2020_3_OR_NEWER && UNITY_EDITOR_WIN
|
||||
[OverrideMustCallBase(BaseCallMustBeFirstStatement = true)]
|
||||
#endif
|
||||
public virtual void OnStartClient()
|
||||
{
|
||||
OnStartClientCalled = true;
|
||||
}
|
||||
/// <summary>
|
||||
/// Called on the client before deinitializing this object.
|
||||
/// </summary>
|
||||
#if UNITY_2020_3_OR_NEWER && UNITY_EDITOR_WIN
|
||||
[OverrideMustCallBase(BaseCallMustBeFirstStatement = true)]
|
||||
#endif
|
||||
public virtual void OnStopClient()
|
||||
{
|
||||
OnStartClientCalled = false;
|
||||
}
|
||||
/// <summary>
|
||||
/// Called on the client after gaining or losing ownership.
|
||||
/// </summary>
|
||||
/// <param name="prevOwner">Previous owner of this object.</param>
|
||||
#if UNITY_2020_3_OR_NEWER && UNITY_EDITOR_WIN
|
||||
[OverrideMustCallBase(BaseCallMustBeFirstStatement = true)]
|
||||
#endif
|
||||
public virtual void OnOwnershipClient(NetworkConnection prevOwner)
|
||||
{
|
||||
//If losing or gaining ownership then clear replicate cache.
|
||||
if (IsOwner || prevOwner == LocalConnection)
|
||||
ClearReplicateCache_Internal(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9ddaf08801752b49bcfe720217df74a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
22
Assets/FishNet/Runtime/Object/NetworkBehaviour.Logging.cs
Normal file
22
Assets/FishNet/Runtime/Object/NetworkBehaviour.Logging.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using FishNet.Managing.Logging;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
|
||||
public abstract partial class NetworkBehaviour : MonoBehaviour
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// True if can log for loggingType.
|
||||
/// </summary>
|
||||
/// <param name="loggingType">Type of logging being filtered.</param>
|
||||
/// <returns></returns>
|
||||
public bool CanLog(LoggingType loggingType)
|
||||
{
|
||||
return (NetworkManager == null) ? false : NetworkManager.CanLog(loggingType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ad4dd2795a9a00e4d814892ac1a67157
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
746
Assets/FishNet/Runtime/Object/NetworkBehaviour.Prediction.cs
Normal file
746
Assets/FishNet/Runtime/Object/NetworkBehaviour.Prediction.cs
Normal file
@ -0,0 +1,746 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Managing.Predicting;
|
||||
using FishNet.Managing.Timing;
|
||||
using FishNet.Object.Prediction;
|
||||
using FishNet.Object.Prediction.Delegating;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Serializing.Helping;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Utility.Constant;
|
||||
using FishNet.Utility.Extension;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
using UnityScene = UnityEngine.SceneManagement.Scene;
|
||||
|
||||
[assembly: InternalsVisibleTo(UtilityConstants.CODEGEN_ASSEMBLY_NAME)]
|
||||
namespace FishNet.Object
|
||||
{
|
||||
|
||||
public abstract partial class NetworkBehaviour : MonoBehaviour
|
||||
{
|
||||
#region Public.
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
private uint _lastReconcileTick;
|
||||
/// <summary>
|
||||
/// Gets the last tick this NetworkBehaviour reconciled with.
|
||||
/// </summary>
|
||||
public uint GetLastReconcileTick() => _lastReconcileTick;
|
||||
|
||||
internal void SetLastReconcileTick(uint value, bool updateGlobals = true)
|
||||
{
|
||||
_lastReconcileTick = value;
|
||||
if (updateGlobals)
|
||||
PredictionManager.LastReconcileTick = value;
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
private uint _lastReplicateTick;
|
||||
/// <summary>
|
||||
/// Gets the last tick this NetworkBehaviour replicated with.
|
||||
/// </summary>
|
||||
public uint GetLastReplicateTick() => _lastReplicateTick;
|
||||
/// <summary>
|
||||
/// Sets the last tick this NetworkBehaviour replicated with.
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
private void SetLastReplicateTick(uint value, bool updateGlobals = true)
|
||||
{
|
||||
_lastReplicateTick = value;
|
||||
if (updateGlobals)
|
||||
{
|
||||
Owner.LocalReplicateTick = TimeManager.LocalTick;
|
||||
PredictionManager.LastReplicateTick = value;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// True if this object is reconciling.
|
||||
/// </summary>
|
||||
public bool IsReconciling { get; internal set; }
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Registered Replicate methods.
|
||||
/// </summary>
|
||||
private readonly Dictionary<uint, ReplicateRpcDelegate> _replicateRpcDelegates = new Dictionary<uint, ReplicateRpcDelegate>();
|
||||
/// <summary>
|
||||
/// Registered Reconcile methods.
|
||||
/// </summary>
|
||||
private readonly Dictionary<uint, ReconcileRpcDelegate> _reconcileRpcDelegates = new Dictionary<uint, ReconcileRpcDelegate>();
|
||||
/// <summary>
|
||||
/// True if initialized compnents for prediction.
|
||||
/// </summary>
|
||||
private bool _predictionInitialized;
|
||||
/// <summary>
|
||||
/// Rigidbody found on this object. This is used for prediction.
|
||||
/// </summary>
|
||||
private Rigidbody _predictionRigidbody;
|
||||
/// <summary>
|
||||
/// Rigidbody2D found on this object. This is used for prediction.
|
||||
/// </summary>
|
||||
private Rigidbody2D _predictionRigidbody2d;
|
||||
/// <summary>
|
||||
/// Last position for TransformMayChange.
|
||||
/// </summary>
|
||||
private Vector3 _lastMayChangePosition;
|
||||
/// <summary>
|
||||
/// Last rotation for TransformMayChange.
|
||||
/// </summary>
|
||||
private Quaternion _lastMayChangeRotation;
|
||||
/// <summary>
|
||||
/// Last scale for TransformMayChange.
|
||||
/// </summary>
|
||||
private Vector3 _lastMayChangeScale;
|
||||
/// <summary>
|
||||
/// Number of resends which may occur. This could be for client resending replicates to the server or the server resending reconciles to the client.
|
||||
/// </summary>
|
||||
private int _remainingResends;
|
||||
/// <summary>
|
||||
/// Last enqueued replicate tick on the server.
|
||||
/// </summary>
|
||||
private uint _lastReceivedReplicateTick;
|
||||
/// <summary>
|
||||
/// Last tick of a reconcile received from the server.
|
||||
/// </summary>
|
||||
private uint _lastReceivedReconcileTick;
|
||||
/// <summary>
|
||||
/// True if the client has cached reconcile
|
||||
/// </summary>
|
||||
private bool _clientHasReconcileData;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Registers a RPC method.
|
||||
/// Internal use.
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <param name="del"></param>
|
||||
[APIExclude]
|
||||
[CodegenMakePublic]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected internal void RegisterReplicateRpc_Internal(uint hash, ReplicateRpcDelegate del)
|
||||
{
|
||||
_replicateRpcDelegates[hash] = del;
|
||||
}
|
||||
/// <summary>
|
||||
/// Registers a RPC method.
|
||||
/// Internal use.
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <param name="del"></param>
|
||||
[APIExclude]
|
||||
[CodegenMakePublic]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected internal void RegisterReconcileRpc_Internal(uint hash, ReconcileRpcDelegate del)
|
||||
{
|
||||
_reconcileRpcDelegates[hash] = del;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called when a replicate is received.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void OnReplicateRpc(uint? methodHash, PooledReader reader, NetworkConnection sendingClient, Channel channel)
|
||||
{
|
||||
if (methodHash == null)
|
||||
methodHash = ReadRpcHash(reader);
|
||||
|
||||
if (sendingClient == null)
|
||||
{
|
||||
_networkObjectCache.NetworkManager.LogError($"NetworkConnection is null. Replicate {methodHash.Value} on {gameObject.name}, behaviour {GetType().Name} will not complete. Remainder of packet may become corrupt.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_replicateRpcDelegates.TryGetValueIL2CPP(methodHash.Value, out ReplicateRpcDelegate del))
|
||||
del.Invoke(reader, sendingClient, channel);
|
||||
else
|
||||
_networkObjectCache.NetworkManager.LogWarning($"Replicate not found for hash {methodHash.Value} on {gameObject.name}, behaviour {GetType().Name}. Remainder of packet may become corrupt.");
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called when a reconcile is received.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void OnReconcileRpc(uint? methodHash, PooledReader reader, Channel channel)
|
||||
{
|
||||
if (methodHash == null)
|
||||
methodHash = ReadRpcHash(reader);
|
||||
|
||||
if (_reconcileRpcDelegates.TryGetValueIL2CPP(methodHash.Value, out ReconcileRpcDelegate del))
|
||||
del.Invoke(reader, channel);
|
||||
else
|
||||
_networkObjectCache.NetworkManager.LogWarning($"Reconcile not found for hash {methodHash.Value}. Remainder of packet may become corrupt.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears cached replicates. This can be useful to call on server and client after teleporting.
|
||||
/// </summary>
|
||||
/// <param name="asServer">True to reset values for server, false to reset values for client.</param>
|
||||
public void ClearReplicateCache(bool asServer)
|
||||
{
|
||||
ResetLastPredictionTicks();
|
||||
ClearReplicateCache_Internal(asServer);
|
||||
}
|
||||
/// <summary>
|
||||
/// Clears cached replicates for server and client. This can be useful to call on server and client after teleporting.
|
||||
/// </summary>
|
||||
public void ClearReplicateCache()
|
||||
{
|
||||
ResetLastPredictionTicks();
|
||||
ClearReplicateCache_Internal(true);
|
||||
ClearReplicateCache_Internal(false);
|
||||
}
|
||||
/// <summary>
|
||||
/// Resets last predirection tick values.
|
||||
/// </summary>
|
||||
private void ResetLastPredictionTicks()
|
||||
{
|
||||
_lastSentReplicateTick = 0;
|
||||
_lastReceivedReplicateTick = 0;
|
||||
_lastReceivedReconcileTick = 0;
|
||||
SetLastReconcileTick(0, false);
|
||||
SetLastReplicateTick(0, false);
|
||||
}
|
||||
/// <summary>
|
||||
/// Clears cached replicates.
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <param name="asServer"></param>
|
||||
[CodegenMakePublic]
|
||||
[APIExclude]
|
||||
protected internal virtual void ClearReplicateCache_Internal(bool asServer) { }
|
||||
|
||||
/// <summary>
|
||||
/// Writes number of past inputs from buffer to writer and sends it to the server.
|
||||
/// Internal use.
|
||||
/// </summary>
|
||||
[CodegenMakePublic]
|
||||
[APIExclude]
|
||||
private void SendReplicateRpc<T>(uint hash, List<T> replicates, Channel channel) where T : IReplicateData
|
||||
{
|
||||
if (!IsSpawnedWithWarning())
|
||||
return;
|
||||
|
||||
int bufferCount = replicates.Count;
|
||||
int lastBufferIndex = (bufferCount - 1);
|
||||
//Nothing to send; should never be possible.
|
||||
if (lastBufferIndex < 0)
|
||||
return;
|
||||
|
||||
//Number of past inputs to send.
|
||||
int pastInputs = Mathf.Min(PredictionManager.GetRedundancyCount(), bufferCount);
|
||||
/* Where to start writing from. When passed
|
||||
* into the writer values from this offset
|
||||
* and forward will be written. */
|
||||
int offset = bufferCount - pastInputs;
|
||||
if (offset < 0)
|
||||
offset = 0;
|
||||
|
||||
uint lastReplicateTick = _lastSentReplicateTick;
|
||||
if (lastReplicateTick > 0)
|
||||
{
|
||||
uint diff = TimeManager.LocalTick - GetLastReplicateTick();
|
||||
offset += (int)diff - 1;
|
||||
if (offset >= replicates.Count)
|
||||
return;
|
||||
}
|
||||
|
||||
_lastSentReplicateTick = TimeManager.LocalTick;
|
||||
|
||||
//Write history to methodWriter.
|
||||
PooledWriter methodWriter = WriterPool.GetWriter(WriterPool.LENGTH_BRACKET);
|
||||
methodWriter.WriteReplicate<T>(replicates, offset);
|
||||
PooledWriter writer;
|
||||
//if (_rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
|
||||
//writer = CreateLinkedRpc(link, methodWriter, Channel.Unreliable);
|
||||
//else //todo add support for -> server rpc links.
|
||||
|
||||
writer = CreateRpc(hash, methodWriter, PacketId.Replicate, channel);
|
||||
NetworkManager.TransportManager.SendToServer((byte)channel, writer.GetArraySegment(), false);
|
||||
|
||||
/* If being sent as reliable then clear buffer
|
||||
* since we know it will get there.
|
||||
* Also reset remaining resends. */
|
||||
if (channel == Channel.Reliable)
|
||||
{
|
||||
replicates.Clear();
|
||||
_remainingResends = 0;
|
||||
}
|
||||
|
||||
methodWriter.DisposeLength();
|
||||
writer.DisposeLength();
|
||||
}
|
||||
|
||||
private uint _lastSentReplicateTick;
|
||||
|
||||
/// <summary>
|
||||
/// Sends a RPC to target.
|
||||
/// Internal use.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[CodegenMakePublic]
|
||||
[APIExclude]
|
||||
internal void SendReconcileRpc<T>(uint hash, T reconcileData, Channel channel)
|
||||
{
|
||||
if (!IsSpawned)
|
||||
return;
|
||||
if (!Owner.IsActive)
|
||||
return;
|
||||
|
||||
PooledWriter methodWriter = WriterPool.GetWriter();
|
||||
methodWriter.WriteUInt32(GetLastReplicateTick());
|
||||
methodWriter.Write(reconcileData);
|
||||
|
||||
PooledWriter writer;
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
if (NetworkManager.DebugManager.ReconcileRpcLinks && _rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
|
||||
#else
|
||||
if (_rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
|
||||
#endif
|
||||
writer = CreateLinkedRpc(link, methodWriter, channel);
|
||||
else
|
||||
writer = CreateRpc(hash, methodWriter, PacketId.Reconcile, channel);
|
||||
|
||||
_networkObjectCache.NetworkManager.TransportManager.SendToClient((byte)channel, writer.GetArraySegment(), Owner);
|
||||
|
||||
methodWriter.Dispose();
|
||||
writer.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if there is a chance the transform may change after the tick.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected internal bool TransformMayChange()
|
||||
{
|
||||
if (!_predictionInitialized)
|
||||
{
|
||||
_predictionInitialized = true;
|
||||
_predictionRigidbody = GetComponentInParent<Rigidbody>();
|
||||
_predictionRigidbody2d = GetComponentInParent<Rigidbody2D>();
|
||||
}
|
||||
|
||||
/* Use distance when checking if changed because rigidbodies can twitch
|
||||
* or move an extremely small amount. These small moves are not worth
|
||||
* resending over because they often fix themselves each frame. */
|
||||
float changeDistance = 0.000004f;
|
||||
|
||||
bool positionChanged = (transform.position - _lastMayChangePosition).sqrMagnitude > changeDistance;
|
||||
bool rotationChanged = (transform.rotation.eulerAngles - _lastMayChangeRotation.eulerAngles).sqrMagnitude > changeDistance;
|
||||
bool scaleChanged = (transform.localScale - _lastMayChangeScale).sqrMagnitude > changeDistance;
|
||||
bool transformChanged = (positionChanged || rotationChanged || scaleChanged);
|
||||
/* Returns true if transform.hasChanged, or if either
|
||||
* of the rigidbodies have velocity. */
|
||||
bool changed = (
|
||||
transformChanged ||
|
||||
(_predictionRigidbody != null && (_predictionRigidbody.velocity != Vector3.zero || _predictionRigidbody.angularVelocity != Vector3.zero)) ||
|
||||
(_predictionRigidbody2d != null && (_predictionRigidbody2d.velocity != Vector2.zero || _predictionRigidbody2d.angularVelocity != 0f))
|
||||
);
|
||||
|
||||
//If transform changed update last values.
|
||||
if (transformChanged)
|
||||
{
|
||||
_lastMayChangePosition = transform.position;
|
||||
_lastMayChangeRotation = transform.rotation;
|
||||
_lastMayChangeScale = transform.localScale;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks conditions for a replicate.
|
||||
/// </summary>
|
||||
/// <param name="asServer">True if checking as server.</param>
|
||||
/// <returns>Returns true if to exit the replicate early.</returns>
|
||||
[CodegenMakePublic] //internal
|
||||
[APIExclude]
|
||||
public bool Replicate_ExitEarly_A_Internal(bool asServer, bool replaying)
|
||||
{
|
||||
bool isOwner = IsOwner;
|
||||
//Server.
|
||||
if (asServer)
|
||||
{
|
||||
//No owner, do not try to replicate 'owner' input.
|
||||
if (!Owner.IsActive)
|
||||
{
|
||||
ClearReplicateCache(true);
|
||||
return true;
|
||||
}
|
||||
//Is client host, no need to use CSP; trust client.
|
||||
if (isOwner)
|
||||
{
|
||||
ClearReplicateCache();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
//Client.
|
||||
else
|
||||
{
|
||||
//Server does not replay; this should never happen.
|
||||
if (replaying && IsServer)
|
||||
return true;
|
||||
//Spectators cannot replicate.
|
||||
if (!isOwner)
|
||||
{
|
||||
ClearReplicateCache(false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//Checks pass.
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears a Queue.
|
||||
/// This is used as a patch for Unity 2022 Burst compiler bugs.
|
||||
/// Using Queue.Clear via codegen throws for an unknown reason.
|
||||
/// </summary>
|
||||
public void ClearQueue_Server_Internal<T>(Queue<T> q)
|
||||
{
|
||||
q.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next replicate in queue.
|
||||
/// </summary>
|
||||
[CodegenMakePublic] //internal
|
||||
[APIExclude]
|
||||
public void Replicate_Server_Internal<T>(ReplicateUserLogicDelegate<T> del, Queue<T> q, Channel channel) where T : IReplicateData
|
||||
{
|
||||
int count = q.Count;
|
||||
if (count > 0)
|
||||
{
|
||||
ReplicateData(q.Dequeue());
|
||||
count--;
|
||||
|
||||
PredictionManager pm = PredictionManager;
|
||||
bool consumeExcess = !pm.DropExcessiveReplicates;
|
||||
//Number of entries to leave in buffer when consuming.
|
||||
const int leaveInBuffer = 2;
|
||||
//Only consume if the queue count is over leaveInBuffer.
|
||||
if (consumeExcess && count > leaveInBuffer)
|
||||
{
|
||||
byte maximumAllowedConsumes = pm.GetMaximumConsumeCount();
|
||||
int maximumPossibleConsumes = (count - leaveInBuffer);
|
||||
int consumeAmount = Mathf.Min(maximumAllowedConsumes, maximumPossibleConsumes);
|
||||
|
||||
for (int i = 0; i < consumeAmount; i++)
|
||||
ReplicateData(q.Dequeue());
|
||||
}
|
||||
|
||||
void ReplicateData(T data)
|
||||
{
|
||||
uint tick = data.GetTick();
|
||||
SetLastReplicateTick(tick);
|
||||
del.Invoke(data, true, channel, false);
|
||||
}
|
||||
|
||||
_remainingResends = pm.GetRedundancyCount();
|
||||
}
|
||||
else
|
||||
{
|
||||
del.Invoke(default, true, channel, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the maximum amount of replicates when consuming multiple inputs per tick.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private int GetMaximumReplicatesWhenConsuming() => (TimeManager.TickRate * 3);
|
||||
|
||||
/// <summary>
|
||||
/// Returns if a replicates data changed and updates resends as well data tick.
|
||||
/// </summary>
|
||||
/// <param name="enqueueData">True to enqueue data for replaying.</param>
|
||||
/// <returns>True if data has changed..</returns>
|
||||
[CodegenMakePublic] //internal
|
||||
[APIExclude]
|
||||
public void Replicate_Client_Internal<T>(ReplicateUserLogicDelegate<T> del, uint methodHash, List<T> replicates, T data, Channel channel) where T : IReplicateData
|
||||
{
|
||||
//Only check to enqueu/send if not clientHost.
|
||||
if (!IsServer)
|
||||
{
|
||||
Func<T, bool> isDefaultDel = GeneratedComparer<T>.IsDefault;
|
||||
if (isDefaultDel == null)
|
||||
{
|
||||
NetworkManager.LogError($"ReplicateComparers not found for type {typeof(T).FullName}");
|
||||
return;
|
||||
}
|
||||
|
||||
//If there's no datas then reset last replicate send tick.
|
||||
if (replicates.Count == 0)
|
||||
_lastSentReplicateTick = 0;
|
||||
|
||||
PredictionManager pm = NetworkManager.PredictionManager;
|
||||
|
||||
bool isDefault = isDefaultDel.Invoke(data);
|
||||
bool mayChange = TransformMayChange();
|
||||
bool resetResends = (pm.UsingRigidbodies || mayChange || !isDefault);
|
||||
/* If there is going to be a resend then enqueue data no matter what.
|
||||
* Then ensures there are no data gaps for ticks. EG, input may
|
||||
* look like this...
|
||||
* Move - tick 0.
|
||||
* Idle - tick 1.
|
||||
* Move - tick 2.
|
||||
*
|
||||
* If there were no 'using rigidbodies' then resetResends may be false.
|
||||
* As result the queue would be filled like this...
|
||||
* Move - tick 0.
|
||||
* Move - tick 2.
|
||||
*
|
||||
* The ticks are not sent per data, just once and incremented once per data.
|
||||
* Due to this the results would actually be...
|
||||
* Move - tick 0.
|
||||
* Move - tick 1 (should be tick 2!).
|
||||
*
|
||||
* But by including data if there will be resends the defaults will become added. */
|
||||
if (resetResends)
|
||||
_remainingResends = pm.GetRedundancyCount();
|
||||
|
||||
bool enqueueData = (_remainingResends > 0);
|
||||
if (enqueueData)
|
||||
{
|
||||
/* Replicates will be limited to 1 second
|
||||
* worth on the client. That means the client
|
||||
* will only lose replays if they do not receive
|
||||
* a response back from the server for over a second.
|
||||
* When a client drops a replay it does not necessarily mean
|
||||
* they will be out of synchronization, but rather they
|
||||
* will not be able to reconcile that tick. */
|
||||
/* Even though limit is 1 second only remove entries if over 2 seconds
|
||||
* to prevent constant remove calls to the collection. */
|
||||
int maximumReplicates = (TimeManager.TickRate * 2);
|
||||
//If over then remove half the replicates.
|
||||
if (replicates.Count >= maximumReplicates)
|
||||
{
|
||||
int removeCount = (maximumReplicates / 2);
|
||||
//Dispose first.
|
||||
for (int i = 0; i < removeCount; i++)
|
||||
replicates[i].Dispose();
|
||||
//Then remove.
|
||||
replicates.RemoveRange(0, removeCount);
|
||||
}
|
||||
|
||||
uint localTick = TimeManager.LocalTick;
|
||||
//Update tick on the data to current.
|
||||
data.SetTick(localTick);
|
||||
//Add to collection.
|
||||
replicates.Add(data);
|
||||
}
|
||||
|
||||
//If theres resends left.
|
||||
if (_remainingResends > 0)
|
||||
{
|
||||
_remainingResends--;
|
||||
SendReplicateRpc<T>(methodHash, replicates, channel);
|
||||
//Update last replicate tick.
|
||||
SetLastReplicateTick(TimeManager.LocalTick);
|
||||
}
|
||||
}
|
||||
|
||||
del.Invoke(data, false, channel, false);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Reads a replicate the client.
|
||||
/// </summary>
|
||||
public void Replicate_Reader_Internal<T>(PooledReader reader, NetworkConnection sender, T[] arrBuffer, Queue<T> replicates, Channel channel) where T : IReplicateData
|
||||
{
|
||||
int receivedReplicatesCount = reader.ReadReplicate<T>(ref arrBuffer, TimeManager.LastPacketTick);
|
||||
if (!OwnerMatches(sender))
|
||||
return;
|
||||
|
||||
PredictionManager pm = PredictionManager;
|
||||
bool consumeExcess = !pm.DropExcessiveReplicates;
|
||||
//Maximum number of replicates allowed to be queued at once.
|
||||
int replicatesCountLimit = (consumeExcess) ?
|
||||
GetMaximumReplicatesWhenConsuming() : pm.GetMaximumServerReplicates();
|
||||
|
||||
for (int i = 0; i < receivedReplicatesCount; i++)
|
||||
{
|
||||
uint tick = arrBuffer[i].GetTick();
|
||||
if (tick > _lastReceivedReplicateTick)
|
||||
{
|
||||
//Cannot queue anymore, discard oldest.
|
||||
if (replicates.Count >= replicatesCountLimit)
|
||||
{
|
||||
T data = replicates.Dequeue();
|
||||
data.Dispose();
|
||||
}
|
||||
|
||||
replicates.Enqueue(arrBuffer[i]);
|
||||
_lastReceivedReplicateTick = tick;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks conditions for a reconcile.
|
||||
/// </summary>
|
||||
/// <param name="asServer">True if checking as server.</param>
|
||||
/// <returns>Returns true if able to continue.</returns>
|
||||
[CodegenMakePublic] //internal
|
||||
[APIExclude]
|
||||
public bool Reconcile_ExitEarly_A_Internal(bool asServer, out Channel channel)
|
||||
{
|
||||
channel = Channel.Unreliable;
|
||||
//Server.
|
||||
if (asServer)
|
||||
{
|
||||
if (_remainingResends <= 0)
|
||||
return true;
|
||||
|
||||
_remainingResends--;
|
||||
if (_remainingResends == 0)
|
||||
channel = Channel.Reliable;
|
||||
}
|
||||
//Client.
|
||||
else
|
||||
{
|
||||
if (!_clientHasReconcileData)
|
||||
return true;
|
||||
|
||||
_clientHasReconcileData = false;
|
||||
/* If clientHost then invoke reconciles but
|
||||
* don't actually reconcile. This is done
|
||||
* because certain user code may
|
||||
* rely on those events running even as host. */
|
||||
if (IsServer)
|
||||
{
|
||||
PredictionManager.InvokeOnReconcile_Internal(this, true);
|
||||
PredictionManager.InvokeOnReconcile_Internal(this, false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//Checks pass.
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates lastReconcileTick as though running asServer.
|
||||
/// </summary>
|
||||
/// <param name="ird">Data to set tick on.</param>
|
||||
public void Reconcile_Server_Internal<T>(uint methodHash, T data, Channel channel) where T : IReconcileData
|
||||
{
|
||||
//Server always uses last replicate tick as reconcile tick.
|
||||
uint tick = _lastReplicateTick;
|
||||
data.SetTick(tick);
|
||||
SetLastReconcileTick(tick);
|
||||
|
||||
PredictionManager.InvokeServerReconcile(this, true);
|
||||
SendReconcileRpc(methodHash, data, channel);
|
||||
PredictionManager.InvokeServerReconcile(this, false);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a reconcile for client.
|
||||
/// </summary>
|
||||
public void Reconcile_Client_Internal<T, T2>(ReconcileUserLogicDelegate<T> reconcileDel, ReplicateUserLogicDelegate<T2> replicateULDel, List<T2> replicates, T data, Channel channel) where T : IReconcileData where T2 : IReplicateData
|
||||
{
|
||||
uint tick = data.GetTick();
|
||||
|
||||
/* If the first entry in cllection has a tick higher than
|
||||
* the received tick then something went wrong, do not reconcile. */
|
||||
if (replicates.Count > 0 && replicates[0].GetTick() > tick)
|
||||
return;
|
||||
|
||||
UnityScene scene = gameObject.scene;
|
||||
PhysicsScene ps = scene.GetPhysicsScene();
|
||||
PhysicsScene2D ps2d = scene.GetPhysicsScene2D();
|
||||
|
||||
//This must be set before reconcile is invoked.
|
||||
SetLastReconcileTick(tick);
|
||||
//Invoke that reconcile is starting.
|
||||
PredictionManager.InvokeOnReconcile_Internal(this, true);
|
||||
//Call reconcile user logic.
|
||||
reconcileDel?.Invoke(data, false, channel);
|
||||
|
||||
//True if the timemanager is handling physics simulations.
|
||||
bool tmPhysics = (TimeManager.PhysicsMode == PhysicsMode.TimeManager);
|
||||
//Sync transforms if using tm physics.
|
||||
if (tmPhysics)
|
||||
{
|
||||
Physics.SyncTransforms();
|
||||
Physics2D.SyncTransforms();
|
||||
}
|
||||
|
||||
//Remove excess from buffered inputs.
|
||||
int queueIndex = -1;
|
||||
for (int i = 0; i < replicates.Count; i++)
|
||||
{
|
||||
if (replicates[i].GetTick() == tick)
|
||||
{
|
||||
queueIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//Now found, weird.
|
||||
if (queueIndex == -1)
|
||||
replicates.Clear();
|
||||
//Remove up to found, including it.
|
||||
else
|
||||
replicates.RemoveRange(0, queueIndex + 1);
|
||||
|
||||
//Number of replays which will be performed.
|
||||
int replays = replicates.Count;
|
||||
float tickDelta = (float)TimeManager.TickDelta;
|
||||
|
||||
for (int i = 0; i < replays; i++)
|
||||
{
|
||||
T2 rData = replicates[i];
|
||||
uint replayTick = rData.GetTick();
|
||||
|
||||
PredictionManager.InvokeOnReplicateReplay_Internal(scene, replayTick, ps, ps2d, true);
|
||||
|
||||
//Replay the data using the replicate logic delegate.
|
||||
replicateULDel.Invoke(rData, false, channel, true);
|
||||
if (tmPhysics)
|
||||
{
|
||||
ps.Simulate(tickDelta);
|
||||
ps2d.Simulate(tickDelta);
|
||||
}
|
||||
|
||||
PredictionManager.InvokeOnReplicateReplay_Internal(scene, replayTick, ps, ps2d, false);
|
||||
}
|
||||
|
||||
//Reconcile ended.
|
||||
PredictionManager.InvokeOnReconcile_Internal(this, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a reconcile the client.
|
||||
/// </summary>
|
||||
public void Reconcile_Reader_Internal<T>(PooledReader reader, ref T data, Channel channel) where T : IReconcileData
|
||||
{
|
||||
uint tick = reader.ReadUInt32();
|
||||
T newData = reader.Read<T>();
|
||||
|
||||
//Tick is old or already processed.
|
||||
if (tick <= _lastReceivedReconcileTick)
|
||||
return;
|
||||
//Only owner reconciles. Maybe ownership changed then packet arrived out of order.
|
||||
if (!IsOwner)
|
||||
return;
|
||||
|
||||
data = newData;
|
||||
data.SetTick(tick);
|
||||
_clientHasReconcileData = true;
|
||||
_lastReceivedReconcileTick = tick;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 074dac4dd3f9f6a4d8c1eb1191334472
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
258
Assets/FishNet/Runtime/Object/NetworkBehaviour.QOL.cs
Normal file
258
Assets/FishNet/Runtime/Object/NetworkBehaviour.QOL.cs
Normal file
@ -0,0 +1,258 @@
|
||||
#if UNITY_2020_3_OR_NEWER && UNITY_EDITOR_WIN
|
||||
using FishNet.CodeAnalysis.Annotations;
|
||||
#endif
|
||||
using FishNet.Component.ColliderRollback;
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Managing.Client;
|
||||
using FishNet.Managing.Observing;
|
||||
using FishNet.Managing.Predicting;
|
||||
using FishNet.Managing.Scened;
|
||||
using FishNet.Managing.Server;
|
||||
using FishNet.Managing.Timing;
|
||||
using FishNet.Managing.Transporting;
|
||||
using FishNet.Observing;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
|
||||
public abstract partial class NetworkBehaviour : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// True if the NetworkObject for this NetworkBehaviour is deinitializing.
|
||||
/// </summary>
|
||||
public bool IsDeinitializing => _networkObjectCache.IsDeinitializing;
|
||||
/// <summary>
|
||||
/// NetworkManager for this object.
|
||||
/// </summary>
|
||||
public NetworkManager NetworkManager => _networkObjectCache.NetworkManager;
|
||||
/// <summary>
|
||||
/// ServerManager for this object.
|
||||
/// </summary>
|
||||
public ServerManager ServerManager => _networkObjectCache.ServerManager;
|
||||
/// <summary>
|
||||
/// ClientManager for this object.
|
||||
/// </summary>
|
||||
public ClientManager ClientManager => _networkObjectCache.ClientManager;
|
||||
/// <summary>
|
||||
/// ObserverManager for this object.
|
||||
/// </summary>
|
||||
public ObserverManager ObserverManager => _networkObjectCache.ObserverManager;
|
||||
/// <summary>
|
||||
/// TransportManager for this object.
|
||||
/// </summary>
|
||||
public TransportManager TransportManager => _networkObjectCache.TransportManager;
|
||||
/// <summary>
|
||||
/// TimeManager for this object.
|
||||
/// </summary>
|
||||
public TimeManager TimeManager => _networkObjectCache.TimeManager;
|
||||
/// <summary>
|
||||
/// SceneManager for this object.
|
||||
/// </summary>
|
||||
public SceneManager SceneManager => _networkObjectCache.SceneManager;
|
||||
/// <summary>
|
||||
/// PredictionManager for this object.
|
||||
/// </summary>
|
||||
public PredictionManager PredictionManager => _networkObjectCache.PredictionManager;
|
||||
/// <summary>
|
||||
/// RollbackManager for this object.
|
||||
/// </summary>
|
||||
public RollbackManager RollbackManager => _networkObjectCache.RollbackManager;
|
||||
/// <summary>
|
||||
/// NetworkObserver on this object.
|
||||
/// </summary>
|
||||
public NetworkObserver NetworkObserver => _networkObjectCache.NetworkObserver;
|
||||
/// <summary>
|
||||
/// True if the client is active and authenticated.
|
||||
/// </summary>
|
||||
public bool IsClient => _networkObjectCache.IsClient;
|
||||
/// <summary>
|
||||
/// True if only the client is active and authenticated.
|
||||
/// </summary>
|
||||
public bool IsClientOnly => _networkObjectCache.IsClientOnly;
|
||||
/// <summary>
|
||||
/// True if server is active.
|
||||
/// </summary>
|
||||
public bool IsServer => _networkObjectCache.IsServer;
|
||||
/// <summary>
|
||||
/// True if only the server is active.
|
||||
/// </summary>
|
||||
public bool IsServerOnly => _networkObjectCache.IsServerOnly;
|
||||
/// <summary>
|
||||
/// True if client and server are active.
|
||||
/// </summary>
|
||||
public bool IsHost => _networkObjectCache.IsHost;
|
||||
/// <summary>
|
||||
/// True if client nor server are active.
|
||||
/// </summary>
|
||||
public bool IsOffline => _networkObjectCache.IsOffline;
|
||||
/// <summary>
|
||||
/// Observers for this NetworkBehaviour.
|
||||
/// </summary>
|
||||
public HashSet<NetworkConnection> Observers => _networkObjectCache.Observers;
|
||||
/// <summary>
|
||||
/// True if the local client is the owner of this object.
|
||||
/// </summary>
|
||||
#if UNITY_2020_3_OR_NEWER && UNITY_EDITOR_WIN
|
||||
[PreventUsageInside("global::FishNet.Object.NetworkBehaviour", "OnStartServer", "")]
|
||||
[PreventUsageInside("global::FishNet.Object.NetworkBehaviour", "OnStartNetwork", " Use base.Owner.IsLocalClient instead.")]
|
||||
[PreventUsageInside("global::FishNet.Object.NetworkBehaviour", "Awake", "")]
|
||||
[PreventUsageInside("global::FishNet.Object.NetworkBehaviour", "Start", "")]
|
||||
#endif
|
||||
public bool IsOwner => _networkObjectCache.IsOwner;
|
||||
/// <summary>
|
||||
/// Owner of this object.
|
||||
/// </summary>
|
||||
public NetworkConnection Owner
|
||||
{
|
||||
get
|
||||
{
|
||||
//Ensures a null Owner is never returned.
|
||||
if (_networkObjectCache == null)
|
||||
return FishNet.Managing.NetworkManager.EmptyConnection;
|
||||
|
||||
return _networkObjectCache.Owner;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// ClientId for this NetworkObject owner.
|
||||
/// </summary>
|
||||
public int OwnerId => _networkObjectCache.OwnerId;
|
||||
/// <summary>
|
||||
/// Unique Id for this _networkObjectCache. This does not represent the object owner.
|
||||
/// </summary>
|
||||
public int ObjectId => _networkObjectCache.ObjectId;
|
||||
/// <summary>
|
||||
/// The local connection of the client calling this method.
|
||||
/// </summary>
|
||||
public NetworkConnection LocalConnection => _networkObjectCache.LocalConnection;
|
||||
/// <summary>
|
||||
/// Returns if a connection is the owner of this object.
|
||||
/// </summary>
|
||||
/// <param name="connection"></param>
|
||||
/// <returns></returns>
|
||||
public bool OwnerMatches(NetworkConnection connection)
|
||||
{
|
||||
return (_networkObjectCache.Owner == connection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns a GameObject. Only call from the server.
|
||||
/// </summary>
|
||||
/// <param name="go">GameObject to despawn.</param>
|
||||
/// <param name="despawnType">What happens to the object after being despawned.</param>
|
||||
public void Despawn(GameObject go, DespawnType? despawnType = null)
|
||||
{
|
||||
if (!IsNetworkObjectNull(true))
|
||||
_networkObjectCache.Despawn(go, despawnType);
|
||||
}
|
||||
/// <summary>
|
||||
/// Despawns a NetworkObject. Only call from the server.
|
||||
/// </summary>
|
||||
/// <param name="nob">NetworkObject to despawn.</param>
|
||||
/// <param name="despawnType">What happens to the object after being despawned.</param>
|
||||
public void Despawn(NetworkObject nob, DespawnType? despawnType = null)
|
||||
{
|
||||
if (!IsNetworkObjectNull(true))
|
||||
_networkObjectCache.Despawn(nob, despawnType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns this _networkObjectCache. Can only be called on the server.
|
||||
/// </summary>
|
||||
/// <param name="despawnType">What happens to the object after being despawned.</param>
|
||||
public void Despawn(DespawnType? despawnType = null)
|
||||
{
|
||||
if (!IsNetworkObjectNull(true))
|
||||
_networkObjectCache.Despawn(despawnType);
|
||||
}
|
||||
/// <summary>
|
||||
/// Spawns an object over the network. Can only be called on the server.
|
||||
/// </summary>
|
||||
/// <param name="go">GameObject instance to spawn.</param>
|
||||
/// <param name="ownerConnection">Connection to give ownership to.</param>
|
||||
public void Spawn(GameObject go, NetworkConnection ownerConnection = null)
|
||||
{
|
||||
if (IsNetworkObjectNull(true))
|
||||
return;
|
||||
_networkObjectCache.Spawn(go, ownerConnection);
|
||||
}
|
||||
/// <summary>
|
||||
/// Spawns an object over the network. Can only be called on the server.
|
||||
/// </summary>
|
||||
/// <param name="nob">GameObject instance to spawn.</param>
|
||||
/// <param name="ownerConnection">Connection to give ownership to.</param>
|
||||
public void Spawn(NetworkObject nob, NetworkConnection ownerConnection = null)
|
||||
{
|
||||
if (IsNetworkObjectNull(true))
|
||||
return;
|
||||
_networkObjectCache.Spawn(nob, ownerConnection);
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns if NetworkObject is null.
|
||||
/// </summary>
|
||||
/// <param name="warn">True to throw a warning if null.</param>
|
||||
/// <returns></returns>
|
||||
private bool IsNetworkObjectNull(bool warn)
|
||||
{
|
||||
bool isNull = (_networkObjectCache == null);
|
||||
if (isNull && warn)
|
||||
NetworkManager.LogWarning($"NetworkObject is null. This can occur if this object is not spawned, or initialized yet.");
|
||||
|
||||
return isNull;
|
||||
}
|
||||
/// <summary>
|
||||
/// Removes ownership from all clients.
|
||||
/// </summary>
|
||||
public void RemoveOwnership()
|
||||
{
|
||||
_networkObjectCache.GiveOwnership(null, true);
|
||||
}
|
||||
/// <summary>
|
||||
/// Gives ownership to newOwner.
|
||||
/// </summary>
|
||||
/// <param name="newOwner"></param>
|
||||
public void GiveOwnership(NetworkConnection newOwner)
|
||||
{
|
||||
_networkObjectCache.GiveOwnership(newOwner, true);
|
||||
}
|
||||
|
||||
#region Registered components
|
||||
/// <summary>
|
||||
/// Invokes an action when a specified component becomes registered. Action will invoke immediately if already registered.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Component type.</typeparam>
|
||||
/// <param name="handler">Action to invoke.</param>
|
||||
public void RegisterInvokeOnInstance<T>(Action<UnityEngine.Component> handler) where T : UnityEngine.Component => _networkObjectCache.RegisterInvokeOnInstance<T>(handler);
|
||||
/// <summary>
|
||||
/// Removes an action to be invoked when a specified component becomes registered.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Component type.</typeparam>
|
||||
/// <param name="handler">Action to invoke.</param>
|
||||
public void UnregisterInvokeOnInstance<T>(Action<UnityEngine.Component> handler) where T : UnityEngine.Component => _networkObjectCache.UnregisterInvokeOnInstance<T>(handler);
|
||||
/// <summary>
|
||||
/// Returns class of type if found within CodegenBase classes.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public T GetInstance<T>() where T : UnityEngine.Component => _networkObjectCache.GetInstance<T>();
|
||||
/// <summary>
|
||||
/// Registers a new component to this NetworkManager.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to register.</typeparam>
|
||||
/// <param name="component">Reference of the component being registered.</param>
|
||||
/// <param name="replace">True to replace existing references.</param>
|
||||
public void RegisterInstance<T>(T component, bool replace = true) where T : UnityEngine.Component => _networkObjectCache.RegisterInstance<T>(component, replace);
|
||||
/// <summary>
|
||||
/// Unregisters a component from this NetworkManager.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to unregister.</typeparam>
|
||||
public void UnregisterInstance<T>() where T : UnityEngine.Component => _networkObjectCache.UnregisterInstance<T>();
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
}
|
11
Assets/FishNet/Runtime/Object/NetworkBehaviour.QOL.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/NetworkBehaviour.QOL.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d7aef532208d06c4880973c51b1906f5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
140
Assets/FishNet/Runtime/Object/NetworkBehaviour.RPCLinks.cs
Normal file
140
Assets/FishNet/Runtime/Object/NetworkBehaviour.RPCLinks.cs
Normal file
@ -0,0 +1,140 @@
|
||||
using FishNet.Managing.Server;
|
||||
using FishNet.Object.Helping;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
|
||||
public abstract partial class NetworkBehaviour : MonoBehaviour
|
||||
{
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Link indexes for RPCs.
|
||||
/// </summary>
|
||||
private Dictionary<uint, RpcLinkType> _rpcLinks = new Dictionary<uint, RpcLinkType>();
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Initializes RpcLinks. This will only call once even as host.
|
||||
/// </summary>
|
||||
private void InitializeOnceRpcLinks()
|
||||
{
|
||||
if (NetworkManager.IsServer)
|
||||
{
|
||||
/* Link only data from server to clients. While it is
|
||||
* just as easy to link client to server it's usually
|
||||
* not needed because server out data is more valuable
|
||||
* than server in data. */
|
||||
/* Links will be stored in the NetworkBehaviour so that
|
||||
* when the object is destroyed they can be added back
|
||||
* into availableRpcLinks, within the ServerManager. */
|
||||
|
||||
ServerManager serverManager = NetworkManager.ServerManager;
|
||||
//ObserverRpcs.
|
||||
foreach (uint rpcHash in _observersRpcDelegates.Keys)
|
||||
{
|
||||
if (!MakeLink(rpcHash, RpcType.Observers))
|
||||
return;
|
||||
}
|
||||
//TargetRpcs.
|
||||
foreach (uint rpcHash in _targetRpcDelegates.Keys)
|
||||
{
|
||||
if (!MakeLink(rpcHash, RpcType.Target))
|
||||
return;
|
||||
}
|
||||
//ReconcileRpcs.
|
||||
foreach (uint rpcHash in _reconcileRpcDelegates.Keys)
|
||||
{
|
||||
if (!MakeLink(rpcHash, RpcType.Reconcile))
|
||||
return;
|
||||
}
|
||||
|
||||
/* Tries to make a link and returns if
|
||||
* successful. When a link cannot be made the method
|
||||
* should exit as no other links will be possible. */
|
||||
bool MakeLink(uint rpcHash, RpcType rpcType)
|
||||
{
|
||||
if (serverManager.GetRpcLink(out ushort linkIndex))
|
||||
{
|
||||
_rpcLinks[rpcHash] = new RpcLinkType(linkIndex, rpcType);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an estimated length for any Rpc header.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private int GetEstimatedRpcHeaderLength()
|
||||
{
|
||||
/* Imaginary number for how long RPC headers are.
|
||||
* They are well under this value but this exist to
|
||||
* ensure a writer of appropriate length is pulled
|
||||
* from the pool. */
|
||||
return 20;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a PooledWriter and writes the header for a rpc.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private PooledWriter CreateLinkedRpc(RpcLinkType link, PooledWriter methodWriter, Channel channel)
|
||||
{
|
||||
int rpcHeaderBufferLength = GetEstimatedRpcHeaderLength();
|
||||
int methodWriterLength = methodWriter.Length;
|
||||
//Writer containing full packet.
|
||||
PooledWriter writer = WriterPool.GetWriter(rpcHeaderBufferLength + methodWriterLength);
|
||||
writer.WriteUInt16(link.LinkIndex);
|
||||
//Write length only if reliable.
|
||||
if (channel == Channel.Reliable)
|
||||
writer.WriteLength(methodWriter.Length);
|
||||
//Data.
|
||||
writer.WriteArraySegment(methodWriter.GetArraySegment());
|
||||
|
||||
return writer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns RpcLinks the ServerManager.
|
||||
/// </summary>
|
||||
private void ReturnRpcLinks()
|
||||
{
|
||||
if (_rpcLinks.Count == 0)
|
||||
return;
|
||||
|
||||
ServerManager?.StoreRpcLinks(_rpcLinks);
|
||||
_rpcLinks.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes rpcLinks to writer.
|
||||
/// </summary>
|
||||
internal void WriteRpcLinks(Writer writer)
|
||||
{
|
||||
PooledWriter rpcLinkWriter = WriterPool.GetWriter();
|
||||
foreach (KeyValuePair<uint, RpcLinkType> item in _rpcLinks)
|
||||
{
|
||||
//RpcLink index.
|
||||
rpcLinkWriter.WriteUInt16(item.Value.LinkIndex);
|
||||
//Hash.
|
||||
rpcLinkWriter.WriteUInt16((ushort)item.Key);
|
||||
//True/false if observersRpc.
|
||||
rpcLinkWriter.WriteByte((byte)item.Value.RpcType);
|
||||
}
|
||||
|
||||
writer.WriteBytesAndSize(rpcLinkWriter.GetBuffer(), 0, rpcLinkWriter.Length);
|
||||
rpcLinkWriter.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7136e9ee3f7eee44abab09285ab7b939
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
364
Assets/FishNet/Runtime/Object/NetworkBehaviour.RPCs.cs
Normal file
364
Assets/FishNet/Runtime/Object/NetworkBehaviour.RPCs.cs
Normal file
@ -0,0 +1,364 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Transporting;
|
||||
using FishNet.Object.Delegating;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Serializing.Helping;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Utility.Extension;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
|
||||
|
||||
public abstract partial class NetworkBehaviour : MonoBehaviour
|
||||
{
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Registered ServerRpc methods.
|
||||
/// </summary>
|
||||
private readonly Dictionary<uint, ServerRpcDelegate> _serverRpcDelegates = new Dictionary<uint, ServerRpcDelegate>();
|
||||
/// <summary>
|
||||
/// Registered ObserversRpc methods.
|
||||
/// </summary>
|
||||
private readonly Dictionary<uint, ClientRpcDelegate> _observersRpcDelegates = new Dictionary<uint, ClientRpcDelegate>();
|
||||
/// <summary>
|
||||
/// Registered TargetRpc methods.
|
||||
/// </summary>
|
||||
private readonly Dictionary<uint, ClientRpcDelegate> _targetRpcDelegates = new Dictionary<uint, ClientRpcDelegate>();
|
||||
/// <summary>
|
||||
/// Number of total RPC methods for scripts in the same inheritance tree for this instance.
|
||||
/// </summary>
|
||||
private uint _rpcMethodCount;
|
||||
/// <summary>
|
||||
/// Size of every rpcHash for this networkBehaviour.
|
||||
/// </summary>
|
||||
private byte _rpcHashSize = 1;
|
||||
/// <summary>
|
||||
/// RPCs buffered for new clients.
|
||||
/// </summary>
|
||||
private Dictionary<uint, (PooledWriter, Channel)> _bufferedRpcs = new Dictionary<uint, (PooledWriter, Channel)>();
|
||||
/// <summary>
|
||||
/// Connections to exclude from RPCs, such as ExcludeOwner or ExcludeServer.
|
||||
/// </summary>
|
||||
private HashSet<NetworkConnection> _networkConnectionCache = new HashSet<NetworkConnection>();
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Called when buffered RPCs should be sent.
|
||||
/// </summary>
|
||||
internal void SendBufferedRpcs(NetworkConnection conn)
|
||||
{
|
||||
TransportManager tm = _networkObjectCache.NetworkManager.TransportManager;
|
||||
foreach ((PooledWriter writer, Channel ch) in _bufferedRpcs.Values)
|
||||
tm.SendToClient((byte)ch, writer.GetArraySegment(), conn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a RPC method.
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <param name="del"></param>
|
||||
[APIExclude]
|
||||
[CodegenMakePublic]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected internal void RegisterServerRpc_Internal(uint hash, ServerRpcDelegate del)
|
||||
{
|
||||
bool contains = _serverRpcDelegates.ContainsKey(hash);
|
||||
_serverRpcDelegates[hash] = del;
|
||||
if (!contains)
|
||||
IncreaseRpcMethodCount();
|
||||
}
|
||||
/// <summary>
|
||||
/// Registers a RPC method.
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <param name="del"></param>
|
||||
[APIExclude]
|
||||
[CodegenMakePublic]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected internal void RegisterObserversRpc_Internal(uint hash, ClientRpcDelegate del)
|
||||
{
|
||||
bool contains = _observersRpcDelegates.ContainsKey(hash);
|
||||
_observersRpcDelegates[hash] = del;
|
||||
if (!contains)
|
||||
IncreaseRpcMethodCount();
|
||||
}
|
||||
/// <summary>
|
||||
/// Registers a RPC method.
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <param name="del"></param>
|
||||
[APIExclude]
|
||||
[CodegenMakePublic]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected internal void RegisterTargetRpc_Internal(uint hash, ClientRpcDelegate del)
|
||||
{
|
||||
bool contains = _targetRpcDelegates.ContainsKey(hash);
|
||||
_targetRpcDelegates[hash] = del;
|
||||
if (!contains)
|
||||
IncreaseRpcMethodCount();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increases rpcMethodCount and rpcHashSize.
|
||||
/// </summary>
|
||||
private void IncreaseRpcMethodCount()
|
||||
{
|
||||
_rpcMethodCount++;
|
||||
if (_rpcMethodCount <= byte.MaxValue)
|
||||
_rpcHashSize = 1;
|
||||
else
|
||||
_rpcHashSize = 2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all buffered RPCs for this NetworkBehaviour.
|
||||
/// </summary>
|
||||
public void ClearBuffedRpcs()
|
||||
{
|
||||
foreach ((PooledWriter writer, Channel _) in _bufferedRpcs.Values)
|
||||
writer.Dispose();
|
||||
_bufferedRpcs.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a RPC hash.
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
/// <returns></returns>
|
||||
private uint ReadRpcHash(PooledReader reader)
|
||||
{
|
||||
if (_rpcHashSize == 1)
|
||||
return reader.ReadByte();
|
||||
else
|
||||
return reader.ReadUInt16();
|
||||
}
|
||||
/// <summary>
|
||||
/// Called when a ServerRpc is received.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void OnServerRpc(PooledReader reader, NetworkConnection sendingClient, Channel channel)
|
||||
{
|
||||
uint methodHash = ReadRpcHash(reader);
|
||||
|
||||
if (sendingClient == null)
|
||||
{
|
||||
_networkObjectCache.NetworkManager.LogError($"NetworkConnection is null. ServerRpc {methodHash} on object {gameObject.name} [id {ObjectId}] will not complete. Remainder of packet may become corrupt.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_serverRpcDelegates.TryGetValueIL2CPP(methodHash, out ServerRpcDelegate data))
|
||||
data.Invoke(reader, channel, sendingClient);
|
||||
else
|
||||
_networkObjectCache.NetworkManager.LogWarning($"ServerRpc not found for hash {methodHash} on object {gameObject.name} [id {ObjectId}]. Remainder of packet may become corrupt.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when an ObserversRpc is received.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void OnObserversRpc(uint? methodHash, PooledReader reader, Channel channel)
|
||||
{
|
||||
if (methodHash == null)
|
||||
methodHash = ReadRpcHash(reader);
|
||||
|
||||
if (_observersRpcDelegates.TryGetValueIL2CPP(methodHash.Value, out ClientRpcDelegate del))
|
||||
del.Invoke(reader, channel);
|
||||
else
|
||||
_networkObjectCache.NetworkManager.LogWarning($"ObserversRpc not found for hash {methodHash.Value} on object {gameObject.name} [id {ObjectId}] . Remainder of packet may become corrupt.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when an TargetRpc is received.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void OnTargetRpc(uint? methodHash, PooledReader reader, Channel channel)
|
||||
{
|
||||
if (methodHash == null)
|
||||
methodHash = ReadRpcHash(reader);
|
||||
|
||||
if (_targetRpcDelegates.TryGetValueIL2CPP(methodHash.Value, out ClientRpcDelegate del))
|
||||
del.Invoke(reader, channel);
|
||||
else
|
||||
_networkObjectCache.NetworkManager.LogWarning($"TargetRpc not found for hash {methodHash.Value} on object {gameObject.name} [id {ObjectId}] . Remainder of packet may become corrupt.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a RPC to server.
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <param name="methodWriter"></param>
|
||||
/// <param name="channel"></param>
|
||||
[CodegenMakePublic]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SendServerRpc_Internal(uint hash, PooledWriter methodWriter, Channel channel)
|
||||
{
|
||||
if (!IsSpawnedWithWarning())
|
||||
return;
|
||||
|
||||
PooledWriter writer = CreateRpc(hash, methodWriter, PacketId.ServerRpc, channel);
|
||||
_networkObjectCache.NetworkManager.TransportManager.SendToServer((byte)channel, writer.GetArraySegment());
|
||||
writer.DisposeLength();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a RPC to observers.
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <param name="methodWriter"></param>
|
||||
/// <param name="channel"></param>
|
||||
[APIExclude]
|
||||
[CodegenMakePublic] //Make internal.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SendObserversRpc_Internal(uint hash, PooledWriter methodWriter, Channel channel, bool buffered, bool excludeServer, bool excludeOwner)
|
||||
{
|
||||
if (!IsSpawnedWithWarning())
|
||||
return;
|
||||
|
||||
PooledWriter writer;
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
if (NetworkManager.DebugManager.ObserverRpcLinks && _rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
|
||||
#else
|
||||
if (_rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
|
||||
#endif
|
||||
writer = CreateLinkedRpc(link, methodWriter, channel);
|
||||
else
|
||||
writer = CreateRpc(hash, methodWriter, PacketId.ObserversRpc, channel);
|
||||
|
||||
SetNetworkConnectionCache(excludeServer, excludeOwner);
|
||||
_networkObjectCache.NetworkManager.TransportManager.SendToClients((byte)channel, writer.GetArraySegment(), _networkObjectCache.Observers, _networkConnectionCache, true);
|
||||
|
||||
/* If buffered then dispose of any already buffered
|
||||
* writers and replace with new one. Writers should
|
||||
* automatically dispose when references are lost
|
||||
* anyway but better safe than sorry. */
|
||||
if (buffered)
|
||||
{
|
||||
if (_bufferedRpcs.TryGetValueIL2CPP(hash, out (PooledWriter pw, Channel ch) result))
|
||||
result.pw.DisposeLength();
|
||||
_bufferedRpcs[hash] = (writer, channel);
|
||||
}
|
||||
//If not buffered then dispose immediately.
|
||||
else
|
||||
{
|
||||
writer.DisposeLength();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a RPC to target.
|
||||
/// </summary>
|
||||
[CodegenMakePublic] //Make internal.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SendTargetRpc_Internal(uint hash, PooledWriter methodWriter, Channel channel, NetworkConnection target, bool excludeServer, bool validateTarget = true)
|
||||
{
|
||||
if (!IsSpawnedWithWarning())
|
||||
return;
|
||||
|
||||
if (validateTarget)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
_networkObjectCache.NetworkManager.LogWarning($"Action cannot be completed as no Target is specified.");
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
//If target is not an observer.
|
||||
if (!_networkObjectCache.Observers.Contains(target))
|
||||
{
|
||||
_networkObjectCache.NetworkManager.LogWarning($"Action cannot be completed as Target is not an observer for object {gameObject.name} [id {ObjectId}].");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Excluding server.
|
||||
if (excludeServer && target.IsLocalClient)
|
||||
return;
|
||||
|
||||
PooledWriter writer;
|
||||
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
if (NetworkManager.DebugManager.TargetRpcLinks && _rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
|
||||
#else
|
||||
if (_rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
|
||||
#endif
|
||||
writer = CreateLinkedRpc(link, methodWriter, channel);
|
||||
else
|
||||
writer = CreateRpc(hash, methodWriter, PacketId.TargetRpc, channel);
|
||||
|
||||
_networkObjectCache.NetworkManager.TransportManager.SendToClient((byte)channel, writer.GetArraySegment(), target);
|
||||
writer.DisposeLength();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds excluded connections to ExcludedRpcConnections.
|
||||
/// </summary>
|
||||
private void SetNetworkConnectionCache(bool addClientHost, bool addOwner)
|
||||
{
|
||||
_networkConnectionCache.Clear();
|
||||
if (addClientHost && IsClient)
|
||||
_networkConnectionCache.Add(LocalConnection);
|
||||
if (addOwner && Owner.IsValid)
|
||||
_networkConnectionCache.Add(Owner);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns if spawned and throws a warning if not.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool IsSpawnedWithWarning()
|
||||
{
|
||||
bool result = this.IsSpawned;
|
||||
if (!result)
|
||||
_networkObjectCache.NetworkManager.LogWarning($"Action cannot be completed as object {gameObject.name} [Id {ObjectId}] is not spawned.");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a full RPC and returns the writer.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private PooledWriter CreateRpc(uint hash, PooledWriter methodWriter, PacketId packetId, Channel channel)
|
||||
{
|
||||
int rpcHeaderBufferLength = GetEstimatedRpcHeaderLength();
|
||||
int methodWriterLength = methodWriter.Length;
|
||||
//Writer containing full packet.
|
||||
PooledWriter writer = WriterPool.GetWriter(rpcHeaderBufferLength + methodWriterLength);
|
||||
writer.WritePacketId(packetId);
|
||||
writer.WriteNetworkBehaviour(this);
|
||||
//Only write length if reliable.
|
||||
if (channel == Channel.Reliable)
|
||||
writer.WriteLength(methodWriterLength + _rpcHashSize);
|
||||
//Hash and data.
|
||||
WriteRpcHash(hash, writer);
|
||||
writer.WriteArraySegment(methodWriter.GetArraySegment());
|
||||
|
||||
return writer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes rpcHash to writer.
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <param name="writer"></param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void WriteRpcHash(uint hash, PooledWriter writer)
|
||||
{
|
||||
if (_rpcHashSize == 1)
|
||||
writer.WriteByte((byte)hash);
|
||||
else
|
||||
writer.WriteUInt16((byte)hash);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
11
Assets/FishNet/Runtime/Object/NetworkBehaviour.RPCs.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/NetworkBehaviour.RPCs.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 938eacb83fa7d0046bd769b31dac7e80
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
429
Assets/FishNet/Runtime/Object/NetworkBehaviour.SyncTypes.cs
Normal file
429
Assets/FishNet/Runtime/Object/NetworkBehaviour.SyncTypes.cs
Normal file
@ -0,0 +1,429 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Transporting;
|
||||
using FishNet.Object.Synchronizing;
|
||||
using FishNet.Object.Synchronizing.Internal;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Utility.Extension;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
|
||||
public abstract partial class NetworkBehaviour : MonoBehaviour
|
||||
{
|
||||
#region Types.
|
||||
/// <summary>
|
||||
/// Used to generate data sent from synctypes.
|
||||
/// </summary>
|
||||
private class SyncTypeWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// Clients which can be synchronized.
|
||||
/// </summary>
|
||||
public ReadPermission ReadPermission;
|
||||
/// <summary>
|
||||
/// Writers for each channel.
|
||||
/// </summary>
|
||||
public PooledWriter[] Writers { get; private set; }
|
||||
|
||||
public SyncTypeWriter(ReadPermission readPermission)
|
||||
{
|
||||
ReadPermission = readPermission;
|
||||
Writers = new PooledWriter[TransportManager.CHANNEL_COUNT];
|
||||
for (int i = 0; i < Writers.Length; i++)
|
||||
Writers[i] = WriterPool.GetWriter();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets Writers.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
if (Writers == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < Writers.Length; i++)
|
||||
Writers[i].Reset();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Writers for syncTypes. A writer will exist for every ReadPermission type.
|
||||
/// </summary>
|
||||
private SyncTypeWriter[] _syncTypeWriters;
|
||||
/// <summary>
|
||||
/// SyncVars within this NetworkBehaviour.
|
||||
/// </summary>
|
||||
private Dictionary<uint, SyncBase> _syncVars = new Dictionary<uint, SyncBase>();
|
||||
/// <summary>
|
||||
/// True if at least one syncVar is dirty.
|
||||
/// </summary>
|
||||
private bool _syncVarDirty;
|
||||
/// <summary>
|
||||
/// SyncVars within this NetworkBehaviour.
|
||||
/// </summary>
|
||||
private Dictionary<uint, SyncBase> _syncObjects = new Dictionary<uint, SyncBase>();
|
||||
/// <summary>
|
||||
/// True if at least one syncObject is dirty.
|
||||
/// </summary>
|
||||
private bool _syncObjectDirty;
|
||||
/// <summary>
|
||||
/// All ReadPermission values.
|
||||
/// </summary>
|
||||
private static ReadPermission[] _readPermissions;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Registers a SyncType.
|
||||
/// </summary>
|
||||
/// <param name="sb"></param>
|
||||
/// <param name="index"></param>
|
||||
internal void RegisterSyncType(SyncBase sb, uint index)
|
||||
{
|
||||
if (sb.IsSyncObject)
|
||||
_syncObjects.Add(index, sb);
|
||||
else
|
||||
_syncVars.Add(index, sb);
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets a SyncVar as dirty.
|
||||
/// </summary>
|
||||
/// <param name="isSyncObject">True if dirtying a syncObject.</param>
|
||||
/// <returns>True if able to dirty SyncType.</returns>
|
||||
internal bool DirtySyncType(bool isSyncObject)
|
||||
{
|
||||
if (!IsServer)
|
||||
return false;
|
||||
/* No reason to dirty if there are no observers.
|
||||
* This can happen even if a client is going to see
|
||||
* this object because the server side initializes
|
||||
* before observers are built. */
|
||||
if (_networkObjectCache.Observers.Count == 0)
|
||||
return false;
|
||||
|
||||
bool alreadyDirtied = (isSyncObject) ? _syncObjectDirty : _syncVarDirty;
|
||||
if (isSyncObject)
|
||||
_syncObjectDirty = true;
|
||||
else
|
||||
_syncVarDirty = true;
|
||||
|
||||
if (!alreadyDirtied)
|
||||
_networkObjectCache.NetworkManager.ServerManager.Objects.SetDirtySyncType(this, isSyncObject);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes SyncTypes. This will only call once even as host.
|
||||
/// </summary>
|
||||
private void InitializeOnceSyncTypes()
|
||||
{
|
||||
if (_readPermissions == null)
|
||||
{
|
||||
System.Array arr = System.Enum.GetValues(typeof(ReadPermission));
|
||||
_readPermissions = new ReadPermission[arr.Length];
|
||||
|
||||
int count = 0;
|
||||
foreach (ReadPermission rp in arr)
|
||||
{
|
||||
_readPermissions[count] = rp;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
//Build writers for observers and owner.
|
||||
_syncTypeWriters = new SyncTypeWriter[_readPermissions.Length];
|
||||
for (int i = 0; i < _syncTypeWriters.Length; i++)
|
||||
_syncTypeWriters[i] = new SyncTypeWriter(_readPermissions[i]);
|
||||
|
||||
foreach (SyncBase sb in _syncVars.Values)
|
||||
sb.PreInitialize(_networkObjectCache.NetworkManager);
|
||||
foreach (SyncBase sb in _syncObjects.Values)
|
||||
sb.PreInitialize(_networkObjectCache.NetworkManager);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Reads a SyncVar.
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
internal void OnSyncType(PooledReader reader, int length, bool isSyncObject, bool asServer = false)
|
||||
{
|
||||
int readerStart = reader.Position;
|
||||
while (reader.Position - readerStart < length)
|
||||
{
|
||||
byte index = reader.ReadByte();
|
||||
if (isSyncObject)
|
||||
{
|
||||
if (_syncObjects.TryGetValueIL2CPP(index, out SyncBase sb))
|
||||
sb.Read(reader, asServer);
|
||||
else
|
||||
NetworkManager.LogWarning($"SyncObject not found for index {index} on {transform.name}. Remainder of packet may become corrupt.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_syncVars.ContainsKey(index))
|
||||
ReadSyncVar(reader, index, asServer);
|
||||
else
|
||||
NetworkManager.LogWarning($"SyncVar not found for index {index} on {transform.name}. Remainder of packet may become corrupt.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Codegen overrides this method to read syncVars for each script which inherits NetworkBehaviour.
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="asServer">True if reading into SyncVars for the server, false for client. This would be true for predicted spawning if the predicted spawner sent syncvars.</param>
|
||||
[APIExclude]
|
||||
internal virtual bool ReadSyncVar(PooledReader reader, uint index, bool asServer) { return false; }
|
||||
|
||||
/// <summary>
|
||||
/// Writers dirty SyncTypes if their write tick has been met.
|
||||
/// </summary>
|
||||
/// <returns>True if there are no pending dirty sync types.</returns>
|
||||
internal bool WriteDirtySyncTypes(bool isSyncObject, bool ignoreInterval = false)
|
||||
{
|
||||
/* Can occur when a synctype is queued after
|
||||
* the object is marked for destruction. This should not
|
||||
* happen under most conditions since synctypes will be
|
||||
* pushed through when despawn is called. */
|
||||
if (!IsSpawned)
|
||||
{
|
||||
ResetSyncTypes();
|
||||
return true;
|
||||
}
|
||||
|
||||
/* If there is nothing dirty then return true, indicating no more
|
||||
* pending dirty checks. */
|
||||
if (isSyncObject && (!_syncObjectDirty || _syncObjects.Count == 0))
|
||||
return true;
|
||||
else if (!isSyncObject && (!_syncVarDirty || _syncVars.Count == 0))
|
||||
return true;
|
||||
|
||||
/* True if writers have been reset for this check.
|
||||
* For perf writers are only reset when data is to be written. */
|
||||
bool writersReset = false;
|
||||
uint tick = _networkObjectCache.NetworkManager.TimeManager.Tick;
|
||||
|
||||
//True if a syncvar is found to still be dirty.
|
||||
bool dirtyFound = false;
|
||||
//True if data has been written and is ready to send.
|
||||
bool dataWritten = false;
|
||||
Dictionary<uint, SyncBase> collection = (isSyncObject) ? _syncObjects : _syncVars;
|
||||
|
||||
foreach (SyncBase sb in collection.Values)
|
||||
{
|
||||
if (!sb.IsDirty)
|
||||
continue;
|
||||
|
||||
dirtyFound = true;
|
||||
if (ignoreInterval || sb.WriteTimeMet(tick))
|
||||
{
|
||||
//If writers still need to be reset.
|
||||
if (!writersReset)
|
||||
{
|
||||
writersReset = true;
|
||||
//Reset writers.
|
||||
for (int i = 0; i < _syncTypeWriters.Length; i++)
|
||||
_syncTypeWriters[i].Reset();
|
||||
}
|
||||
|
||||
//Find channel.
|
||||
byte channel = (byte)sb.Channel;
|
||||
sb.ResetDirty();
|
||||
//If ReadPermission is owner but no owner skip this syncvar write.
|
||||
if (sb.Settings.ReadPermission == ReadPermission.OwnerOnly && !_networkObjectCache.Owner.IsValid)
|
||||
continue;
|
||||
|
||||
dataWritten = true;
|
||||
//Find PooledWriter to use.
|
||||
PooledWriter writer = null;
|
||||
for (int i = 0; i < _syncTypeWriters.Length; i++)
|
||||
{
|
||||
if (_syncTypeWriters[i].ReadPermission == sb.Settings.ReadPermission)
|
||||
{
|
||||
/* Channel for syncVar is beyond available channels in transport.
|
||||
* Use default reliable. */
|
||||
if (channel >= _syncTypeWriters[i].Writers.Length)
|
||||
channel = (byte)Channel.Reliable;
|
||||
|
||||
writer = _syncTypeWriters[i].Writers[channel];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (writer == null)
|
||||
NetworkManager.LogError($"Writer couldn't be found for permissions {sb.Settings.ReadPermission} on channel {channel}.");
|
||||
else
|
||||
sb.WriteDelta(writer);
|
||||
}
|
||||
}
|
||||
|
||||
//If no dirty were found.
|
||||
if (!dirtyFound)
|
||||
{
|
||||
if (isSyncObject)
|
||||
_syncObjectDirty = false;
|
||||
else
|
||||
_syncVarDirty = false;
|
||||
return true;
|
||||
}
|
||||
//At least one sync type was dirty.
|
||||
else if (dataWritten)
|
||||
{
|
||||
for (int i = 0; i < _syncTypeWriters.Length; i++)
|
||||
{
|
||||
for (byte channel = 0; channel < _syncTypeWriters[i].Writers.Length; channel++)
|
||||
{
|
||||
PooledWriter channelWriter = _syncTypeWriters[i].Writers[channel];
|
||||
//If there is data to send.
|
||||
if (channelWriter.Length > 0)
|
||||
{
|
||||
using (PooledWriter headerWriter = WriterPool.GetWriter())
|
||||
{
|
||||
//Write the packetId and NB information.
|
||||
PacketId packetId = (isSyncObject) ? PacketId.SyncObject : PacketId.SyncVar;
|
||||
headerWriter.WritePacketId(packetId);
|
||||
PooledWriter dataWriter = WriterPool.GetWriter();
|
||||
dataWriter.WriteNetworkBehaviour(this);
|
||||
|
||||
/* SyncVars need length written regardless because amount
|
||||
* of data being sent per syncvar is unknown, and the packet may have
|
||||
* additional data after the syncvars. Because of this we should only
|
||||
* read up to syncvar length then assume the remainder is another packet.
|
||||
*
|
||||
* Reliable always has data written as well even if syncObject. This is so
|
||||
* if an object does not exist for whatever reason the packet can be
|
||||
* recovered by skipping the data.
|
||||
*
|
||||
* Realistically everything will be a syncvar or on the reliable channel unless
|
||||
* the user makes a custom syncobject that utilizes unreliable. */
|
||||
if (!isSyncObject || (Channel)channel == Channel.Reliable)
|
||||
dataWriter.WriteBytesAndSize(channelWriter.GetBuffer(), 0, channelWriter.Length);
|
||||
else
|
||||
dataWriter.WriteBytes(channelWriter.GetBuffer(), 0, channelWriter.Length);
|
||||
|
||||
//Attach data onto packetWriter.
|
||||
headerWriter.WriteArraySegment(dataWriter.GetArraySegment());
|
||||
dataWriter.Dispose();
|
||||
|
||||
|
||||
//If only sending to owner.
|
||||
if (_syncTypeWriters[i].ReadPermission == ReadPermission.OwnerOnly)
|
||||
{
|
||||
_networkObjectCache.NetworkManager.TransportManager.SendToClient(channel, headerWriter.GetArraySegment(), _networkObjectCache.Owner);
|
||||
}
|
||||
//Sending to observers.
|
||||
else
|
||||
{
|
||||
bool excludeOwner = (_syncTypeWriters[i].ReadPermission == ReadPermission.ExcludeOwner);
|
||||
SetNetworkConnectionCache(false, excludeOwner);
|
||||
NetworkConnection excludedConnection = (excludeOwner) ? _networkObjectCache.Owner : null;
|
||||
_networkObjectCache.NetworkManager.TransportManager.SendToClients((byte)channel, headerWriter.GetArraySegment(), _networkObjectCache.Observers, _networkConnectionCache);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Fall through. If here then sync types are still pending
|
||||
* being written or were just written this frame. */
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Resets all SyncTypes for this NetworkBehaviour for server and client side.
|
||||
/// </summary>
|
||||
internal void ResetSyncTypes()
|
||||
{
|
||||
foreach (SyncBase item in _syncVars.Values)
|
||||
item.Reset();
|
||||
foreach (SyncBase item in _syncObjects.Values)
|
||||
item.Reset();
|
||||
|
||||
_syncObjectDirty = false;
|
||||
_syncVarDirty = false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Resets all SyncTypes for this NetworkBehaviour.
|
||||
/// </summary>
|
||||
internal void ResetSyncTypes(bool asServer)
|
||||
{
|
||||
if (asServer || (!asServer && !IsServer))
|
||||
{
|
||||
foreach (SyncBase item in _syncVars.Values)
|
||||
item.Reset();
|
||||
foreach (SyncBase item in _syncObjects.Values)
|
||||
item.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writers syncVars for a spawn message.
|
||||
/// </summary>
|
||||
internal void WriteSyncTypesForSpawn(PooledWriter writer, SyncTypeWriteType writeType)
|
||||
{
|
||||
//Write for owner if writing all or owner, but not observers.
|
||||
bool ownerWrite = (writeType != SyncTypeWriteType.Observers);
|
||||
WriteSyncType(_syncVars);
|
||||
WriteSyncType(_syncObjects);
|
||||
|
||||
void WriteSyncType(Dictionary<uint, SyncBase> collection)
|
||||
{
|
||||
using (PooledWriter syncTypeWriter = WriterPool.GetWriter())
|
||||
{
|
||||
/* Since all values are being written everything is
|
||||
* written in order so there's no reason to pass
|
||||
* indexes. */
|
||||
foreach (SyncBase sb in collection.Values)
|
||||
{
|
||||
//If not for owner and syncvar is owner only.
|
||||
if (!ownerWrite && sb.Settings.ReadPermission == ReadPermission.OwnerOnly)
|
||||
{
|
||||
//If there is an owner then skip.
|
||||
if (_networkObjectCache.Owner.IsValid)
|
||||
continue;
|
||||
}
|
||||
|
||||
sb.WriteFull(syncTypeWriter);
|
||||
}
|
||||
|
||||
writer.WriteBytesAndSize(syncTypeWriter.GetBuffer(), 0, syncTypeWriter.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Manually marks a SyncType as dirty, be it SyncVar or SyncObject.
|
||||
/// </summary>
|
||||
/// <param name="syncType">SyncType variable to dirty.</param>
|
||||
protected void DirtySyncType(object syncType)
|
||||
{
|
||||
/* This doesn't actually do anything.
|
||||
* The codegen replaces calls to this method
|
||||
* with a Dirty call for syncType. */
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e56d5389eb07aa040b8a9ec8b0d7c597
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
190
Assets/FishNet/Runtime/Object/NetworkBehaviour.cs
Normal file
190
Assets/FishNet/Runtime/Object/NetworkBehaviour.cs
Normal file
@ -0,0 +1,190 @@
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Serializing.Helping;
|
||||
using FishNet.Utility.Constant;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
[assembly: InternalsVisibleTo(UtilityConstants.CODEGEN_ASSEMBLY_NAME)]
|
||||
namespace FishNet.Object
|
||||
{
|
||||
/// <summary>
|
||||
/// Scripts which inherit from NetworkBehaviour can be used to gain insight of, and perform actions on the network.
|
||||
/// </summary>
|
||||
public abstract partial class NetworkBehaviour : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// True if this NetworkBehaviour is initialized for the network.
|
||||
/// </summary>
|
||||
public bool IsSpawned => _networkObjectCache.IsSpawned;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[SerializeField, HideInInspector]
|
||||
private byte _componentIndexCache = byte.MaxValue;
|
||||
/// <summary>
|
||||
/// ComponentIndex for this NetworkBehaviour.
|
||||
/// </summary>
|
||||
public byte ComponentIndex
|
||||
{
|
||||
get => _componentIndexCache;
|
||||
private set => _componentIndexCache = value;
|
||||
}
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// NetworkObject automatically added or discovered during edit time.
|
||||
/// </summary>
|
||||
[SerializeField, HideInInspector]
|
||||
private NetworkObject _addedNetworkObject;
|
||||
#endif
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[SerializeField, HideInInspector]
|
||||
private NetworkObject _networkObjectCache;
|
||||
/// <summary>
|
||||
/// NetworkObject this behaviour is for.
|
||||
/// </summary>
|
||||
public NetworkObject NetworkObject => _networkObjectCache;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this script. This will only run once even as host.
|
||||
/// </summary>
|
||||
/// <param name="networkObject"></param>
|
||||
/// <param name="componentIndex"></param>
|
||||
internal void InitializeOnce_Internal()
|
||||
{
|
||||
InitializeOnceSyncTypes();
|
||||
InitializeOnceRpcLinks();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Serializes information for network components.
|
||||
/// </summary>
|
||||
internal void SerializeComponents(NetworkObject nob, byte componentIndex)
|
||||
{
|
||||
_networkObjectCache = nob;
|
||||
ComponentIndex = componentIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manually initializes network content for the NetworkBehaviour if the object it's on is disabled.
|
||||
/// </summary>
|
||||
internal void InitializeIfDisabled()
|
||||
{
|
||||
if (gameObject.activeInHierarchy)
|
||||
return;
|
||||
|
||||
NetworkInitializeIfDisabled();
|
||||
}
|
||||
/// <summary>
|
||||
/// Long name is to prevent users from potentially creating their own method named the same.
|
||||
/// </summary>
|
||||
[CodegenMakePublic]
|
||||
[APIExclude]
|
||||
internal virtual void NetworkInitializeIfDisabled() { }
|
||||
|
||||
#region Editor.
|
||||
protected virtual void Reset()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isPlaying)
|
||||
return;
|
||||
|
||||
TryAddNetworkObject();
|
||||
#endif
|
||||
}
|
||||
|
||||
protected virtual void OnValidate()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isPlaying)
|
||||
return;
|
||||
|
||||
TryAddNetworkObject();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets this NetworkBehaviour so that it may be added to an object pool.
|
||||
/// </summary>
|
||||
internal void ResetForObjectPool()
|
||||
{
|
||||
ResetSyncTypes();
|
||||
ClearReplicateCache();
|
||||
ClearBuffedRpcs();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Tries to add the NetworkObject component.
|
||||
/// </summary>
|
||||
private NetworkObject TryAddNetworkObject()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (Application.isPlaying)
|
||||
return _addedNetworkObject;
|
||||
|
||||
if (_addedNetworkObject != null)
|
||||
{
|
||||
AlertToDuplicateNetworkObjects(_addedNetworkObject.transform);
|
||||
return _addedNetworkObject;
|
||||
}
|
||||
|
||||
/* Manually iterate up the chain because GetComponentInParent doesn't
|
||||
* work when modifying prefabs in the inspector. Unity, you're starting
|
||||
* to suck a lot right now. */
|
||||
NetworkObject result = null;
|
||||
Transform climb = transform;
|
||||
|
||||
while (climb != null)
|
||||
{
|
||||
if (climb.TryGetComponent<NetworkObject>(out result))
|
||||
break;
|
||||
else
|
||||
climb = climb.parent;
|
||||
}
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
_addedNetworkObject = result;
|
||||
}
|
||||
//Not found, add a new nob.
|
||||
else
|
||||
{
|
||||
_addedNetworkObject = transform.root.gameObject.AddComponent<NetworkObject>();
|
||||
Debug.Log($"Script {GetType().Name} on object {gameObject.name} added a NetworkObject component to {transform.root.name}.");
|
||||
}
|
||||
|
||||
AlertToDuplicateNetworkObjects(_addedNetworkObject.transform);
|
||||
return _addedNetworkObject;
|
||||
|
||||
//Removes duplicate network objects from t.
|
||||
void AlertToDuplicateNetworkObjects(Transform t)
|
||||
{
|
||||
NetworkObject[] nobs = t.GetComponents<NetworkObject>();
|
||||
//This shouldn't be possible but does occur sometimes; maybe a unity bug?
|
||||
if (nobs.Length > 1)
|
||||
{
|
||||
//Update added to first entryt.
|
||||
_addedNetworkObject = nobs[0];
|
||||
|
||||
string useMenu = " You may also use the Fish-Networking menu to automatically remove duplicate NetworkObjects.";
|
||||
string sceneName = t.gameObject.scene.name;
|
||||
if (string.IsNullOrEmpty(sceneName))
|
||||
Debug.LogError($"Prefab {t.name} has multiple NetworkObject components. Please remove the extra component(s) to prevent errors.{useMenu}");
|
||||
else
|
||||
Debug.LogError($"Object {t.name} in scene {sceneName} has multiple NetworkObject components. Please remove the extra component(s) to prevent errors.{useMenu}");
|
||||
}
|
||||
|
||||
}
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
}
|
11
Assets/FishNet/Runtime/Object/NetworkBehaviour.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/NetworkBehaviour.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2230f9cdb1ffc9489b53875c963342d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
31
Assets/FishNet/Runtime/Object/NetworkObject.Broadcast.cs
Normal file
31
Assets/FishNet/Runtime/Object/NetworkObject.Broadcast.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using FishNet.Broadcast;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Transporting;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
public sealed partial class NetworkObject : MonoBehaviour
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Sends a broadcast to Observers on this NetworkObject.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of broadcast to send.</typeparam>
|
||||
/// <param name="message">Broadcast data being sent; for example: an instance of your broadcast type.</param>
|
||||
/// <param name="requireAuthenticated">True if the client must be authenticated for this broadcast to send.</param>
|
||||
/// <param name="channel">Channel to send on.</param>
|
||||
public void Broadcast<T>(T message, bool requireAuthenticated = true, Channel channel = Channel.Reliable) where T : struct, IBroadcast
|
||||
{
|
||||
if (NetworkManager == null)
|
||||
{
|
||||
NetworkManager.StaticLogWarning($"Cannot send broadcast from {gameObject.name}, NetworkManager reference is null. This may occur if the object is not spawned or initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
NetworkManager.ServerManager.Broadcast(Observers, message, requireAuthenticated, channel);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 55d793117b52da549affcc9ec30b05c0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
150
Assets/FishNet/Runtime/Object/NetworkObject.Callbacks.cs
Normal file
150
Assets/FishNet/Runtime/Object/NetworkObject.Callbacks.cs
Normal file
@ -0,0 +1,150 @@
|
||||
using FishNet.Connection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
public sealed partial class NetworkObject : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Called after all data is synchronized with this NetworkObject.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void InitializeCallbacks(bool asServer, bool invokeSyncTypeCallbacks)
|
||||
{
|
||||
/* Note: When invoking OnOwnership here previous owner will
|
||||
* always be an empty connection, since the object is just
|
||||
* now initializing. */
|
||||
|
||||
if (!asServer)
|
||||
ClientInitialized = true;
|
||||
|
||||
//Set that client or server is active before callbacks.
|
||||
SetActiveStatus(true, asServer);
|
||||
|
||||
//Invoke OnStartNetwork.
|
||||
for (int i = 0; i < NetworkBehaviours.Length; i++)
|
||||
NetworkBehaviours[i].InvokeOnNetwork(true);
|
||||
|
||||
//As server.
|
||||
if (asServer)
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Length; i++)
|
||||
NetworkBehaviours[i].OnStartServer();
|
||||
for (int i = 0; i < NetworkBehaviours.Length; i++)
|
||||
NetworkBehaviours[i].OnOwnershipServer(FishNet.Managing.NetworkManager.EmptyConnection);
|
||||
if (invokeSyncTypeCallbacks)
|
||||
InvokeSyncTypeCallbacks(true);
|
||||
}
|
||||
//As client.
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Length; i++)
|
||||
NetworkBehaviours[i].OnStartClient();
|
||||
for (int i = 0; i < NetworkBehaviours.Length; i++)
|
||||
NetworkBehaviours[i].OnOwnershipClient(FishNet.Managing.NetworkManager.EmptyConnection);
|
||||
if (invokeSyncTypeCallbacks)
|
||||
InvokeSyncTypeCallbacks(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Invokes pending SyncType callbacks.
|
||||
/// </summary>
|
||||
/// <param name="asServer"></param>
|
||||
internal void InvokeSyncTypeCallbacks(bool asServer)
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Length; i++)
|
||||
NetworkBehaviours[i].InvokeSyncTypeCallbacks(asServer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes events to be called after OnServerStart.
|
||||
/// This is made one method to save instruction calls.
|
||||
/// </summary>
|
||||
/// <param name=""></param>
|
||||
internal void InvokePostOnServerStart(NetworkConnection conn)
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Length; i++)
|
||||
NetworkBehaviours[i].SendBufferedRpcs(conn);
|
||||
|
||||
for (int i = 0; i < NetworkBehaviours.Length; i++)
|
||||
NetworkBehaviours[i].OnSpawnServer(conn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on the server before it sends a despawn message to a client.
|
||||
/// </summary>
|
||||
/// <param name="conn">Connection spawn was sent to.</param>
|
||||
internal void InvokeOnServerDespawn(NetworkConnection conn)
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Length; i++)
|
||||
NetworkBehaviours[i].OnDespawnServer(conn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes OnStop callbacks.
|
||||
/// </summary>
|
||||
/// <param name="asServer"></param>
|
||||
internal void InvokeStopCallbacks(bool asServer)
|
||||
{
|
||||
if (asServer)
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Length; i++)
|
||||
NetworkBehaviours[i].OnStopServer();
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Length; i++)
|
||||
NetworkBehaviours[i].OnStopClient();
|
||||
}
|
||||
|
||||
/* Invoke OnStopNetwork if server is calling
|
||||
* or if client and not as server. */
|
||||
if (asServer || (!asServer && !IsServer))
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Length; i++)
|
||||
NetworkBehaviours[i].InvokeOnNetwork(false);
|
||||
}
|
||||
|
||||
if (asServer)
|
||||
IsServer = false;
|
||||
else
|
||||
IsClient = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes OnOwnership callbacks.
|
||||
/// </summary>
|
||||
/// <param name="prevOwner"></param>
|
||||
private void InvokeOwnership(NetworkConnection prevOwner, bool asServer)
|
||||
{
|
||||
if (asServer)
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Length; i++)
|
||||
NetworkBehaviours[i].OnOwnershipServer(prevOwner);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* If local client is owner and not server then only
|
||||
* invoke if the prevOwner is different. This prevents
|
||||
* the owner change callback from happening twice when
|
||||
* using TakeOwnership.
|
||||
*
|
||||
* Further explained, the TakeOwnership sets local client
|
||||
* as owner client-side, which invokes the OnOwnership method.
|
||||
* Then when the server approves the owner change it would invoke
|
||||
* again, which is not needed. */
|
||||
bool blockInvoke = ((IsOwner && !IsServer) && (prevOwner == Owner));
|
||||
if (!blockInvoke)
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Length; i++)
|
||||
NetworkBehaviours[i].OnOwnershipClient(prevOwner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e9fbf0d6eb10e94d892dd4e817030bc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
213
Assets/FishNet/Runtime/Object/NetworkObject.Observers.cs
Normal file
213
Assets/FishNet/Runtime/Object/NetworkObject.Observers.cs
Normal file
@ -0,0 +1,213 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Observing;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
public sealed partial class NetworkObject : MonoBehaviour
|
||||
{
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// Called when this NetworkObject losses all observers or gains observers while previously having none.
|
||||
/// </summary>
|
||||
public event Action<NetworkObject> OnObserversActive;
|
||||
/// <summary>
|
||||
/// NetworkObserver on this object.
|
||||
/// </summary>
|
||||
[HideInInspector]
|
||||
public NetworkObserver NetworkObserver = null;
|
||||
/// <summary>
|
||||
/// Clients which can see and get messages from this NetworkObject.
|
||||
/// </summary>
|
||||
public HashSet<NetworkConnection> Observers = new HashSet<NetworkConnection>();
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// True if NetworkObserver has been initialized.
|
||||
/// </summary>
|
||||
private bool _networkObserverInitiliazed = false;
|
||||
/// <summary>
|
||||
/// Found renderers on the NetworkObject and it's children. This is only used as clientHost to hide non-observers objects.
|
||||
/// </summary>
|
||||
[System.NonSerialized]
|
||||
private Renderer[] _renderers;
|
||||
/// <summary>
|
||||
/// True if renderers have been looked up.
|
||||
/// </summary>
|
||||
private bool _renderersPopulated;
|
||||
/// <summary>
|
||||
/// Last visibility value for clientHost on this object.
|
||||
/// </summary>
|
||||
private bool _lastClientHostVisibility;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Updates cached renderers used to managing clientHost visibility.
|
||||
/// </summary>
|
||||
/// <param name="updateVisibility">True to also update visibility if clientHost.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void UpdateRenderers(bool updateVisibility = true)
|
||||
{
|
||||
UpdateRenderers_Internal(updateVisibility);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the renderer visibility for clientHost.
|
||||
/// </summary>
|
||||
/// <param name="visible">True if renderers are to be visibile.</param>
|
||||
/// <param name="force">True to skip blocking checks.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetRenderersVisible(bool visible, bool force = false)
|
||||
{
|
||||
if (!force)
|
||||
{
|
||||
if (!NetworkObserver.UpdateHostVisibility)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_renderersPopulated)
|
||||
{
|
||||
UpdateRenderers_Internal(false);
|
||||
_renderersPopulated = true;
|
||||
}
|
||||
|
||||
UpdateRenderVisibility(visible);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears and updates renderers.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void UpdateRenderers_Internal(bool updateVisibility)
|
||||
{
|
||||
_renderers = GetComponentsInChildren<Renderer>(true);
|
||||
List<Renderer> enabledRenderers = new List<Renderer>();
|
||||
foreach (Renderer r in _renderers)
|
||||
{
|
||||
if (r.enabled)
|
||||
enabledRenderers.Add(r);
|
||||
}
|
||||
//If there are any disabled renderers then change _renderers to cached values.
|
||||
if (enabledRenderers.Count != _renderers.Length)
|
||||
_renderers = enabledRenderers.ToArray();
|
||||
|
||||
if (updateVisibility)
|
||||
UpdateRenderVisibility(_lastClientHostVisibility);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates visibilites on renders without checks.
|
||||
/// </summary>
|
||||
/// <param name="visible"></param>
|
||||
private void UpdateRenderVisibility(bool visible)
|
||||
{
|
||||
bool rebuildRenderers = false;
|
||||
|
||||
Renderer[] rs = _renderers;
|
||||
int count = rs.Length;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Renderer r = rs[i];
|
||||
if (r == null)
|
||||
{
|
||||
rebuildRenderers = true;
|
||||
break;
|
||||
}
|
||||
|
||||
r.enabled = visible;
|
||||
}
|
||||
|
||||
_lastClientHostVisibility = visible;
|
||||
//If to rebuild then do so, while updating visibility.
|
||||
if (rebuildRenderers)
|
||||
UpdateRenderers(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the default NetworkObserver conditions using the ObserverManager.
|
||||
/// </summary>
|
||||
private void AddDefaultNetworkObserverConditions()
|
||||
{
|
||||
if (_networkObserverInitiliazed)
|
||||
return;
|
||||
|
||||
NetworkObserver = NetworkManager.ObserverManager.AddDefaultConditions(this);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Removes a connection from observers for this object returning if the connection was removed.
|
||||
/// </summary>
|
||||
/// <param name="connection"></param>
|
||||
internal bool RemoveObserver(NetworkConnection connection)
|
||||
{
|
||||
int startCount = Observers.Count;
|
||||
bool removed = Observers.Remove(connection);
|
||||
if (removed)
|
||||
TryInvokeOnObserversActive(startCount);
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the connection to observers if conditions are met.
|
||||
/// </summary>
|
||||
/// <param name="connection"></param>
|
||||
/// <returns>True if added to Observers.</returns>
|
||||
internal ObserverStateChange RebuildObservers(NetworkConnection connection, bool timedOnly)
|
||||
{
|
||||
//If not a valid connection.
|
||||
if (!connection.IsValid)
|
||||
{
|
||||
NetworkManager.LogWarning($"An invalid connection was used when rebuilding observers.");
|
||||
return ObserverStateChange.Unchanged;
|
||||
}
|
||||
//Valid not not active.
|
||||
else if (!connection.IsActive)
|
||||
{
|
||||
/* Just remove from observers since connection isn't active
|
||||
* and return unchanged because nothing should process
|
||||
* given the connection isnt active. */
|
||||
Observers.Remove(connection);
|
||||
return ObserverStateChange.Unchanged;
|
||||
}
|
||||
else if (IsDeinitializing)
|
||||
{
|
||||
/* If object is deinitializing it's either being despawned
|
||||
* this frame or it's not spawned. If we've made it this far,
|
||||
* it's most likely being despawned. */
|
||||
return ObserverStateChange.Unchanged;
|
||||
}
|
||||
|
||||
int startCount = Observers.Count;
|
||||
ObserverStateChange osc = NetworkObserver.RebuildObservers(connection, timedOnly);
|
||||
if (osc == ObserverStateChange.Added)
|
||||
Observers.Add(connection);
|
||||
else if (osc == ObserverStateChange.Removed)
|
||||
Observers.Remove(connection);
|
||||
|
||||
if (osc != ObserverStateChange.Unchanged)
|
||||
TryInvokeOnObserversActive(startCount);
|
||||
|
||||
return osc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes OnObserversActive if observers are now 0 but previously were not, or if was previously 0 but now has observers.
|
||||
/// </summary>
|
||||
/// <param name="startCount"></param>
|
||||
private void TryInvokeOnObserversActive(int startCount)
|
||||
{
|
||||
if ((Observers.Count > 0 && startCount == 0) ||
|
||||
Observers.Count == 0 && startCount > 0)
|
||||
OnObserversActive?.Invoke(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 816dbea70a70ab949a44f485155f0087
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
313
Assets/FishNet/Runtime/Object/NetworkObject.QOL.cs
Normal file
313
Assets/FishNet/Runtime/Object/NetworkObject.QOL.cs
Normal file
@ -0,0 +1,313 @@
|
||||
using FishNet.Component.ColliderRollback;
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Managing.Client;
|
||||
using FishNet.Managing.Observing;
|
||||
using FishNet.Managing.Predicting;
|
||||
using FishNet.Managing.Scened;
|
||||
using FishNet.Managing.Server;
|
||||
using FishNet.Managing.Timing;
|
||||
using FishNet.Managing.Transporting;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
public sealed partial class NetworkObject : MonoBehaviour
|
||||
{
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// True if predicted spawning is allowed for this object.
|
||||
/// </summary>
|
||||
internal bool AllowPredictedSpawning => (PredictedSpawn == null) ? false : PredictedSpawn.GetAllowSpawning();
|
||||
/// <summary>
|
||||
/// True if predicted spawning is allowed for this object.
|
||||
/// </summary>
|
||||
internal bool AllowPredictedDespawning => (PredictedSpawn == null) ? false : PredictedSpawn.GetAllowDespawning();
|
||||
/// <summary>
|
||||
/// True to allow clients to predicted set syncTypes prior to spawning the item. Set values will be applied on the server and sent to other clients.
|
||||
/// </summary>
|
||||
internal bool AllowPredictedSyncTypes => (PredictedSpawn == null) ? false : PredictedSpawn.GetAllowSyncTypes();
|
||||
/// <summary>
|
||||
/// True if this object has been initialized on the client side.
|
||||
/// This is set true right before client callbacks.
|
||||
/// </summary>
|
||||
public bool ClientInitialized { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
private bool _isClient;
|
||||
/// <summary>
|
||||
/// True if the client is active and authenticated.
|
||||
/// </summary>
|
||||
public bool IsClient
|
||||
{
|
||||
/* This needs to use a special check when
|
||||
* player is acting as host. Clients won't
|
||||
* set IsClient until they receive the spawn message
|
||||
* but the user may expect this true after client
|
||||
* gains observation but before client gets spawn. */
|
||||
get
|
||||
{
|
||||
if (IsServer)
|
||||
return (NetworkManager == null) ? false : NetworkManager.IsClient;
|
||||
else
|
||||
return _isClient;
|
||||
}
|
||||
|
||||
private set => _isClient = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if only the client is active and authenticated.
|
||||
/// </summary>
|
||||
public bool IsClientOnly => (IsClient && !IsServer);
|
||||
/// <summary>
|
||||
/// True if server is active.
|
||||
/// </summary>
|
||||
public bool IsServer { get; private set; }
|
||||
/// <summary>
|
||||
/// True if only the server is active.
|
||||
/// </summary>
|
||||
public bool IsServerOnly => (IsServer && !IsClient);
|
||||
/// <summary>
|
||||
/// True if client and server are active.
|
||||
/// </summary>
|
||||
public bool IsHost => (IsClient && IsServer);
|
||||
/// <summary>
|
||||
/// True if client nor server are active.
|
||||
/// </summary>
|
||||
public bool IsOffline => (!IsClient && !IsServer);
|
||||
/// <summary>
|
||||
/// True if the local client is the owner of this object.
|
||||
/// </summary>
|
||||
public bool IsOwner
|
||||
{
|
||||
get
|
||||
{
|
||||
/* ClientInitialized becomes true when this
|
||||
* NetworkObject has been initialized on the client side.
|
||||
*
|
||||
* This value is used to prevent IsOwner from returning true
|
||||
* when running as host; primarily in Update or Tick callbacks
|
||||
* where IsOwner would be true as host but OnStartClient has
|
||||
* not called yet.
|
||||
*
|
||||
* EG: server will set owner when it spawns the object.
|
||||
* If IsOwner is checked before the object spawns on the
|
||||
* client-host then it would also return true, since the
|
||||
* Owner reference would be the same as what was set by server.
|
||||
*
|
||||
* This is however bad when the client hasn't initialized the object
|
||||
* yet because it gives a false sense of execution order.
|
||||
* As a result, Update or Ticks may return IsOwner as true well before OnStartClient
|
||||
* is called. Many users rightfully create code with the assumption the client has been
|
||||
* initialized by the time IsOwner is true.
|
||||
*
|
||||
* This is a double edged sword though because now IsOwner would return true
|
||||
* within OnStartNetwork for clients only, but not for host given the client
|
||||
* side won't be initialized yet as host. As a work around CodeAnalysis will
|
||||
* inform users to instead use base.Owner.IsLocalClient within OnStartNetwork. */
|
||||
if (!ClientInitialized)
|
||||
return false;
|
||||
|
||||
return Owner.IsLocalClient;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
private NetworkConnection _owner;
|
||||
/// <summary>
|
||||
/// Owner of this object.
|
||||
/// </summary>
|
||||
public NetworkConnection Owner
|
||||
{
|
||||
get
|
||||
{
|
||||
//Ensures a null Owner is never returned.
|
||||
if (_owner == null)
|
||||
return FishNet.Managing.NetworkManager.EmptyConnection;
|
||||
|
||||
return _owner;
|
||||
}
|
||||
private set { _owner = value; }
|
||||
}
|
||||
/// <summary>
|
||||
/// ClientId for this NetworkObject owner.
|
||||
/// </summary>
|
||||
public int OwnerId => (!Owner.IsValid) ? -1 : Owner.ClientId;
|
||||
/// <summary>
|
||||
/// True if the object is initialized for the network.
|
||||
/// </summary>
|
||||
public bool IsSpawned => (!IsDeinitializing && ObjectId != NetworkObject.UNSET_OBJECTID_VALUE);
|
||||
/// <summary>
|
||||
/// The local connection of the client calling this method.
|
||||
/// </summary>
|
||||
public NetworkConnection LocalConnection => (NetworkManager == null) ? new NetworkConnection() : NetworkManager.ClientManager.Connection;
|
||||
/// <summary>
|
||||
/// NetworkManager for this object.
|
||||
/// </summary>
|
||||
public NetworkManager NetworkManager { get; private set; }
|
||||
/// <summary>
|
||||
/// ServerManager for this object.
|
||||
/// </summary>
|
||||
public ServerManager ServerManager { get; private set; }
|
||||
/// <summary>
|
||||
/// ClientManager for this object.
|
||||
/// </summary>
|
||||
public ClientManager ClientManager { get; private set; }
|
||||
/// <summary>
|
||||
/// ObserverManager for this object.
|
||||
/// </summary>
|
||||
public ObserverManager ObserverManager { get; private set; }
|
||||
/// <summary>
|
||||
/// TransportManager for this object.
|
||||
/// </summary>
|
||||
public TransportManager TransportManager { get; private set; }
|
||||
/// <summary>
|
||||
/// TimeManager for this object.
|
||||
/// </summary>
|
||||
public TimeManager TimeManager { get; private set; }
|
||||
/// <summary>
|
||||
/// SceneManager for this object.
|
||||
/// </summary>
|
||||
public SceneManager SceneManager { get; private set; }
|
||||
/// <summary>
|
||||
/// PredictionManager for this object.
|
||||
/// </summary>
|
||||
public PredictionManager PredictionManager {get;private set;}
|
||||
/// <summary>
|
||||
/// RollbackManager for this object.
|
||||
/// </summary>
|
||||
public RollbackManager RollbackManager { get; private set; }
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Returns a NetworkBehaviour on this NetworkObject.
|
||||
/// </summary>
|
||||
/// <param name="componentIndex">ComponentIndex of the NetworkBehaviour.</param>
|
||||
/// <param name="error">True to error if not found.</param>
|
||||
/// <returns></returns>
|
||||
public NetworkBehaviour GetNetworkBehaviour(byte componentIndex, bool error)
|
||||
{
|
||||
if (componentIndex >= NetworkBehaviours.Length)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
string message = $"ComponentIndex of {componentIndex} is out of bounds on {gameObject.name} [id {ObjectId}]. This may occur if you have modified your gameObject/prefab without saving it, or the scene.";
|
||||
if (NetworkManager == null)
|
||||
NetworkManager.StaticLogError(message);
|
||||
else
|
||||
NetworkManager.LogError(message);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return NetworkBehaviours[componentIndex];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns a GameObject. Only call from the server.
|
||||
/// </summary>
|
||||
/// <param name="go">GameObject to despawn.</param>
|
||||
/// <param name="despawnType">What happens to the object after being despawned.</param>
|
||||
public void Despawn(GameObject go, DespawnType? despawnType = null)
|
||||
{
|
||||
NetworkManager?.ServerManager.Despawn(go, despawnType);
|
||||
}
|
||||
/// <summary>
|
||||
/// Despawns a NetworkObject. Only call from the server.
|
||||
/// </summary>
|
||||
/// <param name="nob">NetworkObject to despawn.</param>
|
||||
/// <param name="despawnType">What happens to the object after being despawned.</param>
|
||||
public void Despawn(NetworkObject nob, DespawnType? despawnType = null)
|
||||
{
|
||||
NetworkManager?.ServerManager.Despawn(nob, despawnType);
|
||||
}
|
||||
/// <summary>
|
||||
/// Despawns this NetworkObject. Only call from the server.
|
||||
/// </summary>
|
||||
/// <param name="despawnType">What happens to the object after being despawned.</param>
|
||||
public void Despawn(DespawnType? despawnType = null)
|
||||
{
|
||||
NetworkObject nob = this;
|
||||
NetworkManager?.ServerManager.Despawn(nob, despawnType);
|
||||
}
|
||||
/// <summary>
|
||||
/// Spawns an object over the network. Only call from the server.
|
||||
/// </summary>
|
||||
public void Spawn(GameObject go, NetworkConnection ownerConnection = null)
|
||||
{
|
||||
NetworkManager?.ServerManager.Spawn(go, ownerConnection);
|
||||
}
|
||||
/// <summary>
|
||||
/// Spawns an object over the network. Only call from the server.
|
||||
/// </summary>
|
||||
public void Spawn(NetworkObject nob, NetworkConnection ownerConnection = null)
|
||||
{
|
||||
NetworkManager?.ServerManager.Spawn(nob, ownerConnection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes ownership of this object and child network objects, allowing immediate control.
|
||||
/// </summary>
|
||||
/// <param name="caller">Connection to give ownership to.</param>
|
||||
public void SetLocalOwnership(NetworkConnection caller)
|
||||
{
|
||||
NetworkConnection prevOwner = Owner;
|
||||
SetOwner(caller);
|
||||
|
||||
int count;
|
||||
count = NetworkBehaviours.Length;
|
||||
for (int i = 0; i < count; i++)
|
||||
NetworkBehaviours[i].OnOwnershipClient(prevOwner);
|
||||
count = ChildNetworkObjects.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
ChildNetworkObjects[i].SetLocalOwnership(caller);
|
||||
}
|
||||
|
||||
#region Registered components
|
||||
/// <summary>
|
||||
/// Invokes an action when a specified component becomes registered. Action will invoke immediately if already registered.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Component type.</typeparam>
|
||||
/// <param name="handler">Action to invoke.</param>
|
||||
public void RegisterInvokeOnInstance<T>(Action<UnityEngine.Component> handler) where T : UnityEngine.Component => NetworkManager.RegisterInvokeOnInstance<T>(handler);
|
||||
/// <summary>
|
||||
/// Removes an action to be invoked when a specified component becomes registered.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Component type.</typeparam>
|
||||
/// <param name="handler">Action to invoke.</param>
|
||||
public void UnregisterInvokeOnInstance<T>(Action<UnityEngine.Component> handler) where T : UnityEngine.Component => NetworkManager.UnregisterInvokeOnInstance<T>(handler);
|
||||
/// <summary>
|
||||
/// Returns if an instance exists for type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public bool HasInstance<T>() where T : UnityEngine.Component => NetworkManager.HasInstance<T>();
|
||||
/// <summary>
|
||||
/// Returns class of type if found within CodegenBase classes.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
public T GetInstance<T>() where T : UnityEngine.Component => NetworkManager.GetInstance<T>();
|
||||
/// <summary>
|
||||
/// Registers a new component to this NetworkManager.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to register.</typeparam>
|
||||
/// <param name="component">Reference of the component being registered.</param>
|
||||
/// <param name="replace">True to replace existing references.</param>
|
||||
public void RegisterInstance<T>(T component, bool replace = true) where T : UnityEngine.Component => NetworkManager.RegisterInstance<T>(component, replace);
|
||||
/// <summary>
|
||||
/// Unregisters a component from this NetworkManager.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to unregister.</typeparam>
|
||||
public void UnregisterInstance<T>() where T : UnityEngine.Component => NetworkManager.UnregisterInstance<T>();
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
11
Assets/FishNet/Runtime/Object/NetworkObject.QOL.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/NetworkObject.QOL.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd30b4b61d50d01499c94a63a6eeb863
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
213
Assets/FishNet/Runtime/Object/NetworkObject.ReferenceIds.cs
Normal file
213
Assets/FishNet/Runtime/Object/NetworkObject.ReferenceIds.cs
Normal file
@ -0,0 +1,213 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using FishNet.Object.Helping;
|
||||
using System.Linq;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor.Experimental.SceneManagement;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
public sealed partial class NetworkObject : MonoBehaviour
|
||||
{
|
||||
#region Serialized.
|
||||
|
||||
/// <summary>
|
||||
/// Networked PrefabId assigned to this Prefab.
|
||||
/// </summary>
|
||||
[field: SerializeField, HideInInspector]
|
||||
public ushort PrefabId { get; internal set; } = 0;
|
||||
/// <summary>
|
||||
/// Spawn collection to use assigned to this Prefab.
|
||||
/// </summary>
|
||||
[field: SerializeField, HideInInspector]
|
||||
public ushort SpawnableCollectionId { get; internal set; } = 0;
|
||||
#pragma warning disable 414 //Disabled because Unity thinks tihs is unused when building.
|
||||
/// <summary>
|
||||
/// Hash to the scene which this object resides.
|
||||
/// </summary>
|
||||
[SerializeField, HideInInspector]
|
||||
private uint _scenePathHash;
|
||||
#pragma warning restore 414
|
||||
/// <summary>
|
||||
/// Network Id for this scene object.
|
||||
/// </summary>
|
||||
[field: SerializeField, HideInInspector]
|
||||
internal ulong SceneId { get; private set; }
|
||||
/// <summary>
|
||||
/// Hash for the path which this asset resides. This value is set during edit time.
|
||||
/// </summary>
|
||||
[field: SerializeField, HideInInspector]
|
||||
public ulong AssetPathHash { get; private set; }
|
||||
/// <summary>
|
||||
/// Sets AssetPathhash value.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to use.</param>
|
||||
public void SetAssetPathHash(ulong value) => AssetPathHash = value;
|
||||
#endregion
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// This is used to store NetworkObjects in the scene during edit time.
|
||||
/// SceneIds are compared against this collection to ensure there are no duplicated.
|
||||
/// </summary>
|
||||
[SerializeField, HideInInspector]
|
||||
private List<NetworkObject> _sceneNetworkObjects = new List<NetworkObject>();
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Removes SceneObject state.
|
||||
/// This may only be called at runtime.
|
||||
/// </summary>
|
||||
internal void ClearRuntimeSceneObject()
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
Debug.LogError($"ClearRuntimeSceneObject may only be called at runtime.");
|
||||
return;
|
||||
}
|
||||
|
||||
SceneId = 0;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// Tries to generate a SceneId.
|
||||
/// </summary>
|
||||
internal void TryCreateSceneID()
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
return;
|
||||
//Unity bug, sometimes this can be null depending on editor callback orders.
|
||||
if (gameObject == null)
|
||||
return;
|
||||
//Not a scene object.
|
||||
if (string.IsNullOrEmpty(gameObject.scene.name))
|
||||
{
|
||||
SceneId = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
ulong startId = SceneId;
|
||||
uint startPath = _scenePathHash;
|
||||
|
||||
ulong sceneId = 0;
|
||||
uint scenePathHash = 0;
|
||||
//If prefab or part of a prefab, not a scene object.
|
||||
if (PrefabUtility.IsPartOfPrefabAsset(this) || IsEditingInPrefabMode() ||
|
||||
//Not in a scene, another prefab check.
|
||||
!gameObject.scene.IsValid() ||
|
||||
//Stored on disk, so is a prefab. Somehow prefabutility missed it.
|
||||
EditorUtility.IsPersistent(this))
|
||||
{
|
||||
//These are all failing conditions, don't do additional checks.
|
||||
}
|
||||
else
|
||||
{
|
||||
System.Random rnd = new System.Random();
|
||||
scenePathHash = gameObject.scene.path.ToLower().GetStableHash32();
|
||||
sceneId = SceneId;
|
||||
//Not a valid sceneId or is a duplicate.
|
||||
if (scenePathHash != _scenePathHash || SceneId == 0 || IsDuplicateSceneId(SceneId))
|
||||
{
|
||||
/* If a scene has not been opened since an id has been
|
||||
* generated then it will not be serialized in editor. The id
|
||||
* would be correct in build but not if running in editor.
|
||||
* Should conditions be true where scene is building without
|
||||
* being opened then cancel build and request user to open and save
|
||||
* scene. */
|
||||
if (BuildPipeline.isBuildingPlayer)
|
||||
throw new InvalidOperationException($"Networked GameObject {gameObject.name} in scene {gameObject.scene.path} is missing a SceneId. Open the scene, select the Fish-Networking menu, and choose Rebuild SceneIds. If the problem persist ensures {gameObject.name} does not have any missing script references on it's prefab or in the scene. Also ensure that you have any prefab changes for the object applied.");
|
||||
|
||||
ulong shiftedHash = (ulong)scenePathHash << 32;
|
||||
ulong randomId = 0;
|
||||
while (randomId == 0 || IsDuplicateSceneId(randomId))
|
||||
{
|
||||
uint next = (uint)(rnd.Next(int.MinValue, int.MaxValue) + int.MaxValue);
|
||||
/* Since the collection is lost when a scene loads the it's possible to
|
||||
* have a sceneid from another scene. Because of this the scene path is
|
||||
* inserted into the sceneid. */
|
||||
randomId = (next & 0xFFFFFFFF) | shiftedHash;
|
||||
}
|
||||
|
||||
sceneId = randomId;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool idChanged = (sceneId != startId);
|
||||
bool pathChanged = (startPath != scenePathHash);
|
||||
//If either changed then dirty and set.
|
||||
if (idChanged || pathChanged)
|
||||
{
|
||||
//Set dirty so changes will be saved.
|
||||
EditorUtility.SetDirty(this);
|
||||
/* Add to sceneIds collection. This must be done
|
||||
* even if a new sceneId was not generated because
|
||||
* the collection information is lost when the
|
||||
* scene is existed. Essentially, it gets repopulated
|
||||
* when the scene is re-opened. */
|
||||
SceneId = sceneId;
|
||||
_scenePathHash = scenePathHash;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsEditingInPrefabMode()
|
||||
{
|
||||
if (EditorUtility.IsPersistent(this))
|
||||
{
|
||||
// if the game object is stored on disk, it is a prefab of some kind, despite not returning true for IsPartOfPrefabAsset =/
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the GameObject is not persistent let's determine which stage we are in first because getting Prefab info depends on it
|
||||
StageHandle mainStage = StageUtility.GetMainStageHandle();
|
||||
StageHandle currentStage = StageUtility.GetStageHandle(gameObject);
|
||||
if (currentStage != mainStage)
|
||||
{
|
||||
var prefabStage = PrefabStageUtility.GetPrefabStage(gameObject);
|
||||
if (prefabStage != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if the Id used is a sceneId already belonging to another object.
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
private bool IsDuplicateSceneId(ulong id)
|
||||
{
|
||||
//Find all nobs in scene.
|
||||
_sceneNetworkObjects = GameObject.FindObjectsOfType<NetworkObject>().ToList();
|
||||
foreach (NetworkObject nob in _sceneNetworkObjects)
|
||||
{
|
||||
if (nob != null && nob != this && nob.SceneId == id)
|
||||
return true;
|
||||
}
|
||||
//If here all checks pass.
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ReferenceIds_OnValidate()
|
||||
{
|
||||
TryCreateSceneID();
|
||||
}
|
||||
private void ReferenceIds_Reset()
|
||||
{
|
||||
TryCreateSceneID();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: be0a4b0a32b02f64495ba3b1d22f89c4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
34
Assets/FishNet/Runtime/Object/NetworkObject.RpcLinks.cs
Normal file
34
Assets/FishNet/Runtime/Object/NetworkObject.RpcLinks.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
public sealed partial class NetworkObject : MonoBehaviour
|
||||
{
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// RpcLinks being used within this NetworkObject.
|
||||
/// </summary>
|
||||
private List<ushort> _rpcLinkIndexes;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Sets rpcLinkIndexes to values.
|
||||
/// </summary>
|
||||
internal void SetRpcLinkIndexes(List<ushort> values)
|
||||
{
|
||||
_rpcLinkIndexes = values;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes used link indexes from ClientObjects.
|
||||
/// </summary>
|
||||
internal void RemoveClientRpcLinkIndexes()
|
||||
{
|
||||
NetworkManager.ClientManager.Objects.RemoveLinkIndexes(_rpcLinkIndexes);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
11
Assets/FishNet/Runtime/Object/NetworkObject.RpcLinks.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/NetworkObject.RpcLinks.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b2f6927cf3ef254d91b89e5f99a92b9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
25
Assets/FishNet/Runtime/Object/NetworkObject.SyncTypes.cs
Normal file
25
Assets/FishNet/Runtime/Object/NetworkObject.SyncTypes.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
public sealed partial class NetworkObject : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Writers dirty SyncTypes for all Networkbehaviours if their write tick has been met.
|
||||
/// </summary>
|
||||
internal void WriteDirtySyncTypes()
|
||||
{
|
||||
NetworkBehaviour[] nbs = NetworkBehaviours;
|
||||
int count = nbs.Length;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
//There was a null check here before, shouldn't be needed so it was removed.
|
||||
NetworkBehaviour nb = nbs[i];
|
||||
nb.WriteDirtySyncTypes(true, true);
|
||||
nb.WriteDirtySyncTypes(false, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ffb07b7ea3c5b4419dc5f9941b2d204
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
862
Assets/FishNet/Runtime/Object/NetworkObject.cs
Normal file
862
Assets/FishNet/Runtime/Object/NetworkObject.cs
Normal file
@ -0,0 +1,862 @@
|
||||
using FishNet.Managing;
|
||||
using FishNet.Connection;
|
||||
using UnityEngine;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using FishNet.Utility.Performance;
|
||||
using System;
|
||||
using FishNet.Managing.Object;
|
||||
using FishNet.Component.Ownership;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public sealed partial class NetworkObject : MonoBehaviour
|
||||
{
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// True if was nested during scene setup or within a prefab.
|
||||
/// </summary>
|
||||
[field: SerializeField, HideInInspector]
|
||||
public bool IsNested { get; private set; }
|
||||
/// <summary>
|
||||
/// NetworkConnection which predicted spawned this object.
|
||||
/// </summary>
|
||||
public NetworkConnection PredictedSpawner { get; private set; } = NetworkManager.EmptyConnection;
|
||||
/// <summary>
|
||||
/// True if this NetworkObject was active during edit. Will be true if placed in scene during edit, and was in active state on run.
|
||||
/// </summary>
|
||||
[System.NonSerialized]
|
||||
internal bool ActiveDuringEdit;
|
||||
/// <summary>
|
||||
/// Returns if this object was placed in the scene during edit-time.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsSceneObject => (SceneId > 0);
|
||||
/// <summary>
|
||||
/// ComponentIndex for this NetworkBehaviour.
|
||||
/// </summary>
|
||||
[field: SerializeField, HideInInspector]
|
||||
public byte ComponentIndex { get; private set; }
|
||||
/// <summary>
|
||||
/// Unique Id for this NetworkObject. This does not represent the object owner.
|
||||
/// </summary>
|
||||
public int ObjectId { get; private set; }
|
||||
/// <summary>
|
||||
/// True if this NetworkObject is deinitializing. Will also be true until Initialize is called. May be false until the object is cleaned up if object is destroyed without using Despawn.
|
||||
/// </summary>
|
||||
internal bool IsDeinitializing { get; private set; } = true;
|
||||
/// <summary>
|
||||
/// PredictedSpawn component on this object. Will be null if not added manually.
|
||||
/// </summary>
|
||||
[field: SerializeField, HideInInspector]
|
||||
public PredictedSpawn PredictedSpawn { get; private set; }
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[field: SerializeField, HideInInspector]
|
||||
private NetworkBehaviour[] _networkBehaviours;
|
||||
/// <summary>
|
||||
/// NetworkBehaviours within the root and children of this object.
|
||||
/// </summary>
|
||||
public NetworkBehaviour[] NetworkBehaviours
|
||||
{
|
||||
get => _networkBehaviours;
|
||||
private set => _networkBehaviours = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// NetworkObject parenting this instance. The parent NetworkObject will be null if there was no parent during serialization.
|
||||
/// </summary>
|
||||
[field: SerializeField, HideInInspector]
|
||||
public NetworkObject ParentNetworkObject { get; private set; }
|
||||
/// <summary>
|
||||
/// NetworkObjects nested beneath this one. Recursive NetworkObjects may exist within each entry of this field.
|
||||
/// </summary>
|
||||
[field: SerializeField, HideInInspector]
|
||||
public List<NetworkObject> ChildNetworkObjects { get; private set; } = new List<NetworkObject>();
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[SerializeField, HideInInspector]
|
||||
internal TransformProperties SerializedTransformProperties = new TransformProperties();
|
||||
/// <summary>
|
||||
/// Current state of the NetworkObject.
|
||||
/// </summary>
|
||||
[System.NonSerialized]
|
||||
internal NetworkObjectState State = NetworkObjectState.Unset;
|
||||
#endregion
|
||||
|
||||
#region Serialized.
|
||||
/// <summary>
|
||||
/// True if the object will always initialize as a networked object. When false the object will not automatically initialize over the network. Using Spawn() on an object will always set that instance as networked.
|
||||
/// </summary>
|
||||
public bool IsNetworked
|
||||
{
|
||||
get => _isNetworked;
|
||||
private set => _isNetworked = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets IsNetworked value. This method must be called before Start.
|
||||
/// </summary>
|
||||
/// <param name="value">New IsNetworked value.</param>
|
||||
public void SetIsNetworked(bool value)
|
||||
{
|
||||
IsNetworked = value;
|
||||
}
|
||||
[Tooltip("True if the object will always initialize as a networked object. When false the object will not automatically initialize over the network. Using Spawn() on an object will always set that instance as networked.")]
|
||||
[SerializeField]
|
||||
private bool _isNetworked = true;
|
||||
/// <summary>
|
||||
/// True to make this object global, and added to the DontDestroyOnLoad scene. This value may only be set for instantiated objects, and can be changed if done immediately after instantiating.
|
||||
/// </summary>
|
||||
public bool IsGlobal
|
||||
{
|
||||
get => _isGlobal;
|
||||
private set => _isGlobal = value;
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets IsGlobal value.
|
||||
/// </summary>
|
||||
/// <param name="value">New global value.</param>
|
||||
public void SetIsGlobal(bool value)
|
||||
{
|
||||
if (IsNested)
|
||||
{
|
||||
NetworkManager.StaticLogWarning($"Object {gameObject.name} cannot change IsGlobal because it is nested. Only root objects may be set global.");
|
||||
return;
|
||||
}
|
||||
if (!IsDeinitializing)
|
||||
{
|
||||
NetworkManager.StaticLogWarning($"Object {gameObject.name} cannot change IsGlobal as it's already initialized. IsGlobal may only be changed immediately after instantiating.");
|
||||
return;
|
||||
}
|
||||
if (IsSceneObject)
|
||||
{
|
||||
NetworkManager.StaticLogWarning($"Object {gameObject.name} cannot have be global because it is a scene object. Only instantiated objects may be global.");
|
||||
return;
|
||||
}
|
||||
|
||||
_networkObserverInitiliazed = false;
|
||||
IsGlobal = value;
|
||||
}
|
||||
[Tooltip("True to make this object global, and added to the DontDestroyOnLoad scene. This value may only be set for instantiated objects, and can be changed if done immediately after instantiating.")]
|
||||
[SerializeField]
|
||||
private bool _isGlobal;
|
||||
/// <summary>
|
||||
/// Order to initialize this object's callbacks when spawned with other NetworkObjects in the same tick. Default value is 0, negative values will execute callbacks first.
|
||||
/// </summary>
|
||||
public sbyte GetInitializeOrder() => _initializeOrder;
|
||||
[Tooltip("Order to initialize this object's callbacks when spawned with other NetworkObjects in the same tick. Default value is 0, negative values will execute callbacks first.")]
|
||||
[SerializeField]
|
||||
private sbyte _initializeOrder = 0;
|
||||
/// <summary>
|
||||
/// How to handle this object when it despawns. Scene objects are never destroyed when despawning.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
[Tooltip("How to handle this object when it despawns. Scene objects are never destroyed when despawning.")]
|
||||
private DespawnType _defaultDespawnType = DespawnType.Destroy;
|
||||
/// <summary>
|
||||
/// True to use configured ObjectPool rather than destroy this NetworkObject when being despawned. Scene objects are never destroyed.
|
||||
/// </summary>
|
||||
public DespawnType GetDefaultDespawnType() => _defaultDespawnType;
|
||||
/// <summary>
|
||||
/// Sets DespawnType value.
|
||||
/// </summary>
|
||||
/// <param name="despawnType">Default despawn type for this NetworkObject.</param>
|
||||
public void SetDefaultDespawnType(DespawnType despawnType)
|
||||
{
|
||||
_defaultDespawnType = despawnType;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// True if disabled NetworkBehaviours have been initialized.
|
||||
/// </summary>
|
||||
private bool _disabledNetworkBehavioursInitialized;
|
||||
#endregion
|
||||
|
||||
#region Const.
|
||||
/// <summary>
|
||||
/// Value used when the ObjectId has not been set.
|
||||
/// </summary>
|
||||
public const int UNSET_OBJECTID_VALUE = ushort.MaxValue;
|
||||
/// <summary>
|
||||
/// Value used when the PrefabId has not been set.
|
||||
/// </summary>
|
||||
public const int UNSET_PREFABID_VALUE = ushort.MaxValue;
|
||||
#endregion
|
||||
|
||||
#region Editor Debug.
|
||||
#if UNITY_EDITOR
|
||||
private int _editorOwnerId;
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
SetChildDespawnedState();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
TryStartDeactivation();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes NetworkBehaviours if they are disabled.
|
||||
/// </summary>
|
||||
private void InitializeNetworkBehavioursIfDisabled()
|
||||
{
|
||||
if (_disabledNetworkBehavioursInitialized)
|
||||
return;
|
||||
_disabledNetworkBehavioursInitialized = true;
|
||||
|
||||
for (int i = 0; i < NetworkBehaviours.Length; i++)
|
||||
NetworkBehaviours[i].InitializeIfDisabled();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets Despawned on child NetworkObjects if they are not enabled.
|
||||
/// </summary>
|
||||
private void SetChildDespawnedState()
|
||||
{
|
||||
NetworkObject nob;
|
||||
for (int i = 0; i < ChildNetworkObjects.Count; i++)
|
||||
{
|
||||
nob = ChildNetworkObjects[i];
|
||||
if (!nob.gameObject.activeSelf)
|
||||
nob.State = NetworkObjectState.Despawned;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivates this NetworkObject during it's start cycle if conditions are met.
|
||||
/// </summary>
|
||||
internal void TryStartDeactivation()
|
||||
{
|
||||
if (!IsNetworked)
|
||||
return;
|
||||
|
||||
//Global.
|
||||
if (IsGlobal && !IsSceneObject)
|
||||
DontDestroyOnLoad(gameObject);
|
||||
|
||||
if (NetworkManager == null || (!NetworkManager.IsClient && !NetworkManager.IsServer))
|
||||
{
|
||||
//ActiveDuringEdit is only used for scene objects.
|
||||
if (IsSceneObject)
|
||||
ActiveDuringEdit = true;
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
/* If deinitializing and an owner exist
|
||||
* then remove object from owner. */
|
||||
if (IsDeinitializing && Owner.IsValid)
|
||||
Owner.RemoveObject(this);
|
||||
/* If not nested then check to despawn this OnDisable.
|
||||
* A nob may become disabled without being despawned if it's
|
||||
* beneath another deinitializing nob. This can be true even while
|
||||
* not nested because users may move a nob under another at runtime.
|
||||
*
|
||||
* This object must also be activeSelf, meaning that it became disabled
|
||||
* because a parent was. If not activeSelf then it's possible the
|
||||
* user simply deactivated the object themselves. */
|
||||
else if (IsServer && !IsNested && gameObject.activeSelf)
|
||||
{
|
||||
bool canDespawn = false;
|
||||
Transform nextParent = transform.parent;
|
||||
while (nextParent != null)
|
||||
{
|
||||
if (nextParent.TryGetComponent(out NetworkObject pNob))
|
||||
{
|
||||
/* If pNob is not the same as ParentNetworkObject
|
||||
* then that means this object was moved around. It could be
|
||||
* that this was previously a child of something else
|
||||
* or that was given a parent later on in it's life cycle.
|
||||
^
|
||||
* When this occurs do not send a despawn for this object.
|
||||
* Rather, let it destroy from unity callbacks which will force
|
||||
* the proper destroy/stop cycle. */
|
||||
if (pNob != ParentNetworkObject)
|
||||
break;
|
||||
//If nob is deinitialized then this one cannot exist.
|
||||
if (pNob.IsDeinitializing)
|
||||
{
|
||||
canDespawn = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
nextParent = nextParent.parent;
|
||||
}
|
||||
|
||||
if (canDespawn)
|
||||
Despawn();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
//Does this need to be here? I'm thinking no, remove it and examine later. //todo
|
||||
if (Owner.IsValid)
|
||||
Owner.RemoveObject(this);
|
||||
//Already being deinitialized by FishNet.
|
||||
if (IsDeinitializing)
|
||||
return;
|
||||
|
||||
if (NetworkManager != null)
|
||||
{
|
||||
//Was destroyed without going through the proper methods.
|
||||
if (NetworkManager.IsServer)
|
||||
NetworkManager.ServerManager.Objects.NetworkObjectUnexpectedlyDestroyed(this, true);
|
||||
if (NetworkManager.IsClient)
|
||||
NetworkManager.ClientManager.Objects.NetworkObjectUnexpectedlyDestroyed(this, false);
|
||||
}
|
||||
|
||||
/* When destroyed unexpectedly it's
|
||||
* impossible to know if this occurred on
|
||||
* the server or client side, so send callbacks
|
||||
* for both. */
|
||||
if (IsServer)
|
||||
InvokeStopCallbacks(true);
|
||||
if (IsClient)
|
||||
InvokeStopCallbacks(false);
|
||||
|
||||
/* If owner exist then remove object from owner.
|
||||
* This has to be called here as well OnDisable because
|
||||
* the OnDisable will only remove the object if
|
||||
* deinitializing. This is because the object shouldn't
|
||||
* be removed from owner if the object is simply being
|
||||
* disabled, but not deinitialized. But in the scenario
|
||||
* the object is unexpectedly destroyed, which is how we
|
||||
* arrive here, the object needs to be removed from owner. */
|
||||
if (Owner.IsValid)
|
||||
Owner.RemoveObject(this);
|
||||
|
||||
Observers.Clear();
|
||||
IsDeinitializing = true;
|
||||
|
||||
SetActiveStatus(false);
|
||||
//Do not need to set state if being destroyed.
|
||||
//Don't need to reset sync types if object is being destroyed.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets IsClient or IsServer to isActive.
|
||||
/// </summary>
|
||||
private void SetActiveStatus(bool isActive, bool server)
|
||||
{
|
||||
if (server)
|
||||
IsServer = isActive;
|
||||
else
|
||||
IsClient = isActive;
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets IsClient and IsServer to isActive.
|
||||
/// </summary>
|
||||
private void SetActiveStatus(bool isActive)
|
||||
{
|
||||
IsServer = isActive;
|
||||
IsClient = isActive;
|
||||
}
|
||||
/// <summary>
|
||||
/// Initializes this script. This is only called once even when as host.
|
||||
/// </summary>
|
||||
/// <param name="networkManager"></param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void Preinitialize_Internal(NetworkManager networkManager, int objectId, NetworkConnection owner, bool asServer)
|
||||
{
|
||||
State = NetworkObjectState.Spawned;
|
||||
InitializeNetworkBehavioursIfDisabled();
|
||||
IsDeinitializing = false;
|
||||
//QOL references.
|
||||
NetworkManager = networkManager;
|
||||
ServerManager = networkManager.ServerManager;
|
||||
ClientManager = networkManager.ClientManager;
|
||||
ObserverManager = networkManager.ObserverManager;
|
||||
TransportManager = networkManager.TransportManager;
|
||||
TimeManager = networkManager.TimeManager;
|
||||
SceneManager = networkManager.SceneManager;
|
||||
PredictionManager = networkManager.PredictionManager;
|
||||
RollbackManager = networkManager.RollbackManager;
|
||||
|
||||
SetOwner(owner);
|
||||
ObjectId = objectId;
|
||||
|
||||
/* This must be called at the beginning
|
||||
* so that all conditions are handled by the observer
|
||||
* manager prior to the preinitialize call on networkobserver.
|
||||
* The method called is dependent on NetworkManager being set. */
|
||||
AddDefaultNetworkObserverConditions();
|
||||
|
||||
for (int i = 0; i < NetworkBehaviours.Length; i++)
|
||||
NetworkBehaviours[i].InitializeOnce_Internal();
|
||||
|
||||
/* NetworkObserver uses some information from
|
||||
* NetworkBehaviour so it must be preinitialized
|
||||
* after NetworkBehaviours are. */
|
||||
if (asServer)
|
||||
NetworkObserver.PreInitialize(this);
|
||||
_networkObserverInitiliazed = true;
|
||||
|
||||
//Add to connection objects if owner exist.
|
||||
if (owner != null)
|
||||
owner.AddObject(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a NetworkBehaviour and serializes it's components.
|
||||
/// </summary>
|
||||
internal T AddAndSerialize<T>() where T : NetworkBehaviour //runtimeNB make public.
|
||||
{
|
||||
int startingLength = NetworkBehaviours.Length;
|
||||
T result = gameObject.AddComponent<T>();
|
||||
//Add to network behaviours.
|
||||
Array.Resize(ref _networkBehaviours, startingLength + 1);
|
||||
_networkBehaviours[startingLength] = result;
|
||||
//Serialize values and return.
|
||||
result.SerializeComponents(this, (byte)startingLength);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates NetworkBehaviours and initializes them with serialized values.
|
||||
/// </summary>
|
||||
/// <param name="fromPrefabCollection">True if this call originated from a prefab collection, such as during it's initialization.</param>
|
||||
internal void UpdateNetworkBehaviours(NetworkObject parentNob, ref byte componentIndex) //runtimeNB make public.
|
||||
{
|
||||
/* This method can be called by the developer initializing prefabs, the prefab collection doing it automatically,
|
||||
* or when the networkobject is modified or added to an object.
|
||||
*
|
||||
* Prefab collections generally contain all prefabs, meaning they will not only call this on the topmost
|
||||
* networkobject but also each child, as the child would be it's own prefab in the collection. This assumes
|
||||
* that is, the child is a nested prefab.
|
||||
*
|
||||
* Because of this potential a check must be done where if the componentIndex is 0 we must look
|
||||
* for a networkobject above this one. If there is a networkObject above this one then we know the prefab
|
||||
* is being initialized individually, not part of a recursive check. In this case exit early
|
||||
* as the parent would have already resolved the needed information. */
|
||||
|
||||
//If first componentIndex make sure there's no more than maximum allowed nested nobs.
|
||||
if (componentIndex == 0)
|
||||
{
|
||||
//Not possible for index to be 0 and nested.
|
||||
if (IsNested)
|
||||
return;
|
||||
byte maxNobs = 255;
|
||||
if (GetComponentsInChildren<NetworkObject>(true).Length > maxNobs)
|
||||
{
|
||||
Debug.LogError($"The number of child NetworkObjects on {gameObject.name} exceeds the maximum of {maxNobs}.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
PredictedSpawn = GetComponent<PredictedSpawn>();
|
||||
ComponentIndex = componentIndex;
|
||||
ParentNetworkObject = parentNob;
|
||||
|
||||
//Transforms which can be searched for networkbehaviours.
|
||||
ListCache<Transform> transformCache = ListCaches.GetTransformCache();
|
||||
transformCache.Reset();
|
||||
ChildNetworkObjects.Clear();
|
||||
|
||||
transformCache.AddValue(transform);
|
||||
for (int z = 0; z < transformCache.Written; z++)
|
||||
{
|
||||
Transform currentT = transformCache.Collection[z];
|
||||
for (int i = 0; i < currentT.childCount; i++)
|
||||
{
|
||||
Transform t = currentT.GetChild(i);
|
||||
/* If contains a nob then do not add to transformsCache.
|
||||
* Do add to ChildNetworkObjects so it can be initialized when
|
||||
* parent is. */
|
||||
if (t.TryGetComponent(out NetworkObject childNob))
|
||||
{
|
||||
/* Make sure both objects have the same value for
|
||||
* IsSceneObject. It's possible the user instantiated
|
||||
* an object and placed it beneath a scene object
|
||||
* before the scene initialized. They may also
|
||||
* add a scene object under an instantiated, even though
|
||||
* this almost certainly will break things. */
|
||||
if (IsSceneObject == childNob.IsSceneObject)
|
||||
ChildNetworkObjects.Add(childNob);
|
||||
}
|
||||
else
|
||||
{
|
||||
transformCache.AddValue(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int written;
|
||||
//Iterate all cached transforms and get networkbehaviours.
|
||||
ListCache<NetworkBehaviour> nbCache = ListCaches.GetNetworkBehaviourCache();
|
||||
nbCache.Reset();
|
||||
written = transformCache.Written;
|
||||
List<Transform> ts = transformCache.Collection;
|
||||
//
|
||||
for (int i = 0; i < written; i++)
|
||||
nbCache.AddValues(ts[i].GetNetworkBehaviours());
|
||||
|
||||
//Copy to array.
|
||||
written = nbCache.Written;
|
||||
List<NetworkBehaviour> nbs = nbCache.Collection;
|
||||
NetworkBehaviours = new NetworkBehaviour[written];
|
||||
//
|
||||
for (int i = 0; i < written; i++)
|
||||
{
|
||||
NetworkBehaviours[i] = nbs[i];
|
||||
NetworkBehaviours[i].SerializeComponents(this, (byte)i);
|
||||
}
|
||||
|
||||
ListCaches.StoreCache(transformCache);
|
||||
ListCaches.StoreCache(nbCache);
|
||||
|
||||
//Tell children nobs to update their NetworkBehaviours.
|
||||
foreach (NetworkObject item in ChildNetworkObjects)
|
||||
{
|
||||
componentIndex++;
|
||||
item.UpdateNetworkBehaviours(this, ref componentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after all data is synchronized with this NetworkObject.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void Initialize(bool asServer, bool invokeSyncTypeCallbacks)
|
||||
{
|
||||
InitializeCallbacks(asServer, invokeSyncTypeCallbacks);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called to prepare this object to be destroyed or disabled.
|
||||
/// </summary>
|
||||
internal void Deinitialize(bool asServer)
|
||||
{
|
||||
InvokeStopCallbacks(asServer);
|
||||
if (asServer)
|
||||
{
|
||||
IsDeinitializing = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ClientManager.Connection.LevelOfDetails.Remove(this);
|
||||
//Client only.
|
||||
if (!NetworkManager.IsServer)
|
||||
IsDeinitializing = true;
|
||||
|
||||
RemoveClientRpcLinkIndexes();
|
||||
}
|
||||
|
||||
SetActiveStatus(false, asServer);
|
||||
if (asServer)
|
||||
Observers.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets states for object to be pooled.
|
||||
/// </summary>
|
||||
/// <param name="asServer">True if performing as server.</param>
|
||||
public void ResetForObjectPool()
|
||||
{
|
||||
int count = NetworkBehaviours.Length;
|
||||
for (int i = 0; i < count; i++)
|
||||
NetworkBehaviours[i].ResetForObjectPool();
|
||||
|
||||
State = NetworkObjectState.Unset;
|
||||
SetOwner(NetworkManager.EmptyConnection);
|
||||
NetworkObserver.Deinitialize();
|
||||
//QOL references.
|
||||
NetworkManager = null;
|
||||
ServerManager = null;
|
||||
ClientManager = null;
|
||||
ObserverManager = null;
|
||||
TransportManager = null;
|
||||
TimeManager = null;
|
||||
SceneManager = null;
|
||||
RollbackManager = null;
|
||||
//Misc sets.
|
||||
ObjectId = 0;
|
||||
ClientInitialized = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes ownership from all clients.
|
||||
/// </summary>
|
||||
public void RemoveOwnership()
|
||||
{
|
||||
GiveOwnership(null, true);
|
||||
}
|
||||
/// <summary>
|
||||
/// Gives ownership to newOwner.
|
||||
/// </summary>
|
||||
/// <param name="newOwner"></param>
|
||||
public void GiveOwnership(NetworkConnection newOwner)
|
||||
{
|
||||
GiveOwnership(newOwner, true);
|
||||
}
|
||||
/// <summary>
|
||||
/// Gives ownership to newOwner.
|
||||
/// </summary>
|
||||
/// <param name="newOwner"></param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void GiveOwnership(NetworkConnection newOwner, bool asServer)
|
||||
{
|
||||
/* Additional asServer checks. */
|
||||
if (asServer)
|
||||
{
|
||||
if (!NetworkManager.IsServer)
|
||||
{
|
||||
NetworkManager.LogWarning($"Ownership cannot be given for object {gameObject.name}. Only server may give ownership.");
|
||||
return;
|
||||
}
|
||||
|
||||
//If the same owner don't bother sending a message, just ignore request.
|
||||
if (newOwner == Owner && asServer)
|
||||
return;
|
||||
|
||||
if (newOwner != null && newOwner.IsActive && !newOwner.LoadedStartScenes(true))
|
||||
{
|
||||
NetworkManager.LogWarning($"Ownership has been transfered to ConnectionId {newOwner.ClientId} but this is not recommended until after they have loaded start scenes. You can be notified when a connection loads start scenes by using connection.OnLoadedStartScenes on the connection, or SceneManager.OnClientLoadStartScenes.");
|
||||
}
|
||||
}
|
||||
|
||||
bool activeNewOwner = (newOwner != null && newOwner.IsActive);
|
||||
|
||||
//Set prevOwner, disallowing null.
|
||||
NetworkConnection prevOwner = Owner;
|
||||
if (prevOwner == null)
|
||||
prevOwner = NetworkManager.EmptyConnection;
|
||||
|
||||
SetOwner(newOwner);
|
||||
/* Only modify objects if asServer or not
|
||||
* host. When host, server would
|
||||
* have already modified objects
|
||||
* collection so there is no need
|
||||
* for client to as well. */
|
||||
if (asServer || !NetworkManager.IsHost)
|
||||
{
|
||||
if (activeNewOwner)
|
||||
newOwner.AddObject(this);
|
||||
if (prevOwner.IsValid && prevOwner != newOwner)
|
||||
prevOwner.RemoveObject(this);
|
||||
}
|
||||
|
||||
//After changing owners invoke callbacks.
|
||||
InvokeOwnership(prevOwner, asServer);
|
||||
|
||||
//If asServer send updates to clients as needed.
|
||||
if (asServer)
|
||||
{
|
||||
if (activeNewOwner)
|
||||
ServerManager.Objects.RebuildObservers(this, newOwner);
|
||||
|
||||
using (PooledWriter writer = WriterPool.GetWriter())
|
||||
{
|
||||
writer.WritePacketId(PacketId.OwnershipChange);
|
||||
writer.WriteNetworkObject(this);
|
||||
writer.WriteNetworkConnection(Owner);
|
||||
//If sharing then send to all observers.
|
||||
if (NetworkManager.ServerManager.ShareIds)
|
||||
{
|
||||
NetworkManager.TransportManager.SendToClients((byte)Channel.Reliable, writer.GetArraySegment(), this);
|
||||
}
|
||||
//Only sending to old / new.
|
||||
else
|
||||
{
|
||||
if (prevOwner.IsActive)
|
||||
NetworkManager.TransportManager.SendToClient((byte)Channel.Reliable, writer.GetArraySegment(), prevOwner);
|
||||
if (activeNewOwner)
|
||||
NetworkManager.TransportManager.SendToClient((byte)Channel.Reliable, writer.GetArraySegment(), newOwner);
|
||||
}
|
||||
}
|
||||
|
||||
if (prevOwner.IsActive)
|
||||
ServerManager.Objects.RebuildObservers(prevOwner);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a predicted object for client.
|
||||
/// </summary>
|
||||
internal void InitializePredictedObject_Server(NetworkManager manager, NetworkConnection predictedSpawner)
|
||||
{
|
||||
NetworkManager = manager;
|
||||
PredictedSpawner = predictedSpawner;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a predicted object for client.
|
||||
/// </summary>
|
||||
internal void PreinitializePredictedObject_Client(NetworkManager manager, int objectId, NetworkConnection owner, NetworkConnection predictedSpawner)
|
||||
{
|
||||
PredictedSpawner = predictedSpawner;
|
||||
Preinitialize_Internal(manager, objectId, owner, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deinitializes this predicted spawned object.
|
||||
/// </summary>
|
||||
internal void DeinitializePredictedObject_Client()
|
||||
{
|
||||
/* For the time being we're just going to disable the object because
|
||||
* deinitializing instead could present a lot of problems.
|
||||
* For example: if client deinitializes rpc links are unregistered,
|
||||
* and if server had a rpc on the way already the link would
|
||||
* not be found. This would cause the reader length to be wrong
|
||||
* resulting in packet corruption. */
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the owner of this object.
|
||||
/// </summary>
|
||||
/// <param name="owner"></param>
|
||||
/// <param name="allowNull"></param>
|
||||
private void SetOwner(NetworkConnection owner)
|
||||
{
|
||||
Owner = owner;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if this NetworkObject is a scene object, and has changed.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal ChangedTransformProperties GetTransformChanges(TransformProperties stp)
|
||||
{
|
||||
ChangedTransformProperties ctp = ChangedTransformProperties.Unset;
|
||||
if (transform.localPosition != stp.Position)
|
||||
ctp |= ChangedTransformProperties.LocalPosition;
|
||||
if (transform.localRotation != stp.Rotation)
|
||||
ctp |= ChangedTransformProperties.LocalRotation;
|
||||
if (transform.localScale != stp.LocalScale)
|
||||
ctp |= ChangedTransformProperties.LocalScale;
|
||||
|
||||
return ctp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if this NetworkObject is a scene object, and has changed.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal ChangedTransformProperties GetTransformChanges(GameObject prefab)
|
||||
{
|
||||
Transform t = prefab.transform;
|
||||
ChangedTransformProperties ctp = ChangedTransformProperties.Unset;
|
||||
if (transform.position != t.position)
|
||||
ctp |= ChangedTransformProperties.LocalPosition;
|
||||
if (transform.rotation != t.rotation)
|
||||
ctp |= ChangedTransformProperties.LocalRotation;
|
||||
if (transform.localScale != t.localScale)
|
||||
ctp |= ChangedTransformProperties.LocalScale;
|
||||
|
||||
return ctp;
|
||||
}
|
||||
|
||||
#region Editor.
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// Removes duplicate NetworkObject components on this object returning the removed count.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal int RemoveDuplicateNetworkObjects()
|
||||
{
|
||||
NetworkObject[] nobs = GetComponents<NetworkObject>();
|
||||
for (int i = 1; i < nobs.Length; i++)
|
||||
DestroyImmediate(nobs[i]);
|
||||
|
||||
return (nobs.Length - 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets IsNested and returns the result.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private bool SetIsNestedThroughTraversal()
|
||||
{
|
||||
Transform parent = transform.parent;
|
||||
//Iterate long as parent isn't null, and isnt self.
|
||||
while (parent != null && parent != transform)
|
||||
{
|
||||
if (parent.TryGetComponent<NetworkObject>(out _))
|
||||
{
|
||||
IsNested = true;
|
||||
return IsNested;
|
||||
}
|
||||
|
||||
parent = parent.parent;
|
||||
}
|
||||
|
||||
//No NetworkObject found in parents, meaning this is not nested.
|
||||
IsNested = false;
|
||||
return IsNested;
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
SetIsNestedThroughTraversal();
|
||||
SceneUpdateNetworkBehaviours();
|
||||
ReferenceIds_OnValidate();
|
||||
|
||||
if (IsGlobal && IsSceneObject)
|
||||
Debug.LogWarning($"Object {gameObject.name} will have it's IsGlobal state ignored because it is a scene object. Instantiated copies will still be global. This warning is informative only.");
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
SetIsNestedThroughTraversal();
|
||||
SerializeTransformProperties();
|
||||
SceneUpdateNetworkBehaviours();
|
||||
ReferenceIds_Reset();
|
||||
}
|
||||
|
||||
private void SceneUpdateNetworkBehaviours()
|
||||
{
|
||||
//In a scene.
|
||||
if (!string.IsNullOrEmpty(gameObject.scene.name))
|
||||
{
|
||||
if (IsNested)
|
||||
return;
|
||||
|
||||
byte componentIndex = 0;
|
||||
UpdateNetworkBehaviours(null, ref componentIndex);
|
||||
}
|
||||
|
||||
}
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
_editorOwnerId = (Owner == null) ? -1 : Owner.ClientId;
|
||||
SerializeTransformProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializes TransformProperties to current transform properties.
|
||||
/// </summary>
|
||||
private void SerializeTransformProperties()
|
||||
{
|
||||
/* Use this method to set scene data since it doesn't need to exist outside
|
||||
* the editor and because its updated regularly while selected. */
|
||||
//If a scene object.
|
||||
if (!EditorApplication.isPlaying && !string.IsNullOrEmpty(gameObject.scene.name))
|
||||
{
|
||||
SerializedTransformProperties = new TransformProperties(
|
||||
transform.localPosition, transform.localRotation, transform.localScale);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
|
11
Assets/FishNet/Runtime/Object/NetworkObject.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/NetworkObject.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26b716c41e9b56b4baafaf13a523ba2e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
24
Assets/FishNet/Runtime/Object/NetworkObjectState.cs
Normal file
24
Assets/FishNet/Runtime/Object/NetworkObjectState.cs
Normal file
@ -0,0 +1,24 @@
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
/// <summary>
|
||||
/// Current state of the NetworkObject.
|
||||
/// </summary>
|
||||
internal enum NetworkObjectState : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// State has not been set. This occurs when the object has never been spawned or despawned.
|
||||
/// </summary>
|
||||
Unset = 0,
|
||||
/// <summary>
|
||||
/// Object is currently spawned.
|
||||
/// </summary>
|
||||
Spawned = 1,
|
||||
/// <summary>
|
||||
/// Object is currently despawned.
|
||||
/// </summary>
|
||||
Despawned = 2,
|
||||
}
|
||||
|
||||
}
|
||||
|
11
Assets/FishNet/Runtime/Object/NetworkObjectState.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/NetworkObjectState.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 536f137f11dc6654eab9fbe94ca14cd8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
14
Assets/FishNet/Runtime/Object/PredictedSpawningType.cs
Normal file
14
Assets/FishNet/Runtime/Object/PredictedSpawningType.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using FishNet.Object.Helping;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
[System.Flags]
|
||||
public enum PredictedSpawningType : byte
|
||||
{
|
||||
Disabled = 0,
|
||||
Spawn = 1,
|
||||
Despawn = 2,
|
||||
}
|
||||
|
||||
|
||||
}
|
11
Assets/FishNet/Runtime/Object/PredictedSpawningType.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/PredictedSpawningType.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4b112cfcb8cd57429eb0a4a32c5a774
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/FishNet/Runtime/Object/Prediction.meta
Normal file
8
Assets/FishNet/Runtime/Object/Prediction.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8bce6e27e176a2d4d91783beffe40ac2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
29
Assets/FishNet/Runtime/Object/Prediction/Attributes.cs
Normal file
29
Assets/FishNet/Runtime/Object/Prediction/Attributes.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using System;
|
||||
|
||||
namespace FishNet.Object.Prediction
|
||||
{
|
||||
/// <summary>
|
||||
/// Replicated methods are to be called from clients and will run the same data and logic on the server.
|
||||
/// Only data used as method arguments will be serialized.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
|
||||
public class ReplicateAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// How many past datas to resend.
|
||||
/// </summary>
|
||||
public byte Resends = 5;
|
||||
}
|
||||
/// <summary>
|
||||
/// Reconcile methods indicate how to reset your script or object after the server has replicated user data.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
|
||||
public class ReconcileAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// How many times to resend reconcile.
|
||||
/// </summary>
|
||||
public byte Resends = 3;
|
||||
}
|
||||
|
||||
}
|
11
Assets/FishNet/Runtime/Object/Prediction/Attributes.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/Prediction/Attributes.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b082d36535ce0404d8438bc1b0499e53
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
20
Assets/FishNet/Runtime/Object/Prediction/Delegates.cs
Normal file
20
Assets/FishNet/Runtime/Object/Prediction/Delegates.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Utility.Constant;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo(UtilityConstants.CODEGEN_ASSEMBLY_NAME)]
|
||||
namespace FishNet.Object.Prediction.Delegating
|
||||
{
|
||||
[APIExclude]
|
||||
public delegate void ReplicateRpcDelegate(PooledReader reader, NetworkConnection sender, Channel channel);
|
||||
[APIExclude]
|
||||
public delegate void ReconcileRpcDelegate(PooledReader reader, Channel channel);
|
||||
|
||||
[APIExclude]
|
||||
public delegate void ReplicateUserLogicDelegate<T>(T data, bool asServer, Channel channel, bool replaying);
|
||||
[APIExclude]
|
||||
public delegate void ReconcileUserLogicDelegate<T>(T data, bool asServer, Channel channel);
|
||||
}
|
11
Assets/FishNet/Runtime/Object/Prediction/Delegates.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/Prediction/Delegates.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c9904192dacd41a4ba7b29bc3199ec3a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
18
Assets/FishNet/Runtime/Object/Prediction/Interfaces.cs
Normal file
18
Assets/FishNet/Runtime/Object/Prediction/Interfaces.cs
Normal file
@ -0,0 +1,18 @@
|
||||
|
||||
namespace FishNet.Object.Prediction
|
||||
{
|
||||
public interface IReplicateData
|
||||
{
|
||||
uint GetTick();
|
||||
void SetTick(uint value);
|
||||
void Dispose();
|
||||
}
|
||||
|
||||
public interface IReconcileData
|
||||
{
|
||||
uint GetTick();
|
||||
void SetTick(uint value);
|
||||
void Dispose();
|
||||
}
|
||||
|
||||
}
|
11
Assets/FishNet/Runtime/Object/Prediction/Interfaces.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/Prediction/Interfaces.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 515754257f85574438408c7f5b268590
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
25
Assets/FishNet/Runtime/Object/RpcLinkType.cs
Normal file
25
Assets/FishNet/Runtime/Object/RpcLinkType.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using FishNet.Object.Helping;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
|
||||
|
||||
internal struct RpcLinkType
|
||||
{
|
||||
/// <summary>
|
||||
/// Index of link.
|
||||
/// </summary>
|
||||
public ushort LinkIndex;
|
||||
/// <summary>
|
||||
/// Type of Rpc link is for.
|
||||
/// </summary>
|
||||
public RpcType RpcType;
|
||||
|
||||
public RpcLinkType(ushort linkIndex, RpcType rpcType)
|
||||
{
|
||||
LinkIndex = linkIndex;
|
||||
RpcType = rpcType;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
11
Assets/FishNet/Runtime/Object/RpcLinkType.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/RpcLinkType.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4fa68ca6a21d08f42980dcf68f984d53
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
12
Assets/FishNet/Runtime/Object/SyncTypeWriteType.cs
Normal file
12
Assets/FishNet/Runtime/Object/SyncTypeWriteType.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace FishNet.Object
|
||||
{
|
||||
|
||||
internal enum SyncTypeWriteType
|
||||
{
|
||||
Observers = 0,
|
||||
Owner = 1,
|
||||
All = 2,
|
||||
}
|
||||
|
||||
|
||||
}
|
11
Assets/FishNet/Runtime/Object/SyncTypeWriteType.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/SyncTypeWriteType.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e6406cc7d5fe47c44a26298145f54b00
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/FishNet/Runtime/Object/Synchronizing.meta
Normal file
8
Assets/FishNet/Runtime/Object/Synchronizing.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 340de3ebc97a18642be780cbfeda01dc
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
19
Assets/FishNet/Runtime/Object/Synchronizing/ICustomSync.cs
Normal file
19
Assets/FishNet/Runtime/Object/Synchronizing/ICustomSync.cs
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
namespace FishNet.Object.Synchronizing
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom SyncObjects must inherit from SyncBase and implement this interface.
|
||||
/// </summary>
|
||||
public interface ICustomSync
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the serialized type.
|
||||
/// This must return the value type you are synchronizing, for example a struct or class.
|
||||
/// If you are not synchronizing a particular value but instead of supported values such as int, bool, ect, then you may return null on this method.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
object GetSerializedType();
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2024b0be0cd1cc744a442f3e2e6ba483
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
48
Assets/FishNet/Runtime/Object/Synchronizing/ISyncObject.cs
Normal file
48
Assets/FishNet/Runtime/Object/Synchronizing/ISyncObject.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using FishNet.Managing;
|
||||
using FishNet.Serializing;
|
||||
using System;
|
||||
|
||||
namespace FishNet.Object.Synchronizing.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// A sync object is an object that can synchronize it's state
|
||||
/// between server and client, such as a SyncList
|
||||
/// </summary>
|
||||
public interface ISyncType
|
||||
{
|
||||
/// <summary>
|
||||
/// true if there are changes since the last flush
|
||||
/// </summary>
|
||||
bool IsDirty { get; }
|
||||
/// <summary>
|
||||
/// Sets index for the SyncType.
|
||||
/// </summary>
|
||||
void SetRegistered();
|
||||
/// <summary>
|
||||
/// PreInitializes this for use with the network.
|
||||
/// </summary>
|
||||
void PreInitialize(NetworkManager networkManager);
|
||||
/// <summary>
|
||||
/// Writes all changed values.
|
||||
/// </summary>
|
||||
/// <param name="writer"></param>
|
||||
///<param name="resetSyncTick">True to set the next time data may sync.</param>
|
||||
void WriteDelta(PooledWriter writer, bool resetSyncTick = true);
|
||||
/// <summary>
|
||||
/// Writers all values if not initial values.
|
||||
/// </summary>
|
||||
/// <param name="writer"></param>
|
||||
void WriteFull(PooledWriter writer);
|
||||
/// <summary>
|
||||
/// Sets current values.
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
void Read(PooledReader reader);
|
||||
/// <summary>
|
||||
/// Resets the SyncObject so that it can be re-used
|
||||
/// </summary>
|
||||
void Reset();
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d0e81c03149ecd4eba926bba2d0edbe
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,11 @@
|
||||
namespace FishNet.Object
|
||||
{
|
||||
|
||||
internal enum MissingObjectPacketLength : int
|
||||
{
|
||||
Reliable = -1,
|
||||
PurgeRemaiming = -2,
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3d177496f9519e246b8e3ef199d83437
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,21 @@
|
||||
namespace FishNet.Object.Synchronizing
|
||||
{
|
||||
/// <summary>
|
||||
/// Which clients may receive synchronization updates.
|
||||
/// </summary>
|
||||
public enum ReadPermission : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// All observers will receive updates.
|
||||
/// </summary>
|
||||
Observers,
|
||||
/// <summary>
|
||||
/// Only owner will receive updates.
|
||||
/// </summary>
|
||||
OwnerOnly,
|
||||
/// <summary>
|
||||
/// Send to all observers except owner.
|
||||
/// </summary>
|
||||
ExcludeOwner
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8050ef114e01f74409d8e29b821b6fc0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8c38d8297956cf34c9826ebe5fbadff6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,21 @@
|
||||
//using FishNet.Documenting;
|
||||
|
||||
//Remove on 2023/06/01
|
||||
//namespace FishNet.Object.Synchronizing.SecretMenu
|
||||
//{
|
||||
// /// <summary>
|
||||
// /// Internal SyncVar extensions.
|
||||
// /// </summary>
|
||||
// [APIExclude]
|
||||
// public static class SyncVarExtensions
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// Dirties SyncVars.
|
||||
// /// </summary>
|
||||
// /// <param name="obj"></param>
|
||||
// [APIExclude]
|
||||
// public static void Dirty(this object obj) { }
|
||||
// }
|
||||
|
||||
|
||||
//}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1f982cd721eb344c88bab105d779127
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
53
Assets/FishNet/Runtime/Object/Synchronizing/Settings.cs
Normal file
53
Assets/FishNet/Runtime/Object/Synchronizing/Settings.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using FishNet.Transporting;
|
||||
|
||||
namespace FishNet.Object.Synchronizing.Internal
|
||||
{
|
||||
public class Settings
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the write permissions for this var
|
||||
/// </summary>
|
||||
public WritePermission WritePermission = WritePermission.ServerOnly;
|
||||
/// <summary>
|
||||
/// Clients which may receive updated values.
|
||||
/// </summary>
|
||||
public ReadPermission ReadPermission = ReadPermission.Observers;
|
||||
/// <summary>
|
||||
/// How often this variable may synchronize.
|
||||
/// </summary>
|
||||
public float SendRate = 0f;
|
||||
/// <summary>
|
||||
/// Channel to send values on.
|
||||
/// </summary>
|
||||
public Channel Channel = Channel.Reliable;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new NetworkedVarSettings instance
|
||||
/// </summary>
|
||||
public Settings()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Settings(WritePermission writePermission, ReadPermission readPermission, float sendRate, Channel channel)
|
||||
{
|
||||
WritePermission = writePermission;
|
||||
ReadPermission = readPermission;
|
||||
SendRate = sendRate;
|
||||
Channel = channel;
|
||||
}
|
||||
|
||||
public Settings(float sendTickrate)
|
||||
{
|
||||
SendRate = sendTickrate;
|
||||
}
|
||||
|
||||
public Settings(ReadPermission readPermission, float sendRate, Channel channel)
|
||||
{
|
||||
ReadPermission = readPermission;
|
||||
SendRate = sendRate;
|
||||
Channel = channel;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
11
Assets/FishNet/Runtime/Object/Synchronizing/Settings.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/Synchronizing/Settings.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 24688925098da4d42be8dbfa66b88b82
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
265
Assets/FishNet/Runtime/Object/Synchronizing/SyncBase.cs
Normal file
265
Assets/FishNet/Runtime/Object/Synchronizing/SyncBase.cs
Normal file
@ -0,0 +1,265 @@
|
||||
using FishNet.Managing;
|
||||
using FishNet.Managing.Timing;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object.Synchronizing.Internal
|
||||
{
|
||||
public class SyncBase : ISyncType
|
||||
{
|
||||
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// True if this SyncBase has been registered within it's containing class.
|
||||
/// </summary>
|
||||
public bool IsRegistered { get; private set; }
|
||||
/// <summary>
|
||||
/// True if the object for which this SyncType is for has been initialized for the network.
|
||||
/// </summary>
|
||||
public bool IsNetworkInitialized => (IsRegistered && (NetworkBehaviour.IsServer || NetworkBehaviour.IsClient));
|
||||
/// <summary>
|
||||
/// True if a SyncObject, false if a SyncVar.
|
||||
/// </summary>
|
||||
public bool IsSyncObject { get; private set; }
|
||||
/// <summary>
|
||||
/// The settings for this SyncVar.
|
||||
/// </summary>
|
||||
public Settings Settings = new Settings();
|
||||
/// <summary>
|
||||
/// How often updates may send.
|
||||
/// </summary>
|
||||
public float SendRate => Settings.SendRate;
|
||||
/// <summary>
|
||||
/// True if this SyncVar needs to send data.
|
||||
/// </summary>
|
||||
public bool IsDirty { get; private set; }
|
||||
/// <summary>
|
||||
/// NetworkManager this uses.
|
||||
/// </summary>
|
||||
public NetworkManager NetworkManager = null;
|
||||
/// <summary>
|
||||
/// NetworkBehaviour this SyncVar belongs to.
|
||||
/// </summary>
|
||||
public NetworkBehaviour NetworkBehaviour = null;
|
||||
/// <summary>
|
||||
/// Next time a SyncVar may send data/
|
||||
/// </summary>
|
||||
public uint NextSyncTick = 0;
|
||||
/// <summary>
|
||||
/// Index within the sync collection.
|
||||
/// </summary>
|
||||
public uint SyncIndex { get; protected set; } = 0;
|
||||
/// <summary>
|
||||
/// Channel to send on.
|
||||
/// </summary>
|
||||
internal Channel Channel => _currentChannel;
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Sync interval converted to ticks.
|
||||
/// </summary>
|
||||
private uint _timeToTicks;
|
||||
/// <summary>
|
||||
/// Channel to use for next write. To ensure eventual consistency this eventually changes to reliable when Settings are unreliable.
|
||||
/// </summary>
|
||||
private Channel _currentChannel;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this SyncBase.
|
||||
/// </summary>
|
||||
public void InitializeInstance(NetworkBehaviour nb, uint syncIndex, WritePermission writePermissions, ReadPermission readPermissions, float tickRate, Channel channel, bool isSyncObject)
|
||||
{
|
||||
NetworkBehaviour = nb;
|
||||
SyncIndex = syncIndex;
|
||||
_currentChannel = channel;
|
||||
IsSyncObject = isSyncObject;
|
||||
Settings = new Settings()
|
||||
{
|
||||
WritePermission = writePermissions,
|
||||
ReadPermission = readPermissions,
|
||||
SendRate = tickRate,
|
||||
Channel = channel
|
||||
};
|
||||
|
||||
NetworkBehaviour.RegisterSyncType(this, SyncIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the SyncIndex.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetRegistered()
|
||||
{
|
||||
Registered();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the SyncType has been registered, but not yet initialized over the network.
|
||||
/// </summary>
|
||||
protected virtual void Registered()
|
||||
{
|
||||
IsRegistered = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PreInitializes this for use with the network.
|
||||
/// </summary>
|
||||
public void PreInitialize(NetworkManager networkManager)
|
||||
{
|
||||
NetworkManager = networkManager;
|
||||
if (Settings.SendRate < 0f)
|
||||
Settings.SendRate = networkManager.ServerManager.GetSynctypeRate();
|
||||
|
||||
_timeToTicks = NetworkManager.TimeManager.TimeToTicks(Settings.SendRate, TickRounding.RoundUp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after OnStartXXXX has occurred.
|
||||
/// </summary>
|
||||
/// <param name="asServer">True if OnStartServer was called, false if OnStartClient.</param>
|
||||
public virtual void OnStartCallback(bool asServer) { }
|
||||
|
||||
protected bool CanNetworkSetValues(bool warn = true)
|
||||
{
|
||||
/* If not registered then values can be set
|
||||
* since at this point the object is still being initialized
|
||||
* in awake so we want those values to be applied. */
|
||||
if (!IsRegistered)
|
||||
return true;
|
||||
/* If the network is not initialized yet then let
|
||||
* values be set. Values set here will not synchronize
|
||||
* to the network. We are assuming the user is setting
|
||||
* these values on client and server appropriately
|
||||
* since they are being applied prior to this object
|
||||
* being networked. */
|
||||
if (!IsNetworkInitialized)
|
||||
return true;
|
||||
//If server is active then values can be set no matter what.
|
||||
if (NetworkBehaviour.IsServer)
|
||||
return true;
|
||||
//Predicted spawning is enabled.
|
||||
if (NetworkManager != null && NetworkManager.PredictionManager.GetAllowPredictedSpawning() && NetworkBehaviour.NetworkObject.AllowPredictedSpawning)
|
||||
return true;
|
||||
/* If here then server is not active and additional
|
||||
* checks must be performed. */
|
||||
bool result = (Settings.ReadPermission == ReadPermission.ExcludeOwner && NetworkBehaviour.IsOwner);
|
||||
if (!result && warn)
|
||||
LogServerNotActiveWarning();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs that the operation could not be completed because the server is not active.
|
||||
/// </summary>
|
||||
protected void LogServerNotActiveWarning()
|
||||
{
|
||||
if (NetworkManager != null)
|
||||
NetworkManager.LogWarning($"Cannot complete operation as server when server is not active.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dirties this Sync and the NetworkBehaviour.
|
||||
/// </summary>
|
||||
public bool Dirty()
|
||||
{
|
||||
/* Reset channel even if already dirty.
|
||||
* This is because the value might have changed
|
||||
* which will reset the eventual consistency state. */
|
||||
_currentChannel = Settings.Channel;
|
||||
|
||||
/* Once dirty don't undirty until it's
|
||||
* processed. This ensures that data
|
||||
* is flushed. */
|
||||
bool canDirty = NetworkBehaviour.DirtySyncType(IsSyncObject);
|
||||
IsDirty |= canDirty;
|
||||
|
||||
return canDirty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets IsDirty to false.
|
||||
/// </summary>
|
||||
internal void ResetDirty()
|
||||
{
|
||||
//If not a sync object and using unreliable channel.
|
||||
if (!IsSyncObject && Settings.Channel == Channel.Unreliable)
|
||||
{
|
||||
//Check if dirty can be unset or if another tick must be run using reliable.
|
||||
if (_currentChannel == Channel.Unreliable)
|
||||
_currentChannel = Channel.Reliable;
|
||||
//Already sent reliable, can undirty. Channel will reset next time this dirties.
|
||||
else
|
||||
IsDirty = false;
|
||||
}
|
||||
//If syncObject or using reliable unset dirty.
|
||||
else
|
||||
{
|
||||
IsDirty = false;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// True if dirty and enough time has passed to write changes.
|
||||
/// </summary>
|
||||
/// <param name="tick"></param>
|
||||
/// <returns></returns>
|
||||
internal bool WriteTimeMet(uint tick)
|
||||
{
|
||||
return (IsDirty && tick >= NextSyncTick);
|
||||
}
|
||||
/// <summary>
|
||||
/// Writes current value.
|
||||
/// </summary>
|
||||
/// <param name="writer"></param>
|
||||
/// <param name="resetSyncTick">True to set the next time data may sync.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public virtual void WriteDelta(PooledWriter writer, bool resetSyncTick = true)
|
||||
{
|
||||
WriteHeader(writer, resetSyncTick);
|
||||
}
|
||||
/// <summary>
|
||||
/// Writers the header for this SyncType.
|
||||
/// </summary>
|
||||
/// <param name="writer"></param>
|
||||
/// <param name="resetSyncTick"></param>
|
||||
protected virtual void WriteHeader(PooledWriter writer, bool resetSyncTick = true)
|
||||
{
|
||||
if (resetSyncTick)
|
||||
NextSyncTick = NetworkManager.TimeManager.Tick + _timeToTicks;
|
||||
|
||||
writer.WriteByte((byte)SyncIndex);
|
||||
}
|
||||
/// <summary>
|
||||
/// Writes current value if not initialized value.
|
||||
/// </summary>
|
||||
/// <param name="writer"></param>
|
||||
public virtual void WriteFull(PooledWriter writer) { }
|
||||
/// <summary>
|
||||
/// Sets current value as client.
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
[Obsolete("Use Read(PooledReader, bool).")]
|
||||
public virtual void Read(PooledReader reader) { }
|
||||
/// <summary>
|
||||
/// Sets current value as server or client.
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
/// <param name="asServer"></param>
|
||||
public virtual void Read(PooledReader reader, bool asServer) { }
|
||||
/// <summary>
|
||||
/// Resets to initialized values.
|
||||
/// </summary>
|
||||
public virtual void Reset()
|
||||
{
|
||||
NextSyncTick = 0;
|
||||
ResetDirty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
11
Assets/FishNet/Runtime/Object/Synchronizing/SyncBase.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/Synchronizing/SyncBase.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a6f26e3f8016cc499b3fa99e7368fbc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
628
Assets/FishNet/Runtime/Object/Synchronizing/SyncDictionary.cs
Normal file
628
Assets/FishNet/Runtime/Object/Synchronizing/SyncDictionary.cs
Normal file
@ -0,0 +1,628 @@
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Object.Synchronizing.Internal;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Utility.Extension;
|
||||
using JetBrains.Annotations;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object.Synchronizing
|
||||
{
|
||||
|
||||
public class SyncIDictionary<TKey, TValue> : SyncBase, IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue>
|
||||
{
|
||||
|
||||
#region Types.
|
||||
/// <summary>
|
||||
/// Information needed to invoke a callback.
|
||||
/// </summary>
|
||||
private struct CachedOnChange
|
||||
{
|
||||
internal readonly SyncDictionaryOperation Operation;
|
||||
internal readonly TKey Key;
|
||||
internal readonly TValue Value;
|
||||
|
||||
public CachedOnChange(SyncDictionaryOperation operation, TKey key, TValue value)
|
||||
{
|
||||
Operation = operation;
|
||||
Key = key;
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Information about how the collection has changed.
|
||||
/// </summary>
|
||||
private struct ChangeData
|
||||
{
|
||||
internal readonly SyncDictionaryOperation Operation;
|
||||
internal readonly TKey Key;
|
||||
internal readonly TValue Value;
|
||||
|
||||
public ChangeData(SyncDictionaryOperation operation, TKey key, TValue value)
|
||||
{
|
||||
this.Operation = operation;
|
||||
this.Key = key;
|
||||
this.Value = value;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// Implementation from Dictionary<TKey,TValue>. Not used.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
public bool IsReadOnly => false;
|
||||
/// <summary>
|
||||
/// Delegate signature for when SyncDictionary changes.
|
||||
/// </summary>
|
||||
/// <param name="op">Operation being completed, such as Add, Set, Remove.</param>
|
||||
/// <param name="key">Key being modified.</param>
|
||||
/// <param name="value">Value of operation.</param>
|
||||
/// <param name="asServer">True if callback is on the server side. False is on the client side.</param>
|
||||
[APIExclude]
|
||||
public delegate void SyncDictionaryChanged(SyncDictionaryOperation op, TKey key, TValue value, bool asServer);
|
||||
/// <summary>
|
||||
/// Called when the SyncDictionary changes.
|
||||
/// </summary>
|
||||
public event SyncDictionaryChanged OnChange;
|
||||
/// <summary>
|
||||
/// Collection of objects.
|
||||
/// </summary>
|
||||
public readonly IDictionary<TKey, TValue> Collection;
|
||||
/// <summary>
|
||||
/// Copy of objects on client portion when acting as a host.
|
||||
/// </summary>
|
||||
public readonly IDictionary<TKey, TValue> ClientHostCollection = new Dictionary<TKey, TValue>();
|
||||
/// <summary>
|
||||
/// Number of objects in the collection.
|
||||
/// </summary>
|
||||
public int Count => Collection.Count;
|
||||
/// <summary>
|
||||
/// Keys within the collection.
|
||||
/// </summary>
|
||||
public ICollection<TKey> Keys => Collection.Keys;
|
||||
[APIExclude]
|
||||
IEnumerable<TKey> IReadOnlyDictionary<TKey, TValue>.Keys => Collection.Keys;
|
||||
/// <summary>
|
||||
/// Values within the collection.
|
||||
/// </summary>
|
||||
public ICollection<TValue> Values => Collection.Values;
|
||||
[APIExclude]
|
||||
IEnumerable<TValue> IReadOnlyDictionary<TKey, TValue>.Values => Collection.Values;
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Initial values for the dictionary.
|
||||
/// </summary>
|
||||
private IDictionary<TKey, TValue> _initialValues = new Dictionary<TKey, TValue>();
|
||||
/// <summary>
|
||||
/// Changed data which will be sent next tick.
|
||||
/// </summary>
|
||||
private readonly List<ChangeData> _changed = new List<ChangeData>();
|
||||
/// <summary>
|
||||
/// Server OnChange events waiting for start callbacks.
|
||||
/// </summary>
|
||||
private readonly List<CachedOnChange> _serverOnChanges = new List<CachedOnChange>();
|
||||
/// <summary>
|
||||
/// Client OnChange events waiting for start callbacks.
|
||||
/// </summary>
|
||||
private readonly List<CachedOnChange> _clientOnChanges = new List<CachedOnChange>();
|
||||
/// <summary>
|
||||
/// True if values have changed since initialization.
|
||||
/// The only reasonable way to reset this during a Reset call is by duplicating the original list and setting all values to it on reset.
|
||||
/// </summary>
|
||||
private bool _valuesChanged;
|
||||
/// <summary>
|
||||
/// True to send all values in the next WriteDelta.
|
||||
/// </summary>
|
||||
private bool _sendAll;
|
||||
#endregion
|
||||
|
||||
[APIExclude]
|
||||
public SyncIDictionary(IDictionary<TKey, TValue> objects)
|
||||
{
|
||||
this.Collection = objects;
|
||||
//Add to clienthostcollection.
|
||||
foreach (KeyValuePair<TKey, TValue> item in objects)
|
||||
this.ClientHostCollection[item.Key] = item.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection being used within this SyncList.
|
||||
/// </summary>
|
||||
/// <param name="asServer">True if returning the server value, false if client value. The values will only differ when running as host. While asServer is true the most current values on server will be returned, and while false the latest values received by client will be returned.</param>
|
||||
/// <returns>The used collection.</returns>
|
||||
public Dictionary<TKey, TValue> GetCollection(bool asServer)
|
||||
{
|
||||
bool asClientAndHost = (!asServer && base.NetworkManager.IsServer);
|
||||
IDictionary<TKey, TValue> collection = (asClientAndHost) ? ClientHostCollection : Collection;
|
||||
return (collection as Dictionary<TKey, TValue>);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the SyncType has been registered, but not yet initialized over the network.
|
||||
/// </summary>
|
||||
protected override void Registered()
|
||||
{
|
||||
base.Registered();
|
||||
foreach (KeyValuePair<TKey, TValue> item in Collection)
|
||||
_initialValues[item.Key] = item.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an operation and invokes callback locally.
|
||||
/// Internal use.
|
||||
/// May be used for custom SyncObjects.
|
||||
/// </summary>
|
||||
/// <param name="operation"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="value"></param>
|
||||
[APIExclude]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void AddOperation(SyncDictionaryOperation operation, TKey key, TValue value)
|
||||
{
|
||||
if (!base.IsRegistered)
|
||||
return;
|
||||
|
||||
/* asServer might be true if the client is setting the value
|
||||
* through user code. Typically synctypes can only be set
|
||||
* by the server, that's why it is assumed asServer via user code.
|
||||
* However, when excluding owner for the synctype the client should
|
||||
* have permission to update the value locally for use with
|
||||
* prediction. */
|
||||
bool asServerInvoke = (!base.IsNetworkInitialized || base.NetworkBehaviour.IsServer);
|
||||
|
||||
if (asServerInvoke)
|
||||
{
|
||||
_valuesChanged = true;
|
||||
if (base.Dirty())
|
||||
{
|
||||
ChangeData change = new ChangeData(operation, key, value);
|
||||
_changed.Add(change);
|
||||
}
|
||||
}
|
||||
|
||||
InvokeOnChange(operation, key, value, asServerInvoke);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called after OnStartXXXX has occurred.
|
||||
/// </summary>
|
||||
/// <param name="asServer">True if OnStartServer was called, false if OnStartClient.</param>
|
||||
public override void OnStartCallback(bool asServer)
|
||||
{
|
||||
base.OnStartCallback(asServer);
|
||||
List<CachedOnChange> collection = (asServer) ? _serverOnChanges : _clientOnChanges;
|
||||
|
||||
if (OnChange != null)
|
||||
{
|
||||
foreach (CachedOnChange item in collection)
|
||||
OnChange.Invoke(item.Operation, item.Key, item.Value, asServer);
|
||||
}
|
||||
|
||||
collection.Clear();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Writes all changed values.
|
||||
/// Internal use.
|
||||
/// May be used for custom SyncObjects.
|
||||
/// </summary>
|
||||
/// <param name="writer"></param>
|
||||
///<param name="resetSyncTick">True to set the next time data may sync.</param>
|
||||
[APIExclude]
|
||||
public override void WriteDelta(PooledWriter writer, bool resetSyncTick = true)
|
||||
{
|
||||
base.WriteDelta(writer, resetSyncTick);
|
||||
|
||||
//If sending all then clear changed and write full.
|
||||
if (_sendAll)
|
||||
{
|
||||
_sendAll = false;
|
||||
_changed.Clear();
|
||||
WriteFull(writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
//False for not full write.
|
||||
writer.WriteBoolean(false);
|
||||
writer.WriteInt32(_changed.Count);
|
||||
|
||||
for (int i = 0; i < _changed.Count; i++)
|
||||
{
|
||||
ChangeData change = _changed[i];
|
||||
writer.WriteByte((byte)change.Operation);
|
||||
|
||||
//Clear does not need to write anymore data so it is not included in checks.
|
||||
if (change.Operation == SyncDictionaryOperation.Add ||
|
||||
change.Operation == SyncDictionaryOperation.Set)
|
||||
{
|
||||
writer.Write(change.Key);
|
||||
writer.Write(change.Value);
|
||||
}
|
||||
else if (change.Operation == SyncDictionaryOperation.Remove)
|
||||
{
|
||||
writer.Write(change.Key);
|
||||
}
|
||||
}
|
||||
|
||||
_changed.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Writers all values if not initial values.
|
||||
/// Internal use.
|
||||
/// May be used for custom SyncObjects.
|
||||
/// </summary>
|
||||
/// <param name="writer"></param>
|
||||
[APIExclude]
|
||||
public override void WriteFull(PooledWriter writer)
|
||||
{
|
||||
if (!_valuesChanged)
|
||||
return;
|
||||
|
||||
base.WriteHeader(writer, false);
|
||||
//True for full write.
|
||||
writer.WriteBoolean(true);
|
||||
writer.WriteInt32(Collection.Count);
|
||||
foreach (KeyValuePair<TKey, TValue> item in Collection)
|
||||
{
|
||||
writer.WriteByte((byte)SyncDictionaryOperation.Add);
|
||||
writer.Write(item.Key);
|
||||
writer.Write(item.Value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Reads and sets the current values for server or client.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void Read(PooledReader reader, bool asServer)
|
||||
{
|
||||
/* When !asServer don't make changes if server is running.
|
||||
* This is because changes would have already been made on
|
||||
* the server side and doing so again would result in duplicates
|
||||
* and potentially overwrite data not yet sent. */
|
||||
bool asClientAndHost = (!asServer && base.NetworkBehaviour.IsServer);
|
||||
IDictionary<TKey, TValue> collection = (asClientAndHost) ? ClientHostCollection : Collection;
|
||||
|
||||
//Clear collection since it's a full write.
|
||||
bool fullWrite = reader.ReadBoolean();
|
||||
if (fullWrite)
|
||||
collection.Clear();
|
||||
|
||||
int changes = reader.ReadInt32();
|
||||
for (int i = 0; i < changes; i++)
|
||||
{
|
||||
SyncDictionaryOperation operation = (SyncDictionaryOperation)reader.ReadByte();
|
||||
TKey key = default;
|
||||
TValue value = default;
|
||||
|
||||
/* Add, Set.
|
||||
* Use the Set code for add and set,
|
||||
* especially so collection doesn't throw
|
||||
* if entry has already been added. */
|
||||
if (operation == SyncDictionaryOperation.Add || operation == SyncDictionaryOperation.Set)
|
||||
{
|
||||
key = reader.Read<TKey>();
|
||||
value = reader.Read<TValue>();
|
||||
collection[key] = value;
|
||||
}
|
||||
//Clear.
|
||||
else if (operation == SyncDictionaryOperation.Clear)
|
||||
{
|
||||
collection.Clear();
|
||||
}
|
||||
//Remove.
|
||||
else if (operation == SyncDictionaryOperation.Remove)
|
||||
{
|
||||
key = reader.Read<TKey>();
|
||||
collection.Remove(key);
|
||||
}
|
||||
|
||||
InvokeOnChange(operation, key, value, false);
|
||||
}
|
||||
|
||||
//If changes were made invoke complete after all have been read.
|
||||
if (changes > 0)
|
||||
InvokeOnChange(SyncDictionaryOperation.Complete, default, default, false);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Invokes OnChanged callback.
|
||||
/// </summary>
|
||||
private void InvokeOnChange(SyncDictionaryOperation operation, TKey key, TValue value, bool asServer)
|
||||
{
|
||||
if (asServer)
|
||||
{
|
||||
if (base.NetworkBehaviour.OnStartServerCalled)
|
||||
OnChange?.Invoke(operation, key, value, asServer);
|
||||
else
|
||||
_serverOnChanges.Add(new CachedOnChange(operation, key, value));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (base.NetworkBehaviour.OnStartClientCalled)
|
||||
OnChange?.Invoke(operation, key, value, asServer);
|
||||
else
|
||||
_clientOnChanges.Add(new CachedOnChange(operation, key, value));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Resets to initialized values.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
_sendAll = false;
|
||||
_changed.Clear();
|
||||
Collection.Clear();
|
||||
ClientHostCollection.Clear();
|
||||
_valuesChanged = false;
|
||||
|
||||
foreach (KeyValuePair<TKey, TValue> item in _initialValues)
|
||||
{
|
||||
Collection[item.Key] = item.Value;
|
||||
ClientHostCollection[item.Key] = item.Value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds item.
|
||||
/// </summary>
|
||||
/// <param name="item">Item to add.</param>
|
||||
public void Add(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
Add(item.Key, item.Value);
|
||||
}
|
||||
/// <summary>
|
||||
/// Adds key and value.
|
||||
/// </summary>
|
||||
/// <param name="key">Key to add.</param>
|
||||
/// <param name="value">Value for key.</param>
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
Add(key, value, true);
|
||||
}
|
||||
private void Add(TKey key, TValue value, bool asServer)
|
||||
{
|
||||
if (!base.CanNetworkSetValues(true))
|
||||
return;
|
||||
|
||||
Collection.Add(key, value);
|
||||
if (asServer)
|
||||
AddOperation(SyncDictionaryOperation.Add, key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all values.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
Clear(true);
|
||||
}
|
||||
private void Clear(bool asServer)
|
||||
{
|
||||
if (!base.CanNetworkSetValues(true))
|
||||
return;
|
||||
|
||||
Collection.Clear();
|
||||
if (asServer)
|
||||
AddOperation(SyncDictionaryOperation.Clear, default, default);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns if key exist.
|
||||
/// </summary>
|
||||
/// <param name="key">Key to use.</param>
|
||||
/// <returns>True if found.</returns>
|
||||
public bool ContainsKey(TKey key)
|
||||
{
|
||||
return Collection.ContainsKey(key);
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns if item exist.
|
||||
/// </summary>
|
||||
/// <param name="item">Item to use.</param>
|
||||
/// <returns>True if found.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Contains(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
return TryGetValue(item.Key, out TValue value) && EqualityComparer<TValue>.Default.Equals(value, item.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies collection to an array.
|
||||
/// </summary>
|
||||
/// <param name="array">Array to copy to.</param>
|
||||
/// <param name="offset">Offset of array data is copied to.</param>
|
||||
public void CopyTo([NotNull] KeyValuePair<TKey, TValue>[] array, int offset)
|
||||
{
|
||||
if (offset <= -1 || offset >= array.Length)
|
||||
{
|
||||
base.NetworkManager.LogError($"Index is out of range.");
|
||||
return;
|
||||
}
|
||||
|
||||
int remaining = array.Length - offset;
|
||||
if (remaining < Count)
|
||||
{
|
||||
base.NetworkManager.LogError($"Array is not large enough to copy data. Array is of length {array.Length}, index is {offset}, and number of values to be copied is {Count.ToString()}.");
|
||||
return;
|
||||
}
|
||||
|
||||
int i = offset;
|
||||
foreach (KeyValuePair<TKey, TValue> item in Collection)
|
||||
{
|
||||
array[i] = item;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Removes a key.
|
||||
/// </summary>
|
||||
/// <param name="key">Key to remove.</param>
|
||||
/// <returns>True if removed.</returns>
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
if (!base.CanNetworkSetValues(true))
|
||||
return false;
|
||||
|
||||
if (Collection.Remove(key))
|
||||
{
|
||||
AddOperation(SyncDictionaryOperation.Remove, key, default);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Removes an item.
|
||||
/// </summary>
|
||||
/// <param name="item">Item to remove.</param>
|
||||
/// <returns>True if removed.</returns>
|
||||
public bool Remove(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
return Remove(item.Key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get value from key.
|
||||
/// </summary>
|
||||
/// <param name="key">Key to use.</param>
|
||||
/// <param name="value">Variable to output to.</param>
|
||||
/// <returns>True if able to output value.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
return Collection.TryGetValueIL2CPP(key, out value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets value for a key.
|
||||
/// </summary>
|
||||
/// <param name="key">Key to use.</param>
|
||||
/// <returns>Value when using as Get.</returns>
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get => Collection[key];
|
||||
set
|
||||
{
|
||||
if (!base.CanNetworkSetValues(true))
|
||||
return;
|
||||
|
||||
Collection[key] = value;
|
||||
AddOperation(SyncDictionaryOperation.Set, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dirties the entire collection forcing a full send.
|
||||
/// </summary>
|
||||
public void DirtyAll()
|
||||
{
|
||||
if (!base.IsRegistered)
|
||||
return;
|
||||
if (!base.CanNetworkSetValues(true))
|
||||
return;
|
||||
|
||||
if (base.Dirty())
|
||||
_sendAll = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dirties an entry by key.
|
||||
/// </summary>
|
||||
/// <param name="key">Key to dirty.</param>
|
||||
public void Dirty(TKey key)
|
||||
{
|
||||
if (!base.IsRegistered)
|
||||
return;
|
||||
if (!base.CanNetworkSetValues(true))
|
||||
return;
|
||||
|
||||
if (Collection.TryGetValueIL2CPP(key, out TValue value))
|
||||
AddOperation(SyncDictionaryOperation.Set, key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dirties an entry by value.
|
||||
/// This operation can be very expensive, will cause allocations, and may fail if your value cannot be compared.
|
||||
/// </summary>
|
||||
/// <param name="value">Value to dirty.</param>
|
||||
/// <returns>True if value was found and marked dirty.</returns>
|
||||
public bool Dirty(TValue value, EqualityComparer<TValue> comparer = null)
|
||||
{
|
||||
if (!base.IsRegistered)
|
||||
return false;
|
||||
if (!base.CanNetworkSetValues(true))
|
||||
return false;
|
||||
|
||||
if (comparer == null)
|
||||
comparer = EqualityComparer<TValue>.Default;
|
||||
|
||||
foreach (KeyValuePair<TKey, TValue> item in Collection)
|
||||
{
|
||||
if (comparer.Equals(item.Value, value))
|
||||
{
|
||||
AddOperation(SyncDictionaryOperation.Set, item.Key, value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//Not found.
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the IEnumerator for the collection.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => Collection.GetEnumerator();
|
||||
/// <summary>
|
||||
/// Gets the IEnumerator for the collection.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IEnumerator IEnumerable.GetEnumerator() => Collection.GetEnumerator();
|
||||
|
||||
}
|
||||
|
||||
[APIExclude]
|
||||
public class SyncDictionary<TKey, TValue> : SyncIDictionary<TKey, TValue>
|
||||
{
|
||||
[APIExclude]
|
||||
public SyncDictionary() : base(new Dictionary<TKey, TValue>()) { }
|
||||
[APIExclude]
|
||||
public SyncDictionary(IEqualityComparer<TKey> eq) : base(new Dictionary<TKey, TValue>(eq)) { }
|
||||
[APIExclude]
|
||||
public new Dictionary<TKey, TValue>.ValueCollection Values => ((Dictionary<TKey, TValue>)Collection).Values;
|
||||
[APIExclude]
|
||||
public new Dictionary<TKey, TValue>.KeyCollection Keys => ((Dictionary<TKey, TValue>)Collection).Keys;
|
||||
[APIExclude]
|
||||
public new Dictionary<TKey, TValue>.Enumerator GetEnumerator() => ((Dictionary<TKey, TValue>)Collection).GetEnumerator();
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54751f912587a854cb61ff80a82087bf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,31 @@
|
||||
|
||||
using FishNet.Documenting;
|
||||
|
||||
namespace FishNet.Object.Synchronizing
|
||||
{
|
||||
[APIExclude]
|
||||
public enum SyncDictionaryOperation : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// A key and value have been added to the collection.
|
||||
/// </summary>
|
||||
Add,
|
||||
/// <summary>
|
||||
/// Collection has been cleared.
|
||||
/// </summary>
|
||||
Clear,
|
||||
/// <summary>
|
||||
/// A key was removed from the collection.
|
||||
/// </summary>
|
||||
Remove,
|
||||
/// <summary>
|
||||
/// A value has been set for a key in the collection.
|
||||
/// </summary>
|
||||
Set,
|
||||
/// <summary>
|
||||
/// All operations for the tick have been processed.
|
||||
/// </summary>
|
||||
Complete
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5d6ed9db47a8224fa9ed4d2ff54586f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,31 @@
|
||||
|
||||
using FishNet.Documenting;
|
||||
|
||||
namespace FishNet.Object.Synchronizing
|
||||
{
|
||||
[APIExclude]
|
||||
public enum SyncHashSetOperation : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// An item is added to the collection.
|
||||
/// </summary>
|
||||
Add,
|
||||
/// <summary>
|
||||
/// An item is removed from the collection.
|
||||
/// </summary>
|
||||
Remove,
|
||||
/// <summary>
|
||||
/// Collection is cleared.
|
||||
/// </summary>
|
||||
Clear,
|
||||
/// <summary>
|
||||
/// All operations for the tick have been processed. This only occurs on clients as the server is unable to be aware of when the user is done modifying the list.
|
||||
/// </summary>
|
||||
Complete,
|
||||
/// <summary>
|
||||
/// An item has been updated within the collection. This is generally used when modifying data within a container.
|
||||
/// </summary>
|
||||
Update,
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 914089f5707003340a68fd6cd718e4c4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
616
Assets/FishNet/Runtime/Object/Synchronizing/SyncHashset.cs
Normal file
616
Assets/FishNet/Runtime/Object/Synchronizing/SyncHashset.cs
Normal file
@ -0,0 +1,616 @@
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Object.Synchronizing.Internal;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Serializing.Helping;
|
||||
using FishNet.Utility.Performance;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object.Synchronizing
|
||||
{
|
||||
|
||||
public class SyncHashSet<T> : SyncBase, ISet<T>
|
||||
{
|
||||
#region Types.
|
||||
/// <summary>
|
||||
/// Information needed to invoke a callback.
|
||||
/// </summary>
|
||||
private struct CachedOnChange
|
||||
{
|
||||
internal readonly SyncHashSetOperation Operation;
|
||||
internal readonly T Item;
|
||||
|
||||
public CachedOnChange(SyncHashSetOperation operation, T item)
|
||||
{
|
||||
Operation = operation;
|
||||
Item = item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Information about how the collection has changed.
|
||||
/// </summary>
|
||||
private struct ChangeData
|
||||
{
|
||||
internal readonly SyncHashSetOperation Operation;
|
||||
internal readonly T Item;
|
||||
|
||||
public ChangeData(SyncHashSetOperation operation, T item)
|
||||
{
|
||||
Operation = operation;
|
||||
|
||||
Item = item;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// Implementation from List<T>. Not used.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
public bool IsReadOnly => false;
|
||||
/// <summary>
|
||||
/// Delegate signature for when SyncList changes.
|
||||
/// </summary>
|
||||
/// <param name="op">Type of change.</param>
|
||||
/// <param name="item">Item which was modified.</param>
|
||||
/// <param name="asServer">True if callback is occuring on the server.</param>
|
||||
[APIExclude]
|
||||
public delegate void SyncHashSetChanged(SyncHashSetOperation op, T item, bool asServer);
|
||||
/// <summary>
|
||||
/// Called when the SyncList changes.
|
||||
/// </summary>
|
||||
public event SyncHashSetChanged OnChange;
|
||||
/// <summary>
|
||||
/// Collection of objects.
|
||||
/// </summary>
|
||||
public readonly ISet<T> Collection;
|
||||
/// <summary>
|
||||
/// Copy of objects on client portion when acting as a host.
|
||||
/// </summary>
|
||||
public readonly ISet<T> ClientHostCollection = new HashSet<T>();
|
||||
/// <summary>
|
||||
/// Number of objects in the collection.
|
||||
/// </summary>
|
||||
public int Count => Collection.Count;
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// ListCache for comparing.
|
||||
/// </summary>
|
||||
private ListCache<T> _listCache;
|
||||
/// <summary>
|
||||
/// Values upon initialization.
|
||||
/// </summary>
|
||||
private ISet<T> _initialValues = new HashSet<T>();
|
||||
/// <summary>
|
||||
/// Comparer to see if entries change when calling public methods.
|
||||
/// </summary>
|
||||
private readonly IEqualityComparer<T> _comparer;
|
||||
/// <summary>
|
||||
/// Changed data which will be sent next tick.
|
||||
/// </summary>
|
||||
private readonly List<ChangeData> _changed = new List<ChangeData>();
|
||||
/// <summary>
|
||||
/// Server OnChange events waiting for start callbacks.
|
||||
/// </summary>
|
||||
private readonly List<CachedOnChange> _serverOnChanges = new List<CachedOnChange>();
|
||||
/// <summary>
|
||||
/// Client OnChange events waiting for start callbacks.
|
||||
/// </summary>
|
||||
private readonly List<CachedOnChange> _clientOnChanges = new List<CachedOnChange>();
|
||||
/// <summary>
|
||||
/// True if values have changed since initialization.
|
||||
/// The only reasonable way to reset this during a Reset call is by duplicating the original list and setting all values to it on reset.
|
||||
/// </summary>
|
||||
private bool _valuesChanged;
|
||||
/// <summary>
|
||||
/// True to send all values in the next WriteDelta.
|
||||
/// </summary>
|
||||
private bool _sendAll;
|
||||
#endregion
|
||||
|
||||
[APIExclude]
|
||||
public SyncHashSet() : this(new HashSet<T>(), EqualityComparer<T>.Default) { }
|
||||
[APIExclude]
|
||||
public SyncHashSet(IEqualityComparer<T> comparer) : this(new HashSet<T>(), (comparer == null) ? EqualityComparer<T>.Default : comparer) { }
|
||||
[APIExclude]
|
||||
public SyncHashSet(ISet<T> collection, IEqualityComparer<T> comparer = null)
|
||||
{
|
||||
this._comparer = (comparer == null) ? EqualityComparer<T>.Default : comparer;
|
||||
this.Collection = collection;
|
||||
//Add each in collection to clienthostcollection.
|
||||
foreach (T item in collection)
|
||||
ClientHostCollection.Add(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the SyncType has been registered, but not yet initialized over the network.
|
||||
/// </summary>
|
||||
protected override void Registered()
|
||||
{
|
||||
base.Registered();
|
||||
foreach (T item in Collection)
|
||||
_initialValues.Add(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection being used within this SyncList.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public HashSet<T> GetCollection(bool asServer)
|
||||
{
|
||||
bool asClientAndHost = (!asServer && base.NetworkManager.IsServer);
|
||||
ISet<T> collection = (asClientAndHost) ? ClientHostCollection : Collection;
|
||||
return (collection as HashSet<T>);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an operation and invokes locally.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void AddOperation(SyncHashSetOperation operation, T item)
|
||||
{
|
||||
if (!base.IsRegistered)
|
||||
return;
|
||||
|
||||
bool asServerInvoke = (!base.IsNetworkInitialized || base.NetworkBehaviour.IsServer);
|
||||
|
||||
if (asServerInvoke)
|
||||
{
|
||||
_valuesChanged = true;
|
||||
if (base.Dirty())
|
||||
{
|
||||
ChangeData change = new ChangeData(operation, item);
|
||||
_changed.Add(change);
|
||||
}
|
||||
}
|
||||
|
||||
InvokeOnChange(operation, item, asServerInvoke);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after OnStartXXXX has occurred.
|
||||
/// </summary>
|
||||
/// <param name="asServer">True if OnStartServer was called, false if OnStartClient.</param>
|
||||
public override void OnStartCallback(bool asServer)
|
||||
{
|
||||
base.OnStartCallback(asServer);
|
||||
List<CachedOnChange> collection = (asServer) ? _serverOnChanges : _clientOnChanges;
|
||||
if (OnChange != null)
|
||||
{
|
||||
foreach (CachedOnChange item in collection)
|
||||
OnChange.Invoke(item.Operation, item.Item, asServer);
|
||||
}
|
||||
|
||||
collection.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes all changed values.
|
||||
/// </summary>
|
||||
/// <param name="writer"></param>
|
||||
///<param name="resetSyncTick">True to set the next time data may sync.</param>
|
||||
public override void WriteDelta(PooledWriter writer, bool resetSyncTick = true)
|
||||
{
|
||||
//If sending all then clear changed and write full.
|
||||
if (_sendAll)
|
||||
{
|
||||
_sendAll = false;
|
||||
_changed.Clear();
|
||||
WriteFull(writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.WriteDelta(writer, resetSyncTick);
|
||||
//False for not full write.
|
||||
writer.WriteBoolean(false);
|
||||
writer.WriteInt32(_changed.Count);
|
||||
|
||||
for (int i = 0; i < _changed.Count; i++)
|
||||
{
|
||||
ChangeData change = _changed[i];
|
||||
writer.WriteByte((byte)change.Operation);
|
||||
|
||||
//Clear does not need to write anymore data so it is not included in checks.
|
||||
if (change.Operation == SyncHashSetOperation.Add || change.Operation == SyncHashSetOperation.Remove || change.Operation == SyncHashSetOperation.Update)
|
||||
{
|
||||
writer.Write(change.Item);
|
||||
}
|
||||
}
|
||||
|
||||
_changed.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes all values if not initial values.
|
||||
/// </summary>
|
||||
/// <param name="writer"></param>
|
||||
public override void WriteFull(PooledWriter writer)
|
||||
{
|
||||
if (!_valuesChanged)
|
||||
return;
|
||||
|
||||
base.WriteHeader(writer, false);
|
||||
//True for full write.
|
||||
writer.WriteBoolean(true);
|
||||
int count = Collection.Count;
|
||||
writer.WriteInt32(count);
|
||||
foreach (T item in Collection)
|
||||
{
|
||||
writer.WriteByte((byte)SyncHashSetOperation.Add);
|
||||
writer.Write(item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and sets the current values for server or client.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[APIExclude]
|
||||
public override void Read(PooledReader reader, bool asServer)
|
||||
{
|
||||
/* When !asServer don't make changes if server is running.
|
||||
* This is because changes would have already been made on
|
||||
* the server side and doing so again would result in duplicates
|
||||
* and potentially overwrite data not yet sent. */
|
||||
bool asClientAndHost = (!asServer && base.NetworkManager.IsServer);
|
||||
ISet<T> collection = (asClientAndHost) ? ClientHostCollection : Collection;
|
||||
|
||||
//Clear collection since it's a full write.
|
||||
bool fullWrite = reader.ReadBoolean();
|
||||
if (fullWrite)
|
||||
collection.Clear();
|
||||
|
||||
int changes = reader.ReadInt32();
|
||||
for (int i = 0; i < changes; i++)
|
||||
{
|
||||
SyncHashSetOperation operation = (SyncHashSetOperation)reader.ReadByte();
|
||||
T next = default;
|
||||
|
||||
//Add.
|
||||
if (operation == SyncHashSetOperation.Add)
|
||||
{
|
||||
next = reader.Read<T>();
|
||||
collection.Add(next);
|
||||
}
|
||||
//Clear.
|
||||
else if (operation == SyncHashSetOperation.Clear)
|
||||
{
|
||||
collection.Clear();
|
||||
}
|
||||
//Remove.
|
||||
else if (operation == SyncHashSetOperation.Remove)
|
||||
{
|
||||
next = reader.Read<T>();
|
||||
collection.Remove(next);
|
||||
}
|
||||
//Updated.
|
||||
else if (operation == SyncHashSetOperation.Update)
|
||||
{
|
||||
next = reader.Read<T>();
|
||||
collection.Remove(next);
|
||||
collection.Add(next);
|
||||
}
|
||||
|
||||
InvokeOnChange(operation, next, false);
|
||||
}
|
||||
|
||||
//If changes were made invoke complete after all have been read.
|
||||
if (changes > 0)
|
||||
InvokeOnChange(SyncHashSetOperation.Complete, default, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes OnChanged callback.
|
||||
/// </summary>
|
||||
private void InvokeOnChange(SyncHashSetOperation operation, T item, bool asServer)
|
||||
{
|
||||
if (asServer)
|
||||
{
|
||||
if (base.NetworkBehaviour.OnStartServerCalled)
|
||||
OnChange?.Invoke(operation, item, asServer);
|
||||
else
|
||||
_serverOnChanges.Add(new CachedOnChange(operation, item));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (base.NetworkBehaviour.OnStartClientCalled)
|
||||
OnChange?.Invoke(operation, item, asServer);
|
||||
else
|
||||
_clientOnChanges.Add(new CachedOnChange(operation, item));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets to initialized values.
|
||||
/// </summary>
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
_sendAll = false;
|
||||
_changed.Clear();
|
||||
Collection.Clear();
|
||||
ClientHostCollection.Clear();
|
||||
|
||||
foreach (T item in _initialValues)
|
||||
{
|
||||
Collection.Add(item);
|
||||
ClientHostCollection.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds value.
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
public bool Add(T item)
|
||||
{
|
||||
return Add(item, true);
|
||||
}
|
||||
private bool Add(T item, bool asServer)
|
||||
{
|
||||
if (!base.CanNetworkSetValues(true))
|
||||
return false;
|
||||
|
||||
bool result = Collection.Add(item);
|
||||
//Only process if remove was successful.
|
||||
if (result && asServer)
|
||||
{
|
||||
if (base.NetworkManager == null)
|
||||
ClientHostCollection.Add(item);
|
||||
AddOperation(SyncHashSetOperation.Add, item);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
/// <summary>
|
||||
/// Adds a range of values.
|
||||
/// </summary>
|
||||
/// <param name="range"></param>
|
||||
public void AddRange(IEnumerable<T> range)
|
||||
{
|
||||
foreach (T entry in range)
|
||||
Add(entry, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all values.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
Clear(true);
|
||||
}
|
||||
private void Clear(bool asServer)
|
||||
{
|
||||
if (!base.CanNetworkSetValues(true))
|
||||
return;
|
||||
|
||||
Collection.Clear();
|
||||
if (asServer)
|
||||
{
|
||||
if (base.NetworkManager == null)
|
||||
ClientHostCollection.Clear();
|
||||
AddOperation(SyncHashSetOperation.Clear, default);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if value exist.
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <returns></returns>
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return Collection.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a value.
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <returns></returns>
|
||||
public bool Remove(T item)
|
||||
{
|
||||
return Remove(item, true);
|
||||
}
|
||||
private bool Remove(T item, bool asServer)
|
||||
{
|
||||
if (!base.CanNetworkSetValues(true))
|
||||
return false;
|
||||
|
||||
bool result = Collection.Remove(item);
|
||||
//Only process if remove was successful.
|
||||
if (result && asServer)
|
||||
{
|
||||
if (base.NetworkManager == null)
|
||||
ClientHostCollection.Remove(item);
|
||||
AddOperation(SyncHashSetOperation.Remove, item);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dirties the entire collection forcing a full send.
|
||||
/// </summary>
|
||||
public void DirtyAll()
|
||||
{
|
||||
if (!base.IsRegistered)
|
||||
return;
|
||||
if (!base.CanNetworkSetValues(true))
|
||||
return;
|
||||
|
||||
if (base.Dirty())
|
||||
_sendAll = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up obj in Collection and if found marks it's index as dirty.
|
||||
/// This operation can be very expensive, will cause allocations, and may fail if your value cannot be compared.
|
||||
/// </summary>
|
||||
/// <param name="obj">Object to lookup.</param>
|
||||
public void Dirty(T obj)
|
||||
{
|
||||
if (!base.IsRegistered)
|
||||
return;
|
||||
if (!base.CanNetworkSetValues(true))
|
||||
return;
|
||||
|
||||
foreach (T item in Collection)
|
||||
{
|
||||
if (item.Equals(obj))
|
||||
{
|
||||
AddOperation(SyncHashSetOperation.Update, obj);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//Not found.
|
||||
base.NetworkManager.LogError($"Could not find object within SyncHashSet, dirty will not be set.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns Enumerator for collection.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerator GetEnumerator() => Collection.GetEnumerator();
|
||||
[APIExclude]
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator() => Collection.GetEnumerator();
|
||||
[APIExclude]
|
||||
IEnumerator IEnumerable.GetEnumerator() => Collection.GetEnumerator();
|
||||
|
||||
public void ExceptWith(IEnumerable<T> other)
|
||||
{
|
||||
//Again, removing from self is a clear.
|
||||
if (other == Collection)
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (T item in other)
|
||||
Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void IntersectWith(IEnumerable<T> other)
|
||||
{
|
||||
ISet<T> set;
|
||||
if (other is ISet<T> setA)
|
||||
set = setA;
|
||||
else
|
||||
set = new HashSet<T>(other);
|
||||
|
||||
IntersectWith(set);
|
||||
}
|
||||
|
||||
private void IntersectWith(ISet<T> other)
|
||||
{
|
||||
Intersect(Collection);
|
||||
if (base.NetworkManager == null)
|
||||
Intersect(ClientHostCollection);
|
||||
|
||||
void Intersect(ISet<T> collection)
|
||||
{
|
||||
if (_listCache == null)
|
||||
_listCache = new ListCache<T>();
|
||||
else
|
||||
_listCache.Reset();
|
||||
|
||||
_listCache.AddValues(collection);
|
||||
|
||||
int count = _listCache.Written;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
T entry = _listCache.Collection[i];
|
||||
if (!other.Contains(entry))
|
||||
Remove(entry);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public bool IsProperSubsetOf(IEnumerable<T> other)
|
||||
{
|
||||
return Collection.IsProperSubsetOf(other);
|
||||
}
|
||||
|
||||
public bool IsProperSupersetOf(IEnumerable<T> other)
|
||||
{
|
||||
return Collection.IsProperSupersetOf(other);
|
||||
}
|
||||
|
||||
public bool IsSubsetOf(IEnumerable<T> other)
|
||||
{
|
||||
return Collection.IsSubsetOf(other);
|
||||
}
|
||||
|
||||
public bool IsSupersetOf(IEnumerable<T> other)
|
||||
{
|
||||
return Collection.IsSupersetOf(other);
|
||||
}
|
||||
|
||||
public bool Overlaps(IEnumerable<T> other)
|
||||
{
|
||||
bool result = Collection.Overlaps(other);
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool SetEquals(IEnumerable<T> other)
|
||||
{
|
||||
return Collection.SetEquals(other);
|
||||
}
|
||||
|
||||
public void SymmetricExceptWith(IEnumerable<T> other)
|
||||
{
|
||||
//If calling except on self then that is the same as a clear.
|
||||
if (other == Collection)
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (T item in other)
|
||||
Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void UnionWith(IEnumerable<T> other)
|
||||
{
|
||||
if (other == Collection)
|
||||
return;
|
||||
|
||||
foreach (T item in other)
|
||||
Add(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an item.
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
void ICollection<T>.Add(T item)
|
||||
{
|
||||
Add(item, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies values to an array.
|
||||
/// </summary>
|
||||
/// <param name="array"></param>
|
||||
/// <param name="index"></param>
|
||||
public void CopyTo(T[] array, int index)
|
||||
{
|
||||
Collection.CopyTo(array, index);
|
||||
if (base.NetworkManager == null)
|
||||
ClientHostCollection.CopyTo(array, index);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 624322b9d999d4b43a560134460955c6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
748
Assets/FishNet/Runtime/Object/Synchronizing/SyncList.cs
Normal file
748
Assets/FishNet/Runtime/Object/Synchronizing/SyncList.cs
Normal file
@ -0,0 +1,748 @@
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Object.Synchronizing.Internal;
|
||||
using FishNet.Serializing;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object.Synchronizing
|
||||
{
|
||||
|
||||
public class SyncList<T> : SyncBase, IList<T>, IReadOnlyList<T>
|
||||
{
|
||||
#region Types.
|
||||
/// <summary>
|
||||
/// Information needed to invoke a callback.
|
||||
/// </summary>
|
||||
private struct CachedOnChange
|
||||
{
|
||||
internal readonly SyncListOperation Operation;
|
||||
internal readonly int Index;
|
||||
internal readonly T Previous;
|
||||
internal readonly T Next;
|
||||
|
||||
public CachedOnChange(SyncListOperation operation, int index, T previous, T next)
|
||||
{
|
||||
Operation = operation;
|
||||
Index = index;
|
||||
Previous = previous;
|
||||
Next = next;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Information about how the collection has changed.
|
||||
/// </summary>
|
||||
private struct ChangeData
|
||||
{
|
||||
internal readonly SyncListOperation Operation;
|
||||
internal readonly int Index;
|
||||
internal readonly T Item;
|
||||
|
||||
public ChangeData(SyncListOperation operation, int index, T item)
|
||||
{
|
||||
Operation = operation;
|
||||
Index = index;
|
||||
Item = item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom enumerator to prevent garbage collection.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
public struct Enumerator : IEnumerator<T>
|
||||
{
|
||||
public T Current { get; private set; }
|
||||
private readonly SyncList<T> _list;
|
||||
private int _index;
|
||||
|
||||
public Enumerator(SyncList<T> list)
|
||||
{
|
||||
this._list = list;
|
||||
_index = -1;
|
||||
Current = default;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
_index++;
|
||||
if (_index >= _list.Count)
|
||||
return false;
|
||||
Current = _list[_index];
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset() => _index = -1;
|
||||
object IEnumerator.Current => Current;
|
||||
public void Dispose() { }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// Implementation from List<T>. Not used.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
public bool IsReadOnly => false;
|
||||
/// <summary>
|
||||
/// Delegate signature for when SyncList changes.
|
||||
/// </summary>
|
||||
/// <param name="op"></param>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="oldItem"></param>
|
||||
/// <param name="newItem"></param>
|
||||
[APIExclude]
|
||||
public delegate void SyncListChanged(SyncListOperation op, int index, T oldItem, T newItem, bool asServer);
|
||||
/// <summary>
|
||||
/// Called when the SyncList changes.
|
||||
/// </summary>
|
||||
public event SyncListChanged OnChange;
|
||||
/// <summary>
|
||||
/// Collection of objects.
|
||||
/// </summary>
|
||||
public readonly IList<T> Collection;
|
||||
/// <summary>
|
||||
/// Copy of objects on client portion when acting as a host.
|
||||
/// </summary>
|
||||
public readonly IList<T> ClientHostCollection = new List<T>();
|
||||
/// <summary>
|
||||
/// Number of objects in the collection.
|
||||
/// </summary>
|
||||
public int Count => Collection.Count;
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Values upon initialization.
|
||||
/// </summary>
|
||||
private IList<T> _initialValues = new List<T>();
|
||||
/// <summary>
|
||||
/// Comparer to see if entries change when calling public methods.
|
||||
/// </summary>
|
||||
private readonly IEqualityComparer<T> _comparer;
|
||||
/// <summary>
|
||||
/// Changed data which will be sent next tick.
|
||||
/// </summary>
|
||||
private readonly List<ChangeData> _changed = new List<ChangeData>();
|
||||
/// <summary>
|
||||
/// Server OnChange events waiting for start callbacks.
|
||||
/// </summary>
|
||||
private readonly List<CachedOnChange> _serverOnChanges = new List<CachedOnChange>();
|
||||
/// <summary>
|
||||
/// Client OnChange events waiting for start callbacks.
|
||||
/// </summary>
|
||||
private readonly List<CachedOnChange> _clientOnChanges = new List<CachedOnChange>();
|
||||
/// <summary>
|
||||
/// True if values have changed since initialization.
|
||||
/// The only reasonable way to reset this during a Reset call is by duplicating the original list and setting all values to it on reset.
|
||||
/// </summary>
|
||||
private bool _valuesChanged;
|
||||
/// <summary>
|
||||
/// True to send all values in the next WriteDelta.
|
||||
/// </summary>
|
||||
private bool _sendAll;
|
||||
#endregion
|
||||
|
||||
[APIExclude]
|
||||
public SyncList() : this(new List<T>(), EqualityComparer<T>.Default) { }
|
||||
[APIExclude]
|
||||
public SyncList(IEqualityComparer<T> comparer) : this(new List<T>(), (comparer == null) ? EqualityComparer<T>.Default : comparer) { }
|
||||
[APIExclude]
|
||||
public SyncList(IList<T> collection, IEqualityComparer<T> comparer = null)
|
||||
{
|
||||
this._comparer = (comparer == null) ? EqualityComparer<T>.Default : comparer;
|
||||
this.Collection = collection;
|
||||
//Add each in collection to clienthostcollection.
|
||||
foreach (T item in collection)
|
||||
this.ClientHostCollection.Add(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the SyncType has been registered, but not yet initialized over the network.
|
||||
/// </summary>
|
||||
protected override void Registered()
|
||||
{
|
||||
base.Registered();
|
||||
foreach (T item in Collection)
|
||||
_initialValues.Add(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection being used within this SyncList.
|
||||
/// </summary>
|
||||
/// <param name="asServer">True if returning the server value, false if client value. The values will only differ when running as host. While asServer is true the most current values on server will be returned, and while false the latest values received by client will be returned.</param>
|
||||
/// <returns></returns>
|
||||
public List<T> GetCollection(bool asServer)
|
||||
{
|
||||
bool asClientAndHost = (!asServer && base.NetworkManager.IsServer);
|
||||
IList<T> collection = (asClientAndHost) ? ClientHostCollection : Collection;
|
||||
return (collection as List<T>);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an operation and invokes locally.
|
||||
/// </summary>
|
||||
/// <param name="operation"></param>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="prev"></param>
|
||||
/// <param name="next"></param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void AddOperation(SyncListOperation operation, int index, T prev, T next)
|
||||
{
|
||||
if (!base.IsRegistered)
|
||||
return;
|
||||
|
||||
/* asServer might be true if the client is setting the value
|
||||
* through user code. Typically synctypes can only be set
|
||||
* by the server, that's why it is assumed asServer via user code.
|
||||
* However, when excluding owner for the synctype the client should
|
||||
* have permission to update the value locally for use with
|
||||
* prediction. */
|
||||
bool asServerInvoke = (!base.IsNetworkInitialized || base.NetworkBehaviour.IsServer);
|
||||
|
||||
/* Only the adds asServer may set
|
||||
* this synctype as dirty and add
|
||||
* to pending changes. However, the event may still
|
||||
* invoke for clientside. */
|
||||
if (asServerInvoke)
|
||||
{
|
||||
/* Set as changed even if cannot dirty.
|
||||
* Dirty is only set when there are observers,
|
||||
* but even if there are not observers
|
||||
* values must be marked as changed so when
|
||||
* there are observers, new values are sent. */
|
||||
_valuesChanged = true;
|
||||
|
||||
/* If unable to dirty then do not add to changed.
|
||||
* A dirty may fail if the server is not started
|
||||
* or if there's no observers. Changed doesn't need
|
||||
* to be populated in this situations because clients
|
||||
* will get the full collection on spawn. If we
|
||||
* were to also add to changed clients would get the full
|
||||
* collection as well the changed, which would double results. */
|
||||
if (base.Dirty())
|
||||
{
|
||||
ChangeData change = new ChangeData(operation, index, next);
|
||||
_changed.Add(change);
|
||||
}
|
||||
}
|
||||
|
||||
InvokeOnChange(operation, index, prev, next, asServerInvoke);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after OnStartXXXX has occurred.
|
||||
/// </summary>
|
||||
/// <param name="asServer">True if OnStartServer was called, false if OnStartClient.</param>
|
||||
public override void OnStartCallback(bool asServer)
|
||||
{
|
||||
base.OnStartCallback(asServer);
|
||||
List<CachedOnChange> collection = (asServer) ? _serverOnChanges : _clientOnChanges;
|
||||
|
||||
if (OnChange != null)
|
||||
{
|
||||
foreach (CachedOnChange item in collection)
|
||||
OnChange.Invoke(item.Operation, item.Index, item.Previous, item.Next, asServer);
|
||||
}
|
||||
|
||||
collection.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes all changed values.
|
||||
/// </summary>
|
||||
/// <param name="writer"></param>
|
||||
///<param name="resetSyncTick">True to set the next time data may sync.</param>
|
||||
public override void WriteDelta(PooledWriter writer, bool resetSyncTick = true)
|
||||
{
|
||||
//If sending all then clear changed and write full.
|
||||
if (_sendAll)
|
||||
{
|
||||
_sendAll = false;
|
||||
_changed.Clear();
|
||||
WriteFull(writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.WriteDelta(writer, resetSyncTick);
|
||||
//False for not full write.
|
||||
writer.WriteBoolean(false);
|
||||
writer.WriteInt32(_changed.Count);
|
||||
|
||||
for (int i = 0; i < _changed.Count; i++)
|
||||
{
|
||||
ChangeData change = _changed[i];
|
||||
writer.WriteByte((byte)change.Operation);
|
||||
|
||||
//Clear does not need to write anymore data so it is not included in checks.
|
||||
if (change.Operation == SyncListOperation.Add)
|
||||
{
|
||||
writer.Write(change.Item);
|
||||
}
|
||||
else if (change.Operation == SyncListOperation.RemoveAt)
|
||||
{
|
||||
writer.WriteInt32(change.Index);
|
||||
}
|
||||
else if (change.Operation == SyncListOperation.Insert || change.Operation == SyncListOperation.Set)
|
||||
{
|
||||
writer.WriteInt32(change.Index);
|
||||
writer.Write(change.Item);
|
||||
}
|
||||
}
|
||||
|
||||
_changed.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes all values if not initial values.
|
||||
/// </summary>
|
||||
/// <param name="writer"></param>
|
||||
public override void WriteFull(PooledWriter writer)
|
||||
{
|
||||
if (!_valuesChanged)
|
||||
return;
|
||||
|
||||
base.WriteHeader(writer, false);
|
||||
//True for full write.
|
||||
writer.WriteBoolean(true);
|
||||
writer.WriteInt32(Collection.Count);
|
||||
for (int i = 0; i < Collection.Count; i++)
|
||||
{
|
||||
writer.WriteByte((byte)SyncListOperation.Add);
|
||||
writer.Write(Collection[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and sets the current values for server or client.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[APIExclude]
|
||||
public override void Read(PooledReader reader, bool asServer)
|
||||
{
|
||||
/* When !asServer don't make changes if server is running.
|
||||
* This is because changes would have already been made on
|
||||
* the server side and doing so again would result in duplicates
|
||||
* and potentially overwrite data not yet sent. */
|
||||
bool asClientAndHost = (!asServer && base.NetworkManager.IsServer);
|
||||
IList<T> collection = (asClientAndHost) ? ClientHostCollection : Collection;
|
||||
|
||||
//Clear collection since it's a full write.
|
||||
bool fullWrite = reader.ReadBoolean();
|
||||
if (fullWrite)
|
||||
collection.Clear();
|
||||
|
||||
int changes = reader.ReadInt32();
|
||||
|
||||
for (int i = 0; i < changes; i++)
|
||||
{
|
||||
SyncListOperation operation = (SyncListOperation)reader.ReadByte();
|
||||
int index = -1;
|
||||
T prev = default;
|
||||
T next = default;
|
||||
|
||||
//Add.
|
||||
if (operation == SyncListOperation.Add)
|
||||
{
|
||||
next = reader.Read<T>();
|
||||
index = collection.Count;
|
||||
collection.Add(next);
|
||||
}
|
||||
//Clear.
|
||||
else if (operation == SyncListOperation.Clear)
|
||||
{
|
||||
collection.Clear();
|
||||
}
|
||||
//Insert.
|
||||
else if (operation == SyncListOperation.Insert)
|
||||
{
|
||||
index = reader.ReadInt32();
|
||||
next = reader.Read<T>();
|
||||
collection.Insert(index, next);
|
||||
}
|
||||
//RemoveAt.
|
||||
else if (operation == SyncListOperation.RemoveAt)
|
||||
{
|
||||
index = reader.ReadInt32();
|
||||
prev = collection[index];
|
||||
collection.RemoveAt(index);
|
||||
}
|
||||
//Set
|
||||
else if (operation == SyncListOperation.Set)
|
||||
{
|
||||
index = reader.ReadInt32();
|
||||
next = reader.Read<T>();
|
||||
prev = collection[index];
|
||||
collection[index] = next;
|
||||
}
|
||||
|
||||
InvokeOnChange(operation, index, prev, next, false);
|
||||
}
|
||||
|
||||
//If changes were made invoke complete after all have been read.
|
||||
if (changes > 0)
|
||||
InvokeOnChange(SyncListOperation.Complete, -1, default, default, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes OnChanged callback.
|
||||
/// </summary>
|
||||
private void InvokeOnChange(SyncListOperation operation, int index, T prev, T next, bool asServer)
|
||||
{
|
||||
if (asServer)
|
||||
{
|
||||
if (base.NetworkBehaviour.OnStartServerCalled)
|
||||
OnChange?.Invoke(operation, index, prev, next, asServer);
|
||||
else
|
||||
_serverOnChanges.Add(new CachedOnChange(operation, index, prev, next));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (base.NetworkBehaviour.OnStartClientCalled)
|
||||
OnChange?.Invoke(operation, index, prev, next, asServer);
|
||||
else
|
||||
_clientOnChanges.Add(new CachedOnChange(operation, index, prev, next));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets to initialized values.
|
||||
/// </summary>
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
_sendAll = false;
|
||||
_changed.Clear();
|
||||
ClientHostCollection.Clear();
|
||||
Collection.Clear();
|
||||
|
||||
foreach (T item in _initialValues)
|
||||
{
|
||||
Collection.Add(item);
|
||||
ClientHostCollection.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds value.
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
public void Add(T item)
|
||||
{
|
||||
Add(item, true);
|
||||
}
|
||||
private void Add(T item, bool asServer)
|
||||
{
|
||||
if (!base.CanNetworkSetValues(true))
|
||||
return;
|
||||
|
||||
Collection.Add(item);
|
||||
if (asServer)
|
||||
{
|
||||
if (base.NetworkManager == null)
|
||||
ClientHostCollection.Add(item);
|
||||
AddOperation(SyncListOperation.Add, Collection.Count - 1, default, item);
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Adds a range of values.
|
||||
/// </summary>
|
||||
/// <param name="range"></param>
|
||||
public void AddRange(IEnumerable<T> range)
|
||||
{
|
||||
foreach (T entry in range)
|
||||
Add(entry, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all values.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
Clear(true);
|
||||
}
|
||||
private void Clear(bool asServer)
|
||||
{
|
||||
if (!base.CanNetworkSetValues(true))
|
||||
return;
|
||||
|
||||
Collection.Clear();
|
||||
if (asServer)
|
||||
{
|
||||
if (base.NetworkManager == null)
|
||||
ClientHostCollection.Clear();
|
||||
AddOperation(SyncListOperation.Clear, -1, default, default);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if value exist.
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <returns></returns>
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return (IndexOf(item) >= 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies values to an array.
|
||||
/// </summary>
|
||||
/// <param name="array"></param>
|
||||
/// <param name="index"></param>
|
||||
public void CopyTo(T[] array, int index)
|
||||
{
|
||||
Collection.CopyTo(array, index);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of value.
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <returns></returns>
|
||||
public int IndexOf(T item)
|
||||
{
|
||||
for (int i = 0; i < Collection.Count; ++i)
|
||||
if (_comparer.Equals(item, Collection[i]))
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds index using match.
|
||||
/// </summary>
|
||||
/// <param name="match"></param>
|
||||
/// <returns></returns>
|
||||
public int FindIndex(Predicate<T> match)
|
||||
{
|
||||
for (int i = 0; i < Collection.Count; ++i)
|
||||
if (match(Collection[i]))
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds value using match.
|
||||
/// </summary>
|
||||
/// <param name="match"></param>
|
||||
/// <returns></returns>
|
||||
public T Find(Predicate<T> match)
|
||||
{
|
||||
int i = FindIndex(match);
|
||||
return (i != -1) ? Collection[i] : default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all values using match.
|
||||
/// </summary>
|
||||
/// <param name="match"></param>
|
||||
/// <returns></returns>
|
||||
public List<T> FindAll(Predicate<T> match)
|
||||
{
|
||||
List<T> results = new List<T>();
|
||||
for (int i = 0; i < Collection.Count; ++i)
|
||||
if (match(Collection[i]))
|
||||
results.Add(Collection[i]);
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts value at index.
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="item"></param>
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
Insert(index, item, true);
|
||||
}
|
||||
private void Insert(int index, T item, bool asServer)
|
||||
{
|
||||
if (!base.CanNetworkSetValues(true))
|
||||
return;
|
||||
|
||||
Collection.Insert(index, item);
|
||||
if (asServer)
|
||||
{
|
||||
if (base.NetworkManager == null)
|
||||
ClientHostCollection.Insert(index, item);
|
||||
AddOperation(SyncListOperation.Insert, index, default, item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a range of values.
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="range"></param>
|
||||
public void InsertRange(int index, IEnumerable<T> range)
|
||||
{
|
||||
foreach (T entry in range)
|
||||
{
|
||||
Insert(index, entry);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a value.
|
||||
/// </summary>
|
||||
/// <param name="item"></param>
|
||||
/// <returns></returns>
|
||||
public bool Remove(T item)
|
||||
{
|
||||
int index = IndexOf(item);
|
||||
bool result = index >= 0;
|
||||
if (result)
|
||||
RemoveAt(index);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes value at index.
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="asServer"></param>
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
RemoveAt(index, true);
|
||||
}
|
||||
private void RemoveAt(int index, bool asServer)
|
||||
{
|
||||
if (!base.CanNetworkSetValues(true))
|
||||
return;
|
||||
|
||||
T oldItem = Collection[index];
|
||||
Collection.RemoveAt(index);
|
||||
if (asServer)
|
||||
{
|
||||
if (base.NetworkManager == null)
|
||||
ClientHostCollection.RemoveAt(index);
|
||||
AddOperation(SyncListOperation.RemoveAt, index, oldItem, default);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all values within the collection.
|
||||
/// </summary>
|
||||
/// <param name="match"></param>
|
||||
/// <returns></returns>
|
||||
public int RemoveAll(Predicate<T> match)
|
||||
{
|
||||
List<T> toRemove = new List<T>();
|
||||
for (int i = 0; i < Collection.Count; ++i)
|
||||
if (match(Collection[i]))
|
||||
toRemove.Add(Collection[i]);
|
||||
|
||||
foreach (T entry in toRemove)
|
||||
Remove(entry);
|
||||
|
||||
return toRemove.Count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets value at an index.
|
||||
/// </summary>
|
||||
/// <param name="i"></param>
|
||||
/// <returns></returns>
|
||||
public T this[int i]
|
||||
{
|
||||
get => Collection[i];
|
||||
set => Set(i, value, true, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dirties the entire collection forcing a full send.
|
||||
/// This will not invoke the callback on server.
|
||||
/// </summary>
|
||||
public void DirtyAll()
|
||||
{
|
||||
if (!base.IsRegistered)
|
||||
return;
|
||||
|
||||
if (base.NetworkManager != null && !base.NetworkBehaviour.IsServer)
|
||||
{
|
||||
base.NetworkManager.LogWarning($"Cannot complete operation as server when server is not active.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (base.Dirty())
|
||||
_sendAll = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up obj in Collection and if found marks it's index as dirty.
|
||||
/// While using this operation previous value will be the same as next.
|
||||
/// This operation can be very expensive, and may fail if your value cannot be compared.
|
||||
/// </summary>
|
||||
/// <param name="obj">Object to lookup.</param>
|
||||
public void Dirty(T obj)
|
||||
{
|
||||
int index = Collection.IndexOf(obj);
|
||||
if (index != -1)
|
||||
Dirty(index);
|
||||
else
|
||||
base.NetworkManager.LogError($"Could not find object within SyncList, dirty will not be set.");
|
||||
}
|
||||
/// <summary>
|
||||
/// Marks an index as dirty.
|
||||
/// While using this operation previous value will be the same as next.
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
public void Dirty(int index)
|
||||
{
|
||||
if (!base.CanNetworkSetValues(true))
|
||||
return;
|
||||
|
||||
bool asServer = true;
|
||||
T value = Collection[index];
|
||||
if (asServer)
|
||||
AddOperation(SyncListOperation.Set, index, value, value);
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets value at index.
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="value"></param>
|
||||
public void Set(int index, T value, bool force = true)
|
||||
{
|
||||
Set(index, value, true, force);
|
||||
}
|
||||
private void Set(int index, T value, bool asServer, bool force)
|
||||
{
|
||||
if (!base.CanNetworkSetValues(true))
|
||||
return;
|
||||
|
||||
bool sameValue = (!force && !_comparer.Equals(Collection[index], value));
|
||||
if (!sameValue)
|
||||
{
|
||||
T prev = Collection[index];
|
||||
Collection[index] = value;
|
||||
if (asServer)
|
||||
{
|
||||
if (base.NetworkManager == null)
|
||||
ClientHostCollection[index] = value;
|
||||
AddOperation(SyncListOperation.Set, index, prev, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns Enumerator for collection.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Enumerator GetEnumerator() => new Enumerator(this);
|
||||
[APIExclude]
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator() => new Enumerator(this);
|
||||
[APIExclude]
|
||||
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);
|
||||
|
||||
}
|
||||
}
|
11
Assets/FishNet/Runtime/Object/Synchronizing/SyncList.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/Synchronizing/SyncList.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f3a4c0d0a34e5142be66143d732c079
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,35 @@
|
||||
|
||||
using FishNet.Documenting;
|
||||
|
||||
namespace FishNet.Object.Synchronizing
|
||||
{
|
||||
[APIExclude]
|
||||
public enum SyncListOperation : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// An item is added to the collection.
|
||||
/// </summary>
|
||||
Add,
|
||||
/// <summary>
|
||||
/// An item is inserted into the collection.
|
||||
/// </summary>
|
||||
Insert,
|
||||
/// <summary>
|
||||
/// An item is set in the collection.
|
||||
/// </summary>
|
||||
Set,
|
||||
/// <summary>
|
||||
/// An item is removed from the collection.
|
||||
/// </summary>
|
||||
RemoveAt,
|
||||
/// <summary>
|
||||
/// Collection is cleared.
|
||||
/// </summary>
|
||||
Clear,
|
||||
/// <summary>
|
||||
/// All operations for the tick have been processed. This only occurs on clients as the server is unable to be aware of when the user is done modifying the list.
|
||||
/// </summary>
|
||||
Complete
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4fa53fc807605df4997f0b63a6570bcf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
290
Assets/FishNet/Runtime/Object/Synchronizing/SyncVar.cs
Normal file
290
Assets/FishNet/Runtime/Object/Synchronizing/SyncVar.cs
Normal file
@ -0,0 +1,290 @@
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Object.Helping;
|
||||
using FishNet.Object.Synchronizing;
|
||||
using FishNet.Object.Synchronizing.Internal;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Serializing.Helping;
|
||||
using FishNet.Transporting;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object.Synchronizing
|
||||
{
|
||||
[APIExclude]
|
||||
[StructLayout(LayoutKind.Auto, CharSet = CharSet.Auto)]
|
||||
public class SyncVar<T> : SyncBase
|
||||
{
|
||||
#region Types.
|
||||
/// <summary>
|
||||
/// Information needed to invoke a callback.
|
||||
/// </summary>
|
||||
private struct CachedOnChange
|
||||
{
|
||||
internal readonly T Previous;
|
||||
internal readonly T Next;
|
||||
|
||||
public CachedOnChange(T previous, T next)
|
||||
{
|
||||
Previous = previous;
|
||||
Next = next;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// Called when the SyncDictionary changes.
|
||||
/// </summary>
|
||||
public event Action<T, T, bool> OnChange;
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Server OnChange event waiting for start callbacks.
|
||||
/// </summary>
|
||||
private CachedOnChange? _serverOnChange;
|
||||
/// <summary>
|
||||
/// Client OnChange event waiting for start callbacks.
|
||||
/// </summary>
|
||||
private CachedOnChange? _clientOnChange;
|
||||
/// <summary>
|
||||
/// Value before the network is initialized on the containing object.
|
||||
/// </summary>
|
||||
private T _initialValue;
|
||||
/// <summary>
|
||||
/// Previous value on the client.
|
||||
/// </summary>
|
||||
private T _previousClientValue;
|
||||
/// <summary>
|
||||
/// Current value on the server, or client.
|
||||
/// </summary>
|
||||
private T _value;
|
||||
#endregion
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public SyncVar(NetworkBehaviour nb, uint syncIndex, WritePermission writePermission, ReadPermission readPermission, float sendRate, Channel channel, T value)
|
||||
{
|
||||
SetInitialValues(value);
|
||||
base.InitializeInstance(nb, syncIndex, writePermission, readPermission, sendRate, channel, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the SyncType has been registered, but not yet initialized over the network.
|
||||
/// </summary>
|
||||
protected override void Registered()
|
||||
{
|
||||
base.Registered();
|
||||
_initialValue = _value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets initial values to next.
|
||||
/// </summary>
|
||||
/// <param name="next"></param>
|
||||
private void SetInitialValues(T next)
|
||||
{
|
||||
_initialValue = next;
|
||||
UpdateValues(next);
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets current and previous values.
|
||||
/// </summary>
|
||||
/// <param name="next"></param>
|
||||
private void UpdateValues(T next)
|
||||
{
|
||||
_previousClientValue = next;
|
||||
_value = next;
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets current value and marks the SyncVar dirty when able to. Returns if able to set value.
|
||||
/// </summary>
|
||||
/// <param name="calledByUser">True if SetValue was called in response to user code. False if from automated code.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetValue(T nextValue, bool calledByUser)
|
||||
{
|
||||
/* If not registered then that means Awake
|
||||
* has not completed on the owning class. This would be true
|
||||
* when setting values within awake on the owning class. Registered
|
||||
* is called at the end of awake, so it would be unset until awake completed.
|
||||
*
|
||||
* Registered however will be true when setting from another script,
|
||||
* even if the owning class of this was just spawned. This is because
|
||||
* the unity cycle will fire awake on the object soon as it's spawned,
|
||||
* completing awake, and the user would set the value after. */
|
||||
if (!base.IsRegistered)
|
||||
return;
|
||||
|
||||
/* If not client or server then set skipChecks
|
||||
* as true. When neither is true it's likely user is changing
|
||||
* value before object is initialized. This is allowed
|
||||
* but checks cannot be processed because they would otherwise
|
||||
* stop setting the value. */
|
||||
bool isNetworkInitialized = base.IsNetworkInitialized;
|
||||
|
||||
//Object is deinitializing.
|
||||
if (isNetworkInitialized && CodegenHelper.NetworkObject_Deinitializing(this.NetworkBehaviour))
|
||||
return;
|
||||
|
||||
//If being set by user code.
|
||||
if (calledByUser)
|
||||
{
|
||||
if (!base.CanNetworkSetValues(true))
|
||||
return;
|
||||
|
||||
/* We will only be this far if the network is not active yet,
|
||||
* server is active, or client has setting permissions.
|
||||
* We only need to set asServerInvoke to false if the network
|
||||
* is initialized and the server is not active. */
|
||||
bool asServerInvoke = (!isNetworkInitialized || base.NetworkBehaviour.IsServer);
|
||||
|
||||
/* If the network has not been network initialized then
|
||||
* Value is expected to be set on server and client since
|
||||
* it's being set before the object is initialized. */
|
||||
if (!isNetworkInitialized)
|
||||
{
|
||||
T prev = _value;
|
||||
UpdateValues(nextValue);
|
||||
//Still call invoke because change will be cached for when the network initializes.
|
||||
InvokeOnChange(prev, _value, calledByUser);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Comparers.EqualityCompare<T>(_value, nextValue))
|
||||
return;
|
||||
|
||||
T prev = _value;
|
||||
_value = nextValue;
|
||||
InvokeOnChange(prev, _value, asServerInvoke);
|
||||
}
|
||||
|
||||
TryDirty(asServerInvoke);
|
||||
}
|
||||
//Not called by user.
|
||||
else
|
||||
{
|
||||
/* Previously clients were not allowed to set values
|
||||
* but this has been changed because clients may want
|
||||
* to update values locally while occasionally
|
||||
* letting the syncvar adjust their side. */
|
||||
T prev = _previousClientValue;
|
||||
if (Comparers.EqualityCompare<T>(prev, nextValue))
|
||||
return;
|
||||
|
||||
/* If also server do not update value.
|
||||
* Server side has say of the current value. */
|
||||
if (!base.NetworkManager.IsServer)
|
||||
UpdateValues(nextValue);
|
||||
else
|
||||
_previousClientValue = nextValue;
|
||||
|
||||
InvokeOnChange(prev, nextValue, calledByUser);
|
||||
}
|
||||
|
||||
|
||||
/* Tries to dirty so update
|
||||
* is sent over network. This needs to be called
|
||||
* anytime the data changes because there is no way
|
||||
* to know if the user set the value on both server
|
||||
* and client or just one side. */
|
||||
void TryDirty(bool asServer)
|
||||
{
|
||||
//Cannot dirty when network is not initialized.
|
||||
if (!isNetworkInitialized)
|
||||
return;
|
||||
|
||||
if (asServer)
|
||||
base.Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes OnChanged callback.
|
||||
/// </summary>
|
||||
private void InvokeOnChange(T prev, T next, bool asServer)
|
||||
{
|
||||
if (asServer)
|
||||
{
|
||||
if (base.NetworkBehaviour.OnStartServerCalled)
|
||||
OnChange?.Invoke(prev, next, asServer);
|
||||
else
|
||||
_serverOnChange = new CachedOnChange(prev, next);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (base.NetworkBehaviour.OnStartClientCalled)
|
||||
OnChange?.Invoke(prev, next, asServer);
|
||||
else
|
||||
_clientOnChange = new CachedOnChange(prev, next);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Called after OnStartXXXX has occurred.
|
||||
/// </summary>
|
||||
/// <param name="asServer">True if OnStartServer was called, false if OnStartClient.</param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override void OnStartCallback(bool asServer)
|
||||
{
|
||||
base.OnStartCallback(asServer);
|
||||
|
||||
if (OnChange != null)
|
||||
{
|
||||
CachedOnChange? change = (asServer) ? _serverOnChange : _clientOnChange;
|
||||
if (change != null)
|
||||
InvokeOnChange(change.Value.Previous, change.Value.Next, asServer);
|
||||
}
|
||||
|
||||
if (asServer)
|
||||
_serverOnChange = null;
|
||||
else
|
||||
_clientOnChange = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes current value.
|
||||
/// </summary>
|
||||
/// <param name="resetSyncTick">True to set the next time data may sync.</param>
|
||||
public override void WriteDelta(PooledWriter writer, bool resetSyncTick = true)
|
||||
{
|
||||
base.WriteDelta(writer, resetSyncTick);
|
||||
writer.Write<T>(_value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes current value if not initialized value.
|
||||
/// </summary>m>
|
||||
public override void WriteFull(PooledWriter obj0)
|
||||
{
|
||||
if (Comparers.EqualityCompare<T>(_initialValue, _value))
|
||||
return;
|
||||
/* SyncVars only hold latest value, so just
|
||||
* write current delta. */
|
||||
WriteDelta(obj0, false);
|
||||
}
|
||||
|
||||
//Read isn't used by SyncVar<T>, it's done within the NB.
|
||||
//public override void Read(PooledReader reader) { }
|
||||
|
||||
/// <summary>
|
||||
/// Gets current value.
|
||||
/// </summary>
|
||||
/// <param name="calledByUser"></param>
|
||||
/// <returns></returns>
|
||||
public T GetValue(bool calledByUser) => (calledByUser) ? _value : _previousClientValue;
|
||||
|
||||
/// <summary>
|
||||
/// Resets to initialized values.
|
||||
/// </summary>
|
||||
public override void Reset()
|
||||
{
|
||||
base.Reset();
|
||||
_value = _initialValue;
|
||||
_previousClientValue = _initialValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
11
Assets/FishNet/Runtime/Object/Synchronizing/SyncVar.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/Synchronizing/SyncVar.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f319403eec508734a93d723617ab1136
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,10 @@
|
||||
namespace FishNet.Object.Synchronizing
|
||||
{
|
||||
/// <summary>
|
||||
/// Which clients or server may write updates.
|
||||
/// </summary>
|
||||
public enum WritePermission
|
||||
{
|
||||
ServerOnly
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2696d0da2ff02e8499a8351a3021008f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
20
Assets/FishNet/Runtime/Object/TransformProperties.cs
Normal file
20
Assets/FishNet/Runtime/Object/TransformProperties.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
[System.Serializable]
|
||||
public struct TransformProperties
|
||||
{
|
||||
public readonly Vector3 Position;
|
||||
public readonly Quaternion Rotation;
|
||||
public readonly Vector3 LocalScale;
|
||||
|
||||
public TransformProperties(Vector3 position, Quaternion rotation, Vector3 localScale)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
LocalScale = localScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
11
Assets/FishNet/Runtime/Object/TransformProperties.cs.meta
Normal file
11
Assets/FishNet/Runtime/Object/TransformProperties.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e4ce2bc25fe8364d8b443f5ac7591ae
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user