Add StickGame Assets
This commit is contained in:
181
phr/StickGame/Assets/FishNet/Runtime/Object/Attributes.cs
Normal file
181
phr/StickGame/Assets/FishNet/Runtime/Object/Attributes.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Transporting;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
public enum DataOrderType
|
||||
{
|
||||
/// <summary>
|
||||
/// Data will buffer in the order originally intended.
|
||||
/// EG: SyncTypes will always send last, and RPCs will always send in the order they were called.
|
||||
/// </summary>
|
||||
Default = 0,
|
||||
/// <summary>
|
||||
/// Data will be attached to the end of the packet.
|
||||
/// RPCs can be sent after all SyncTypes by using this value. Multiple RPCs with this order type will send last, in the order they were called.
|
||||
/// </summary>
|
||||
Last = 1,
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
|
||||
public class RpcAttribute : Attribute
|
||||
{
|
||||
/// <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>
|
||||
/// Order in which to send data for this RPC.
|
||||
/// </summary>
|
||||
public DataOrderType OrderType = DataOrderType.Default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ServerRpc methods will send messages to the server.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
|
||||
public class ServerRpcAttribute : RpcAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// True to only allow the owning client to call this RPC.
|
||||
/// </summary>
|
||||
public bool RequireOwnership = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ObserversRpc methods will send messages to all observers.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
|
||||
public class ObserversRpcAttribute : RpcAttribute
|
||||
{
|
||||
/// <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>
|
||||
/// TargetRpc methods will send messages to a single client.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
|
||||
public class TargetRpcAttribute : RpcAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// True to prevent the connection from receiving this Rpc if they are also server.
|
||||
/// </summary>
|
||||
public bool ExcludeServer = 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>
|
||||
/// 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>
|
||||
/// Network roles which may update values.
|
||||
/// </summary>
|
||||
public WritePermission WritePermissions = WritePermission.ServerOnly;
|
||||
/// <summary>
|
||||
/// True if to require the readonly attribute.
|
||||
/// Setting to false will allow inspector serialization of this object. When false you must still initialize this object on it's field declaration, but never anywhere else.
|
||||
/// </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>
|
||||
/// Network roles which may update values.
|
||||
/// </summary>
|
||||
public WritePermission WritePermissions = WritePermission.ServerOnly;
|
||||
/// <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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2c79ec60813585469c43b4539e3d0c5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
phr/StickGame/Assets/FishNet/Runtime/Object/Delegates.cs
Normal file
13
phr/StickGame/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);
|
||||
public delegate bool SyncVarReadDelegate(PooledReader reader, byte index, bool asServer);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5dbd9cdda4843f34ab416273d80f83c8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
13
phr/StickGame/Assets/FishNet/Runtime/Object/DespawnType.cs
Normal file
13
phr/StickGame/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,
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 205c3ddfc86fbaa44aa0b92ecef58474
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8a6a39c46bf52104ba8efe3100bce3f7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
phr/StickGame/Assets/FishNet/Runtime/Object/Helping.meta
Normal file
8
phr/StickGame/Assets/FishNet/Runtime/Object/Helping.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b4af6267cb928f34fa47fdf8f80eccf9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05a91745dd829d043aadf391ac7b233e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace FishNet.Object.Helping
|
||||
{
|
||||
public enum RpcType : int
|
||||
{
|
||||
None = 0,
|
||||
Server = 1,
|
||||
Observers = 2,
|
||||
Target = 4,
|
||||
Replicate = 8,
|
||||
Reconcile = 16
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09f9e7236f988c64fad54645f4cc7f8b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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:
|
||||
@@ -0,0 +1,209 @@
|
||||
#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 System.Runtime.CompilerServices;
|
||||
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 OnStartXXXX for synctypes, letting them know the NetworkBehaviour start cycle has been completed.
|
||||
/// </summary>
|
||||
internal void InvokeSyncTypeOnStartCallbacks(bool asServer)
|
||||
{
|
||||
foreach (SyncBase item in _syncVars.Values)
|
||||
item.OnStartCallback(asServer);
|
||||
foreach (SyncBase item in _syncObjects.Values)
|
||||
item.OnStartCallback(asServer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes OnStopXXXX for synctypes, letting them know the NetworkBehaviour stop cycle is about to start.
|
||||
/// </summary>
|
||||
internal void InvokeSyncTypeOnStopCallbacks(bool asServer)
|
||||
{
|
||||
foreach (SyncBase item in _syncVars.Values)
|
||||
item.OnStopCallback(asServer);
|
||||
foreach (SyncBase item in _syncObjects.Values)
|
||||
item.OnStopCallback(asServer);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the OnStart/StopNetwork.
|
||||
/// </summary>
|
||||
/// <param name="start"></param>
|
||||
internal void InvokeOnNetwork(bool start)
|
||||
{
|
||||
if (start)
|
||||
{
|
||||
if (_onStartNetworkCalled)
|
||||
return;
|
||||
OnStartNetwork_Internal();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_onStopNetworkCalled)
|
||||
return;
|
||||
OnStopNetwork_Internal();
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void OnStartNetwork_Internal()
|
||||
{
|
||||
_onStartNetworkCalled = true;
|
||||
_onStopNetworkCalled = false;
|
||||
OnStartNetwork();
|
||||
}
|
||||
/// <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>
|
||||
public virtual void OnStartNetwork() { }
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void OnStopNetwork_Internal()
|
||||
{
|
||||
_onStopNetworkCalled = true;
|
||||
_onStartNetworkCalled = false;
|
||||
OnStopNetwork();
|
||||
}
|
||||
/// <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>
|
||||
public virtual void OnStopNetwork() { }
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void OnStartServer_Internal()
|
||||
{
|
||||
OnStartServerCalled = true;
|
||||
OnStartServer();
|
||||
}
|
||||
/// <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>
|
||||
public virtual void OnStartServer() { }
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void OnStopServer_Internal()
|
||||
{
|
||||
OnStartServerCalled = false;
|
||||
ReturnRpcLinks();
|
||||
OnStopServer();
|
||||
}
|
||||
/// <summary>
|
||||
/// Called on the server before deinitializing this object.
|
||||
/// </summary>
|
||||
public virtual void OnStopServer() { }
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void OnOwnershipServer_Internal(NetworkConnection prevOwner)
|
||||
{
|
||||
//When switching ownership always clear replicate cache on server.
|
||||
ClearReplicateCache(true);
|
||||
OnOwnershipServer(prevOwner);
|
||||
}
|
||||
/// <summary>
|
||||
/// Called on the server after ownership has changed.
|
||||
/// </summary>
|
||||
/// <param name="prevOwner">Previous owner of this object.</param>
|
||||
|
||||
public virtual void OnOwnershipServer(NetworkConnection prevOwner) { }
|
||||
|
||||
|
||||
/// <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>
|
||||
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>
|
||||
public virtual void OnDespawnServer(NetworkConnection connection) { }
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void OnStartClient_Internal()
|
||||
{
|
||||
OnStartClientCalled = true;
|
||||
OnStartClient();
|
||||
}
|
||||
/// <summary>
|
||||
/// Called on the client after initializing this object.
|
||||
/// </summary>
|
||||
public virtual void OnStartClient() { }
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void OnStopClient_Internal()
|
||||
{
|
||||
OnStartClientCalled = false;
|
||||
OnStopClient();
|
||||
}
|
||||
/// <summary>
|
||||
/// Called on the client before deinitializing this object.
|
||||
/// </summary>
|
||||
public virtual void OnStopClient() { }
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void OnOwnershipClient_Internal(NetworkConnection prevOwner)
|
||||
{
|
||||
//If losing or gaining ownership then clear replicate cache.
|
||||
if (IsOwner || prevOwner == LocalConnection)
|
||||
{
|
||||
ClearReplicateCache(false);
|
||||
}
|
||||
OnOwnershipClient(prevOwner);
|
||||
}
|
||||
/// <summary>
|
||||
/// Called on the client after gaining or losing ownership.
|
||||
/// </summary>
|
||||
/// <param name="prevOwner">Previous owner of this object.</param>
|
||||
public virtual void OnOwnershipClient(NetworkConnection prevOwner) { }
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9ddaf08801752b49bcfe720217df74a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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:
|
||||
@@ -0,0 +1,767 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Managing.Predicting;
|
||||
using FishNet.Managing.Server;
|
||||
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 FishNet.Utility.Performance;
|
||||
using GameKit.Utilities;
|
||||
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>
|
||||
/// True if the client has cached reconcile
|
||||
/// </summary>
|
||||
internal bool ClientHasReconcileData;
|
||||
/// <summary>
|
||||
/// Gets the last tick this NetworkBehaviour reconciled with.
|
||||
/// </summary>
|
||||
public uint GetLastReconcileTick() => _lastReconcileTick;
|
||||
/// <summary>
|
||||
/// Sets the last tick this NetworkBehaviour reconciled with.
|
||||
/// </summary>
|
||||
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 sent replicate by owning client or server to non-owners.
|
||||
/// </summary>
|
||||
private uint _lastSentReplicateTick;
|
||||
/// <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>
|
||||
/// Last tick a reconcile occured.
|
||||
/// </summary>
|
||||
private uint _lastReconcileTick;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Registers a RPC method.
|
||||
/// Internal use.
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <param name="del"></param>
|
||||
[CodegenMakePublic]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void RegisterReplicateRpc(uint hash, ReplicateRpcDelegate del)
|
||||
{
|
||||
_replicateRpcDelegates[hash] = del;
|
||||
}
|
||||
/// <summary>
|
||||
/// Registers a RPC method.
|
||||
/// Internal use.
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <param name="del"></param>
|
||||
[CodegenMakePublic]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void RegisterReconcileRpc(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_Virtual(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_Virtual(true);
|
||||
ClearReplicateCache_Virtual(false);
|
||||
}
|
||||
/// <summary>
|
||||
/// Clears cached replicates.
|
||||
/// For internal use only.
|
||||
/// </summary>
|
||||
/// <param name="asServer"></param>
|
||||
[CodegenMakePublic]
|
||||
internal virtual void ClearReplicateCache_Virtual(bool asServer) { }
|
||||
|
||||
/// <summary>
|
||||
/// Resets last predirection tick values.
|
||||
/// </summary>
|
||||
private void ResetLastPredictionTicks()
|
||||
{
|
||||
_lastSentReplicateTick = 0;
|
||||
_lastReceivedReplicateTick = 0;
|
||||
_lastReceivedReconcileTick = 0;
|
||||
SetLastReconcileTick(0, false);
|
||||
SetLastReplicateTick(0, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes number of past inputs from buffer to writer and sends it to the server.
|
||||
/// Internal use.
|
||||
/// </summary>
|
||||
private void Owner_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.RedundancyCount, 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.Retrieve(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.
|
||||
|
||||
_transportManagerCache.CheckSetReliableChannel(methodWriter.Length + MAXIMUM_RPC_HEADER_SIZE, ref channel);
|
||||
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.StoreLength();
|
||||
writer.StoreLength();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a RPC to target.
|
||||
/// Internal use.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void Server_SendReconcileRpc<T>(uint hash, T reconcileData, Channel channel)
|
||||
{
|
||||
if (!IsSpawned)
|
||||
return;
|
||||
if (!Owner.IsActive)
|
||||
return;
|
||||
|
||||
PooledWriter methodWriter = WriterPool.Retrieve();
|
||||
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.Store();
|
||||
writer.Store();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if there is a chance the transform may change after the tick.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected internal bool PredictedTransformMayChange()
|
||||
{
|
||||
if (TimeManager.PhysicsMode == PhysicsMode.Disabled)
|
||||
return false;
|
||||
|
||||
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
|
||||
internal bool Replicate_ExitEarly_A(bool asServer, bool replaying, bool allowServerControl)
|
||||
{
|
||||
bool isOwner = IsOwner;
|
||||
//Server.
|
||||
if (asServer)
|
||||
{
|
||||
//No owner, do not try to replicate 'owner' input.
|
||||
if (!Owner.IsActive && !allowServerControl)
|
||||
{
|
||||
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>
|
||||
/// Gets the next replicate in perform when server or non-owning client.
|
||||
/// </summary>
|
||||
[CodegenMakePublic] //internal
|
||||
internal void Replicate_NonOwner<T>(ReplicateUserLogicDelegate<T> del, BasicQueue<T> q, T serverControlData, bool allowServerControl, Channel channel) where T : IReplicateData
|
||||
{
|
||||
//If to allow server control make sure there is no owner.
|
||||
if (allowServerControl && !Owner.IsValid)
|
||||
{
|
||||
uint tick = TimeManager.LocalTick;
|
||||
serverControlData.SetTick(tick);
|
||||
SetLastReplicateTick(tick);
|
||||
del.Invoke(serverControlData, true, channel, false);
|
||||
}
|
||||
//Using client inputs.
|
||||
else
|
||||
{
|
||||
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.
|
||||
int leaveInBuffer = (int)pm.QueuedInputs;
|
||||
//Only consume if the queue count is over leaveInBuffer.
|
||||
if (consumeExcess && count > leaveInBuffer)
|
||||
{
|
||||
byte maximumAllowedConsumes = pm.MaximumReplicateConsumeCount;
|
||||
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.RedundancyCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
del.Invoke(default, true, channel, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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
|
||||
internal void Replicate_Owner<T>(ReplicateUserLogicDelegate<T> del, uint methodHash, List<T> replicates, T data, Channel channel) where T : IReplicateData
|
||||
{
|
||||
//Only check to enque/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 = PredictedTransformMayChange();
|
||||
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.RedundancyCount;
|
||||
|
||||
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--;
|
||||
Owner_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>
|
||||
[CodegenMakePublic] //Internal.
|
||||
internal void Replicate_Reader<T>(PooledReader reader, NetworkConnection sender, T[] arrBuffer, BasicQueue<T> replicates, Channel channel) where T : IReplicateData
|
||||
{
|
||||
PredictionManager pm = PredictionManager;
|
||||
|
||||
/* Data can be read even if owner is not valid because user
|
||||
* may switch ownership on an object and recv a replicate from
|
||||
* the previous owner. */
|
||||
int receivedReplicatesCount = reader.ReadReplicate<T>(ref arrBuffer, TimeManager.LastPacketTick);
|
||||
/* Replicate rpc readers relay to this method and
|
||||
* do not have an owner check in the generated code. */
|
||||
if (!OwnerMatches(sender))
|
||||
return;
|
||||
|
||||
if (receivedReplicatesCount > pm.RedundancyCount)
|
||||
{
|
||||
sender.Kick(reader, KickReason.ExploitAttempt, LoggingType.Common, $"Connection {sender.ToString()} sent too many past replicates. Connection will be kicked immediately.");
|
||||
return;
|
||||
}
|
||||
|
||||
Replicate_HandleReceivedReplicate<T>(receivedReplicatesCount, arrBuffer, replicates, channel);
|
||||
}
|
||||
|
||||
private void Replicate_HandleReceivedReplicate<T>(int receivedReplicatesCount, T[] arrBuffer, BasicQueue<T> replicates, Channel channel) where T : IReplicateData
|
||||
{
|
||||
PredictionManager pm = PredictionManager;
|
||||
bool consumeExcess = !pm.DropExcessiveReplicates;
|
||||
//Maximum number of replicates allowed to be queued at once.
|
||||
int replicatesCountLimit = (consumeExcess) ? (TimeManager.TickRate * 2) : 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;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsServer && Owner.IsValid)
|
||||
Owner.AddAverageQueueCount((ushort)replicates.Count, TimeManager.LocalTick);
|
||||
}
|
||||
|
||||
/// <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
|
||||
internal bool Reconcile_ExitEarly_A(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;
|
||||
|
||||
/* 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(this, true);
|
||||
PredictionManager.InvokeOnReconcile(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>
|
||||
[CodegenMakePublic]
|
||||
internal void Reconcile_Server<T>(uint methodHash, T data, Channel channel) where T : IReconcileData
|
||||
{
|
||||
/* //todo
|
||||
* the codegen right now calls this if asServer
|
||||
* and Reconcile_Client if not.
|
||||
* They both need to be called and each handled appropriately,
|
||||
* like done for the replicate method.
|
||||
* Do not forget to make this into a new method using defines,
|
||||
* for for predictionv1 and v2. */
|
||||
//Server always uses last replicate tick as reconcile tick.
|
||||
uint tick = _lastReplicateTick;
|
||||
data.SetTick(tick);
|
||||
SetLastReconcileTick(tick);
|
||||
|
||||
PredictionManager.InvokeServerReconcile(this, true);
|
||||
Server_SendReconcileRpc(methodHash, data, channel);
|
||||
PredictionManager.InvokeServerReconcile(this, false);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes a reconcile for client.
|
||||
/// </summary>
|
||||
[CodegenMakePublic]
|
||||
internal void Reconcile_Client<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(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(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(scene, replayTick, ps, ps2d, false);
|
||||
}
|
||||
|
||||
//Reconcile ended.
|
||||
PredictionManager.InvokeOnReconcile(this, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a reconcile the client.
|
||||
/// </summary>
|
||||
public void Reconcile_Reader<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:
|
||||
@@ -0,0 +1,282 @@
|
||||
#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 this object has been initialized on the client side.
|
||||
/// This is set true right before client start callbacks and after stop callbacks.
|
||||
/// </summary>
|
||||
public bool IsClientInitialized => _networkObjectCache.IsClientInitialized;
|
||||
/// <summary>
|
||||
/// True if the client is started and authenticated.
|
||||
/// </summary>
|
||||
public bool IsClient => _networkObjectCache.IsClient;
|
||||
/// <summary>
|
||||
/// True if only the client is started and authenticated.
|
||||
/// </summary>
|
||||
public bool IsClientOnly => _networkObjectCache.IsClientOnly;
|
||||
/// <summary>
|
||||
/// True if the client is started and authenticated. This will return true on clientHost even if the object has not initialized yet for the client.
|
||||
/// To check if this object has been initialized for the client use IsClientInitialized.
|
||||
/// </summary>C
|
||||
public bool IsServerInitialized => _networkObjectCache.IsServerInitialized;
|
||||
/// <summary>
|
||||
/// True if the server is started. This will return true on clientHost even if the object is being deinitialized on the server.
|
||||
/// To check if this object has been initialized for the server use IsServerInitialized.
|
||||
/// </summary>
|
||||
public bool IsServer => _networkObjectCache.IsServer;
|
||||
/// <summary>
|
||||
/// True if only the server is started.
|
||||
/// </summary>
|
||||
public bool IsServerOnly => _networkObjectCache.IsServerOnly;
|
||||
/// <summary>
|
||||
/// True if client and server are started.
|
||||
/// </summary>
|
||||
public bool IsHost => _networkObjectCache.IsHost;
|
||||
/// <summary>
|
||||
/// True if client nor server are started.
|
||||
/// </summary>
|
||||
public bool IsOffline => _networkObjectCache.IsOffline;
|
||||
/// <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 => _networkObjectCache.IsNetworked;
|
||||
/// <summary>
|
||||
/// Observers for this NetworkBehaviour.
|
||||
/// </summary>
|
||||
public HashSet<NetworkConnection> Observers => _networkObjectCache.Observers;
|
||||
/// <summary>
|
||||
/// True if the local client is the owner of this object.
|
||||
/// This will only return true if IsClientInitialized is also true. You may check ownership status regardless of client initialized state by using Owner.IsLocalClient.
|
||||
/// </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, UnityEngine.SceneManagement.Scene scene = default)
|
||||
{
|
||||
if (IsNetworkObjectNull(true))
|
||||
return;
|
||||
_networkObjectCache.Spawn(go, ownerConnection, scene);
|
||||
}
|
||||
/// <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, UnityEngine.SceneManagement.Scene scene = default)
|
||||
{
|
||||
if (IsNetworkObjectNull(true))
|
||||
return;
|
||||
_networkObjectCache.Spawn(nob, ownerConnection, scene);
|
||||
}
|
||||
/// <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>
|
||||
/// Tries to registers a new component to this NetworkManager.
|
||||
/// This will not register the instance if another already exists.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to register.</typeparam>
|
||||
/// <param name="component">Reference of the component being registered.</param>
|
||||
/// <returns>True if was able to register, false if an instance is already registered.</returns>
|
||||
public bool TryRegisterInstance<T>(T component) where T : UnityEngine.Component => _networkObjectCache.TryRegisterInstance<T>(component);
|
||||
/// <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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d7aef532208d06c4880973c51b1906f5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,137 @@
|
||||
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 InitializeRpcLinks()
|
||||
{
|
||||
/* 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.Retrieve(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.Retrieve();
|
||||
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.Store();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7136e9ee3f7eee44abab09285ab7b939
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,409 @@
|
||||
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 GameKit.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
|
||||
|
||||
public abstract partial class NetworkBehaviour : MonoBehaviour
|
||||
{
|
||||
#region Types.
|
||||
private struct BufferedRpc
|
||||
{
|
||||
public PooledWriter Writer;
|
||||
public Channel Channel;
|
||||
public DataOrderType OrderType;
|
||||
|
||||
public BufferedRpc(PooledWriter writer, Channel channel, DataOrderType orderType)
|
||||
{
|
||||
Writer = writer;
|
||||
Channel = channel;
|
||||
OrderType = orderType;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#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, BufferedRpc> _bufferedRpcs = new Dictionary<uint, BufferedRpc>();
|
||||
/// <summary>
|
||||
/// Connections to exclude from RPCs, such as ExcludeOwner or ExcludeServer.
|
||||
/// </summary>
|
||||
private HashSet<NetworkConnection> _networkConnectionCache = new HashSet<NetworkConnection>();
|
||||
#endregion
|
||||
|
||||
#region Const.
|
||||
/// <summary>
|
||||
/// This is an estimated value of what the maximum possible size of a RPC could be.
|
||||
/// Realistically this value is much smaller but this value is used as a buffer.
|
||||
/// </summary>
|
||||
private const int MAXIMUM_RPC_HEADER_SIZE = 10;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Called when buffered RPCs should be sent.
|
||||
/// </summary>
|
||||
internal void SendBufferedRpcs(NetworkConnection conn)
|
||||
{
|
||||
TransportManager tm = _networkObjectCache.NetworkManager.TransportManager;
|
||||
foreach (BufferedRpc bRpc in _bufferedRpcs.Values)
|
||||
tm.SendToClient((byte)bRpc.Channel, bRpc.Writer.GetArraySegment(), conn, true, bRpc.OrderType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a RPC method.
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <param name="del"></param>
|
||||
[APIExclude]
|
||||
[CodegenMakePublic]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void RegisterServerRpc(uint hash, ServerRpcDelegate del)
|
||||
{
|
||||
if (_serverRpcDelegates.TryGetValueIL2CPP(hash, out ServerRpcDelegate currentDelegate))
|
||||
{
|
||||
FishNet.Managing.NetworkManager.StaticLogError($"ServerRpc hash {hash} registered multiple times. First registration by {currentDelegate.Method.DeclaringType.GetType().FullName}. New registration by {GetType().FullName}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_serverRpcDelegates[hash] = del;
|
||||
IncreaseRpcMethodCount();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Registers a RPC method.
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <param name="del"></param>
|
||||
[APIExclude]
|
||||
[CodegenMakePublic]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void RegisterObserversRpc(uint hash, ClientRpcDelegate del)
|
||||
{
|
||||
if (_observersRpcDelegates.TryGetValueIL2CPP(hash, out ClientRpcDelegate currentDelegate))
|
||||
{
|
||||
FishNet.Managing.NetworkManager.StaticLogError($"ObserverRpc hash {hash} registered multiple times. First registration by {currentDelegate.Method.DeclaringType.GetType().FullName}. New registration by {GetType().FullName}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_observersRpcDelegates[hash] = del;
|
||||
IncreaseRpcMethodCount();
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Registers a RPC method.
|
||||
/// </summary>
|
||||
/// <param name="hash"></param>
|
||||
/// <param name="del"></param>
|
||||
[APIExclude]
|
||||
[CodegenMakePublic]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void RegisterTargetRpc(uint hash, ClientRpcDelegate del)
|
||||
{
|
||||
if (_targetRpcDelegates.TryGetValueIL2CPP(hash, out ClientRpcDelegate currentDelegate))
|
||||
{
|
||||
FishNet.Managing.NetworkManager.StaticLogError($"TargetRpc hash {hash} registered multiple times. First registration by {currentDelegate.Method.DeclaringType.GetType().FullName}. New registration by {GetType().FullName}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
_targetRpcDelegates[hash] = del;
|
||||
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 (BufferedRpc bRpc in _bufferedRpcs.Values)
|
||||
bRpc.Writer.Store();
|
||||
_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)]
|
||||
protected internal void SendServerRpc(uint hash, PooledWriter methodWriter, Channel channel, DataOrderType orderType)
|
||||
{
|
||||
if (!IsSpawnedWithWarning())
|
||||
return;
|
||||
_transportManagerCache.CheckSetReliableChannel(methodWriter.Length + MAXIMUM_RPC_HEADER_SIZE, ref channel);
|
||||
|
||||
PooledWriter writer = CreateRpc(hash, methodWriter, PacketId.ServerRpc, channel);
|
||||
_networkObjectCache.NetworkManager.TransportManager.SendToServer((byte)channel, writer.GetArraySegment(), true, orderType);
|
||||
writer.StoreLength();
|
||||
}
|
||||
|
||||
|
||||
/// <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)]
|
||||
protected internal void SendObserversRpc(uint hash, PooledWriter methodWriter, Channel channel, DataOrderType orderType, bool bufferLast, bool excludeServer, bool excludeOwner)
|
||||
{
|
||||
if (!IsSpawnedWithWarning())
|
||||
return;
|
||||
_transportManagerCache.CheckSetReliableChannel(methodWriter.Length + MAXIMUM_RPC_HEADER_SIZE, ref channel);
|
||||
|
||||
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, orderType);
|
||||
|
||||
/* 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 (bufferLast)
|
||||
{
|
||||
if (_bufferedRpcs.TryGetValueIL2CPP(hash, out BufferedRpc result))
|
||||
result.Writer.StoreLength();
|
||||
_bufferedRpcs[hash] = new BufferedRpc(writer, channel, orderType);
|
||||
}
|
||||
//If not buffered then dispose immediately.
|
||||
else
|
||||
{
|
||||
writer.StoreLength();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a RPC to target.
|
||||
/// </summary>
|
||||
[CodegenMakePublic] //Make internal.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected internal void SendTargetRpc(uint hash, PooledWriter methodWriter, Channel channel, DataOrderType orderType, NetworkConnection target, bool excludeServer, bool validateTarget = true)
|
||||
{
|
||||
if (!IsSpawnedWithWarning())
|
||||
return;
|
||||
|
||||
_transportManagerCache.CheckSetReliableChannel(methodWriter.Length + MAXIMUM_RPC_HEADER_SIZE, ref channel);
|
||||
|
||||
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, true, orderType);
|
||||
writer.Store();
|
||||
}
|
||||
|
||||
/// <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.Retrieve(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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 938eacb83fa7d0046bd769b31dac7e80
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,467 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Managing.Transporting;
|
||||
using FishNet.Object.Delegating;
|
||||
using FishNet.Object.Synchronizing;
|
||||
using FishNet.Object.Synchronizing.Internal;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Serializing.Helping;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Utility.Extension;
|
||||
using GameKit.Utilities;
|
||||
using System;
|
||||
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.Retrieve();
|
||||
}
|
||||
|
||||
/// <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;
|
||||
/// <summary>
|
||||
/// Delegates to read methods for SyncVars.
|
||||
/// </summary>
|
||||
private List<SyncVarReadDelegate> _syncVarReadDelegates = new List<SyncVarReadDelegate>();
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Registers a SyncVarReadDelegate for this NetworkBehaviour.
|
||||
/// </summary>
|
||||
[CodegenMakePublic]
|
||||
internal void RegisterSyncVarRead(SyncVarReadDelegate del)
|
||||
{
|
||||
_syncVarReadDelegates.Add(del);
|
||||
}
|
||||
|
||||
/// <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 && !_networkObjectCache.PredictedSpawner.IsValid)
|
||||
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(bool asServer)
|
||||
{
|
||||
if (asServer)
|
||||
{
|
||||
if (!_initializedOnceServer)
|
||||
{
|
||||
//optimization Cache synctypewriters on despawn and get from cache on spawn.
|
||||
//Only need to initialize readpermissions once, it's static.
|
||||
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]);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Reset writers.
|
||||
for (int i = 0; i < _syncTypeWriters.Length; i++)
|
||||
_syncTypeWriters[i].Reset();
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialize synctypes every spawn because there could be
|
||||
* callbacks which occur that the user or even we may implement
|
||||
* during the initialization. */
|
||||
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
|
||||
{
|
||||
bool readSyncVar = false;
|
||||
//Try reading with each delegate.
|
||||
for (int i = 0; i < _syncVarReadDelegates.Count; i++)
|
||||
{
|
||||
//Success.
|
||||
if (_syncVarReadDelegates[i](reader, index, asServer))
|
||||
{
|
||||
readSyncVar = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!readSyncVar)
|
||||
NetworkManager.LogWarning($"SyncVar not found for index {index} on {transform.name}. Remainder of packet may become corrupt.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
SyncTypes_ResetState();
|
||||
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.SyncTimeMet(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)
|
||||
{
|
||||
PooledWriter headerWriter = WriterPool.Retrieve();
|
||||
//Write the packetId and NB information.
|
||||
PacketId packetId = (isSyncObject) ? PacketId.SyncObject : PacketId.SyncVar;
|
||||
headerWriter.WritePacketId(packetId);
|
||||
PooledWriter dataWriter = WriterPool.Retrieve();
|
||||
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.Store();
|
||||
|
||||
|
||||
//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);
|
||||
_networkObjectCache.NetworkManager.TransportManager.SendToClients((byte)channel, headerWriter.GetArraySegment(), _networkObjectCache.Observers, _networkConnectionCache);
|
||||
|
||||
}
|
||||
|
||||
headerWriter.Store();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 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.
|
||||
/// </summary>
|
||||
internal void SyncTypes_ResetState()
|
||||
{
|
||||
foreach (SyncBase item in _syncVars.Values)
|
||||
{
|
||||
byte syncIndex = (byte)item.SyncIndex;
|
||||
item.ResetState();
|
||||
/* Should never be possible to be out of bounds but check anyway.
|
||||
* This block of code resets the field to values from the SyncBase(syncVar class). */
|
||||
if (syncIndex < _syncVarReadDelegates.Count)
|
||||
_syncVarReadDelegates[syncIndex]?.Invoke(null, syncIndex, true);
|
||||
}
|
||||
|
||||
_syncObjectDirty = false;
|
||||
_syncVarDirty = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets all SyncVar fields for the class to the values within their SyncVar class.
|
||||
/// EG: _mySyncVar = generated_mySyncVar.GetValue(...)
|
||||
/// </summary>
|
||||
[CodegenMakePublic]
|
||||
internal virtual void ResetSyncVarFields() { }
|
||||
|
||||
/// <summary>
|
||||
/// Writers syncVars for a spawn message.
|
||||
/// </summary>
|
||||
/// <param name="conn">Connection SyncTypes are being written for.</param>
|
||||
internal void WriteSyncTypesForSpawn(PooledWriter writer, NetworkConnection conn)
|
||||
{
|
||||
WriteSyncType(_syncVars);
|
||||
WriteSyncType(_syncObjects);
|
||||
|
||||
void WriteSyncType(Dictionary<uint, SyncBase> collection)
|
||||
{
|
||||
PooledWriter syncTypeWriter = WriterPool.Retrieve();
|
||||
/* 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 connection is null then write for all.
|
||||
* This can only occur when client is sending syncTypes
|
||||
* to the server. This will be removed when predicted
|
||||
* spawning payload is added in. */ //todo remove this after predicted spawning payload.
|
||||
if (conn != null)
|
||||
{
|
||||
//True if conn is the owner of this object.
|
||||
bool connIsOwner = (conn == _networkObjectCache.Owner);
|
||||
//Read permissions for the synctype.
|
||||
ReadPermission rp = sb.Settings.ReadPermission;
|
||||
/* SyncType only allows owner to receive values and
|
||||
* conn is not the owner. */
|
||||
if (rp == ReadPermission.OwnerOnly && !connIsOwner)
|
||||
continue;
|
||||
//Write to everyone but the owner.
|
||||
if (rp == ReadPermission.ExcludeOwner && connIsOwner)
|
||||
continue;
|
||||
}
|
||||
|
||||
//Anything beyond this is fine to write for everyone.
|
||||
sb.WriteFull(syncTypeWriter);
|
||||
}
|
||||
|
||||
writer.WriteBytesAndSize(syncTypeWriter.GetBuffer(), 0, syncTypeWriter.Length);
|
||||
syncTypeWriter.Store();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Manually marks a SyncType as dirty, be it SyncVar or SyncObject.
|
||||
/// </summary>
|
||||
/// <param name="syncType">SyncType variable to dirty.</param>
|
||||
[Obsolete("This method does not function.")]
|
||||
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:
|
||||
232
phr/StickGame/Assets/FishNet/Runtime/Object/NetworkBehaviour.cs
Normal file
232
phr/StickGame/Assets/FishNet/Runtime/Object/NetworkBehaviour.cs
Normal file
@@ -0,0 +1,232 @@
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Managing.Transporting;
|
||||
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
|
||||
{
|
||||
#region Public.
|
||||
/// <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>
|
||||
/// Cache of the TransportManager.
|
||||
/// </summary>
|
||||
private TransportManager _transportManagerCache;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
[SerializeField, HideInInspector]
|
||||
private NetworkObject _networkObjectCache;
|
||||
/// <summary>
|
||||
/// NetworkObject this behaviour is for.
|
||||
/// </summary>
|
||||
public NetworkObject NetworkObject => _networkObjectCache;
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// True if initialized at some point asServer.
|
||||
/// </summary>
|
||||
private bool _initializedOnceServer;
|
||||
#pragma warning disable CS0414
|
||||
/// <summary>
|
||||
/// True if initialized at some point not asServer.
|
||||
/// </summary>
|
||||
private bool _initializedOnceClient;
|
||||
#pragma warning restore CS0414
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Outputs data about this NetworkBehaviour to string.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Name [{gameObject.name}] ComponentId [{ComponentIndex}] NetworkObject Name [{_networkObjectCache.name}] NetworkObject Id [{_networkObjectCache.ObjectId}]";
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Preinitializes this script for the network.
|
||||
/// </summary>
|
||||
internal void Preinitialize_Internal(NetworkObject nob, bool asServer)
|
||||
{
|
||||
_transportManagerCache = nob.TransportManager;
|
||||
|
||||
InitializeOnceSyncTypes(asServer);
|
||||
if (asServer)
|
||||
{
|
||||
InitializeRpcLinks();
|
||||
_initializedOnceServer = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_initializedOnceClient = true;
|
||||
}
|
||||
}
|
||||
|
||||
internal void Deinitialize(bool asServer)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <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 ResetState()
|
||||
{
|
||||
SyncTypes_ResetState();
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2230f9cdb1ffc9489b53875c963342d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,31 @@
|
||||
using FishNet.Broadcast;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Transporting;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
public 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:
|
||||
@@ -0,0 +1,149 @@
|
||||
using FishNet.Connection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
public 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. */
|
||||
|
||||
//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_Internal();
|
||||
for (int i = 0; i < NetworkBehaviours.Length; i++)
|
||||
NetworkBehaviours[i].OnOwnershipServer_Internal(FishNet.Managing.NetworkManager.EmptyConnection);
|
||||
}
|
||||
//As client.
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Length; i++)
|
||||
NetworkBehaviours[i].OnStartClient_Internal();
|
||||
for (int i = 0; i < NetworkBehaviours.Length; i++)
|
||||
NetworkBehaviours[i].OnOwnershipClient_Internal(FishNet.Managing.NetworkManager.EmptyConnection);
|
||||
}
|
||||
|
||||
if (invokeSyncTypeCallbacks)
|
||||
InvokeOnStartSyncTypeCallbacks(true);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Invokes OnStartXXXX for synctypes, letting them know the NetworkBehaviour start cycle has been completed.
|
||||
/// </summary>
|
||||
internal void InvokeOnStartSyncTypeCallbacks(bool asServer)
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Length; i++)
|
||||
NetworkBehaviours[i].InvokeSyncTypeOnStartCallbacks(asServer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes OnStopXXXX for synctypes, letting them know the NetworkBehaviour stop cycle is about to start.
|
||||
/// </summary>
|
||||
internal void InvokeOnStopSyncTypeCallbacks(bool asServer)
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Length; i++)
|
||||
NetworkBehaviours[i].InvokeSyncTypeOnStopCallbacks(asServer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes events to be called after OnServerStart.
|
||||
/// This is made one method to save instruction calls.
|
||||
/// </summary>
|
||||
/// <param name=""></param>
|
||||
internal void OnSpawnServer(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)
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Length; i++)
|
||||
NetworkBehaviours[i].InvokeSyncTypeOnStopCallbacks(asServer);
|
||||
|
||||
if (asServer)
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Length; i++)
|
||||
NetworkBehaviours[i].OnStopServer_Internal();
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Length; i++)
|
||||
NetworkBehaviours[i].OnStopClient_Internal();
|
||||
}
|
||||
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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_Internal(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_Internal(prevOwner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e9fbf0d6eb10e94d892dd4e817030bc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,280 @@
|
||||
using FishNet.Component.Observing;
|
||||
using FishNet.Connection;
|
||||
using FishNet.Observing;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
public partial class NetworkObject : MonoBehaviour
|
||||
{
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// Called when the clientHost gains or loses visibility of this object.
|
||||
/// Boolean value will be true if clientHost has visibility.
|
||||
/// </summary>
|
||||
public event HostVisibilityUpdatedDelegate OnHostVisibilityUpdated;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="prevVisible">True if clientHost was known to have visibility of the object prior to this invoking.</param>
|
||||
/// <param name="nextVisible">True if the clientHost now has visibility of the object.</param>
|
||||
public delegate void HostVisibilityUpdatedDelegate(bool prevVisible, bool nextVisible);
|
||||
/// <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>
|
||||
[HideInInspector]
|
||||
public HashSet<NetworkConnection> Observers = new HashSet<NetworkConnection>();
|
||||
#endregion
|
||||
|
||||
#region Internal.
|
||||
/// <summary>
|
||||
/// Current HashGrid entry this belongs to.
|
||||
/// </summary>
|
||||
internal GridEntry HashGridEntry;
|
||||
#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;
|
||||
/// <summary>
|
||||
/// HashGrid for this object.
|
||||
/// </summary>
|
||||
private HashGrid _hashGrid;
|
||||
/// <summary>
|
||||
/// Next time this object may update it's position for HashGrid.
|
||||
/// </summary>
|
||||
private float _nextHashGridUpdateTime;
|
||||
/// <summary>
|
||||
/// True if this gameObject is static.
|
||||
/// </summary>
|
||||
private bool _isStatic;
|
||||
/// <summary>
|
||||
/// Current grid position.
|
||||
/// </summary>
|
||||
private Vector2Int _hashGridPosition = HashGrid.UnsetGridPosition;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Updates Objects positions in the HashGrid for this Networkmanager.
|
||||
/// </summary>
|
||||
internal void UpdateForNetworkObject(bool force)
|
||||
{
|
||||
if (_hashGrid == null)
|
||||
return;
|
||||
if (_isStatic)
|
||||
return;
|
||||
|
||||
float unscaledTime = Time.unscaledTime;
|
||||
//Not enough time has passed to update.
|
||||
if (!force && unscaledTime < _nextHashGridUpdateTime)
|
||||
return;
|
||||
|
||||
const float updateInterval = 1f;
|
||||
_nextHashGridUpdateTime = unscaledTime + updateInterval;
|
||||
Vector2Int newPosition = _hashGrid.GetHashGridPosition(this);
|
||||
if (newPosition != _hashGridPosition)
|
||||
{
|
||||
_hashGridPosition = newPosition;
|
||||
HashGridEntry = _hashGrid.GetGridEntry(newPosition);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
}
|
||||
|
||||
OnHostVisibilityUpdated?.Invoke(_lastClientHostVisibility, 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;
|
||||
}
|
||||
|
||||
//Update hashgrid if needed.
|
||||
UpdateForNetworkObject(!timedOnly);
|
||||
|
||||
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:
|
||||
318
phr/StickGame/Assets/FishNet/Runtime/Object/NetworkObject.QOL.cs
Normal file
318
phr/StickGame/Assets/FishNet/Runtime/Object/NetworkObject.QOL.cs
Normal file
@@ -0,0 +1,318 @@
|
||||
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.Serializing.Helping;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
public 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 start callbacks and after stop callbacks.
|
||||
/// </summary>
|
||||
public bool IsClientInitialized { get; private set; }
|
||||
[Obsolete("Use IsClientInitialized.")]
|
||||
public bool ClientInitialized => IsClientInitialized;
|
||||
/// <summary>
|
||||
/// True if the client is started and authenticated. This will return true on clientHost even if the object has not initialized yet for the client.
|
||||
/// To check if this object has been initialized for the client use IsClientInitialized.
|
||||
/// </summary>
|
||||
public bool IsClient => (NetworkManager == null) ? false : NetworkManager.IsClient;
|
||||
/// <summary>
|
||||
/// True if only the client is started and authenticated.
|
||||
/// </summary>
|
||||
public bool IsClientOnly => (IsClient && !IsServer);
|
||||
/// <summary>
|
||||
/// True if this object has been initialized on the server side.
|
||||
/// This is set true right before server start callbacks and after stop callbacks.
|
||||
/// </summary>
|
||||
public bool IsServerInitialized { get; private set; }
|
||||
/// <summary>
|
||||
/// True if the server is started. This will return true on clientHost even if the object is being deinitialized on the server.
|
||||
/// To check if this object has been initialized for the server use IsServerInitialized.
|
||||
/// </summary>
|
||||
public bool IsServer => (NetworkManager == null) ? false : NetworkManager.IsServer;
|
||||
/// <summary>
|
||||
/// True if only the server is started.
|
||||
/// </summary>
|
||||
public bool IsServerOnly => (IsServer && !IsClient);
|
||||
/// <summary>
|
||||
/// True if client and server are started.
|
||||
/// </summary>
|
||||
public bool IsHost => (IsClient && IsServer);
|
||||
/// <summary>
|
||||
/// True if client nor server are started.
|
||||
/// </summary>
|
||||
public bool IsOffline => (!IsClient && !IsServer);
|
||||
/// <summary>
|
||||
/// True if the local client is the owner of this object.
|
||||
/// This will only return true if IsClientInitialized is also true. You may check ownership status regardless of client initialized state by using Owner.IsLocalClient.
|
||||
/// </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 (!IsClientInitialized)
|
||||
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, UnityEngine.SceneManagement.Scene scene = default)
|
||||
{
|
||||
NetworkManager?.ServerManager.Spawn(go, ownerConnection, scene);
|
||||
}
|
||||
/// <summary>
|
||||
/// Spawns an object over the network. Only call from the server.
|
||||
/// </summary>
|
||||
public void Spawn(NetworkObject nob, NetworkConnection ownerConnection = null, UnityEngine.SceneManagement.Scene scene = default)
|
||||
{
|
||||
NetworkManager?.ServerManager.Spawn(nob, ownerConnection, scene);
|
||||
}
|
||||
|
||||
/// <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_Internal(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>
|
||||
/// Tries to registers a new component to this NetworkManager.
|
||||
/// This will not register the instance if another already exists.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type to register.</typeparam>
|
||||
/// <param name="component">Reference of the component being registered.</param>
|
||||
/// <returns>True if was able to register, false if an instance is already registered.</returns>
|
||||
public bool TryRegisterInstance<T>(T component) where T : UnityEngine.Component => NetworkManager.TryRegisterInstance<T>(component);
|
||||
/// <summary>
|
||||
/// Returns class of type from registered instances.
|
||||
/// </summary>
|
||||
/// <param name="component">Outputted component.</param>
|
||||
/// <typeparam name="T">Type to get.</typeparam>
|
||||
/// <returns>True if was able to get instance.</returns>
|
||||
public bool TryGetInstance<T>(out T component) where T : UnityEngine.Component => NetworkManager.TryGetInstance<T>(out component);
|
||||
/// <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
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd30b4b61d50d01499c94a63a6eeb863
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,201 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using GameKit.Utilities;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor.Experimental.SceneManagement;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
public 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
|
||||
|
||||
/// <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().GetStableHashU32();
|
||||
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)
|
||||
{
|
||||
NetworkObject[] nobs = GameObject.FindObjectsOfType<NetworkObject>();
|
||||
foreach (NetworkObject n in nobs)
|
||||
{
|
||||
if (n != null && n != this && n.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:
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
public 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b2f6927cf3ef254d91b89e5f99a92b9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,25 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
public partial class NetworkObject : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes 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:
|
||||
1096
phr/StickGame/Assets/FishNet/Runtime/Object/NetworkObject.cs
Normal file
1096
phr/StickGame/Assets/FishNet/Runtime/Object/NetworkObject.cs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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:
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 536f137f11dc6654eab9fbe94ca14cd8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 46773e0f27a85d643867f04d902fa007
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,83 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object.Prediction
|
||||
{
|
||||
/// <summary>
|
||||
/// How to favor smoothing for predicted objects.
|
||||
/// </summary>
|
||||
internal enum AdaptiveSmoothingType
|
||||
{
|
||||
/// <summary>
|
||||
/// Favor accurate collisions. With fast moving objects this may result in some jitter with higher latencies.
|
||||
/// </summary>
|
||||
Accuracy = 0,
|
||||
/// <summary>
|
||||
/// A mix between Accuracy and Smoothness.
|
||||
/// </summary>
|
||||
Mixed = 1,
|
||||
/// <summary>
|
||||
/// Prefer smooth movement and corrections. Fast moving objects may collide before the graphical representation catches up.
|
||||
/// </summary>
|
||||
Gradual = 2,
|
||||
/// <summary>
|
||||
/// Configure values to your preference.
|
||||
/// </summary>
|
||||
Custom = 3,
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public struct AdaptiveInterpolationSmoothingData
|
||||
{
|
||||
[HideInInspector, System.NonSerialized]
|
||||
public bool SmoothPosition;
|
||||
[HideInInspector, System.NonSerialized]
|
||||
public bool SmoothRotation;
|
||||
[HideInInspector, System.NonSerialized]
|
||||
public bool SmoothScale;
|
||||
[HideInInspector,System.NonSerialized]
|
||||
public Transform GraphicalObject;
|
||||
[HideInInspector,System.NonSerialized]
|
||||
public NetworkObject NetworkObject;
|
||||
[HideInInspector, System.NonSerialized]
|
||||
public float TeleportThreshold;
|
||||
|
||||
/// <summary>
|
||||
/// Percentage of ping to use as interpolation. Higher values will result in more interpolation.
|
||||
/// </summary>
|
||||
[Tooltip("Percentage of ping to use as interpolation. Higher values will result in more interpolation.")]
|
||||
[Range(0.01f, 5f)]
|
||||
public float NormalPercent;
|
||||
/// <summary>
|
||||
/// Percentage of ping to use as interpolation when colliding with an object local client owns.
|
||||
/// This is used to speed up local interpolation when predicted objects collide with a player as well keep graphics closer to the objects root while colliding.
|
||||
/// </summary>
|
||||
[Tooltip("Percentage of ping to use as interpolation when colliding with an object local client owns." +
|
||||
"This is used to speed up local interpolation when predicted objects collide with a player as well keep graphics closer to the objects root while colliding.")]
|
||||
[Range(0.01f, 5f)]
|
||||
public float CollisionPercent;
|
||||
/// <summary>
|
||||
/// How much per tick to decrease to collision interpolation when colliding with a local player object.
|
||||
/// Higher values will set interpolation to collision settings faster.
|
||||
/// </summary>
|
||||
[Tooltip("How much per tick to decrease to collision interpolation when colliding with a local player object. Higher values will set interpolation to collision settings faster.")]
|
||||
[Range(0.1f, 10f)]
|
||||
public float CollisionStep;
|
||||
/// <summary>
|
||||
/// How much per tick to increase to normal interpolation when not colliding with a local player object.
|
||||
/// Higher values will set interpolation to normal settings faster.
|
||||
/// </summary>
|
||||
[Tooltip("How much per tick to increase to normal interpolation when not colliding with a local player object. Higher values will set interpolation to normal settings faster.")]
|
||||
[Range(0.1f, 10f)]
|
||||
public float NormalStep;
|
||||
|
||||
/// <summary>
|
||||
/// Interpolation applied regardless of settings when colliding with a localClient object.
|
||||
/// </summary>
|
||||
internal const byte BASE_COLLISION_INTERPOLATION = 0;
|
||||
/// <summary>
|
||||
/// Interpolation applied regardless of settings when not colliding with a localClient object.
|
||||
/// </summary>
|
||||
internal const byte BASE_NORMAL_INTERPOLATION = 1;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9fcf00526b5e1bf40a6565c3f521d926
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,48 @@
|
||||
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>
|
||||
[Obsolete("Use PredictionManager.RedundancyCount.")] //Remove on 2023/06/01
|
||||
public byte Resends = 5;
|
||||
/// <summary>
|
||||
/// True to allow running input passed in with asServer true when there is no owner.
|
||||
/// </summary>
|
||||
public bool AllowServerControl = false;
|
||||
}
|
||||
/// <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>
|
||||
[Obsolete("Use PredictionManager.RedundancyCount.")] //Remove on 2023/06/01
|
||||
public byte Resends = 3;
|
||||
}
|
||||
|
||||
|
||||
/// <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 ReplicateV2Attribute : Attribute { }
|
||||
/// <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 ReconcileV2Attribute : Attribute { }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b082d36535ce0404d8438bc1b0499e53
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,21 @@
|
||||
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);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c9904192dacd41a4ba7b29bc3199ec3a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
namespace FishNet.Object.Prediction
|
||||
{
|
||||
public interface IReplicateData
|
||||
{
|
||||
/// <summary>
|
||||
/// Local tick when the data was created.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
uint GetTick();
|
||||
/// <summary>
|
||||
/// Sets the local tick when data was created.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
void SetTick(uint value);
|
||||
/// <summary>
|
||||
/// Allows for any cleanup when the data is being discarded.
|
||||
/// </summary>
|
||||
void Dispose();
|
||||
}
|
||||
|
||||
public interface IReconcileData
|
||||
{
|
||||
/// <summary>
|
||||
/// Local tick when the data was created.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
uint GetTick();
|
||||
/// <summary>
|
||||
/// Sets the local tick when data was created.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
void SetTick(uint value);
|
||||
/// <summary>
|
||||
/// Allows for any cleanup when the data is being discarded.
|
||||
/// </summary>
|
||||
void Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 515754257f85574438408c7f5b268590
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,232 @@
|
||||
|
||||
using GameKit.Utilities;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace FishNet.Object.Prediction
|
||||
{
|
||||
/// <summary>
|
||||
/// Data to be used to configure smoothing for an owned predicted object.
|
||||
/// </summary>
|
||||
internal struct MoveRates
|
||||
{
|
||||
public float Position;
|
||||
public float Rotation;
|
||||
public float Scale;
|
||||
|
||||
public MoveRates(float value)
|
||||
{
|
||||
Position = value;
|
||||
Rotation = value;
|
||||
Scale = value;
|
||||
}
|
||||
public MoveRates(float position, float rotation)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
Scale = INSTANT_VALUE;
|
||||
}
|
||||
public MoveRates(float position, float rotation, float scale)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
Scale = scale;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if a positional move rate is set.
|
||||
/// </summary>
|
||||
public bool PositionSet => (Position != UNSET_VALUE);
|
||||
/// <summary>
|
||||
/// True if rotation move rate is set.
|
||||
/// </summary>
|
||||
public bool RotationSet => (Rotation != UNSET_VALUE);
|
||||
/// <summary>
|
||||
/// True if a scale move rate is set.
|
||||
/// </summary>
|
||||
public bool ScaleSet => (Scale != UNSET_VALUE);
|
||||
/// <summary>
|
||||
/// True if any move rate is set.
|
||||
/// </summary>
|
||||
public bool AnySet => (PositionSet || RotationSet || ScaleSet);
|
||||
|
||||
/// <summary>
|
||||
/// True if position move rate should be instant.
|
||||
/// </summary>
|
||||
public bool InstantPosition => (Position == INSTANT_VALUE);
|
||||
/// <summary>
|
||||
/// True if rotation move rate should be instant.
|
||||
/// </summary>
|
||||
public bool InstantRotation => (Rotation == INSTANT_VALUE);
|
||||
/// <summary>
|
||||
/// True if scale move rate should be instant.
|
||||
/// </summary>
|
||||
public bool InstantScale => (Scale == INSTANT_VALUE);
|
||||
|
||||
/// <summary>
|
||||
/// Sets all rates to instant.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetInstantRates()
|
||||
{
|
||||
Update(INSTANT_VALUE);
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets all rates to the same value.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update(float value)
|
||||
{
|
||||
Update(value, value, value);
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets rates for each property.
|
||||
/// </summary>
|
||||
public void Update(float position, float rotation, float scale)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
Scale = scale;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Value used when data is not set.
|
||||
/// </summary>
|
||||
public const float UNSET_VALUE = float.NegativeInfinity;
|
||||
/// <summary>
|
||||
/// Value used when move rate should be instant.
|
||||
/// </summary>
|
||||
public const float INSTANT_VALUE = float.PositiveInfinity;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Data to be used to configure smoothing for an owned predicted object.
|
||||
/// </summary>
|
||||
internal class MoveRatesCls : IResettable
|
||||
{
|
||||
public float Position;
|
||||
public float Rotation;
|
||||
public float Scale;
|
||||
|
||||
public float LastMultiplier { get; private set; } = 1f;
|
||||
|
||||
public MoveRatesCls(float value)
|
||||
{
|
||||
Position = value;
|
||||
Rotation = value;
|
||||
Scale = value;
|
||||
}
|
||||
public MoveRatesCls(float position, float rotation)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
Scale = INSTANT_VALUE;
|
||||
}
|
||||
public MoveRatesCls(float position, float rotation, float scale)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
Scale = scale;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if a positional move rate is set.
|
||||
/// </summary>
|
||||
public bool PositionSet => (Position != UNSET_VALUE);
|
||||
/// <summary>
|
||||
/// True if rotation move rate is set.
|
||||
/// </summary>
|
||||
public bool RotationSet => (Rotation != UNSET_VALUE);
|
||||
/// <summary>
|
||||
/// True if a scale move rate is set.
|
||||
/// </summary>
|
||||
public bool ScaleSet => (Scale != UNSET_VALUE);
|
||||
/// <summary>
|
||||
/// True if any move rate is set.
|
||||
/// </summary>
|
||||
public bool AnySet => (PositionSet || RotationSet || ScaleSet);
|
||||
|
||||
/// <summary>
|
||||
/// True if position move rate should be instant.
|
||||
/// </summary>
|
||||
public bool InstantPosition => (Position == INSTANT_VALUE);
|
||||
/// <summary>
|
||||
/// True if rotation move rate should be instant.
|
||||
/// </summary>
|
||||
public bool InstantRotation => (Rotation == INSTANT_VALUE);
|
||||
/// <summary>
|
||||
/// True if scale move rate should be instant.
|
||||
/// </summary>
|
||||
public bool InstantScale => (Scale == INSTANT_VALUE);
|
||||
|
||||
public MoveRatesCls()
|
||||
{
|
||||
Update(UNSET_VALUE, UNSET_VALUE, UNSET_VALUE);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies all rates by value.
|
||||
/// </summary>
|
||||
public void Multiply(float value)
|
||||
{
|
||||
LastMultiplier = value;
|
||||
Position *= value;
|
||||
Rotation *= value;
|
||||
Scale *= value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets all rates to instant.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetInstantRates()
|
||||
{
|
||||
Update(INSTANT_VALUE);
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets all rates to the same value.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update(float value)
|
||||
{
|
||||
Update(value, value, value);
|
||||
}
|
||||
/// <summary>
|
||||
/// Updaes values.
|
||||
/// </summary>
|
||||
public void Update(float position, float rotation, float scale)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
Scale = scale;
|
||||
}
|
||||
/// <summary>
|
||||
/// Updaes values.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update(MoveRatesCls mr)
|
||||
{
|
||||
Update(mr.Position, mr.Rotation, mr.Scale);
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
Position = UNSET_VALUE;
|
||||
Rotation = UNSET_VALUE;
|
||||
Scale = UNSET_VALUE;
|
||||
}
|
||||
|
||||
public void InitializeState() { }
|
||||
|
||||
/// <summary>
|
||||
/// Value used when data is not set.
|
||||
/// </summary>
|
||||
public const float UNSET_VALUE = float.NegativeInfinity;
|
||||
/// <summary>
|
||||
/// Value used when move rate should be instant.
|
||||
/// </summary>
|
||||
public const float INSTANT_VALUE = float.PositiveInfinity;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1bddf57861232884ca21f7e97a6d662d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,20 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object.Prediction
|
||||
{
|
||||
/// <summary>
|
||||
/// Data to be used to configure smoothing for an owned predicted object.
|
||||
/// </summary>
|
||||
internal struct SetInterpolationSmootherData
|
||||
{
|
||||
public Transform GraphicalObject;
|
||||
public byte Interpolation;
|
||||
public NetworkObject NetworkObject;
|
||||
public bool SmoothPosition;
|
||||
public bool SmoothRotation;
|
||||
public bool SmoothScale;
|
||||
public float TeleportThreshold;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5fecbbc171a81f4a8aaf30aebb1caef
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,56 @@
|
||||
using FishNet.Utility.Constant;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo(UtilityConstants.CODEGEN_ASSEMBLY_NAME)]
|
||||
namespace FishNet.Object
|
||||
{
|
||||
|
||||
public enum ReplicateState : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// The default value of this state.
|
||||
/// This value should never occur when a replicate runs.
|
||||
/// </summary>
|
||||
Invalid = 0,
|
||||
/// <summary>
|
||||
/// Data is user made, such if it were created within OnTick.
|
||||
/// This occurs when a replicate is called from user code.
|
||||
/// </summary>
|
||||
UserCreated = 1,
|
||||
/// <summary>
|
||||
/// No data was made from the user; default data is used with an estimated tick.
|
||||
/// This occurs on non-owned objects or server when a replicate is called from user code, and there are no datas enqeued.
|
||||
/// </summary>
|
||||
Predicted = 2,
|
||||
/// <summary>
|
||||
/// Data is user made, such if it were created within OnTick.
|
||||
/// This occurs when a replicate is replaying past datas, triggered by a reconcile.
|
||||
/// </summary>
|
||||
ReplayedUserCreated = 3,
|
||||
/// <summary>
|
||||
/// No data was made from the user; default data is used with an estimated tick.
|
||||
/// This occurs when a replicate would be replaying past datas, triggered by a reconcile, but there is no user created data for the tick.
|
||||
/// </summary>
|
||||
ReplayedPredicted = 4,
|
||||
}
|
||||
|
||||
public static class ReplicateStateExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns if value is valid.
|
||||
/// </summary>
|
||||
public static bool IsValid(this ReplicateState value) => (value != ReplicateState.Invalid);
|
||||
/// <summary>
|
||||
/// Returns if value is replayed.
|
||||
/// </summary>
|
||||
public static bool IsReplayed(this ReplicateState value) => (value == ReplicateState.ReplayedPredicted || value == ReplicateState.ReplayedUserCreated);
|
||||
/// <summary>
|
||||
/// Returns if value is user created.
|
||||
/// </summary>
|
||||
public static bool IsUserCreated(this ReplicateState value) => (value == ReplicateState.UserCreated || value == ReplicateState.ReplayedUserCreated);
|
||||
/// <summary>
|
||||
/// Returns if value is predicted.
|
||||
/// </summary>
|
||||
public static bool IsPredicted(this ReplicateState value) => !value.IsUserCreated();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f8deb74673db557489cf93509af3cb21
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
25
phr/StickGame/Assets/FishNet/Runtime/Object/RpcLinkType.cs
Normal file
25
phr/StickGame/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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4fa68ca6a21d08f42980dcf68f984d53
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace FishNet.Object
|
||||
{
|
||||
|
||||
internal enum SyncTypeWriteType
|
||||
{
|
||||
Observers = 0,
|
||||
Owner = 1,
|
||||
All = 2,
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e6406cc7d5fe47c44a26298145f54b00
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5429986c563a23a4c86f7b6724e41a70
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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:
|
||||
@@ -0,0 +1,53 @@
|
||||
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 SyncType so that it can be re-used
|
||||
/// </summary>
|
||||
[Obsolete("Use ResetState().")]
|
||||
void Reset();
|
||||
/// <summary>
|
||||
/// Resets the SyncType so that it can be re-used
|
||||
/// </summary>
|
||||
void ResetState();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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,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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 24688925098da4d42be8dbfa66b88b82
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,664 @@
|
||||
#if FISHNET_RELEASE_MODE
|
||||
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>
|
||||
/// True if the server side has initialized this SyncType.
|
||||
/// </summary>
|
||||
public bool OnStartServerCalled { get; private set; }
|
||||
/// <summary>
|
||||
/// True if the client side has initialized this SyncType.
|
||||
/// </summary>
|
||||
public bool OnStartClientCalled { get; private set; }
|
||||
/// <summary>
|
||||
/// Next time this SyncType may send data.
|
||||
/// This is also the next time a client may send to the server when using client-authoritative SyncTypes.
|
||||
/// </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 for the NetworkBehaviour.
|
||||
/// </summary>
|
||||
/// <param name="asServer">True if OnStartServer was called, false if OnStartClient.</param>
|
||||
public virtual void OnStartCallback(bool asServer)
|
||||
{
|
||||
if (asServer)
|
||||
OnStartServerCalled = true;
|
||||
else
|
||||
OnStartClientCalled = true;
|
||||
}
|
||||
/// <summary>
|
||||
/// Called before OnStopXXXX has occurred for the NetworkBehaviour.
|
||||
/// </summary>
|
||||
/// <param name="asServer">True if OnStopServer was called, false if OnStopClient.</param>
|
||||
public virtual void OnStopCallback(bool asServer)
|
||||
{
|
||||
if (asServer)
|
||||
OnStartServerCalled = false;
|
||||
else
|
||||
OnStartClientCalled = false;
|
||||
}
|
||||
|
||||
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.WritePermission == WritePermission.ClientUnsynchronized) || (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. You can disable this warning by setting WritePermissions to {WritePermission.ClientUnsynchronized.ToString()}.");
|
||||
}
|
||||
|
||||
/// <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 SyncTimeMet(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.LocalTick + _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).")] //Remove on 2023/06/01
|
||||
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>
|
||||
[Obsolete("Use ResetState().")]
|
||||
public virtual void Reset() { }
|
||||
/// <summary>
|
||||
/// Resets initialized values.
|
||||
/// </summary>
|
||||
public virtual void ResetState()
|
||||
{
|
||||
NextSyncTick = 0;
|
||||
ResetDirty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
#else
|
||||
|
||||
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>
|
||||
/// True if the server side has initialized this SyncType.
|
||||
/// </summary>
|
||||
public bool OnStartServerCalled { get; private set; }
|
||||
/// <summary>
|
||||
/// True if the client side has initialized this SyncType.
|
||||
/// </summary>
|
||||
public bool OnStartClientCalled { get; private set; }
|
||||
/// <summary>
|
||||
/// Next time this SyncType may send data.
|
||||
/// This is also the next time a client may send to the server when using client-authoritative SyncTypes.
|
||||
/// </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;
|
||||
/// <summary>
|
||||
/// Last localTick when full data was written.
|
||||
/// </summary>
|
||||
protected uint _lastWriteFullLocalTick;
|
||||
/// <summary>
|
||||
/// Id of the current change since the last full write.
|
||||
/// This is used to prevent duplicates caused by deltas writing after full writes when clients already received the delta in the full write, such as a spawn message.
|
||||
/// </summary>
|
||||
protected uint _changeId;
|
||||
/// <summary>
|
||||
/// Last changeId read.
|
||||
/// </summary>
|
||||
private long _lastReadDirtyId = DEFAULT_LAST_READ_DIRTYID;
|
||||
#endregion
|
||||
|
||||
#region Const.
|
||||
/// <summary>
|
||||
/// Default value for LastReadDirtyId.
|
||||
/// </summary>
|
||||
private const long DEFAULT_LAST_READ_DIRTYID = -1;
|
||||
#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 for the NetworkBehaviour.
|
||||
/// </summary>
|
||||
/// <param name="asServer">True if OnStartServer was called, false if OnStartClient.</param>
|
||||
public virtual void OnStartCallback(bool asServer)
|
||||
{
|
||||
if (asServer)
|
||||
OnStartServerCalled = true;
|
||||
else
|
||||
OnStartClientCalled = true;
|
||||
}
|
||||
/// <summary>
|
||||
/// Called before OnStopXXXX has occurred for the NetworkBehaviour.
|
||||
/// </summary>
|
||||
/// <param name="asServer">True if OnStopServer was called, false if OnStopClient.</param>
|
||||
public virtual void OnStopCallback(bool asServer)
|
||||
{
|
||||
if (asServer)
|
||||
OnStartServerCalled = false;
|
||||
else
|
||||
OnStartClientCalled = false;
|
||||
}
|
||||
|
||||
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.WritePermission == WritePermission.ClientUnsynchronized) || (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. You can disable this warning by setting WritePermissions to {WritePermission.ClientUnsynchronized.ToString()}.");
|
||||
}
|
||||
|
||||
/// <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);
|
||||
//If first time dirtying increase dirtyId.
|
||||
if (IsDirty != canDirty)
|
||||
_changeId++;
|
||||
IsDirty |= canDirty;
|
||||
|
||||
return canDirty;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Reads the change Id and returns if changes should be ignored.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected bool ReadChangeId(PooledReader reader)
|
||||
{
|
||||
bool reset = reader.ReadBoolean();
|
||||
|
||||
uint id = reader.ReadUInt32();
|
||||
bool ignoreResults = !reset && (id <= _lastReadDirtyId);
|
||||
_lastReadDirtyId = id;
|
||||
return ignoreResults;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writers the current ChangeId, and if it has been reset.
|
||||
/// </summary>
|
||||
protected void WriteChangeId(PooledWriter writer, bool fullWrite)
|
||||
{
|
||||
/* Fullwrites do not reset the Id, only
|
||||
* delta changes do. */
|
||||
bool resetId = (!fullWrite && NetworkManager.TimeManager.LocalTick > _lastWriteFullLocalTick);
|
||||
writer.WriteBoolean(resetId);
|
||||
//If to reset Id then do so.
|
||||
if (resetId)
|
||||
_changeId = 0;
|
||||
//Write Id.
|
||||
writer.WriteUInt32(_changeId);
|
||||
}
|
||||
|
||||
/// <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 SyncTimeMet(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>
|
||||
protected virtual void WriteHeader(PooledWriter writer, bool resetSyncTick = true)
|
||||
{
|
||||
if (resetSyncTick)
|
||||
NextSyncTick = NetworkManager.TimeManager.LocalTick + _timeToTicks;
|
||||
|
||||
writer.WriteByte((byte)SyncIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that a full write has occurred.
|
||||
/// This is called from WriteFull, or can be called manually.
|
||||
/// </summary>
|
||||
protected void FullWritten()
|
||||
{
|
||||
_lastWriteFullLocalTick = NetworkManager.TimeManager.LocalTick;
|
||||
}
|
||||
/// <summary>
|
||||
/// Writes all values for the SyncType.
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public virtual void WriteFull(PooledWriter writer)
|
||||
{
|
||||
FullWritten();
|
||||
}
|
||||
/// <summary>
|
||||
/// Sets current value as client.
|
||||
/// </summary>
|
||||
/// <param name="reader"></param>
|
||||
[Obsolete("Use Read(PooledReader, bool).")] //Remove on 2023/06/01
|
||||
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>
|
||||
[Obsolete("Use ResetState().")]
|
||||
public virtual void Reset() { }
|
||||
/// <summary>
|
||||
/// Resets initialized values.
|
||||
/// </summary>
|
||||
public virtual void ResetState()
|
||||
{
|
||||
_lastWriteFullLocalTick = 0;
|
||||
_changeId = 0;
|
||||
_lastReadDirtyId = DEFAULT_LAST_READ_DIRTYID;
|
||||
NextSyncTick = 0;
|
||||
ResetDirty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a6f26e3f8016cc499b3fa99e7368fbc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,636 @@
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Managing.Logging;
|
||||
using FishNet.Object.Synchronizing.Internal;
|
||||
using FishNet.Serializing;
|
||||
using GameKit.Utilities;
|
||||
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);
|
||||
//True to warn if this object was deinitialized on the server.
|
||||
bool deinitialized = (asClientAndHost && !base.OnStartServerCalled);
|
||||
if (deinitialized)
|
||||
base.NetworkManager.LogWarning($"SyncType {GetType().Name} received a Read but was deinitialized on the server. Client callback values may be incorrect. This is a ClientHost limitation.");
|
||||
|
||||
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>();
|
||||
if (!deinitialized)
|
||||
collection[key] = value;
|
||||
}
|
||||
//Clear.
|
||||
else if (operation == SyncDictionaryOperation.Clear)
|
||||
{
|
||||
if (!deinitialized)
|
||||
collection.Clear();
|
||||
}
|
||||
//Remove.
|
||||
else if (operation == SyncDictionaryOperation.Remove)
|
||||
{
|
||||
key = reader.Read<TKey>();
|
||||
if (!deinitialized)
|
||||
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 ResetState()
|
||||
{
|
||||
base.ResetState();
|
||||
_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:
|
||||
@@ -0,0 +1,623 @@
|
||||
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 static List<T> _cache = new List<T>();
|
||||
/// <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);
|
||||
//True to warn if this object was deinitialized on the server.
|
||||
bool deinitialized = (asClientAndHost && !base.OnStartServerCalled);
|
||||
if (deinitialized)
|
||||
base.NetworkManager.LogWarning($"SyncType {GetType().Name} received a Read but was deinitialized on the server. Client callback values may be incorrect. This is a ClientHost limitation.");
|
||||
|
||||
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>();
|
||||
if (!deinitialized)
|
||||
collection.Add(next);
|
||||
}
|
||||
//Clear.
|
||||
else if (operation == SyncHashSetOperation.Clear)
|
||||
{
|
||||
if (!deinitialized)
|
||||
collection.Clear();
|
||||
}
|
||||
//Remove.
|
||||
else if (operation == SyncHashSetOperation.Remove)
|
||||
{
|
||||
next = reader.Read<T>();
|
||||
if (!deinitialized)
|
||||
collection.Remove(next);
|
||||
}
|
||||
//Updated.
|
||||
else if (operation == SyncHashSetOperation.Update)
|
||||
{
|
||||
next = reader.Read<T>();
|
||||
if (!deinitialized)
|
||||
{
|
||||
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 ResetState()
|
||||
{
|
||||
base.ResetState();
|
||||
_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)
|
||||
{
|
||||
_cache.AddRange(collection);
|
||||
|
||||
int count = _cache.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
T entry = _cache[i];
|
||||
if (!other.Contains(entry))
|
||||
Remove(entry);
|
||||
}
|
||||
}
|
||||
|
||||
_cache.Clear();
|
||||
}
|
||||
|
||||
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:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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:
|
||||
@@ -0,0 +1,309 @@
|
||||
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);
|
||||
|
||||
T prev = _value;
|
||||
|
||||
/* If the network has not been initialized yet then the
|
||||
* object has not yet spawned. While we know this is being called
|
||||
* from user code, because the object is not spawned we do not
|
||||
* know if server or client is active. When this happens invoke
|
||||
* will be called on server and client, and since this is not
|
||||
* network initialized the values will be cached where the invoke
|
||||
* will occur after spawn. */
|
||||
if (!isNetworkInitialized)
|
||||
{
|
||||
//Value did not change.
|
||||
if (Comparers.EqualityCompare<T>(_value, nextValue))
|
||||
return;
|
||||
|
||||
_value = nextValue;
|
||||
InvokeOnChange(prev, _value, true);
|
||||
InvokeOnChange(prev, _value, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
//Value did not change.
|
||||
if (Comparers.EqualityCompare<T>(_value, nextValue))
|
||||
return;
|
||||
|
||||
_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)
|
||||
{
|
||||
//Do not mark dirty if the object is not been network 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);
|
||||
_serverOnChange = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
_serverOnChange = new CachedOnChange(prev, next);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (base.NetworkBehaviour.OnStartClientCalled)
|
||||
{
|
||||
OnChange?.Invoke(prev, next, asServer);
|
||||
_clientOnChange = null;
|
||||
}
|
||||
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 ResetState()
|
||||
{
|
||||
base.ResetState();
|
||||
_value = _initialValue;
|
||||
_previousClientValue = _initialValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f319403eec508734a93d723617ab1136
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace FishNet.Object.Synchronizing
|
||||
{
|
||||
/// <summary>
|
||||
/// Which clients or server may write updates.
|
||||
/// </summary>
|
||||
public enum WritePermission : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Only the server can change the value of the SyncType.
|
||||
/// </summary>
|
||||
ServerOnly = 0,
|
||||
/// <summary>
|
||||
/// Server and clients can change the value of the SyncType. When changed by client the value is not sent to the server.
|
||||
/// </summary>
|
||||
ClientUnsynchronized = 1,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2696d0da2ff02e8499a8351a3021008f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,117 @@
|
||||
using GameKit.Utilities;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
[System.Serializable]
|
||||
public class TransformPropertiesCls : IResettable
|
||||
{
|
||||
public Vector3 Position;
|
||||
public Quaternion Rotation;
|
||||
public Vector3 LocalScale;
|
||||
|
||||
public TransformPropertiesCls() { }
|
||||
public TransformPropertiesCls(Vector3 position, Quaternion rotation, Vector3 localScale)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
LocalScale = localScale;
|
||||
}
|
||||
|
||||
public void InitializeState() { }
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ResetState()
|
||||
{
|
||||
Update(Vector3.zero, Quaternion.identity, Vector3.zero);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update(Transform t)
|
||||
{
|
||||
Update(t.position, t.rotation, t.localScale);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update(TransformPropertiesCls tp)
|
||||
{
|
||||
Update(tp.Position, tp.Rotation, tp.LocalScale);
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update(TransformProperties tp)
|
||||
{
|
||||
Update(tp.Position, tp.Rotation, tp.LocalScale);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update(Vector3 position, Quaternion rotation)
|
||||
{
|
||||
Update(position, rotation, LocalScale);
|
||||
}
|
||||
|
||||
public void Update(Vector3 position, Quaternion rotation, Vector3 localScale)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
LocalScale = localScale;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns this classes values as the struct version of TransformProperties.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public TransformProperties ToStruct()
|
||||
{
|
||||
TransformProperties result = new TransformProperties(Position, Rotation, LocalScale);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public struct TransformProperties
|
||||
{
|
||||
public Vector3 Position;
|
||||
public Quaternion Rotation;
|
||||
public Vector3 LocalScale;
|
||||
|
||||
public TransformProperties(Vector3 position, Quaternion rotation, Vector3 localScale)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
LocalScale = localScale;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Reset()
|
||||
{
|
||||
Update(Vector3.zero, Quaternion.identity, Vector3.zero);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update(Transform t)
|
||||
{
|
||||
Update(t.position, t.rotation, t.localScale);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update(TransformProperties tp)
|
||||
{
|
||||
Update(tp.Position, tp.Rotation, tp.LocalScale);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Update(Vector3 position, Quaternion rotation)
|
||||
{
|
||||
Update(position, rotation, LocalScale);
|
||||
}
|
||||
|
||||
public void Update(Vector3 position, Quaternion rotation, Vector3 localScale)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
LocalScale = localScale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user