上传YomovSDK

This commit is contained in:
Sora丶kong
2026-03-03 03:15:46 +08:00
parent 9096da7e6c
commit eb97f31065
6477 changed files with 1932208 additions and 3 deletions

View 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;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e2c79ec60813585469c43b4539e3d0c5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,35 @@
using FishNet.Documenting;
namespace FishNet.Object
{
/// <summary>
/// Properties which have changed on a transform.
/// </summary>
[System.Flags]
[APIExclude]
internal enum ChangedTransformProperties : byte
{
Unset = 0,
LocalPosition = 1,
LocalRotation = 2,
LocalScale = 4,
}
[APIExclude]
internal static partial class ChangedTransformPropertiesEnum
{
/// <summary>
/// Returns if whole contains part.
/// </summary>
/// <param name="whole"></param>
/// <param name="part"></param>
/// <returns></returns>
public static bool Contains(ChangedTransformProperties whole, ChangedTransformProperties part)
{
return (whole & part) == part;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1870202c019b99348aaedbe2029caf33
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5dbd9cdda4843f34ab416273d80f83c8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,13 @@
using FishNet.Object.Helping;
namespace FishNet.Object
{
public enum DespawnType : byte
{
Destroy = 0,
Pool = 1,
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 205c3ddfc86fbaa44aa0b92ecef58474
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0cd019316f743a94a8131c7c60d8ebd6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,144 @@
#if UNITY_EDITOR
#if PREDICTION_V2
using FishNet.Editing;
using FishNet.Object;
using FishNet.Object.Prediction;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
namespace FishNet.Object.Editing
{
[CustomEditor(typeof(NetworkObject), true)]
[CanEditMultipleObjects]
public class NetworkObjectEditor : Editor
{
private SerializedProperty _aiv;
private SerializedProperty _isNetworked;
private SerializedProperty _isGlobal;
private SerializedProperty _initializeOrder;
private SerializedProperty _defaultDespawnType;
private SerializedProperty _enablePrediction;
private SerializedProperty _predictionType;
private SerializedProperty _graphicalObject;
private SerializedProperty _enableStateForwarding;
private SerializedProperty _ownerInterpolation;
private SerializedProperty _enableTeleport;
private SerializedProperty _ownerTeleportThreshold;
//private SerializedProperty _futurePredictionTime;
private SerializedProperty _spectatorAdaptiveInterpolation;
private SerializedProperty _spectatorInterpolation;
private SerializedProperty _adaptiveSmoothingType;
private SerializedProperty _customSmoothingData;
private SerializedProperty _preconfiguredSmoothingDataPreview;
protected virtual void OnEnable()
{
_aiv = serializedObject.FindProperty("AdaptiveInterpolationValue");
_isNetworked = serializedObject.FindProperty(nameof(_isNetworked));
_isGlobal = serializedObject.FindProperty(nameof(_isGlobal));
_initializeOrder = serializedObject.FindProperty(nameof(_initializeOrder));
_defaultDespawnType = serializedObject.FindProperty(nameof(_defaultDespawnType));
_enablePrediction = serializedObject.FindProperty(nameof(_enablePrediction));
_predictionType = serializedObject.FindProperty(nameof(_predictionType));
_graphicalObject = serializedObject.FindProperty(nameof(_graphicalObject));
_enableStateForwarding = serializedObject.FindProperty(nameof(_enableStateForwarding));
_ownerInterpolation = serializedObject.FindProperty(nameof(_ownerInterpolation));
_enableTeleport = serializedObject.FindProperty(nameof(_enableTeleport));
_ownerTeleportThreshold = serializedObject.FindProperty(nameof(_ownerTeleportThreshold));
//_futurePredictionTime = serializedObject.FindProperty(nameof(_futurePredictionTime));
_spectatorAdaptiveInterpolation = serializedObject.FindProperty(nameof(_spectatorAdaptiveInterpolation));
_spectatorInterpolation = serializedObject.FindProperty(nameof(_spectatorInterpolation));
_adaptiveSmoothingType = serializedObject.FindProperty(nameof(_adaptiveSmoothingType));
_customSmoothingData = serializedObject.FindProperty(nameof(_customSmoothingData));
_preconfiguredSmoothingDataPreview = serializedObject.FindProperty(nameof(_preconfiguredSmoothingDataPreview));
}
public override void OnInspectorGUI()
{
serializedObject.Update();
NetworkObject nob = (NetworkObject)target;
GUI.enabled = false;
EditorGUILayout.ObjectField("Script:", MonoScript.FromMonoBehaviour(nob), typeof(NetworkObject), false);
GUI.enabled = true;
EditorGUILayout.PropertyField(_aiv);
EditorGUILayout.PropertyField(_isNetworked);
EditorGUILayout.PropertyField(_isGlobal);
EditorGUILayout.PropertyField(_initializeOrder);
EditorGUILayout.PropertyField(_defaultDespawnType);
EditorGUILayout.PropertyField(_enablePrediction);
if (_enablePrediction.boolValue == true)
{
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_predictionType);
EditorGUILayout.PropertyField(_graphicalObject);
GUI.enabled = false;
EditorGUILayout.PropertyField(_enableStateForwarding);
GUI.enabled = true;
EditorGUILayout.LabelField("Owner", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_ownerInterpolation, new GUIContent("Interpolation"));
EditorGUILayout.PropertyField(_enableTeleport);
if (_enableTeleport.boolValue == true)
{
EditorGUI.indentLevel++;
EditorGUILayout.PropertyField(_ownerTeleportThreshold, new GUIContent("Teleport Threshold"));
EditorGUI.indentLevel--;
}
EditorGUI.indentLevel--;
EditorGUILayout.LabelField("Spectator", EditorStyles.boldLabel);
EditorGUI.indentLevel++;
GUI.enabled = false;
EditorGUILayout.PropertyField(_spectatorAdaptiveInterpolation, new GUIContent("Adaptive Interpolation"));
GUI.enabled = true;
//if (_futurePredictionTime.floatValue <= 0f)
if (_spectatorAdaptiveInterpolation.boolValue == false)
{
EditorGUILayout.PropertyField(_spectatorInterpolation, new GUIContent("Interpolation"));
}
else
{
EditorGUILayout.PropertyField(_adaptiveSmoothingType);
EditorGUI.indentLevel++;
if (_adaptiveSmoothingType.intValue == (int)AdaptiveSmoothingType.Custom)
{
EditorGUILayout.PropertyField(_customSmoothingData);
}
else
{
GUI.enabled = false;
EditorGUILayout.PropertyField(_preconfiguredSmoothingDataPreview, new GUIContent("Preconfigured Smoothing Data"));
GUI.enabled = true;
}
EditorGUI.indentLevel--;
}
EditorGUI.indentLevel--;
EditorGUI.indentLevel--;
}
EditorGUILayout.Space();
serializedObject.ApplyModifiedProperties();
}
}
}
#endif
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b2d667433eee79d4e8661a84b1587b8a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,13 @@

namespace FishNet.Object
{
/// <summary>
/// This may be added at runtime to find objects without any network scripts, beneath a NetworkObject.
/// </summary>
public class EmptyNetworkBehaviour : NetworkBehaviour
{
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8a6a39c46bf52104ba8efe3100bce3f7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b4af6267cb928f34fa47fdf8f80eccf9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,39 @@
using FishNet.Object.Helping;
namespace FishNet.Object
{
#region Types.
/// <summary>
/// Lookup data for a RPC Link.
/// </summary>
internal struct RpcLink
{
/// <summary>
/// ObjectId for link.
/// </summary>
public int ObjectId;
/// <summary>
/// NetworkBehaviour component index on ObjectId.
/// </summary>
public byte ComponentIndex;
/// <summary>
/// RpcHash for link.
/// </summary>
public uint RpcHash;
/// <summary>
/// Type of Rpc link is for.
/// </summary>
public RpcType RpcType;
public RpcLink(int objectId, byte componentIndex, uint rpcHash, RpcType rpcType)
{
ObjectId = objectId;
ComponentIndex = componentIndex;
RpcHash = rpcHash;
RpcType = rpcType;
}
}
#endregion
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 05a91745dd829d043aadf391ac7b233e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,13 @@
namespace FishNet.Object.Helping
{
public enum RpcType : int
{
None = 0,
Server = 1,
Observers = 2,
Target = 4,
Replicate = 8,
Reconcile = 16
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 09f9e7236f988c64fad54645f4cc7f8b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,49 @@

namespace FishNet.Object.Helping
{
public static class CodegenHelper
{
/// <summary>
/// Returns if a NetworkObject is deinitializing.
/// </summary>
/// <param name="nb"></param>
/// <returns></returns>
public static bool NetworkObject_Deinitializing(NetworkBehaviour nb)
{
if (nb == null)
return true;
return nb.IsDeinitializing;
}
/// <summary>
/// Returns if running as server.
/// </summary>
/// <param name="nb"></param>
/// <returns></returns>
public static bool IsServer(NetworkBehaviour nb)
{
if (nb == null)
return false;
return nb.IsServer;
}
/// <summary>
/// Returns if running as client.
/// </summary>
/// <param name="nb"></param>
/// <returns></returns>
public static bool IsClient(NetworkBehaviour nb)
{
if (nb == null)
return false;
return nb.IsClient;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 149cde0042627604d810c2c7fc0f9176
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,217 @@
#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.
#if !PREDICTION_V2
ClearReplicateCache_Virtual(true);
#else
ClearReplicateCache();
#endif
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)
{
#if !PREDICTION_V2
ClearReplicateCache_Virtual(false);
#else
ClearReplicateCache();
#endif
}
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) { }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a9ddaf08801752b49bcfe720217df74a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,22 @@
using FishNet.Managing.Logging;
using UnityEngine;
namespace FishNet.Object
{
public abstract partial class NetworkBehaviour : MonoBehaviour
{
/// <summary>
/// True if can log for loggingType.
/// </summary>
/// <param name="loggingType">Type of logging being filtered.</param>
/// <returns></returns>
public bool CanLog(LoggingType loggingType)
{
return (NetworkManager == null) ? false : NetworkManager.CanLog(loggingType);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ad4dd2795a9a00e4d814892ac1a67157
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 074dac4dd3f9f6a4d8c1eb1191334472
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,270 @@
#if UNITY_2020_3_OR_NEWER && UNITY_EDITOR_WIN
using FishNet.CodeAnalysis.Annotations;
#endif
using FishNet.Component.ColliderRollback;
using FishNet.Connection;
using FishNet.Managing;
using FishNet.Managing.Client;
using FishNet.Managing.Observing;
using FishNet.Managing.Predicting;
using FishNet.Managing.Scened;
using FishNet.Managing.Server;
using FishNet.Managing.Timing;
using FishNet.Managing.Transporting;
using FishNet.Observing;
using System;
using System.Collections.Generic;
using UnityEngine;
namespace FishNet.Object
{
public abstract partial class NetworkBehaviour : MonoBehaviour
{
/// <summary>
/// True if the NetworkObject for this NetworkBehaviour is deinitializing.
/// </summary>
public bool IsDeinitializing => _networkObjectCache.IsDeinitializing;
/// <summary>
/// NetworkManager for this object.
/// </summary>
public NetworkManager NetworkManager => _networkObjectCache.NetworkManager;
/// <summary>
/// ServerManager for this object.
/// </summary>
public ServerManager ServerManager => _networkObjectCache.ServerManager;
/// <summary>
/// ClientManager for this object.
/// </summary>
public ClientManager ClientManager => _networkObjectCache.ClientManager;
/// <summary>
/// ObserverManager for this object.
/// </summary>
public ObserverManager ObserverManager => _networkObjectCache.ObserverManager;
/// <summary>
/// TransportManager for this object.
/// </summary>
public TransportManager TransportManager => _networkObjectCache.TransportManager;
/// <summary>
/// TimeManager for this object.
/// </summary>
public TimeManager TimeManager => _networkObjectCache.TimeManager;
/// <summary>
/// SceneManager for this object.
/// </summary>
public SceneManager SceneManager => _networkObjectCache.SceneManager;
/// <summary>
/// PredictionManager for this object.
/// </summary>
public PredictionManager PredictionManager => _networkObjectCache.PredictionManager;
/// <summary>
/// RollbackManager for this object.
/// </summary>
public RollbackManager RollbackManager => _networkObjectCache.RollbackManager;
/// <summary>
/// NetworkObserver on this object.
/// </summary>
public NetworkObserver NetworkObserver => _networkObjectCache.NetworkObserver;
/// <summary>
/// True if the client is active and authenticated.
/// </summary>
public bool IsClient => _networkObjectCache.IsClient;
/// <summary>
/// True if only the client is active and authenticated.
/// </summary>
public bool IsClientOnly => _networkObjectCache.IsClientOnly;
/// <summary>
/// True if server is active.
/// </summary>
public bool IsServer => _networkObjectCache.IsServer;
/// <summary>
/// True if only the server is active.
/// </summary>
public bool IsServerOnly => _networkObjectCache.IsServerOnly;
/// <summary>
/// True if client and server are active.
/// </summary>
public bool IsHost => _networkObjectCache.IsHost;
/// <summary>
/// True if client nor server are active.
/// </summary>
public bool IsOffline => _networkObjectCache.IsOffline;
/// <summary>
/// True if this instance is considered 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.
/// </summary>
#if UNITY_2020_3_OR_NEWER && UNITY_EDITOR_WIN
[PreventUsageInside("global::FishNet.Object.NetworkBehaviour", "OnStartServer", "")]
[PreventUsageInside("global::FishNet.Object.NetworkBehaviour", "OnStartNetwork", " Use base.Owner.IsLocalClient instead.")]
[PreventUsageInside("global::FishNet.Object.NetworkBehaviour", "Awake", "")]
[PreventUsageInside("global::FishNet.Object.NetworkBehaviour", "Start", "")]
#endif
public bool IsOwner => _networkObjectCache.IsOwner;
/// <summary>
/// Owner of this object.
/// </summary>
public NetworkConnection Owner
{
get
{
//Ensures a null Owner is never returned.
if (_networkObjectCache == null)
return FishNet.Managing.NetworkManager.EmptyConnection;
return _networkObjectCache.Owner;
}
}
/// <summary>
/// ClientId for this NetworkObject owner.
/// </summary>
public int OwnerId => _networkObjectCache.OwnerId;
/// <summary>
/// Unique Id for this _networkObjectCache. This does not represent the object owner.
/// </summary>
public int ObjectId => _networkObjectCache.ObjectId;
/// <summary>
/// The local connection of the client calling this method.
/// </summary>
public NetworkConnection LocalConnection => _networkObjectCache.LocalConnection;
/// <summary>
/// Returns if a connection is the owner of this object.
/// </summary>
/// <param name="connection"></param>
/// <returns></returns>
public bool OwnerMatches(NetworkConnection connection)
{
return (_networkObjectCache.Owner == connection);
}
/// <summary>
/// Despawns a GameObject. Only call from the server.
/// </summary>
/// <param name="go">GameObject to despawn.</param>
/// <param name="despawnType">What happens to the object after being despawned.</param>
public void Despawn(GameObject go, DespawnType? despawnType = null)
{
if (!IsNetworkObjectNull(true))
_networkObjectCache.Despawn(go, despawnType);
}
/// <summary>
/// Despawns a NetworkObject. Only call from the server.
/// </summary>
/// <param name="nob">NetworkObject to despawn.</param>
/// <param name="despawnType">What happens to the object after being despawned.</param>
public void Despawn(NetworkObject nob, DespawnType? despawnType = null)
{
if (!IsNetworkObjectNull(true))
_networkObjectCache.Despawn(nob, despawnType);
}
/// <summary>
/// Despawns this _networkObjectCache. Can only be called on the server.
/// </summary>
/// <param name="despawnType">What happens to the object after being despawned.</param>
public void Despawn(DespawnType? despawnType = null)
{
if (!IsNetworkObjectNull(true))
_networkObjectCache.Despawn(despawnType);
}
/// <summary>
/// Spawns an object over the network. Can only be called on the server.
/// </summary>
/// <param name="go">GameObject instance to spawn.</param>
/// <param name="ownerConnection">Connection to give ownership to.</param>
public void Spawn(GameObject go, NetworkConnection ownerConnection = null)
{
if (IsNetworkObjectNull(true))
return;
_networkObjectCache.Spawn(go, ownerConnection);
}
/// <summary>
/// Spawns an object over the network. Can only be called on the server.
/// </summary>
/// <param name="nob">GameObject instance to spawn.</param>
/// <param name="ownerConnection">Connection to give ownership to.</param>
public void Spawn(NetworkObject nob, NetworkConnection ownerConnection = null)
{
if (IsNetworkObjectNull(true))
return;
_networkObjectCache.Spawn(nob, ownerConnection);
}
/// <summary>
/// Returns if NetworkObject is null.
/// </summary>
/// <param name="warn">True to throw a warning if null.</param>
/// <returns></returns>
private bool IsNetworkObjectNull(bool warn)
{
bool isNull = (_networkObjectCache == null);
if (isNull && warn)
NetworkManager.LogWarning($"NetworkObject is null. This can occur if this object is not spawned, or initialized yet.");
return isNull;
}
/// <summary>
/// Removes ownership from all clients.
/// </summary>
public void RemoveOwnership()
{
_networkObjectCache.GiveOwnership(null, true);
}
/// <summary>
/// Gives ownership to newOwner.
/// </summary>
/// <param name="newOwner"></param>
public void GiveOwnership(NetworkConnection newOwner)
{
_networkObjectCache.GiveOwnership(newOwner, true);
}
#region Registered components
/// <summary>
/// Invokes an action when a specified component becomes registered. Action will invoke immediately if already registered.
/// </summary>
/// <typeparam name="T">Component type.</typeparam>
/// <param name="handler">Action to invoke.</param>
public void RegisterInvokeOnInstance<T>(Action<UnityEngine.Component> handler) where T : UnityEngine.Component => _networkObjectCache.RegisterInvokeOnInstance<T>(handler);
/// <summary>
/// Removes an action to be invoked when a specified component becomes registered.
/// </summary>
/// <typeparam name="T">Component type.</typeparam>
/// <param name="handler">Action to invoke.</param>
public void UnregisterInvokeOnInstance<T>(Action<UnityEngine.Component> handler) where T : UnityEngine.Component => _networkObjectCache.UnregisterInvokeOnInstance<T>(handler);
/// <summary>
/// Returns class of type if found within CodegenBase classes.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public T GetInstance<T>() where T : UnityEngine.Component => _networkObjectCache.GetInstance<T>();
/// <summary>
/// Registers a new component to this NetworkManager.
/// </summary>
/// <typeparam name="T">Type to register.</typeparam>
/// <param name="component">Reference of the component being registered.</param>
/// <param name="replace">True to replace existing references.</param>
public void RegisterInstance<T>(T component, bool replace = true) where T : UnityEngine.Component => _networkObjectCache.RegisterInstance<T>(component, replace);
/// <summary>
/// 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
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d7aef532208d06c4880973c51b1906f5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7136e9ee3f7eee44abab09285ab7b939
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 938eacb83fa7d0046bd769b31dac7e80
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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)
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. */
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e56d5389eb07aa040b8a9ec8b0d7c597
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,246 @@
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
#if !PREDICTION_V2
/// <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;
}
}
#else
/// <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
{
if (!_initializedOnceClient && nob.EnablePrediction)
nob.RegisterPredictionBehaviourOnce(this);
_initializedOnceClient = true;
}
}
#endif
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
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d2230f9cdb1ffc9489b53875c963342d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 55d793117b52da549affcc9ec30b05c0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,160 @@
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. */
if (!asServer)
ClientInitialized = true;
//Set that client or server is active before callbacks.
SetActiveStatus(true, asServer);
//Invoke OnStartNetwork.
for (int i = 0; i < NetworkBehaviours.Length; i++)
NetworkBehaviours[i].InvokeOnNetwork(true);
//As server.
if (asServer)
{
for (int i = 0; i < NetworkBehaviours.Length; i++)
NetworkBehaviours[i].OnStartServer_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);
}
if (asServer)
IsServer = false;
else
IsClient = false;
}
/// <summary>
/// Invokes OnOwnership callbacks.
/// </summary>
/// <param name="prevOwner"></param>
private void InvokeOwnership(NetworkConnection prevOwner, bool asServer)
{
if (asServer)
{
for (int i = 0; i < NetworkBehaviours.Length; i++)
NetworkBehaviours[i].OnOwnershipServer_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);
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8e9fbf0d6eb10e94d892dd4e817030bc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 816dbea70a70ab949a44f485155f0087
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,441 @@
using FishNet.Component.Prediction;
using FishNet.Managing;
using FishNet.Managing.Timing;
using FishNet.Object.Prediction;
using System.Collections.Generic;
using UnityEngine;
namespace FishNet.Object
{
#if PREDICTION_V2
public partial class NetworkObject : MonoBehaviour
{
#region Types.
#if PREDICTION_V2
/// <summary>
/// Type of prediction movement being used.
/// </summary>
[System.Serializable]
internal enum PredictionType : byte
{
Other = 0,
Rigidbody = 1,
Rigidbody2D = 2
}
#endif
#endregion
#region Public.
/// <summary>
/// Last tick this object replicated.
/// </summary>
internal EstimatedTick ReplicateTick;
/// <summary>
/// Last tick to replicate even if out of order. This could be from tick events or even replaying inputs.
/// </summary>
internal uint LastUnorderedReplicateTick;
#endregion
#region Internal.
/// <summary>
/// Pauses rigidbodies for prediction.
/// </summary>
internal RigidbodyPauser RigidbodyPauser;
#endregion
#region Private.
#region Preset SmoothingDatas.
private static AdaptiveInterpolationSmoothingData _accurateSmoothingData = new AdaptiveInterpolationSmoothingData()
{
InterpolationPercent = 0.5f,
CollisionInterpolationPercent = 0.05f,
InterpolationDecreaseStep = 1,
InterpolationIncreaseStep = 2,
};
private static AdaptiveInterpolationSmoothingData _mixedSmoothingData = new AdaptiveInterpolationSmoothingData()
{
InterpolationPercent = 1f,
CollisionInterpolationPercent = 0.1f,
InterpolationDecreaseStep = 1,
InterpolationIncreaseStep = 3,
};
private static AdaptiveInterpolationSmoothingData _gradualSmoothingData = new AdaptiveInterpolationSmoothingData()
{
InterpolationPercent = 1.5f,
CollisionInterpolationPercent = 0.2f,
InterpolationDecreaseStep = 1,
InterpolationIncreaseStep = 5,
};
#endregion
/// <summary>
/// Graphical smoother to use when using set for owner.
/// </summary>
private SetInterpolationSmoother _ownerSetInterpolationSmoother;
/// <summary>
/// Graphical smoother to use when using set for spectators.
/// </summary>
private SetInterpolationSmoother _spectatorSetInterpolationSmoother;
/// <summary>
/// Graphical smoother to use when using adaptive.
/// </summary>
private AdaptiveInterpolationSmootherFixed _spectatorAdaptiveInterpolationSmoother;
/// <summary>
/// NetworkBehaviours which use prediction.
/// </summary>
private List<NetworkBehaviour> _predictionBehaviours = new List<NetworkBehaviour>();
/// <summary>
/// Tick when CollionStayed last called. This only has value if using prediction.
/// </summary>
private uint _collisionStayedTick;
/// <summary>
/// Local client objects this object is currently colliding with.
/// </summary>
private HashSet<GameObject> _localClientCollidedObjects = new HashSet<GameObject>();
#endregion
private void InitializeSmoothers()
{
bool usesRb = (_predictionType == PredictionType.Rigidbody);
bool usesRb2d = (_predictionType == PredictionType.Rigidbody2D);
if (usesRb || usesRb2d)
{
RigidbodyPauser = new RigidbodyPauser();
RigidbodyType rbType = (usesRb) ? RigidbodyType.Rigidbody : RigidbodyType.Rigidbody2D;
RigidbodyPauser.UpdateRigidbodies(transform, rbType, true, _graphicalObject);
}
//Create SetInterpolation smoother.
_ownerSetInterpolationSmoother = new SetInterpolationSmoother();
float teleportThreshold = (_enableTeleport) ? _ownerTeleportThreshold : MoveRatesCls.UNSET_VALUE;
SetInterpolationSmootherData osd = new SetInterpolationSmootherData()
{
GraphicalObject = _graphicalObject,
Interpolation = _ownerInterpolation,
SmoothPosition = true,
SmoothRotation = true,
SmoothScale = true,
NetworkObject = this,
TeleportThreshold = teleportThreshold,
};
_ownerSetInterpolationSmoother.InitializeOnce(osd);
//Spectator.
_spectatorSetInterpolationSmoother = new SetInterpolationSmoother();
_spectatorSetInterpolationSmoother.InitializeOnce(osd);
//Create adaptive interpolation smoother if enabled.
if (_spectatorAdaptiveInterpolation)
{
_spectatorAdaptiveInterpolationSmoother = new AdaptiveInterpolationSmootherFixed();
//Smoothing values.
AdaptiveInterpolationSmoothingData aisd;
if (_adaptiveSmoothingType == AdaptiveSmoothingType.Custom)
aisd = _customSmoothingData;
else
aisd = _preconfiguredSmoothingDataPreview;
//Other details.
aisd.GraphicalObject = _graphicalObject;
aisd.SmoothPosition = true;
aisd.SmoothRotation = true;
aisd.SmoothScale = true;
aisd.NetworkObject = this;
aisd.TeleportThreshold = teleportThreshold;
_spectatorAdaptiveInterpolationSmoother.Initialize(aisd);
}
}
private void Prediction_Update()
{
if (!_enablePrediction)
return;
_ownerSetInterpolationSmoother.Update();
if (IsHost)
_spectatorSetInterpolationSmoother.Update();
else
_spectatorAdaptiveInterpolationSmoother?.Update();
}
private void TimeManager_OnPreTick()
{
//Do not need to check use prediction because this method only fires if prediction is on for this object.
_ownerSetInterpolationSmoother.OnPreTick();
if (IsHost)
_spectatorSetInterpolationSmoother.OnPreTick();
else
_spectatorAdaptiveInterpolationSmoother?.OnPreTick();
}
private void TimeManager_OnPostTick()
{
//Do not need to check use prediction because this method only fires if prediction is on for this object.
_ownerSetInterpolationSmoother.OnPostTick();
if (IsHost)
_spectatorSetInterpolationSmoother.OnPostTick();
else
_spectatorAdaptiveInterpolationSmoother?.OnPostTick();
TrySetCollisionExited();
}
private void Prediction_Preinitialize(NetworkManager manager, bool asServer)
{
if (!_enablePrediction)
return;
InitializeSmoothers();
if (asServer)
return;
if (_predictionBehaviours.Count > 0)
{
manager.PredictionManager.OnPreReconcile += PredictionManager_OnPreReconcile;
manager.PredictionManager.OnReplicateReplay += PredictionManager_OnReplicateReplay;
manager.PredictionManager.OnPostReconcile += PredictionManager_OnPostReconcile;
if (_spectatorAdaptiveInterpolationSmoother != null)
{
manager.PredictionManager.OnPreReplicateReplay += PredictionManager_OnPreReplicateReplay;
manager.PredictionManager.OnPostReplicateReplay += PredictionManager_OnPostReplicateReplay;
}
manager.TimeManager.OnPreTick += TimeManager_OnPreTick;
manager.TimeManager.OnPostTick += TimeManager_OnPostTick;
}
}
private void Prediction_Deinitialize(bool asServer)
{
if (!_enablePrediction)
return;
/* Only the client needs to unsubscribe from these but
* asServer may not invoke as false if the client is suddenly
* dropping their connection. */
if (_predictionBehaviours.Count > 0 && NetworkManager != null)
{
NetworkManager.PredictionManager.OnPreReconcile -= PredictionManager_OnPreReconcile;
NetworkManager.PredictionManager.OnReplicateReplay -= PredictionManager_OnReplicateReplay;
NetworkManager.PredictionManager.OnPostReconcile -= PredictionManager_OnPostReconcile;
if (_spectatorAdaptiveInterpolationSmoother != null)
{
NetworkManager.PredictionManager.OnPreReplicateReplay -= PredictionManager_OnPreReplicateReplay;
NetworkManager.PredictionManager.OnPostReplicateReplay -= PredictionManager_OnPostReplicateReplay;
}
NetworkManager.TimeManager.OnPreTick -= TimeManager_OnPreTick;
NetworkManager.TimeManager.OnPostTick -= TimeManager_OnPostTick;
}
}
private void PredictionManager_OnPreReconcile(uint clientReconcileTick, uint serverReconcileTick)
{
for (int i = 0; i < _predictionBehaviours.Count; i++)
_predictionBehaviours[i].Reconcile_Client_Start();
}
private void PredictionManager_OnPostReconcile(uint clientReconcileTick, uint serverReconcileTick)
{
_spectatorAdaptiveInterpolationSmoother?.OnPostReconcile(clientReconcileTick, serverReconcileTick);
for (int i = 0; i < _predictionBehaviours.Count; i++)
_predictionBehaviours[i].Reconcile_Client_End();
/* Unpause rigidbody pauser. It's okay to do that here rather
* than per NB, where the pausing occurs, because once here
* the entire object is out of the replay cycle so there's
* no reason to try and unpause per NB. */
RigidbodyPauser?.Unpause();
}
private void PredictionManager_OnReplicateReplay(uint clientTick, uint serverTick)
{
uint replayTick = (IsOwner) ? clientTick : serverTick;
for (int i = 0; i < _predictionBehaviours.Count; i++)
_predictionBehaviours[i].Replicate_Replay_Start(replayTick);
}
private void PredictionManager_OnPreReplicateReplay(uint clientTick, uint serverTick)
{
/* Adaptive smoother uses localTick (clientTick) to track graphical datas.
* There's no need to use serverTick since the only purpose of adaptiveSmoother
* is to smooth graphic changes, not update the transform itself. */
if (!IsHost)
_spectatorAdaptiveInterpolationSmoother?.OnPreReplicateReplay(clientTick, serverTick);
}
private void PredictionManager_OnPostReplicateReplay(uint clientTick, uint serverTick)
{
/* Adaptive smoother uses localTick (clientTick) to track graphical datas.
* There's no need to use serverTick since the only purpose of adaptiveSmoother
* is to smooth graphic changes, not update the transform itself. */
if (!IsHost)
_spectatorAdaptiveInterpolationSmoother?.OnPostReplicateReplay(clientTick, serverTick);
}
/// <summary>
/// Returns if this object is colliding with any local client objects.
/// </summary>
/// <returns></returns>
internal bool CollidingWithLocalClient()
{
/* If it's been more than 1 tick since collision stayed
* then do not consider as collided. */
return (TimeManager.LocalTick - _collisionStayedTick) < 1;
}
/// <summary>
/// Called when colliding with another object.
/// </summary>
private void OnCollisionEnter(Collision collision)
{
if (!ClientInitialized)
return;
if (_predictionType != PredictionType.Rigidbody)
return;
GameObject go = collision.gameObject;
if (CollisionEnteredLocalClientObject(go))
CollisionEntered(go);
}
/// <summary>
/// Called when collision has entered a local clients object.
/// </summary>
private void CollisionEntered(GameObject go)
{
_collisionStayedTick = TimeManager.LocalTick;
_localClientCollidedObjects.Add(go);
}
/// <summary>
/// Called when colliding with another object.
/// </summary>
private void OnCollisionEnter2D(Collision2D collision)
{
if (!ClientInitialized)
return;
if (_predictionType != PredictionType.Rigidbody2D)
return;
GameObject go = collision.gameObject;
if (CollisionEnteredLocalClientObject(go))
CollisionEntered(go);
}
/// <summary>
/// Called when staying in collision with another object.
/// </summary>
private void OnCollisionStay(Collision collision)
{
if (!ClientInitialized)
return;
if (_predictionType != PredictionType.Rigidbody)
return;
if (_localClientCollidedObjects.Contains(collision.gameObject))
_collisionStayedTick = TimeManager.LocalTick;
}
/// <summary>
/// Called when staying in collision with another object.
/// </summary>
private void OnCollisionStay2D(Collision2D collision)
{
if (!ClientInitialized)
return;
if (_predictionType != PredictionType.Rigidbody2D)
return;
if (_localClientCollidedObjects.Contains(collision.gameObject))
_collisionStayedTick = TimeManager.LocalTick;
}
/// <summary>
/// Called when a collision occurs and the smoothing type must perform operations.
/// </summary>
private bool CollisionEnteredLocalClientObject(GameObject go)
{
if (go.TryGetComponent<NetworkObject>(out NetworkObject nob))
return nob.Owner.IsLocalClient;
//Fall through.
return false;
}
/// <summary>
/// Called when collision has exited a local clients object.
/// </summary>
private void TrySetCollisionExited()
{
/* If this object is no longer
* colliding with local client objects
* then unset collision.
* This is done here instead of using
* OnCollisionExit because often collisionexit
* will be missed due to ignored ticks.
* While not ignoring ticks is always an option
* its not ideal because ignoring ticks helps
* prevent over predicting. */
TimeManager tm = TimeManager;
if (tm == null || (_collisionStayedTick != 0 && (tm.LocalTick != _collisionStayedTick)))
{
_localClientCollidedObjects.Clear();
_collisionStayedTick = 0;
}
}
/// <summary>
/// Registers a NetworkBehaviour that uses prediction with the NetworkObject.
/// This method should only be called once throughout the entire lifetime of this object.
/// </summary>
internal void RegisterPredictionBehaviourOnce(NetworkBehaviour nb)
{
_predictionBehaviours.Add(nb);
}
/// <summary>
/// Resets replicate tick and unordered replicate tick.
/// </summary>
internal void ResetReplicateTick()
{
ReplicateTick.Reset();
LastUnorderedReplicateTick = 0;
}
/// <summary>
/// Sets the last tick this NetworkBehaviour replicated with.
/// </summary>
internal void SetReplicateTick(uint value, bool setUnordered)
{
if (setUnordered)
LastUnorderedReplicateTick = value;
ReplicateTick.Update(NetworkManager.TimeManager, value, EstimatedTick.OldTickOption.Discard);
Owner.ReplicateTick.Update(NetworkManager.TimeManager, value, EstimatedTick.OldTickOption.Discard);
}
#if UNITY_EDITOR
private void Prediction_OnValidate()
{
if (Application.isPlaying)
{
// InitializeSmoother(true);
}
else
{
if (_adaptiveSmoothingType == AdaptiveSmoothingType.Accuracy)
_preconfiguredSmoothingDataPreview = _accurateSmoothingData;
else if (_adaptiveSmoothingType == AdaptiveSmoothingType.Mixed)
_preconfiguredSmoothingDataPreview = _mixedSmoothingData;
else if (_adaptiveSmoothingType == AdaptiveSmoothingType.Gradual)
_preconfiguredSmoothingDataPreview = _gradualSmoothingData;
}
}
#endif
}
#endif
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 29401c9ff84500647a0d8718a39f28d4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,328 @@
using FishNet.Component.ColliderRollback;
using FishNet.Connection;
using FishNet.Managing;
using FishNet.Managing.Client;
using FishNet.Managing.Observing;
using FishNet.Managing.Predicting;
using FishNet.Managing.Scened;
using FishNet.Managing.Server;
using FishNet.Managing.Timing;
using FishNet.Managing.Transporting;
using System;
using UnityEngine;
namespace FishNet.Object
{
public partial class NetworkObject : MonoBehaviour
{
#region Public.
/// <summary>
/// True if predicted spawning is allowed for this object.
/// </summary>
internal bool AllowPredictedSpawning => (PredictedSpawn == null) ? false : PredictedSpawn.GetAllowSpawning();
/// <summary>
/// True if predicted spawning is allowed for this object.
/// </summary>
internal bool AllowPredictedDespawning => (PredictedSpawn == null) ? false : PredictedSpawn.GetAllowDespawning();
/// <summary>
/// True to allow clients to predicted set syncTypes prior to spawning the item. Set values will be applied on the server and sent to other clients.
/// </summary>
internal bool AllowPredictedSyncTypes => (PredictedSpawn == null) ? false : PredictedSpawn.GetAllowSyncTypes();
/// <summary>
/// True if this object has been initialized on the client side.
/// This is set true right before client callbacks.
/// </summary>
public bool ClientInitialized { get; private set; }
/// <summary>
///
/// </summary>
private bool _isClient;
/// <summary>
/// True if the client is active and authenticated.
/// </summary>
public bool IsClient
{
/* This needs to use a special check when
* player is acting as host. Clients won't
* set IsClient until they receive the spawn message
* but the user may expect this true after client
* gains observation but before client gets spawn. */
get
{
if (IsServer)
return (NetworkManager == null) ? false : NetworkManager.IsClient;
else
return _isClient;
}
private set => _isClient = value;
}
/// <summary>
/// True if only the client is active and authenticated.
/// </summary>
public bool IsClientOnly => (IsClient && !IsServer);
/// <summary>
/// True if server is active.
/// </summary>
public bool IsServer { get; private set; }
/// <summary>
/// True if only the server is active.
/// </summary>
public bool IsServerOnly => (IsServer && !IsClient);
/// <summary>
/// True if client and server are active.
/// </summary>
public bool IsHost => (IsClient && IsServer);
/// <summary>
/// True if client nor server are active.
/// </summary>
public bool IsOffline => (!IsClient && !IsServer);
/// <summary>
/// True if the local client is the owner of this object.
/// </summary>
public bool IsOwner
{
get
{
/* ClientInitialized becomes true when this
* NetworkObject has been initialized on the client side.
*
* This value is used to prevent IsOwner from returning true
* when running as host; primarily in Update or Tick callbacks
* where IsOwner would be true as host but OnStartClient has
* not called yet.
*
* EG: server will set owner when it spawns the object.
* If IsOwner is checked before the object spawns on the
* client-host then it would also return true, since the
* Owner reference would be the same as what was set by server.
*
* This is however bad when the client hasn't initialized the object
* yet because it gives a false sense of execution order.
* As a result, Update or Ticks may return IsOwner as true well before OnStartClient
* is called. Many users rightfully create code with the assumption the client has been
* initialized by the time IsOwner is true.
*
* This is a double edged sword though because now IsOwner would return true
* within OnStartNetwork for clients only, but not for host given the client
* side won't be initialized yet as host. As a work around CodeAnalysis will
* inform users to instead use base.Owner.IsLocalClient within OnStartNetwork. */
if (!ClientInitialized)
return false;
return Owner.IsLocalClient;
}
}
/// <summary>
///
/// </summary>
private NetworkConnection _owner;
/// <summary>
/// Owner of this object.
/// </summary>
public NetworkConnection Owner
{
get
{
//Ensures a null Owner is never returned.
if (_owner == null)
return FishNet.Managing.NetworkManager.EmptyConnection;
return _owner;
}
private set { _owner = value; }
}
/// <summary>
/// ClientId for this NetworkObject owner.
/// </summary>
public int OwnerId => (!Owner.IsValid) ? -1 : Owner.ClientId;
/// <summary>
/// True if the object is initialized for the network.
/// </summary>
public bool IsSpawned => (!IsDeinitializing && ObjectId != NetworkObject.UNSET_OBJECTID_VALUE);
/// <summary>
/// The local connection of the client calling this method.
/// </summary>
public NetworkConnection LocalConnection => (NetworkManager == null) ? new NetworkConnection() : NetworkManager.ClientManager.Connection;
/// <summary>
/// NetworkManager for this object.
/// </summary>
public NetworkManager NetworkManager { get; private set; }
/// <summary>
/// ServerManager for this object.
/// </summary>
public ServerManager ServerManager { get; private set; }
/// <summary>
/// ClientManager for this object.
/// </summary>
public ClientManager ClientManager { get; private set; }
/// <summary>
/// ObserverManager for this object.
/// </summary>
public ObserverManager ObserverManager { get; private set; }
/// <summary>
/// TransportManager for this object.
/// </summary>
public TransportManager TransportManager { get; private set; }
/// <summary>
/// TimeManager for this object.
/// </summary>
public TimeManager TimeManager { get; private set; }
/// <summary>
/// SceneManager for this object.
/// </summary>
public SceneManager SceneManager { get; private set; }
/// <summary>
/// PredictionManager for this object.
/// </summary>
public PredictionManager PredictionManager {get;private set;}
/// <summary>
/// RollbackManager for this object.
/// </summary>
public RollbackManager RollbackManager { get; private set; }
#endregion
/// <summary>
/// Returns a NetworkBehaviour on this NetworkObject.
/// </summary>
/// <param name="componentIndex">ComponentIndex of the NetworkBehaviour.</param>
/// <param name="error">True to error if not found.</param>
/// <returns></returns>
public NetworkBehaviour GetNetworkBehaviour(byte componentIndex, bool error)
{
if (componentIndex >= NetworkBehaviours.Length)
{
if (error)
{
string message = $"ComponentIndex of {componentIndex} is out of bounds on {gameObject.name} [id {ObjectId}]. This may occur if you have modified your gameObject/prefab without saving it, or the scene.";
if (NetworkManager == null)
NetworkManager.StaticLogError(message);
else
NetworkManager.LogError(message);
}
}
return NetworkBehaviours[componentIndex];
}
/// <summary>
/// Despawns a GameObject. Only call from the server.
/// </summary>
/// <param name="go">GameObject to despawn.</param>
/// <param name="despawnType">What happens to the object after being despawned.</param>
public void Despawn(GameObject go, DespawnType? despawnType = null)
{
NetworkManager?.ServerManager.Despawn(go, despawnType);
}
/// <summary>
/// Despawns a NetworkObject. Only call from the server.
/// </summary>
/// <param name="nob">NetworkObject to despawn.</param>
/// <param name="despawnType">What happens to the object after being despawned.</param>
public void Despawn(NetworkObject nob, DespawnType? despawnType = null)
{
NetworkManager?.ServerManager.Despawn(nob, despawnType);
}
/// <summary>
/// Despawns this NetworkObject. Only call from the server.
/// </summary>
/// <param name="despawnType">What happens to the object after being despawned.</param>
public void Despawn(DespawnType? despawnType = null)
{
NetworkObject nob = this;
NetworkManager?.ServerManager.Despawn(nob, despawnType);
}
/// <summary>
/// Spawns an object over the network. Only call from the server.
/// </summary>
public void Spawn(GameObject go, NetworkConnection ownerConnection = null)
{
NetworkManager?.ServerManager.Spawn(go, ownerConnection);
}
/// <summary>
/// Spawns an object over the network. Only call from the server.
/// </summary>
public void Spawn(NetworkObject nob, NetworkConnection ownerConnection = null)
{
NetworkManager?.ServerManager.Spawn(nob, ownerConnection);
}
/// <summary>
/// Takes ownership of this object and child network objects, allowing immediate control.
/// </summary>
/// <param name="caller">Connection to give ownership to.</param>
public void SetLocalOwnership(NetworkConnection caller)
{
NetworkConnection prevOwner = Owner;
SetOwner(caller);
int count;
count = NetworkBehaviours.Length;
for (int i = 0; i < count; i++)
NetworkBehaviours[i].OnOwnershipClient_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
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fd30b4b61d50d01499c94a63a6eeb863
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: be0a4b0a32b02f64495ba3b1d22f89c4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8b2f6927cf3ef254d91b89e5f99a92b9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,25 @@
using UnityEngine;
namespace FishNet.Object
{
public partial class NetworkObject : MonoBehaviour
{
/// <summary>
/// Writers dirty SyncTypes for all Networkbehaviours if their write tick has been met.
/// </summary>
internal void WriteDirtySyncTypes()
{
NetworkBehaviour[] nbs = NetworkBehaviours;
int count = nbs.Length;
for (int i = 0; i < count; i++)
{
//There was a null check here before, shouldn't be needed so it was removed.
NetworkBehaviour nb = nbs[i];
nb.WriteDirtySyncTypes(true, true);
nb.WriteDirtySyncTypes(false, true);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8ffb07b7ea3c5b4419dc5f9941b2d204
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 26b716c41e9b56b4baafaf13a523ba2e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,24 @@

namespace FishNet.Object
{
/// <summary>
/// Current state of the NetworkObject.
/// </summary>
internal enum NetworkObjectState : byte
{
/// <summary>
/// State has not been set. This occurs when the object has never been spawned or despawned.
/// </summary>
Unset = 0,
/// <summary>
/// Object is currently spawned.
/// </summary>
Spawned = 1,
/// <summary>
/// Object is currently despawned.
/// </summary>
Despawned = 2,
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 536f137f11dc6654eab9fbe94ca14cd8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 46773e0f27a85d643867f04d902fa007
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,887 @@
//using GameKit.Utilities;
//using FishNet.Transporting;
//using FishNet.Utility.Extension;
//using System.Runtime.CompilerServices;
//using UnityEngine;
//using FishNet.Managing.Timing;
//using System.Collections.Generic;
//using FishNet.Managing.Scened;
//namespace FishNet.Object.Prediction
//{
// internal class AdaptiveInterpolationSmoother
// {
// #if PREDICTION_V2
// #region Types.
// /// <summary>
// /// Data on a goal to move towards.
// /// </summary>
// private class GoalData : IResettable
// {
// /// <summary>
// /// True if this GoalData is valid.
// /// </summary>
// public bool IsValid;
// /// <summary>
// /// Tick of the data this GoalData is for.
// /// </summary>
// public uint DataTick;
// /// <summary>
// /// Data on how fast to move to transform values.
// /// </summary>
// public RateData MoveRates = new RateData();
// /// <summary>
// /// Transform values to move towards.
// /// </summary>
// public TransformPropertiesCls TransformProperties = new TransformPropertiesCls();
// public GoalData() { }
// public void InitializeState() { }
// public void ResetState()
// {
// DataTick = 0;
// TransformProperties.ResetState();
// MoveRates.ResetState();
// IsValid = false;
// }
// /// <summary>
// /// Updates values using a GoalData.
// /// </summary>
// public void Update(GoalData gd)
// {
// DataTick = gd.DataTick;
// MoveRates.Update(gd.MoveRates);
// TransformProperties.Update(gd.TransformProperties);
// IsValid = true;
// }
// public void Update(uint dataTick, RateData rd, TransformPropertiesCls tp)
// {
// DataTick = dataTick;
// MoveRates = rd;
// TransformProperties = tp;
// IsValid = true;
// }
// }
// /// <summary>
// /// How fast to move to values.
// /// </summary>
// private class RateData : IResettable
// {
// /// <summary>
// /// Rate for position after smart calculations.
// /// </summary>
// public float Position;
// /// <summary>
// /// Rate for rotation after smart calculations.
// /// </summary>
// public float Rotation;
// /// <summary>
// /// Number of ticks the rates are calculated for.
// /// If TickSpan is 2 then the rates are calculated under the assumption the transform changed over 2 ticks.
// /// </summary>
// public uint TickSpan;
// /// <summary>
// /// Time remaining until transform is expected to reach it's goal.
// /// </summary>
// internal float TimeRemaining;
// public RateData() { }
// public void InitializeState() { }
// /// <summary>
// /// Resets values for re-use.
// /// </summary>
// public void ResetState()
// {
// Position = 0f;
// Rotation = 0f;
// TickSpan = 0;
// TimeRemaining = 0f;
// }
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// public void Update(RateData rd)
// {
// Update(rd.Position, rd.Rotation, rd.TickSpan, rd.TimeRemaining);
// }
// /// <summary>
// /// Updates rates.
// /// </summary>
// public void Update(float position, float rotation, uint tickSpan, float timeRemaining)
// {
// Position = position;
// Rotation = rotation;
// TickSpan = tickSpan;
// TimeRemaining = timeRemaining;
// }
// }
// #endregion
// #region Private.
// /// <summary>
// /// Offsets of the graphical object when this was initialized.
// /// </summary>
// private TransformProperties _graphicalInitializedValues;
// /// <summary>
// /// Offsets of the graphical object during PreTick. This could also be the offset PreReplay.
// /// </summary>
// private TransformProperties _graphicalPretickValues;
// /// <summary>
// /// Offsets of the root transform before simulating. This could be PreTick, or PreReplay.
// /// </summary>
// private TransformProperties _rootPreSimulateValues;
// /// <summary>
// /// SmoothingData to use.
// /// </summary>
// private AdaptiveInterpolationSmoothingData _smoothingData;
// /// <summary>
// /// Current interpolation value. This changes based on ping and settings.
// /// </summary>
// private long _currentInterpolation = 2;
// /// <summary>
// /// Target interpolation when collision is exited. This changes based on ping and settings.
// /// </summary>
// private uint _targetInterpolation;
// /// <summary>
// /// Target interpolation when collision is entered. This changes based on ping and settings.
// /// </summary>
// private uint _targetCollisionInterpolation;
// /// <summary>
// /// Current GoalData being used.
// /// </summary>
// private GoalData _currentGoalData = new GoalData();
// /// <summary>
// /// GoalDatas to move towards.
// /// </summary>
// //private RingBuffer<GoalData> _goalDatas = new RingBuffer<GoalData>();
// private List<GoalData> _goalDatas = new List<GoalData>();
// /// <summary>
// /// Last ping value when it was checked.
// /// </summary>
// private long _lastPing = long.MinValue;
// /// <summary>
// /// Cached NetworkObject reference in SmoothingData for performance.
// /// </summary>
// private NetworkObject _networkObject;
// #endregion
// #region Const.
// /// <summary>
// /// Multiplier to apply to movement speed when buffer is over interpolation.
// /// </summary>
// private const float OVERFLOW_MULTIPLIER = 0.1f;
// /// <summary>
// /// Multiplier to apply to movement speed when buffer is under interpolation.
// /// </summary>
// private const float UNDERFLOW_MULTIPLIER = 0.02f;
// #endregion
// public AdaptiveInterpolationSmoother()
// {
// /* Initialize for up to 50
// * goal datas. Anything beyond that
// * is unreasonable. */
// //_goalDatas.Initialize(50);
// }
// /// <summary>
// /// Initializes this for use.
// /// </summary>
// internal void Initialize(AdaptiveInterpolationSmoothingData data)
// {
// _smoothingData = data;
// _networkObject = data.NetworkObject;
// SetGraphicalObject(data.GraphicalObject);
// }
// /// <summary>
// /// <summary>
// /// Called every frame.
// /// </summary>
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// public void Update()
// {
// if (CanSmooth())
// MoveToTarget();
// }
// private string GetGoalDataTicks()
// {
// string result = string.Empty;
// foreach (var item in _goalDatas)
// result += item.DataTick + ", ";
// return result;
// }
// private string _preTickGoalDatas = string.Empty;
// /// <summary>
// /// Called when the TimeManager invokes OnPreTick.
// /// </summary>
// public void OnPreTick()
// {
// _preTickGoalDatas = GetGoalDataTicks();
// if (CanSmooth())
// {
// UpdatePingInterpolation();
// _graphicalPretickValues.Update(_smoothingData.GraphicalObject);
// //Update the last post simulate data.
// UpdateRootPreSimulateOffsets();
// //UpdateRootPreSimulateOffsets(_networkObject.ReplicateTick.Value(_networkObject.TimeManager) - 1);
// }
// }
// /// <summary>
// /// Called when the TimeManager invokes OnPostTick.
// /// </summary>
// public void OnPostTick()
// {
// if (CanSmooth())
// {
// //Move towards target interpolation.
// UpdateCurrentInterpolation();
// //Reset graphics to start graphicals transforms properties.
// _smoothingData.GraphicalObject.SetPositionAndRotation(_graphicalPretickValues.Position, _graphicalPretickValues.Rotation);
// //Create a goal data for new transform position.
// uint tick = _networkObject.LastUnorderedReplicateTick;
// //Debug.Log($"GoalDatas count {_goalDatas.Count}. Tick {tick}. LocalTick {_networkObject.TimeManager.LocalTick}");
// CreatePostSimulateGoalData(tick, true);
// }
// }
// /// <summary>
// /// Called before a reconcile runs a replay.
// /// </summary>
// public void OnPreReplicateReplay(uint clientTick, uint serverTick)
// {
// //Update the last post simulate data.
// if (CanSmooth())
// {
// UpdateRootPreSimulateOffsets();
// }
// }
// /// <summary>
// /// Called after a reconcile runs a replay.
// /// </summary>
// public void OnPostReplicateReplay(uint clientTick, uint serverTick)
// {
// if (CanSmooth())
// {
// /* Create new goal data from the replay.
// * This must be done every replay. If a desync
// * did occur then the goaldatas would be different
// * from what they were previously. */
// uint tick = _networkObject.LastUnorderedReplicateTick;
// CreatePostSimulateGoalData(tick, false);
// }
// }
// /// <summary>
// /// Updates rootPostSimulateOffsets value with root transform's current values.
// /// </summary>
// private void UpdateRootPreSimulateOffsets()
// {
// Transform t = _networkObject.transform;
// _rootPreSimulateValues.Update(t.position, t.rotation);
// }
// /// <summary>
// /// Moves current interpolation to target interpolation.
// /// </summary>
// private void UpdateCurrentInterpolation()
// {
// AdaptiveInterpolationSmoothingData data = _smoothingData;
// bool colliding = _networkObject.CollidingWithLocalClient();
// if (colliding)
// _currentInterpolation -= data.InterpolationDecreaseStep;
// else
// _currentInterpolation += data.InterpolationIncreaseStep;
// _currentInterpolation = (long)Mathf.Clamp(_currentInterpolation, _targetCollisionInterpolation, _targetInterpolation);
// }
// /// <summary>
// /// Updates interpolation values based on ping.
// /// </summary>
// private void UpdatePingInterpolation()
// {
// /* Only update if ping has changed considerably.
// * This will prevent random lag spikes from throwing
// * off the interpolation. */
// long ping = _networkObject.TimeManager.RoundTripTime;
// ulong difference = (ulong)Mathf.Abs(ping - _lastPing);
// _lastPing = ping;
// //Allow update if ping jump is large enough.
// if (difference > 25)
// SetTargetSmoothing(ping, false);
// }
// /// <summary>
// /// Sets target smoothing values.
// /// </summary>
// /// <param name="setImmediately">True to set current values to targets immediately.</param>
// private void SetTargetSmoothing(long ping, bool setImmediately)
// {
// AdaptiveInterpolationSmoothingData data = _smoothingData;
// TimeManager tm = _networkObject.TimeManager;
// double interpolationTime = (ping / 1000d) * data.InterpolationPercent;
// _targetInterpolation = tm.TimeToTicks(interpolationTime, TickRounding.RoundUp);
// double collisionInterpolationTime = (ping / 1000d) * data.CollisionInterpolationPercent;
// _targetCollisionInterpolation = tm.TimeToTicks(collisionInterpolationTime, TickRounding.RoundUp);
// //If to apply values to targets immediately.
// if (setImmediately)
// _currentInterpolation = (_networkObject.CollidingWithLocalClient()) ? _targetCollisionInterpolation : _targetInterpolation;
// }
// /// <summary>
// /// Sets GraphicalObject.
// /// </summary>
// /// <param name="value"></param>
// public void SetGraphicalObject(Transform value)
// {
// _smoothingData.GraphicalObject = value;
// _graphicalInitializedValues = _networkObject.transform.GetTransformOffsets(_smoothingData.GraphicalObject);
// }
// /// <summary>
// /// Returns if the graphics can be smoothed.
// /// </summary>
// /// <returns></returns>
// private bool CanSmooth()
// {
// if (_networkObject.IsOwner)
// return false;
// if (_networkObject.IsServerOnly)
// return false;
// return true;
// }
// /// <summary>
// /// Returns if this transform matches arguments.
// /// </summary>
// /// <returns></returns>
// private bool GraphicalObjectMatches(Vector3 position, Quaternion rotation)
// {
// bool positionMatches = (!_smoothingData.SmoothPosition || (_smoothingData.GraphicalObject.position == position));
// bool rotationMatches = (!_smoothingData.SmoothRotation || (_smoothingData.GraphicalObject.rotation == rotation));
// return (positionMatches && rotationMatches);
// }
// /// <summary>
// /// Returns if there is any change between two datas.
// /// </summary>
// private bool HasChanged(TransformPropertiesCls a, TransformPropertiesCls b)
// {
// return (a.Position != b.Position) ||
// (a.Rotation != b.Rotation);
// }
// ///// <summary>
// ///// Returns if the transform differs from td.
// ///// </summary>
// //private bool HasChanged(TransformPropertiesCls tp)
// //{
// // Transform t = _networkObject.transform;
// // bool changed = (tp.Position != t.position) || (tp.Rotation != t.rotation);
// // return changed;
// //}
// /// <summary>
// /// Sets CurrentGoalData to the next in queue. Returns if was set successfully.
// /// </summary>
// private bool SetCurrentGoalData()
// {
// if (_goalDatas.Count == 0)
// {
// _currentGoalData.IsValid = false;
// return false;
// }
// else
// {
// /* Update to the next goal data.
// * We could assign _goalDatas[0] as the
// * current and then just remove it from
// * the collection. But if did that _currentGoalData
// * would have to be disposed first. So all the same,
// * we're using the Update then dispose because it's
// * a little easier to follow. */
// _currentGoalData.Update(_goalDatas[0]);
// Vector3 offset = new Vector3(0f, 10f, 0f);
// Debug.DrawLine(_goalDatas[0].TransformProperties.Position + offset, _goalDatas[0].TransformProperties.Position - offset, Color.red, 2f);
// //Store old and remove it.
// ResettableObjectCaches<GoalData>.Store(_goalDatas[0]);
// //_goalDatas.RemoveRange(true, 1);
// _goalDatas.RemoveRange(0, 1);
// //Debug.LogWarning($"Frame {Time.frameCount}. CurrentGoalData set to Tick {_currentGoalData.DataTick}. PosX/Y {_currentGoalData.TransformProperties.Position.x}, {_currentGoalData.TransformProperties.Position.y}. Rate {_currentGoalData.MoveRates.Position}");
// return true;
// }
// }
// /// <summary>
// /// Moves to a GoalData. Automatically determins if to use data from server or client.
// /// </summary>
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// private void MoveToTarget(float deltaOverride = -1f)
// {
// /* If the current goal data is not valid then
// * try to set a new one. If none are available
// * it will remain inactive. */
// if (!_currentGoalData.IsValid)
// {
// if (!SetCurrentGoalData())
// return;
// }
// float delta = (deltaOverride != -1f) ? deltaOverride : Time.deltaTime;
// /* Once here it's safe to assume the object will be moving.
// * Any checks which would stop it from moving be it client
// * auth and owner, or server controlled and server, ect,
// * would have already been run. */
// TransformPropertiesCls td = _currentGoalData.TransformProperties;
// RateData rd = _currentGoalData.MoveRates;
// int queueCount = _goalDatas.Count;
// /* Begin moving even if interpolation buffer isn't
// * met to provide more real-time interactions but
// * speed up when buffer is too large. This should
// * provide a good balance of accuracy. */
// float multiplier;
// int countOverInterpolation = (queueCount - (int)_currentInterpolation - (int)_currentInterpolation);
// if (countOverInterpolation > 0)
// {
// float overflowMultiplier = OVERFLOW_MULTIPLIER;
// overflowMultiplier = 0f;
// multiplier = 1f + overflowMultiplier; //(overflowMultiplier * countOverInterpolation);
// }
// else if (countOverInterpolation < 0)
// {
// float value = (UNDERFLOW_MULTIPLIER * Mathf.Abs(countOverInterpolation));
// const float maximum = 0.9f;
// if (value > maximum)
// value = maximum;
// multiplier = 1f - value;
// }
// else
// {
// multiplier = 1f;
// }
// //Rate to update. Changes per property.
// float rate;
// Transform t = _smoothingData.GraphicalObject;
// //Position.
// if (_smoothingData.SmoothPosition)
// {
// rate = rd.Position;
// Vector3 posGoal = td.Position;
// //Debug.Log($"Rate {rate}. PosY {posGoal.y}. Multiplier {multiplier}. QueueCount {queueCount}");
// if (rate == -1f)
// t.position = td.Position;
// else if (rate > 0f)
// t.position = Vector3.MoveTowards(t.position, posGoal, rate * delta * multiplier);
// }
// //Rotation.
// if (_smoothingData.SmoothRotation)
// {
// rate = rd.Rotation;
// if (rate == -1f)
// t.rotation = td.Rotation;
// else if (rate > 0f)
// t.rotation = Quaternion.RotateTowards(t.rotation, td.Rotation, rate * delta);
// }
// //Subtract time remaining for movement to complete.
// if (rd.TimeRemaining > 0f)
// {
// float subtractionAmount = (delta * multiplier);
// float timeRemaining = rd.TimeRemaining - subtractionAmount;
// rd.TimeRemaining = timeRemaining;
// }
// //If movement shoudl be complete.
// if (rd.TimeRemaining <= 0f)
// {
// //_smoothingData.GraphicalObject.transform.position = _currentGoalData.TransformProperties.Position;
// //_smoothingData.GraphicalObject.transform.rotation = _currentGoalData.TransformProperties.Rotation;
// float leftOver = Mathf.Abs(rd.TimeRemaining);
// if (SetCurrentGoalData())
// {
// if (leftOver > 0f)
// MoveToTarget(leftOver);
// }
// //No more in buffer, see if can extrapolate.
// else
// {
// /* Everything should line up when
// * time remaining is <= 0f but incase it's not,
// * such as if the user manipulated the grapihc object
// * somehow, then set goaldata active again to continue
// * moving it until it lines up with the goal. */
// if (!GraphicalObjectMatches(td.Position, td.Rotation))
// _currentGoalData.IsValid = true;
// }
// }
// }
// #region Rates.
// /// <summary>
// /// Sets move rates which will occur instantly.
// /// </summary>
// private void SetInstantRates(RateData rd)
// {
// Debug.LogError($"Instant rates set.");
// rd.Update(MoveRates.INSTANT_VALUE, MoveRates.INSTANT_VALUE, 1, MoveRates.INSTANT_VALUE);
// }
// /// <summary>
// /// Sets move rates which will occur over time.
// /// </summary>
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// private void SetCalculatedRates(GoalData prevGd, GoalData nextGd, bool datasCleared, Channel channel)
// {
// datasCleared = false;
// /* Only update rates if data has changed.
// * When data comes in reliably for eventual consistency
// * it's possible that it will be the same as the last
// * unreliable packet. When this happens no change has occurred
// * and the distance of change would also be 0; this prevents
// * the object from moving. Only need to compare data if channel is reliable. */
// if (channel == Channel.Reliable && !HasChanged(prevGd.TransformProperties, nextGd.TransformProperties))
// {
// Debug.LogError("Reliable and unchanged.");
// nextGd.MoveRates.Update(prevGd.MoveRates);
// //Set to 0 to indicate settled.
// nextGd.DataTick = 0;
// return;
// }
// uint lastTick = prevGd.DataTick;
// /* How much time has passed between last update and current.
// * If set to 0 then that means the transform has
// * settled. */
// if (lastTick == 0)
// lastTick = (nextGd.DataTick - 1);
// uint tickDifference = (nextGd.DataTick - lastTick);
// float timePassed = (float)_networkObject.TimeManager.TicksToTime(tickDifference);
// RateData nextRd = nextGd.MoveRates;
// float rateMultiplier;
// if (!datasCleared)
// {
// rateMultiplier = 1f;
// }
// else
// {
// float tickDelta = (float)_networkObject.TimeManager.TickDelta;
// rateMultiplier = (_currentGoalData.MoveRates.TimeRemaining / tickDelta);
// }
// //Distance between properties.
// float distance;
// //Position.
// Vector3 lastPosition = prevGd.TransformProperties.Position;
// distance = Vector3.Distance(lastPosition, nextGd.TransformProperties.Position);
// if (tickDifference == 0)
// Debug.LogError($"0 tick difference");
// //If distance teleports assume rest do.
// if (_smoothingData.TeleportThreshold != MoveRates.UNSET_VALUE && distance >= _smoothingData.TeleportThreshold)
// {
// SetInstantRates(nextRd);
// return;
// }
// //Position distance already calculated.
// float positionRate = (distance / timePassed);
// if (positionRate <= 0f || positionRate > 5.6f && !_networkObject.IsServer && !_networkObject.IsOwner)
// //Debug.LogError($"Position Rate {positionRate} for tick {nextGd.LocalTick}. PrevY {prevGd.TransformProperties.Position.y}. NextY {nextGd.TransformProperties.Position.y}");
// //Rotation.
// distance = prevGd.TransformProperties.Rotation.Angle(nextGd.TransformProperties.Rotation, true);
// float rotationRate = (distance / timePassed);
// //if (positionRate > 5.1f || positionRate <= 0.05f)
// //Debug.Log($"Rate {positionRate}. Distance {distance}. TickDifference {tickDifference}.");
// /* If no speed then snap just in case.
// * 0f could be from floating errors. */
// if (positionRate == 0f)
// positionRate = MoveRates.INSTANT_VALUE;
// if (rotationRate == 0f)
// rotationRate = MoveRates.INSTANT_VALUE;
// nextRd.Update(positionRate * rateMultiplier, rotationRate * rateMultiplier, tickDifference, timePassed);
// }
// #endregion
// /// <summary>
// /// Removes GoalDatas which make the queue excessive.
// /// This could cause teleportation but would rarely occur, only potentially during sever network issues.
// /// </summary>
// private void RemoveExcessiveGoalDatas()
// {
// if (_goalDatas.Count > 100)
// Debug.LogError($"Whoa getting kind of high with count of {_goalDatas.Count}");
// ///* Remove entries which are excessive to the buffer.
// //* This could create a starting jitter but it will ensure
// //* the buffer does not fill too much. The buffer next should
// //* actually get unreasonably high but rather safe than sorry. */
// //int maximumBufferAllowance = ((int)_currentInterpolation * 8);
// //int removedBufferCount = (_goalDatas.Count - maximumBufferAllowance);
// ////If there are some to remove.
// //if (removedBufferCount > 0)
// //{
// // for (int i = 0; i < removedBufferCount; i++)
// // ResettableObjectCaches<GoalData>.Store(_goalDatas[0 + i]);
// // //_goalDatas.RemoveRange(true, removedBufferCount);
// // _goalDatas.RemoveRange(0, removedBufferCount);
// //}
// }
// /// <summary>
// /// Returns if a tick is older than or equal to the current GoalData and outputs current GoalData tick.
// /// </summary>
// private bool OldGoalDataTick(uint tick, out uint currentGoalDataTick)
// {
// currentGoalDataTick = _currentGoalData.DataTick;
// return (tick <= currentGoalDataTick);
// }
// /// <summary>
// /// Creates the next GoalData using previous goalData and tick.
// /// </summary>
// /// <param name="tick">Tick to apply for the next goal data.</param>
// private GoalData CreateNextGoalData(uint tick, GoalData prevGoalData, bool datasCleared)
// {
// //Debug.Log($"Creating next GoalData for tick {tick}. PrevGoalData tick {prevGoalData.LocalTick}");
// //Begin building next goal data.
// GoalData nextGoalData = ResettableObjectCaches<GoalData>.Retrieve();
// nextGoalData.DataTick = tick;
// //Set next transform data.
// TransformPropertiesCls nextTp = nextGoalData.TransformProperties;
// nextTp.Update(_networkObject.transform);
// /* Reset properties if smoothing is not enabled
// * for them. It's less checks and easier to do it
// * after the nextGoalData is populated. */
// if (!_smoothingData.SmoothPosition)
// nextTp.Position = _graphicalPretickValues.Position;
// if (!_smoothingData.SmoothRotation)
// nextTp.Rotation = _graphicalPretickValues.Rotation;
// // Debug.Log($"Creating NextGd X {nextTp.Position.x} for tick {tick}.");
// //Calculate rates for prev vs next data.
// SetCalculatedRates(prevGoalData, nextGoalData, datasCleared, Channel.Unreliable);
// return nextGoalData;
// }
// /// <summary>
// /// Makes a GoalData using transform values from rootPostSimulateOffsets.
// /// </summary>
// /// <returns></returns>
// private GoalData CreateGoalDataFromRootPreSimulate(uint tick)
// {
// GoalData gd = ResettableObjectCaches<GoalData>.Retrieve();
// //RigidbodyData contains the data from preTick.
// // Debug.Log($"Creating goalData from X {_rootPostSimulateValues.Position.x}. Tick {tick}");
// gd.TransformProperties.Update(_rootPreSimulateValues);
// gd.DataTick = tick;
// //No need to update rates because this is just a starting point reference for interpolation.
// return gd;
// }
// /// <summary>
// /// Clears all goalDatas.
// /// </summary>
// private void ClearGoalData(bool clearCurrent)
// {
// if (clearCurrent)
// ResettableObjectCaches<GoalData>.Store(_currentGoalData);
// int count = _goalDatas.Count;
// for (int i = 0; i < count; i++)
// ResettableObjectCaches<GoalData>.Store(_goalDatas[i]);
// _goalDatas.Clear();
// }
// private uint _jumpTick;
// private uint _lastPostTickDataTick;
// /// <summary>
// /// Creates a GoalData after a simulate.
// /// </summary>
// /// <param name="postTick">True if being created for OnPostTick.</param>
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// private void CreatePostSimulateGoalData(uint tick, bool postTick)
// {
// bool jumping = (_networkObject.transform.position.y > 0.5f);
// bool dataCleared = false;
// int dataIndex = -1;
// bool useUpdate = false;
// RemoveExcessiveGoalDatas();
// if (tick <= _currentGoalData.DataTick)
// {
// if (!postTick)
// {
// if (jumping)
// Debug.LogWarning($"Frame {Time.frameCount}. Old tick. Tick {tick}. Current {_currentGoalData.DataTick}. QueueCount {_goalDatas.Count}");
// return;
// }
// else
// {
// dataCleared = true;
// ClearGoalData(false);
// Debug.LogWarning($"Frame {Time.frameCount}. Tick {tick}. Current {_currentGoalData.DataTick}. CLEARING!");
// }
// }
// uint prevArrTick = 0;
// for (int i = 0; i < _goalDatas.Count; i++)
// {
// uint arrTick = _goalDatas[i].DataTick;
// if (tick == arrTick)
// {
// dataIndex = i;
// useUpdate = true;
// break;
// }
// else if (i > 0 && tick > prevArrTick && tick < arrTick)
// {
// dataIndex = i;
// break;
// }
// prevArrTick = arrTick;
// }
// if (dataIndex == -1)
// {
// if (_goalDatas.Count > 0 && tick < _goalDatas[0].DataTick)
// {
// // Insert at the beginning.
// dataIndex = 0;
// }
// else
// {
// // Insert at the end.
// dataIndex = _goalDatas.Count;
// }
// }
// GoalData prevGd;
// if (dataCleared)
// {
// prevGd = ResettableObjectCaches<GoalData>.Retrieve();
// prevGd.Update(_currentGoalData);
// prevGd.DataTick = (tick - 1);
// }
// else
// {
// prevGd = CreateGoalDataFromRootPreSimulate(tick - 1);
// }
// GoalData nextGd = CreateNextGoalData(tick, prevGd, dataCleared);
// if (jumping)
// {
// Debug.Log($"Frame {Time.frameCount}. CreateGoalData. Tick {tick}. Next Rate {nextGd.MoveRates.Position}. Next PosY {nextGd.TransformProperties.Position.y}");
// SceneLoadData sld = new SceneLoadData(_networkObject.gameObject.scene);
// _jumpTick = nextGd.DataTick;
// }
// if (useUpdate && _goalDatas[dataIndex].DataTick == _jumpTick)
// {
// Debug.LogError($"Frame {Time.frameCount}. Overwriting jump. Tick {tick}. IndexTick {_goalDatas[dataIndex].DataTick}. CurrentGoalY {_goalDatas[dataIndex].TransformProperties.Position.y}. Next Rate {nextGd.MoveRates.Position}. Next PosY {nextGd.TransformProperties.Position.y}.");
// }
// if (useUpdate)
// _goalDatas[dataIndex].Update(nextGd);
// else
// _goalDatas.Insert(dataIndex, nextGd);
// //Debug.
// if (postTick)
// {
// Vector3 offset = new Vector3(0.15f, 4f, 0f);
// Debug.DrawLine(nextGd.TransformProperties.Position + offset, nextGd.TransformProperties.Position - offset, Color.green, 2f);
// }
// else
// {
// Vector3 offset = new Vector3(-0.15f, 4f, 0f);
// Debug.DrawLine(nextGd.TransformProperties.Position + offset, nextGd.TransformProperties.Position - offset, Color.cyan, 2f);
// }
// }
// uint _postTickGdCount;
// #endif
// }
//}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 28460ed39f6c7e44498b2332d72f1aaf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,73 @@
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 InterpolationPercent;
/// <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 CollisionInterpolationPercent;
/// <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(1, byte.MaxValue)]
public byte InterpolationDecreaseStep;
/// <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(1, byte.MaxValue)]
public byte InterpolationIncreaseStep;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9fcf00526b5e1bf40a6565c3f521d926
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,589 @@
using GameKit.Utilities;
using FishNet.Utility.Extension;
using System.Runtime.CompilerServices;
using UnityEngine;
using System.Collections.Generic;
namespace FishNet.Object.Prediction
{
internal class AdaptiveInterpolationSmootherFixed
{
#if PREDICTION_V2
#region Types.
/// <summary>
/// Data on a goal to move towards.
/// </summary>
private class GoalData : IResettable
{
/// <summary>
/// True if this GoalData is valid.
/// </summary>
public bool IsValid;
/// <summary>
/// Tick of the data this GoalData is for.
/// </summary>
public uint DataTick;
/// <summary>
/// Transform values to move towards.
/// </summary>
public TransformPropertiesCls TransformProperties = new TransformPropertiesCls();
/// <summary>
/// Time remaining to move towards goal.
/// </summary>
public float TimeRemaining;
public GoalData() { }
public void InitializeState() { }
public void ResetState()
{
DataTick = 0;
TimeRemaining = 0f;
TransformProperties.ResetState();
IsValid = false;
}
/// <summary>
/// Updates values using a GoalData.
/// </summary>
public void Update(GoalData gd)
{
DataTick = gd.DataTick;
TransformProperties.Update(gd.TransformProperties);
TimeRemaining = gd.TimeRemaining;
IsValid = true;
}
public void Update(uint dataTick, TransformPropertiesCls tp, float timeRemaining)
{
DataTick = dataTick;
TransformProperties = tp;
TimeRemaining = timeRemaining;
IsValid = true;
}
}
#endregion
#region Private.
/// <summary>
/// Offsets of the root object during PreTick or PreReplicateReplay.
/// </summary>
private TransformProperties _rootPreSimulateWorldValues;
/// <summary>
/// Offsets of the graphical object during PreTick or PreReplicateReplay.
/// </summary>
private TransformProperties _graphicalPreSimulateWorldValues;
/// <summary>
/// SmoothingData to use.
/// </summary>
private AdaptiveInterpolationSmoothingData _smoothingData;
/// <summary>
/// Current interpolation value. This changes based on ping and settings.
/// </summary>
public long _currentInterpolation = 15;
/// <summary>
/// Current GoalData being used.
/// </summary>
private GoalData _currentGoalData = new GoalData();
/// <summary>
/// MoveRates for currentGoalData.
/// </summary>
private MoveRates _currentMoveRates;
/// <summary>
/// GoalDatas to move towards.
/// </summary>
//private RingBuffer<GoalData> _goalDatas = new RingBuffer<GoalData>();
private List<GoalData> _goalDatas = new List<GoalData>();
/// <summary>
/// Cached NetworkObject reference in SmoothingData for performance.
/// </summary>
private NetworkObject _networkObject;
/// <summary>
/// Cached tickDelta on the TimeManager.
/// </summary>
private float _tickDelta;
/// <summary>
/// Multiplier to apply towards movements. This is used to speed up and slow down buffer as needed.
/// </summary>
private float _rateMultiplier = 1f;
#endregion
#region Const.
/// <summary>
/// Multiplier to apply to movement speed when buffer is over interpolation.
/// </summary>
private const float OVERFLOW_MULTIPLIER = 10f;
/// <summary>
/// Multiplier to apply to movement speed when buffer is under interpolation.
/// </summary>
private const float UNDERFLOW_MULTIPLIER = 1f;
#endregion
public AdaptiveInterpolationSmootherFixed()
{
/* Initialize for up to 50
* goal datas. Anything beyond that
* is unreasonable. */
//_goalDatas.Initialize(50);
}
/// <summary>
/// Initializes this for use.
/// </summary>
internal void Initialize(AdaptiveInterpolationSmoothingData data)
{
_smoothingData = data;
_networkObject = data.NetworkObject;
_tickDelta = (float)_networkObject.TimeManager.TickDelta;
SetGraphicalObject(data.GraphicalObject);
}
/// <summary>
/// <summary>
/// Called every frame.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Update()
{
if (CanSmooth())
MoveToTarget();
}
/// <summary>
/// Called when the TimeManager invokes OnPreTick.
/// </summary>
public void OnPreTick()
{
if (CanSmooth())
{
_graphicalPreSimulateWorldValues = _smoothingData.GraphicalObject.GetWorldProperties();
_rootPreSimulateWorldValues.Update(_networkObject.transform);
}
}
/// <summary>
/// Called when the TimeManager invokes OnPostTick.
/// </summary>
public void OnPostTick()
{
if (CanSmooth())
{
//Reset graphics to start graphicals transforms properties.
_smoothingData.GraphicalObject.SetPositionAndRotation(_graphicalPreSimulateWorldValues.Position, _graphicalPreSimulateWorldValues.Rotation);
//Create a goal data for new transform position.
uint tick = _networkObject.LastUnorderedReplicateTick;
CreatePostSimulateGoalData(tick, true);
}
}
/// <summary>
/// Called before a reconcile runs a replay.
/// </summary>
public void OnPreReplicateReplay(uint clientTick, uint serverTick)
{
//Update the last post simulate data.
if (CanSmooth())
_rootPreSimulateWorldValues.Update(_networkObject.transform);
}
/// <summary>
/// Called after a reconcile runs a replay.
/// </summary>
public void OnPostReplicateReplay(uint clientTick, uint serverTick)
{
if (CanSmooth())
{
/* Create new goal data from the replay.
* This must be done every replay. If a desync
* did occur then the goaldatas would be different
* from what they were previously. */
uint tick = _networkObject.LastUnorderedReplicateTick;
CreatePostSimulateGoalData(tick, false);
}
}
public void OnPostReconcile(uint clientReconcileTick, uint serverReconcileTick)
{
if (CanSmooth())
{
_rootPreSimulateWorldValues.Update(_networkObject.transform);
int countOverInterpolation = (_goalDatas.Count - (int)_currentInterpolation);
//Debug.Log($"{Time.frameCount}. CountOver {countOverInterpolation}");
}
}
/// <summary>
/// Sets GraphicalObject.
/// </summary>
/// <param name="value"></param>
public void SetGraphicalObject(Transform value)
{
_smoothingData.GraphicalObject = value;
_graphicalPreSimulateWorldValues.Update(value);
}
/// <summary>
/// Returns if the graphics can be smoothed.
/// </summary>
/// <returns></returns>
private bool CanSmooth()
{
if (_networkObject.IsOwner)
return false;
if (_networkObject.IsServerOnly || _networkObject.IsHost)
return false;
return true;
}
/// <summary>
/// Returns if this transform matches arguments.
/// </summary>
/// <returns></returns>
private bool GraphicalObjectMatches(Vector3 position, Quaternion rotation)
{
bool positionMatches = (!_smoothingData.SmoothPosition || (_smoothingData.GraphicalObject.position == position));
bool rotationMatches = (!_smoothingData.SmoothRotation || (_smoothingData.GraphicalObject.rotation == rotation));
return (positionMatches && rotationMatches);
}
/// <summary>
/// Sets CurrentGoalData to the next in queue. Returns if was set successfully.
/// </summary>
private bool SetCurrentGoalData()
{
if (_goalDatas.Count == 0)
{
_currentGoalData.IsValid = false;
Debug.LogError("No more goal datas.");
return false;
}
else
{
/* Previous will always be current since
* we are getting next in queue. We
* later check if current is valid to determine
* if instant rates should be set or normal rates.
* If current is not valie then instant rates are set
* to teleport graphics to their starting position, and
* future sets will have a valid current. */
GoalData prev = _currentGoalData;
//Set next and make valid.
GoalData next = _goalDatas[0];
//Remove from goalDatas.
_goalDatas.RemoveAt(0);
if (prev != null && prev.IsValid)
SetCurrentMoveRates(prev.DataTick, next.DataTick, prev.TransformProperties, next.TransformProperties);
else
_currentMoveRates.SetInstantRates();
//Store previous.
if (prev != null)
ResettableObjectCaches<GoalData>.Store(prev);
//Assign new current.
_currentGoalData = next;
Debug.LogWarning($"Set CurrentGoalData on tick {_currentGoalData.DataTick}, remaining {_goalDatas.Count}");
return true;
}
}
/// <summary>
/// Moves to a GoalData. Automatically determins if to use data from server or client.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void MoveToTarget(float deltaOverride = -1f)
{
/* If the current goal data is not valid then
* try to set a new one. If none are available
* it will remain inactive. */
if (!_currentGoalData.IsValid)
{
if (!SetCurrentGoalData())
return;
}
GoalData currentGd = _currentGoalData;
float delta = (deltaOverride != -1f) ? deltaOverride : Time.deltaTime;
/* Once here it's safe to assume the object will be moving.
* Any checks which would stop it from moving be it client
* auth and owner, or server controlled and server, ect,
* would have already been run. */
TransformPropertiesCls td = currentGd.TransformProperties;
MoveRates mr = _currentMoveRates;
//How much multiplier should change in either direction over a second.
float multiplierChangeRate = 0.3f;
int queueCount = _goalDatas.Count;
/* Begin moving even if interpolation buffer isn't
* met to provide more real-time interactions but
* speed up when buffer is too large. This should
* provide a good balance of accuracy. */
int countOverInterpolation = (queueCount - (int)_currentInterpolation);
string debugPrint = string.Empty;
//Really high over interpolation, snap to datas.
if (countOverInterpolation > (_currentInterpolation * 30))
{
debugPrint = $"OverInterpolation {countOverInterpolation}. Teleporting.";
mr.SetInstantRates();
//Setting to -1 will force it to go negative, which will clear next goal data for teleport as well.
currentGd.TimeRemaining = -1f;
}
else if (countOverInterpolation > 0)
{
debugPrint = $"OverInterpolation {countOverInterpolation}. Increasing.";
_rateMultiplier += (multiplierChangeRate * delta);
}
else if (countOverInterpolation < 0)
{
debugPrint = $"OverInterpolation {countOverInterpolation}. Slowing.";
_rateMultiplier -= (multiplierChangeRate * delta);
}
else
{
_rateMultiplier = Mathf.MoveTowards(_rateMultiplier, 1f, (multiplierChangeRate * delta));
}
//Clamp multiplier.
const float maximumMultiplier = 1.1f;
const float minimumMultiplier = 0.95f;
_rateMultiplier = Mathf.Clamp(_rateMultiplier, minimumMultiplier, maximumMultiplier);
//Apply multiplier to delta.
delta *= _rateMultiplier;
// if (debugPrint != string.Empty && _networkObject.TimeManager.FrameTicked)
// Debug.Log($"{debugPrint}. Multiplier {_rateMultiplier}");
//multiplier = 1f;
//delta = Time.deltaTime;
//Rate to update. Changes per property.
float rate;
Transform t = _smoothingData.GraphicalObject;
//Position.
if (_smoothingData.SmoothPosition)
{
rate = mr.Position;
Vector3 posGoal = td.Position;
if (rate == MoveRatesCls.INSTANT_VALUE)
t.position = td.Position;
else if (rate > 0f)
t.position = Vector3.MoveTowards(t.position, posGoal, rate * delta);
}
//Rotation.
if (_smoothingData.SmoothRotation)
{
rate = mr.Rotation;
if (rate == MoveRatesCls.INSTANT_VALUE)
t.rotation = td.Rotation;
else if (rate > 0f)
t.rotation = Quaternion.RotateTowards(t.rotation, td.Rotation, rate * delta);
}
if (currentGd.TimeRemaining > 0f)
currentGd.TimeRemaining -= delta;
if (currentGd.TimeRemaining <= 0f)
{
bool graphicsMatch = GraphicalObjectMatches(td.Position, td.Rotation);
if (graphicsMatch)
{
float leftOver = Mathf.Abs(currentGd.TimeRemaining);
if (SetCurrentGoalData())
MoveToTarget(leftOver);
}
}
}
#region Rates.
/// <summary>
/// Sets move rates which will occur over time.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetCurrentMoveRates(uint prevTick, uint tick, TransformPropertiesCls prevTp, TransformPropertiesCls nextTp)
{
long lngTicksPassed = (tick - prevTick);
//Should not happen.
if (lngTicksPassed <= 0)
{
_networkObject.NetworkManager.LogError($"Ticks passed returned negative as {lngTicksPassed}. Instant rates are being set.");
_currentMoveRates.SetInstantRates();
return;
}
//More than 1 tick, also unusual.
else if (lngTicksPassed > 1)
{
_networkObject.NetworkManager.LogError($"Ticks passed are not equal to 1, passed value is {lngTicksPassed}");
// lngTicksPassed = 1;
}
uint ticksPassed = (uint)lngTicksPassed;
float delta = _tickDelta;
float distance;
float rate;
const float v3Tolerance = 0.0001f;
const float qTolerance = 0.2f;
//Position.
rate = prevTp.Position.GetRate(nextTp.Position, delta, out distance, ticksPassed);
//If distance teleports assume rest do.
if (_smoothingData.TeleportThreshold != MoveRates.UNSET_VALUE && distance >= _smoothingData.TeleportThreshold)
{
Debug.Log($"Teleporting threshhold.");
_currentMoveRates.SetInstantRates();
return;
}
float positionRate = rate.SetIfUnderTolerance(v3Tolerance, MoveRates.INSTANT_VALUE);
//Rotation.
rate = prevTp.Rotation.GetRate(nextTp.Rotation, delta, out _, ticksPassed);
float rotationRate = rate.SetIfUnderTolerance(qTolerance, MoveRates.INSTANT_VALUE);
_currentMoveRates.Update(positionRate, rotationRate, MoveRates.INSTANT_VALUE);
}
#endregion
/// <summary>
/// Removes GoalDatas which make the queue excessive.
/// This could cause teleportation but would rarely occur, only potentially during sever network issues.
/// </summary>
private void RemoveExcessiveGoalDatas()
{
if (_goalDatas.Count > 100)
Debug.LogError($"Whoa getting kind of high with count of {_goalDatas.Count}");
///* Remove entries which are excessive to the buffer.
//* This could create a starting jitter but it will ensure
//* the buffer does not fill too much. The buffer next sho0..uld
//* actually get unreasonably high but rather safe than sorry. */
//int maximumBufferAllowance = ((int)_currentInterpolation * 8);
//int removedBufferCount = (_goalDatas.Count - maximumBufferAllowance);
////If there are some to remove.
//if (removedBufferCount > 0)
//{
// for (int i = 0; i < removedBufferCount; i++)
// ResettableObjectCaches<GoalData>.Store(_goalDatas[0 + i]);
// //_goalDatas.RemoveRange(true, removedBufferCount);
// _goalDatas.RemoveRange(0, removedBufferCount);
//}
}
/// <summary>
/// Creates a GoalData after a simulate.
/// </summary>
/// <param name="postTick">True if being created for OnPostTick.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CreatePostSimulateGoalData(uint tick, bool postTick)
{
RemoveExcessiveGoalDatas();
int dataIndex = -1;
bool useUpdate = false;
/* Post ticks always go on the end.
* The tick will be wrong for the post tick, so set it
* to the last entry tick + 1. */
int datasCount = _goalDatas.Count;
if (postTick)
{
if (datasCount > 0)
tick = _goalDatas[datasCount - 1].DataTick + 1;
else
tick = _currentGoalData.DataTick + 1;
dataIndex = datasCount;
}
else
{
/* There is no need to create a goaldata
* if the tick is previous to currentGoalData.
* This would indicate the graphics have already
* moved past tick. */
if (tick < _currentGoalData.DataTick)
{
//Debug.LogWarning($"Frame {Time.frameCount}. Skipping tick {tick}. Current {_currentGoalData.DataTick}. PostTick? {postTick}. QueueCount {_goalDatas.Count}. StatesCount {_networkObject.PredictionManager._recievedStates.Count}");
return;
}
//If current tick then let current play out and do nothing.
else if (tick == _currentGoalData.DataTick)
{
return;
}
uint prevArrTick = 0;
for (int i = 0; i < datasCount; i++)
{
uint arrTick = _goalDatas[i].DataTick;
if (tick == arrTick)
{
dataIndex = i;
useUpdate = true;
break;
}
else if (i > 0 && tick > prevArrTick && tick < arrTick)
{
dataIndex = i;
break;
}
prevArrTick = arrTick;
}
if (dataIndex == -1)
{
//Insert at beginning.
if (datasCount > 0 && tick < _goalDatas[0].DataTick)
dataIndex = 0;
//Insert at end.
else
dataIndex = datasCount;
}
}
Transform rootT = _networkObject.transform;
//Begin building next goal data.
GoalData nextGd = ResettableObjectCaches<GoalData>.Retrieve();
nextGd.DataTick = tick;
nextGd.TimeRemaining = _tickDelta;
nextGd.IsValid = true;
//Set next transform data.
TransformPropertiesCls nextTp = nextGd.TransformProperties;
//Position.
if (!_smoothingData.SmoothPosition)
nextTp.Position = _graphicalPreSimulateWorldValues.Position;
else
nextTp.Position = rootT.position;
//ROtation.
if (!_smoothingData.SmoothRotation)
nextTp.Rotation = _graphicalPreSimulateWorldValues.Rotation;
else
nextTp.Rotation = rootT.rotation;
//Vector3 lineDist = new Vector3(0f, 3f, 0f);
//if (!postTick)
// Debug.DrawLine(rootT.position + lineDist, rootT.position, Color.red, 2f);
//else
// Debug.DrawLine(rootT.position + lineDist + new Vector3(1f, 0f, 0f), rootT.position, Color.blue, 2f);
if (useUpdate)
_goalDatas[dataIndex].Update(nextGd);
else
_goalDatas.Insert(dataIndex, nextGd);
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8444905b030752941a0f1562fd44a782
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,887 @@
//using GameKit.Utilities; //Remove on 04/01/01
//using FishNet.Transporting;
//using FishNet.Utility.Extension;
//using System.Runtime.CompilerServices;
//using UnityEngine;
//using FishNet.Managing.Timing;
//using System.Collections.Generic;
//using FishNet.Managing.Scened;
//namespace FishNet.Object.Prediction
//{
// internal class AdaptiveInterpolationSmoother
// {
// #if PREDICTION_V2
// #region Types.
// /// <summary>
// /// Data on a goal to move towards.
// /// </summary>
// private class GoalData : IResettable
// {
// /// <summary>
// /// True if this GoalData is valid.
// /// </summary>
// public bool IsValid;
// /// <summary>
// /// Tick of the data this GoalData is for.
// /// </summary>
// public uint DataTick;
// /// <summary>
// /// Data on how fast to move to transform values.
// /// </summary>
// public RateData MoveRates = new RateData();
// /// <summary>
// /// Transform values to move towards.
// /// </summary>
// public TransformPropertiesCls TransformProperties = new TransformPropertiesCls();
// public GoalData() { }
// public void InitializeState() { }
// public void ResetState()
// {
// DataTick = 0;
// TransformProperties.ResetState();
// MoveRates.ResetState();
// IsValid = false;
// }
// /// <summary>
// /// Updates values using a GoalData.
// /// </summary>
// public void Update(GoalData gd)
// {
// DataTick = gd.DataTick;
// MoveRates.Update(gd.MoveRates);
// TransformProperties.Update(gd.TransformProperties);
// IsValid = true;
// }
// public void Update(uint dataTick, RateData rd, TransformPropertiesCls tp)
// {
// DataTick = dataTick;
// MoveRates = rd;
// TransformProperties = tp;
// IsValid = true;
// }
// }
// /// <summary>
// /// How fast to move to values.
// /// </summary>
// private class RateData : IResettable
// {
// /// <summary>
// /// Rate for position after smart calculations.
// /// </summary>
// public float Position;
// /// <summary>
// /// Rate for rotation after smart calculations.
// /// </summary>
// public float Rotation;
// /// <summary>
// /// Number of ticks the rates are calculated for.
// /// If TickSpan is 2 then the rates are calculated under the assumption the transform changed over 2 ticks.
// /// </summary>
// public uint TickSpan;
// /// <summary>
// /// Time remaining until transform is expected to reach it's goal.
// /// </summary>
// internal float TimeRemaining;
// public RateData() { }
// public void InitializeState() { }
// /// <summary>
// /// Resets values for re-use.
// /// </summary>
// public void ResetState()
// {
// Position = 0f;
// Rotation = 0f;
// TickSpan = 0;
// TimeRemaining = 0f;
// }
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// public void Update(RateData rd)
// {
// Update(rd.Position, rd.Rotation, rd.TickSpan, rd.TimeRemaining);
// }
// /// <summary>
// /// Updates rates.
// /// </summary>
// public void Update(float position, float rotation, uint tickSpan, float timeRemaining)
// {
// Position = position;
// Rotation = rotation;
// TickSpan = tickSpan;
// TimeRemaining = timeRemaining;
// }
// }
// #endregion
// #region Private.
// /// <summary>
// /// Offsets of the graphical object when this was initialized.
// /// </summary>
// private TransformProperties _graphicalInitializedValues;
// /// <summary>
// /// Offsets of the graphical object during PreTick. This could also be the offset PreReplay.
// /// </summary>
// private TransformProperties _graphicalPretickValues;
// /// <summary>
// /// Offsets of the root transform before simulating. This could be PreTick, or PreReplay.
// /// </summary>
// private TransformProperties _rootPreSimulateValues;
// /// <summary>
// /// SmoothingData to use.
// /// </summary>
// private AdaptiveInterpolationSmoothingData _smoothingData;
// /// <summary>
// /// Current interpolation value. This changes based on ping and settings.
// /// </summary>
// private long _currentInterpolation = 2;
// /// <summary>
// /// Target interpolation when collision is exited. This changes based on ping and settings.
// /// </summary>
// private uint _targetInterpolation;
// /// <summary>
// /// Target interpolation when collision is entered. This changes based on ping and settings.
// /// </summary>
// private uint _targetCollisionInterpolation;
// /// <summary>
// /// Current GoalData being used.
// /// </summary>
// private GoalData _currentGoalData = new GoalData();
// /// <summary>
// /// GoalDatas to move towards.
// /// </summary>
// //private RingBuffer<GoalData> _goalDatas = new RingBuffer<GoalData>();
// private List<GoalData> _goalDatas = new List<GoalData>();
// /// <summary>
// /// Last ping value when it was checked.
// /// </summary>
// private long _lastPing = long.MinValue;
// /// <summary>
// /// Cached NetworkObject reference in SmoothingData for performance.
// /// </summary>
// private NetworkObject _networkObject;
// #endregion
// #region Const.
// /// <summary>
// /// Multiplier to apply to movement speed when buffer is over interpolation.
// /// </summary>
// private const float OVERFLOW_MULTIPLIER = 0.1f;
// /// <summary>
// /// Multiplier to apply to movement speed when buffer is under interpolation.
// /// </summary>
// private const float UNDERFLOW_MULTIPLIER = 0.02f;
// #endregion
// public AdaptiveInterpolationSmoother()
// {
// /* Initialize for up to 50
// * goal datas. Anything beyond that
// * is unreasonable. */
// //_goalDatas.Initialize(50);
// }
// /// <summary>
// /// Initializes this for use.
// /// </summary>
// internal void Initialize(AdaptiveInterpolationSmoothingData data)
// {
// _smoothingData = data;
// _networkObject = data.NetworkObject;
// SetGraphicalObject(data.GraphicalObject);
// }
// /// <summary>
// /// <summary>
// /// Called every frame.
// /// </summary>
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// public void Update()
// {
// if (CanSmooth())
// MoveToTarget();
// }
// private string GetGoalDataTicks()
// {
// string result = string.Empty;
// foreach (var item in _goalDatas)
// result += item.DataTick + ", ";
// return result;
// }
// private string _preTickGoalDatas = string.Empty;
// /// <summary>
// /// Called when the TimeManager invokes OnPreTick.
// /// </summary>
// public void OnPreTick()
// {
// _preTickGoalDatas = GetGoalDataTicks();
// if (CanSmooth())
// {
// UpdatePingInterpolation();
// _graphicalPretickValues.Update(_smoothingData.GraphicalObject);
// //Update the last post simulate data.
// UpdateRootPreSimulateOffsets();
// //UpdateRootPreSimulateOffsets(_networkObject.ReplicateTick.Value(_networkObject.TimeManager) - 1);
// }
// }
// /// <summary>
// /// Called when the TimeManager invokes OnPostTick.
// /// </summary>
// public void OnPostTick()
// {
// if (CanSmooth())
// {
// //Move towards target interpolation.
// UpdateCurrentInterpolation();
// //Reset graphics to start graphicals transforms properties.
// _smoothingData.GraphicalObject.SetPositionAndRotation(_graphicalPretickValues.Position, _graphicalPretickValues.Rotation);
// //Create a goal data for new transform position.
// uint tick = _networkObject.LastUnorderedReplicateTick;
// //Debug.Log($"GoalDatas count {_goalDatas.Count}. Tick {tick}. LocalTick {_networkObject.TimeManager.LocalTick}");
// CreatePostSimulateGoalData(tick, true);
// }
// }
// /// <summary>
// /// Called before a reconcile runs a replay.
// /// </summary>
// public void OnPreReplicateReplay(uint clientTick, uint serverTick)
// {
// //Update the last post simulate data.
// if (CanSmooth())
// {
// UpdateRootPreSimulateOffsets();
// }
// }
// /// <summary>
// /// Called after a reconcile runs a replay.
// /// </summary>
// public void OnPostReplicateReplay(uint clientTick, uint serverTick)
// {
// if (CanSmooth())
// {
// /* Create new goal data from the replay.
// * This must be done every replay. If a desync
// * did occur then the goaldatas would be different
// * from what they were previously. */
// uint tick = _networkObject.LastUnorderedReplicateTick;
// CreatePostSimulateGoalData(tick, false);
// }
// }
// /// <summary>
// /// Updates rootPostSimulateOffsets value with root transform's current values.
// /// </summary>
// private void UpdateRootPreSimulateOffsets()
// {
// Transform t = _networkObject.transform;
// _rootPreSimulateValues.Update(t.position, t.rotation);
// }
// /// <summary>
// /// Moves current interpolation to target interpolation.
// /// </summary>
// private void UpdateCurrentInterpolation()
// {
// AdaptiveInterpolationSmoothingData data = _smoothingData;
// bool colliding = _networkObject.CollidingWithLocalClient();
// if (colliding)
// _currentInterpolation -= data.InterpolationDecreaseStep;
// else
// _currentInterpolation += data.InterpolationIncreaseStep;
// _currentInterpolation = (long)Mathf.Clamp(_currentInterpolation, _targetCollisionInterpolation, _targetInterpolation);
// }
// /// <summary>
// /// Updates interpolation values based on ping.
// /// </summary>
// private void UpdatePingInterpolation()
// {
// /* Only update if ping has changed considerably.
// * This will prevent random lag spikes from throwing
// * off the interpolation. */
// long ping = _networkObject.TimeManager.RoundTripTime;
// ulong difference = (ulong)Mathf.Abs(ping - _lastPing);
// _lastPing = ping;
// //Allow update if ping jump is large enough.
// if (difference > 25)
// SetTargetSmoothing(ping, false);
// }
// /// <summary>
// /// Sets target smoothing values.
// /// </summary>
// /// <param name="setImmediately">True to set current values to targets immediately.</param>
// private void SetTargetSmoothing(long ping, bool setImmediately)
// {
// AdaptiveInterpolationSmoothingData data = _smoothingData;
// TimeManager tm = _networkObject.TimeManager;
// double interpolationTime = (ping / 1000d) * data.InterpolationPercent;
// _targetInterpolation = tm.TimeToTicks(interpolationTime, TickRounding.RoundUp);
// double collisionInterpolationTime = (ping / 1000d) * data.CollisionInterpolationPercent;
// _targetCollisionInterpolation = tm.TimeToTicks(collisionInterpolationTime, TickRounding.RoundUp);
// //If to apply values to targets immediately.
// if (setImmediately)
// _currentInterpolation = (_networkObject.CollidingWithLocalClient()) ? _targetCollisionInterpolation : _targetInterpolation;
// }
// /// <summary>
// /// Sets GraphicalObject.
// /// </summary>
// /// <param name="value"></param>
// public void SetGraphicalObject(Transform value)
// {
// _smoothingData.GraphicalObject = value;
// _graphicalInitializedValues = _networkObject.transform.GetTransformOffsets(_smoothingData.GraphicalObject);
// }
// /// <summary>
// /// Returns if the graphics can be smoothed.
// /// </summary>
// /// <returns></returns>
// private bool CanSmooth()
// {
// if (_networkObject.IsOwner)
// return false;
// if (_networkObject.IsServerOnly)
// return false;
// return true;
// }
// /// <summary>
// /// Returns if this transform matches arguments.
// /// </summary>
// /// <returns></returns>
// private bool GraphicalObjectMatches(Vector3 position, Quaternion rotation)
// {
// bool positionMatches = (!_smoothingData.SmoothPosition || (_smoothingData.GraphicalObject.position == position));
// bool rotationMatches = (!_smoothingData.SmoothRotation || (_smoothingData.GraphicalObject.rotation == rotation));
// return (positionMatches && rotationMatches);
// }
// /// <summary>
// /// Returns if there is any change between two datas.
// /// </summary>
// private bool HasChanged(TransformPropertiesCls a, TransformPropertiesCls b)
// {
// return (a.Position != b.Position) ||
// (a.Rotation != b.Rotation);
// }
// ///// <summary>
// ///// Returns if the transform differs from td.
// ///// </summary>
// //private bool HasChanged(TransformPropertiesCls tp)
// //{
// // Transform t = _networkObject.transform;
// // bool changed = (tp.Position != t.position) || (tp.Rotation != t.rotation);
// // return changed;
// //}
// /// <summary>
// /// Sets CurrentGoalData to the next in queue. Returns if was set successfully.
// /// </summary>
// private bool SetCurrentGoalData()
// {
// if (_goalDatas.Count == 0)
// {
// _currentGoalData.IsValid = false;
// return false;
// }
// else
// {
// /* Update to the next goal data.
// * We could assign _goalDatas[0] as the
// * current and then just remove it from
// * the collection. But if did that _currentGoalData
// * would have to be disposed first. So all the same,
// * we're using the Update then dispose because it's
// * a little easier to follow. */
// _currentGoalData.Update(_goalDatas[0]);
// Vector3 offset = new Vector3(0f, 10f, 0f);
// Debug.DrawLine(_goalDatas[0].TransformProperties.Position + offset, _goalDatas[0].TransformProperties.Position - offset, Color.red, 2f);
// //Store old and remove it.
// ResettableObjectCaches<GoalData>.Store(_goalDatas[0]);
// //_goalDatas.RemoveRange(true, 1);
// _goalDatas.RemoveRange(0, 1);
// //Debug.LogWarning($"Frame {Time.frameCount}. CurrentGoalData set to Tick {_currentGoalData.DataTick}. PosX/Y {_currentGoalData.TransformProperties.Position.x}, {_currentGoalData.TransformProperties.Position.y}. Rate {_currentGoalData.MoveRates.Position}");
// return true;
// }
// }
// /// <summary>
// /// Moves to a GoalData. Automatically determins if to use data from server or client.
// /// </summary>
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// private void MoveToTarget(float deltaOverride = -1f)
// {
// /* If the current goal data is not valid then
// * try to set a new one. If none are available
// * it will remain inactive. */
// if (!_currentGoalData.IsValid)
// {
// if (!SetCurrentGoalData())
// return;
// }
// float delta = (deltaOverride != -1f) ? deltaOverride : Time.deltaTime;
// /* Once here it's safe to assume the object will be moving.
// * Any checks which would stop it from moving be it client
// * auth and owner, or server controlled and server, ect,
// * would have already been run. */
// TransformPropertiesCls td = _currentGoalData.TransformProperties;
// RateData rd = _currentGoalData.MoveRates;
// int queueCount = _goalDatas.Count;
// /* Begin moving even if interpolation buffer isn't
// * met to provide more real-time interactions but
// * speed up when buffer is too large. This should
// * provide a good balance of accuracy. */
// float multiplier;
// int countOverInterpolation = (queueCount - (int)_currentInterpolation - (int)_currentInterpolation);
// if (countOverInterpolation > 0)
// {
// float overflowMultiplier = OVERFLOW_MULTIPLIER;
// overflowMultiplier = 0f;
// multiplier = 1f + overflowMultiplier; //(overflowMultiplier * countOverInterpolation);
// }
// else if (countOverInterpolation < 0)
// {
// float value = (UNDERFLOW_MULTIPLIER * Mathf.Abs(countOverInterpolation));
// const float maximum = 0.9f;
// if (value > maximum)
// value = maximum;
// multiplier = 1f - value;
// }
// else
// {
// multiplier = 1f;
// }
// //Rate to update. Changes per property.
// float rate;
// Transform t = _smoothingData.GraphicalObject;
// //Position.
// if (_smoothingData.SmoothPosition)
// {
// rate = rd.Position;
// Vector3 posGoal = td.Position;
// //Debug.Log($"Rate {rate}. PosY {posGoal.y}. Multiplier {multiplier}. QueueCount {queueCount}");
// if (rate == -1f)
// t.position = td.Position;
// else if (rate > 0f)
// t.position = Vector3.MoveTowards(t.position, posGoal, rate * delta * multiplier);
// }
// //Rotation.
// if (_smoothingData.SmoothRotation)
// {
// rate = rd.Rotation;
// if (rate == -1f)
// t.rotation = td.Rotation;
// else if (rate > 0f)
// t.rotation = Quaternion.RotateTowards(t.rotation, td.Rotation, rate * delta);
// }
// //Subtract time remaining for movement to complete.
// if (rd.TimeRemaining > 0f)
// {
// float subtractionAmount = (delta * multiplier);
// float timeRemaining = rd.TimeRemaining - subtractionAmount;
// rd.TimeRemaining = timeRemaining;
// }
// //If movement shoudl be complete.
// if (rd.TimeRemaining <= 0f)
// {
// //_smoothingData.GraphicalObject.transform.position = _currentGoalData.TransformProperties.Position;
// //_smoothingData.GraphicalObject.transform.rotation = _currentGoalData.TransformProperties.Rotation;
// float leftOver = Mathf.Abs(rd.TimeRemaining);
// if (SetCurrentGoalData())
// {
// if (leftOver > 0f)
// MoveToTarget(leftOver);
// }
// //No more in buffer, see if can extrapolate.
// else
// {
// /* Everything should line up when
// * time remaining is <= 0f but incase it's not,
// * such as if the user manipulated the grapihc object
// * somehow, then set goaldata active again to continue
// * moving it until it lines up with the goal. */
// if (!GraphicalObjectMatches(td.Position, td.Rotation))
// _currentGoalData.IsValid = true;
// }
// }
// }
// #region Rates.
// /// <summary>
// /// Sets move rates which will occur instantly.
// /// </summary>
// private void SetInstantRates(RateData rd)
// {
// Debug.LogError($"Instant rates set.");
// rd.Update(MoveRates.INSTANT_VALUE, MoveRates.INSTANT_VALUE, 1, MoveRates.INSTANT_VALUE);
// }
// /// <summary>
// /// Sets move rates which will occur over time.
// /// </summary>
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// private void SetCalculatedRates(GoalData prevGd, GoalData nextGd, bool datasCleared, Channel channel)
// {
// datasCleared = false;
// /* Only update rates if data has changed.
// * When data comes in reliably for eventual consistency
// * it's possible that it will be the same as the last
// * unreliable packet. When this happens no change has occurred
// * and the distance of change would also be 0; this prevents
// * the object from moving. Only need to compare data if channel is reliable. */
// if (channel == Channel.Reliable && !HasChanged(prevGd.TransformProperties, nextGd.TransformProperties))
// {
// Debug.LogError("Reliable and unchanged.");
// nextGd.MoveRates.Update(prevGd.MoveRates);
// //Set to 0 to indicate settled.
// nextGd.DataTick = 0;
// return;
// }
// uint lastTick = prevGd.DataTick;
// /* How much time has passed between last update and current.
// * If set to 0 then that means the transform has
// * settled. */
// if (lastTick == 0)
// lastTick = (nextGd.DataTick - 1);
// uint tickDifference = (nextGd.DataTick - lastTick);
// float timePassed = (float)_networkObject.TimeManager.TicksToTime(tickDifference);
// RateData nextRd = nextGd.MoveRates;
// float rateMultiplier;
// if (!datasCleared)
// {
// rateMultiplier = 1f;
// }
// else
// {
// float tickDelta = (float)_networkObject.TimeManager.TickDelta;
// rateMultiplier = (_currentGoalData.MoveRates.TimeRemaining / tickDelta);
// }
// //Distance between properties.
// float distance;
// //Position.
// Vector3 lastPosition = prevGd.TransformProperties.Position;
// distance = Vector3.Distance(lastPosition, nextGd.TransformProperties.Position);
// if (tickDifference == 0)
// Debug.LogError($"0 tick difference");
// //If distance teleports assume rest do.
// if (_smoothingData.TeleportThreshold != MoveRates.UNSET_VALUE && distance >= _smoothingData.TeleportThreshold)
// {
// SetInstantRates(nextRd);
// return;
// }
// //Position distance already calculated.
// float positionRate = (distance / timePassed);
// if (positionRate <= 0f || positionRate > 5.6f && !_networkObject.IsServer && !_networkObject.IsOwner)
// //Debug.LogError($"Position Rate {positionRate} for tick {nextGd.LocalTick}. PrevY {prevGd.TransformProperties.Position.y}. NextY {nextGd.TransformProperties.Position.y}");
// //Rotation.
// distance = prevGd.TransformProperties.Rotation.Angle(nextGd.TransformProperties.Rotation, true);
// float rotationRate = (distance / timePassed);
// //if (positionRate > 5.1f || positionRate <= 0.05f)
// //Debug.Log($"Rate {positionRate}. Distance {distance}. TickDifference {tickDifference}.");
// /* If no speed then snap just in case.
// * 0f could be from floating errors. */
// if (positionRate == 0f)
// positionRate = MoveRates.INSTANT_VALUE;
// if (rotationRate == 0f)
// rotationRate = MoveRates.INSTANT_VALUE;
// nextRd.Update(positionRate * rateMultiplier, rotationRate * rateMultiplier, tickDifference, timePassed);
// }
// #endregion
// /// <summary>
// /// Removes GoalDatas which make the queue excessive.
// /// This could cause teleportation but would rarely occur, only potentially during sever network issues.
// /// </summary>
// private void RemoveExcessiveGoalDatas()
// {
// if (_goalDatas.Count > 100)
// Debug.LogError($"Whoa getting kind of high with count of {_goalDatas.Count}");
// ///* Remove entries which are excessive to the buffer.
// //* This could create a starting jitter but it will ensure
// //* the buffer does not fill too much. The buffer next should
// //* actually get unreasonably high but rather safe than sorry. */
// //int maximumBufferAllowance = ((int)_currentInterpolation * 8);
// //int removedBufferCount = (_goalDatas.Count - maximumBufferAllowance);
// ////If there are some to remove.
// //if (removedBufferCount > 0)
// //{
// // for (int i = 0; i < removedBufferCount; i++)
// // ResettableObjectCaches<GoalData>.Store(_goalDatas[0 + i]);
// // //_goalDatas.RemoveRange(true, removedBufferCount);
// // _goalDatas.RemoveRange(0, removedBufferCount);
// //}
// }
// /// <summary>
// /// Returns if a tick is older than or equal to the current GoalData and outputs current GoalData tick.
// /// </summary>
// private bool OldGoalDataTick(uint tick, out uint currentGoalDataTick)
// {
// currentGoalDataTick = _currentGoalData.DataTick;
// return (tick <= currentGoalDataTick);
// }
// /// <summary>
// /// Creates the next GoalData using previous goalData and tick.
// /// </summary>
// /// <param name="tick">Tick to apply for the next goal data.</param>
// private GoalData CreateNextGoalData(uint tick, GoalData prevGoalData, bool datasCleared)
// {
// //Debug.Log($"Creating next GoalData for tick {tick}. PrevGoalData tick {prevGoalData.LocalTick}");
// //Begin building next goal data.
// GoalData nextGoalData = ResettableObjectCaches<GoalData>.Retrieve();
// nextGoalData.DataTick = tick;
// //Set next transform data.
// TransformPropertiesCls nextTp = nextGoalData.TransformProperties;
// nextTp.Update(_networkObject.transform);
// /* Reset properties if smoothing is not enabled
// * for them. It's less checks and easier to do it
// * after the nextGoalData is populated. */
// if (!_smoothingData.SmoothPosition)
// nextTp.Position = _graphicalPretickValues.Position;
// if (!_smoothingData.SmoothRotation)
// nextTp.Rotation = _graphicalPretickValues.Rotation;
// // Debug.Log($"Creating NextGd X {nextTp.Position.x} for tick {tick}.");
// //Calculate rates for prev vs next data.
// SetCalculatedRates(prevGoalData, nextGoalData, datasCleared, Channel.Unreliable);
// return nextGoalData;
// }
// /// <summary>
// /// Makes a GoalData using transform values from rootPostSimulateOffsets.
// /// </summary>
// /// <returns></returns>
// private GoalData CreateGoalDataFromRootPreSimulate(uint tick)
// {
// GoalData gd = ResettableObjectCaches<GoalData>.Retrieve();
// //RigidbodyData contains the data from preTick.
// // Debug.Log($"Creating goalData from X {_rootPostSimulateValues.Position.x}. Tick {tick}");
// gd.TransformProperties.Update(_rootPreSimulateValues);
// gd.DataTick = tick;
// //No need to update rates because this is just a starting point reference for interpolation.
// return gd;
// }
// /// <summary>
// /// Clears all goalDatas.
// /// </summary>
// private void ClearGoalData(bool clearCurrent)
// {
// if (clearCurrent)
// ResettableObjectCaches<GoalData>.Store(_currentGoalData);
// int count = _goalDatas.Count;
// for (int i = 0; i < count; i++)
// ResettableObjectCaches<GoalData>.Store(_goalDatas[i]);
// _goalDatas.Clear();
// }
// private uint _jumpTick;
// private uint _lastPostTickDataTick;
// /// <summary>
// /// Creates a GoalData after a simulate.
// /// </summary>
// /// <param name="postTick">True if being created for OnPostTick.</param>
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// private void CreatePostSimulateGoalData(uint tick, bool postTick)
// {
// bool jumping = (_networkObject.transform.position.y > 0.5f);
// bool dataCleared = false;
// int dataIndex = -1;
// bool useUpdate = false;
// RemoveExcessiveGoalDatas();
// if (tick <= _currentGoalData.DataTick)
// {
// if (!postTick)
// {
// if (jumping)
// Debug.LogWarning($"Frame {Time.frameCount}. Old tick. Tick {tick}. Current {_currentGoalData.DataTick}. QueueCount {_goalDatas.Count}");
// return;
// }
// else
// {
// dataCleared = true;
// ClearGoalData(false);
// Debug.LogWarning($"Frame {Time.frameCount}. Tick {tick}. Current {_currentGoalData.DataTick}. CLEARING!");
// }
// }
// uint prevArrTick = 0;
// for (int i = 0; i < _goalDatas.Count; i++)
// {
// uint arrTick = _goalDatas[i].DataTick;
// if (tick == arrTick)
// {
// dataIndex = i;
// useUpdate = true;
// break;
// }
// else if (i > 0 && tick > prevArrTick && tick < arrTick)
// {
// dataIndex = i;
// break;
// }
// prevArrTick = arrTick;
// }
// if (dataIndex == -1)
// {
// if (_goalDatas.Count > 0 && tick < _goalDatas[0].DataTick)
// {
// // Insert at the beginning.
// dataIndex = 0;
// }
// else
// {
// // Insert at the end.
// dataIndex = _goalDatas.Count;
// }
// }
// GoalData prevGd;
// if (dataCleared)
// {
// prevGd = ResettableObjectCaches<GoalData>.Retrieve();
// prevGd.Update(_currentGoalData);
// prevGd.DataTick = (tick - 1);
// }
// else
// {
// prevGd = CreateGoalDataFromRootPreSimulate(tick - 1);
// }
// GoalData nextGd = CreateNextGoalData(tick, prevGd, dataCleared);
// if (jumping)
// {
// Debug.Log($"Frame {Time.frameCount}. CreateGoalData. Tick {tick}. Next Rate {nextGd.MoveRates.Position}. Next PosY {nextGd.TransformProperties.Position.y}");
// SceneLoadData sld = new SceneLoadData(_networkObject.gameObject.scene);
// _jumpTick = nextGd.DataTick;
// }
// if (useUpdate && _goalDatas[dataIndex].DataTick == _jumpTick)
// {
// Debug.LogError($"Frame {Time.frameCount}. Overwriting jump. Tick {tick}. IndexTick {_goalDatas[dataIndex].DataTick}. CurrentGoalY {_goalDatas[dataIndex].TransformProperties.Position.y}. Next Rate {nextGd.MoveRates.Position}. Next PosY {nextGd.TransformProperties.Position.y}.");
// }
// if (useUpdate)
// _goalDatas[dataIndex].Update(nextGd);
// else
// _goalDatas.Insert(dataIndex, nextGd);
// //Debug.
// if (postTick)
// {
// Vector3 offset = new Vector3(0.15f, 4f, 0f);
// Debug.DrawLine(nextGd.TransformProperties.Position + offset, nextGd.TransformProperties.Position - offset, Color.green, 2f);
// }
// else
// {
// Vector3 offset = new Vector3(-0.15f, 4f, 0f);
// Debug.DrawLine(nextGd.TransformProperties.Position + offset, nextGd.TransformProperties.Position - offset, Color.cyan, 2f);
// }
// }
// uint _postTickGdCount;
// #endif
// }
//}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 10df9daae891dda488c9fa8715b68556
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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 { }
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b082d36535ce0404d8438bc1b0499e53
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,27 @@
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);
#if !PREDICTION_V2
[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);
#else
[APIExclude]
public delegate void ReplicateUserLogicDelegate<T>(T data, ReplicateState state, Channel channel);
[APIExclude]
public delegate void ReconcileUserLogicDelegate<T>(T data, Channel channel);
#endif
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c9904192dacd41a4ba7b29bc3199ec3a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 515754257f85574438408c7f5b268590
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1bddf57861232884ca21f7e97a6d662d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,243 @@
using FishNet.Utility.Extension;
using GameKit.Utilities;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace FishNet.Object.Prediction
{
internal class SetInterpolationSmoother
{
#if PREDICTION_V2
#region Private.
/// <summary>
/// How quickly to move towards goal values.
/// </summary>
private MoveRates _moveRates;
/// <summary>
/// Local values of the graphical object when this was initialized.
/// </summary>
private TransformProperties _graphicalInitializedLocalValues;
/// <summary>
/// Values of the graphical object during PreTick or PreReplay.
/// </summary>
private TransformProperties _graphicalPreSimulateWorldValues;
/// <summary>
/// SmoothingData to use.
/// </summary>
private SetInterpolationSmootherData _smoothingData;
#endregion
/// <summary>
/// Initializes this smoother; should only be completed once.
/// </summary>
/// <param name="data"></param>
internal void InitializeOnce(SetInterpolationSmootherData data)
{
_smoothingData = data;
SetGraphicalObject(data.GraphicalObject);
_moveRates = new MoveRates(MoveRates.UNSET_VALUE);
_graphicalPreSimulateWorldValues = _smoothingData.GraphicalObject.GetWorldProperties();
}
/// <summary>
/// Sets GraphicalObject; can be changed at runtime.
/// </summary>
/// <param name="value"></param>
internal void SetGraphicalObject(Transform value)
{
_smoothingData.GraphicalObject = value;
_graphicalInitializedLocalValues.Update(value.localPosition, value.localRotation, value.localScale);
}
/// <summary>
/// Sets the interpolation value to use when the owner of this object.
/// </summary>
/// <param name="value">New interpolation value.</param>
internal void SetInterpolation(byte value)
{
if (value < 1)
value = 1;
_smoothingData.Interpolation = value;
}
/// <summary>
/// Called every frame.
/// </summary>
internal void Update()
{
if (CanSmooth())
MoveToTarget();
}
/// <summary>
/// Called when the TimeManager invokes OnPreTick.
/// </summary>
internal void OnPreTick()
{
if (CanSmooth())
{
/* Only snap to destination if interpolation is 1.
* This ensures the graphics will be at the proper location
* before the next movement rates are calculated. */
if (_smoothingData.Interpolation == 1)
ResetGraphicalToInitializedLocalOffsets(true, true);
_graphicalPreSimulateWorldValues = _smoothingData.GraphicalObject.GetWorldProperties();
}
}
/// <summary>
/// Called when TimeManager invokes OnPostTick.
/// </summary>
internal void OnPostTick()
{
if (CanSmooth())
{
_smoothingData.GraphicalObject.SetPositionAndRotation(_graphicalPreSimulateWorldValues.Position, _graphicalPreSimulateWorldValues.Rotation);
SetGraphicalMoveRates();
}
}
/// <summary>
/// Returns if prediction can be used on this rigidbody.
/// </summary>
/// <returns></returns>
private bool CanSmooth()
{
NetworkObject nob = _smoothingData.NetworkObject;
if (nob == null)
return false;
if (nob.IsServerOnly)
return false;
if (!nob.IsHost && nob.SpectatorAdaptiveInterpolation && !nob.IsOwner)
return false;
return true;
}
/// <summary>
/// Moves transform to target values.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void MoveToTarget()
{
//No rates are set.
if (!_moveRates.AnySet)
return;
Vector3 posGoal = GetGraphicalGoalPosition();
Quaternion rotGoal = GetGraphicalGoalRotation();
/* Only try to update properties if they have a valid move rate.
* Properties may have 0f move rate if they did not change. */
Transform t = _smoothingData.GraphicalObject;
float delta = Time.deltaTime;
//Position.
if (_smoothingData.SmoothPosition)
{
if (_moveRates.InstantPosition)
ResetGraphicalToInitializedLocalOffsets(true, false);
else if (_moveRates.PositionSet)
t.localPosition = Vector3.MoveTowards(t.localPosition, posGoal, _moveRates.Position * delta);
}
//Rotation.
if (_smoothingData.SmoothRotation)
{
if (_moveRates.InstantRotation)
ResetGraphicalToInitializedLocalOffsets(false, true);
else if (_moveRates.RotationSet)
t.localRotation = Quaternion.RotateTowards(t.localRotation, rotGoal, _moveRates.Rotation * delta);
}
if (GraphicalObjectMatchesLocalValues(posGoal, rotGoal))
_moveRates.Update(MoveRates.UNSET_VALUE);
}
/// <summary>
/// Returns if this transform matches arguments.
/// </summary>
/// <returns></returns>
private bool GraphicalObjectMatchesLocalValues(Vector3 position, Quaternion rotation)
{
bool positionMatches = (!_smoothingData.SmoothPosition || (_smoothingData.GraphicalObject.localPosition == position));
bool rotationMatches = (!_smoothingData.SmoothRotation || (_smoothingData.GraphicalObject.localRotation == rotation));
return (positionMatches && rotationMatches);
}
/// <summary>
/// Sets Position and Rotation move rates to reach Target datas.
/// </summary>
private void SetGraphicalMoveRates()
{
uint interval = _smoothingData.Interpolation;
float delta = (float)_smoothingData.NetworkObject?.TimeManager.TickDelta;
float rate;
float distance;
Transform t = _smoothingData.GraphicalObject;
/* Position. */
rate = t.localPosition.GetRate(_graphicalInitializedLocalValues.Position, delta, out distance, interval);
//If qualifies for teleporting.
if (_smoothingData.TeleportThreshold != MoveRates.UNSET_VALUE && distance >= _smoothingData.TeleportThreshold)
{
_moveRates.Update(MoveRates.INSTANT_VALUE);
}
//Smoothing.
else
{
float positionRate = rate.SetIfUnderTolerance(0.0001f, MoveRates.INSTANT_VALUE);
rate = t.localRotation.GetRate(_graphicalInitializedLocalValues.Rotation, delta, out _, interval);
float rotationRate = rate.SetIfUnderTolerance(0.2f, MoveRates.INSTANT_VALUE);
_moveRates.Update(positionRate, rotationRate, MoveRates.UNSET_VALUE);
}
}
/// <summary>
/// Gets a goal position for the graphical object.
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Vector3 GetGraphicalGoalPosition()
{
if (_smoothingData.SmoothPosition)
return _graphicalInitializedLocalValues.Position;
else
return _smoothingData.GraphicalObject.position;
}
/// <summary>
/// Gets a goal rotation for the graphical object.
/// </summary>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Quaternion GetGraphicalGoalRotation()
{
if (_smoothingData.SmoothRotation)
return _graphicalInitializedLocalValues.Rotation;
else
return _smoothingData.GraphicalObject.rotation;
}
/// <summary>
/// Resets the graphical object to it's transform offsets during instantiation.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ResetGraphicalToInitializedLocalOffsets(bool position, bool rotation)
{
Transform graphical = _smoothingData.GraphicalObject;
if (position)
graphical.localPosition = GetGraphicalGoalPosition();
if (rotation)
graphical.localRotation = GetGraphicalGoalRotation();
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a2adf11043050fb4dbd1c3893c66c9f3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d5fecbbc171a81f4a8aaf30aebb1caef
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,36 @@
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,
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f8deb74673db557489cf93509af3cb21
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4fa68ca6a21d08f42980dcf68f984d53
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,12 @@
namespace FishNet.Object
{
internal enum SyncTypeWriteType
{
Observers = 0,
Owner = 1,
All = 2,
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e6406cc7d5fe47c44a26298145f54b00
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5429986c563a23a4c86f7b6724e41a70
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,19 @@

namespace FishNet.Object.Synchronizing
{
/// <summary>
/// Custom SyncObjects must inherit from SyncBase and implement this interface.
/// </summary>
public interface ICustomSync
{
/// <summary>
/// Get the serialized type.
/// This must return the value type you are synchronizing, for example a struct or class.
/// If you are not synchronizing a particular value but instead of supported values such as int, bool, ect, then you may return null on this method.
/// </summary>
/// <returns></returns>
object GetSerializedType();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2024b0be0cd1cc744a442f3e2e6ba483
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9d0e81c03149ecd4eba926bba2d0edbe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
namespace FishNet.Object
{
internal enum MissingObjectPacketLength : int
{
Reliable = -1,
PurgeRemaiming = -2,
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3d177496f9519e246b8e3ef199d83437
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8050ef114e01f74409d8e29b821b6fc0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,53 @@
using FishNet.Transporting;
namespace FishNet.Object.Synchronizing.Internal
{
public class Settings
{
/// <summary>
/// Defines the write permissions for this var
/// </summary>
public WritePermission WritePermission = WritePermission.ServerOnly;
/// <summary>
/// Clients which may receive updated values.
/// </summary>
public ReadPermission ReadPermission = ReadPermission.Observers;
/// <summary>
/// How often this variable may synchronize.
/// </summary>
public float SendRate = 0f;
/// <summary>
/// Channel to send values on.
/// </summary>
public Channel Channel = Channel.Reliable;
/// <summary>
/// Constructs a new NetworkedVarSettings instance
/// </summary>
public Settings()
{
}
public Settings(WritePermission writePermission, ReadPermission readPermission, float sendRate, Channel channel)
{
WritePermission = writePermission;
ReadPermission = readPermission;
SendRate = sendRate;
Channel = channel;
}
public Settings(float sendTickrate)
{
SendRate = sendTickrate;
}
public Settings(ReadPermission readPermission, float sendRate, Channel channel)
{
ReadPermission = readPermission;
SendRate = sendRate;
Channel = channel;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 24688925098da4d42be8dbfa66b88b82
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,296 @@
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();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1a6f26e3f8016cc499b3fa99e7368fbc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 54751f912587a854cb61ff80a82087bf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d5d6ed9db47a8224fa9ed4d2ff54586f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More