[Add] FishNet
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
// Remove on V5
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1870202c019b99348aaedbe2029caf33
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/ChangedTransformProperties.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0cd019316f743a94a8131c7c60d8ebd6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,194 @@
|
||||
#if UNITY_EDITOR
|
||||
using FishNet.Component.Transforming;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object.Editing
|
||||
{
|
||||
[CustomEditor(typeof(NetworkObject), true)]
|
||||
[CanEditMultipleObjects]
|
||||
public class NetworkObjectEditor : Editor
|
||||
{
|
||||
private SerializedProperty _isNetworked;
|
||||
private SerializedProperty _isSpawnable;
|
||||
private SerializedProperty _isGlobal;
|
||||
private SerializedProperty _initializeOrder;
|
||||
private SerializedProperty _preventDespawnOnDisconnect;
|
||||
private SerializedProperty _defaultDespawnType;
|
||||
private SerializedProperty _enablePrediction;
|
||||
private SerializedProperty _enableStateForwarding;
|
||||
private SerializedProperty _networkTransform;
|
||||
private SerializedProperty _predictionType;
|
||||
private SerializedProperty _localReconcileCorrectionType;
|
||||
private SerializedProperty _graphicalObject;
|
||||
private SerializedProperty _detachGraphicalObject;
|
||||
private SerializedProperty _ownerSmoothedProperties;
|
||||
private SerializedProperty _spectatorSmoothedProperties;
|
||||
private SerializedProperty _ownerInterpolation;
|
||||
private SerializedProperty _adaptiveInterpolation;
|
||||
private SerializedProperty _spectatorInterpolation;
|
||||
private SerializedProperty _enableTeleport;
|
||||
private SerializedProperty _teleportThreshold;
|
||||
private int _tabIndex;
|
||||
private int _savedTabIndex;
|
||||
private const string TAB_INDEX_PREFS_NAME = "FishNet_NetworkObject_TabIndex";
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
_tabIndex = EditorPrefs.GetInt(TAB_INDEX_PREFS_NAME);
|
||||
_savedTabIndex = _tabIndex;
|
||||
|
||||
_isNetworked = serializedObject.FindProperty(nameof(_isNetworked));
|
||||
_isSpawnable = serializedObject.FindProperty(nameof(_isSpawnable));
|
||||
_isGlobal = serializedObject.FindProperty(nameof(_isGlobal));
|
||||
_initializeOrder = serializedObject.FindProperty(nameof(_initializeOrder));
|
||||
_preventDespawnOnDisconnect = serializedObject.FindProperty(nameof(_preventDespawnOnDisconnect));
|
||||
_defaultDespawnType = serializedObject.FindProperty(nameof(_defaultDespawnType));
|
||||
|
||||
_enablePrediction = serializedObject.FindProperty(nameof(_enablePrediction));
|
||||
_enableStateForwarding = serializedObject.FindProperty(nameof(_enableStateForwarding));
|
||||
_networkTransform = serializedObject.FindProperty(nameof(_networkTransform));
|
||||
_predictionType = serializedObject.FindProperty(nameof(_predictionType));
|
||||
_localReconcileCorrectionType = serializedObject.FindProperty(nameof(_localReconcileCorrectionType));
|
||||
_graphicalObject = serializedObject.FindProperty(nameof(_graphicalObject));
|
||||
_detachGraphicalObject = serializedObject.FindProperty(nameof(_detachGraphicalObject));
|
||||
|
||||
_ownerSmoothedProperties = serializedObject.FindProperty(nameof(_ownerSmoothedProperties));
|
||||
_ownerInterpolation = serializedObject.FindProperty(nameof(_ownerInterpolation));
|
||||
_adaptiveInterpolation = serializedObject.FindProperty(nameof(_adaptiveInterpolation));
|
||||
_spectatorSmoothedProperties = serializedObject.FindProperty(nameof(_spectatorSmoothedProperties));
|
||||
_spectatorInterpolation = serializedObject.FindProperty(nameof(_spectatorInterpolation));
|
||||
_enableTeleport = serializedObject.FindProperty(nameof(_enableTeleport));
|
||||
_teleportThreshold = serializedObject.FindProperty(nameof(_teleportThreshold));
|
||||
}
|
||||
|
||||
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.Space();
|
||||
|
||||
_tabIndex = GUILayout.Toolbar(_tabIndex, new string[] { "Settings", "Prediction" });
|
||||
EditorGUILayout.Space();
|
||||
switch (_tabIndex)
|
||||
{
|
||||
case 0:
|
||||
ShowSettingsTab();
|
||||
break;
|
||||
case 1:
|
||||
ShowPredictionTab();
|
||||
break;
|
||||
default:
|
||||
ShowSettingsTab();
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
void ShowSettingsTab()
|
||||
{
|
||||
SaveTabIndex();
|
||||
EditorGUILayout.PropertyField(_isNetworked);
|
||||
EditorGUILayout.PropertyField(_isSpawnable);
|
||||
EditorGUILayout.PropertyField(_isGlobal);
|
||||
EditorGUILayout.PropertyField(_initializeOrder);
|
||||
EditorGUILayout.PropertyField(_preventDespawnOnDisconnect);
|
||||
EditorGUILayout.PropertyField(_defaultDespawnType);
|
||||
}
|
||||
|
||||
void ShowPredictionTab()
|
||||
{
|
||||
SaveTabIndex();
|
||||
EditorGUILayout.PropertyField(_enablePrediction);
|
||||
if (_enablePrediction.boolValue == true)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_predictionType);
|
||||
|
||||
bool isRigidbodyPredictionType = _predictionType.intValue == (int)NetworkObject.PredictionType.Rigidbody2D || _predictionType.intValue == (int)NetworkObject.PredictionType.Rigidbody;
|
||||
if (isRigidbodyPredictionType)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_localReconcileCorrectionType);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUILayout.PropertyField(_enableStateForwarding);
|
||||
if (_enableStateForwarding.boolValue == false)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_networkTransform);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUILayout.HelpBox("Smoothing settings on the NetworkObject will be obsoleted soon. Please unset the graphicalObject and use NetworkTickSmoother instead.", MessageType.Warning);
|
||||
|
||||
bool graphicalSet = _graphicalObject.objectReferenceValue != null;
|
||||
EditorGUILayout.PropertyField(_graphicalObject);
|
||||
if (graphicalSet)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_detachGraphicalObject);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("Smoothing", EditorStyles.boldLabel);
|
||||
if (!graphicalSet)
|
||||
{
|
||||
EditorGUILayout.HelpBox($"More smoothing settings will be displayed when a graphicalObject is set.", MessageType.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_enableTeleport);
|
||||
if (_enableTeleport.boolValue == true)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_teleportThreshold, new GUIContent("Teleport Threshold"));
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("Owner", EditorStyles.boldLabel);
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_ownerInterpolation, new GUIContent("Interpolation"));
|
||||
EditorGUILayout.PropertyField(_ownerSmoothedProperties, new GUIContent("Smoothed Properties"));
|
||||
EditorGUI.indentLevel--;
|
||||
|
||||
EditorGUILayout.LabelField("Spectator", EditorStyles.boldLabel);
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_adaptiveInterpolation);
|
||||
if (_adaptiveInterpolation.intValue == (int)AdaptiveInterpolationType.Off)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.PropertyField(_spectatorInterpolation, new GUIContent("Interpolation"));
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
EditorGUILayout.PropertyField(_spectatorSmoothedProperties, new GUIContent("Smoothed Properties"));
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves tabIndex if it has changed.
|
||||
/// </summary>
|
||||
private void SaveTabIndex()
|
||||
{
|
||||
if (_tabIndex == _savedTabIndex)
|
||||
return;
|
||||
|
||||
_savedTabIndex = _tabIndex;
|
||||
EditorPrefs.SetInt(TAB_INDEX_PREFS_NAME, _tabIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b2d667433eee79d4e8661a84b1587b8a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/Editor/NetworkObjectEditor.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b4af6267cb928f34fa47fdf8f80eccf9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,38 @@
|
||||
using FishNet.Object.Helping;
|
||||
using FishNet.Transporting;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
#region Types.
|
||||
/// <summary>
|
||||
/// Lookup data for a RPC Link.
|
||||
/// </summary>
|
||||
internal readonly struct RpcLink
|
||||
{
|
||||
/// <summary>
|
||||
/// ObjectId for link.
|
||||
/// </summary>
|
||||
public readonly int ObjectId;
|
||||
/// <summary>
|
||||
/// NetworkBehaviour component index on ObjectId.
|
||||
/// </summary>
|
||||
public readonly byte ComponentIndex;
|
||||
/// <summary>
|
||||
/// RpcHash for link.
|
||||
/// </summary>
|
||||
public readonly uint RpcHash;
|
||||
/// <summary>
|
||||
/// PacketId used for the Rpc type when not using links.
|
||||
/// </summary>
|
||||
public readonly PacketId RpcPacketId;
|
||||
|
||||
public RpcLink(int objectId, byte componentIndex, uint rpcHash, PacketId packetId)
|
||||
{
|
||||
ObjectId = objectId;
|
||||
ComponentIndex = componentIndex;
|
||||
RpcHash = rpcHash;
|
||||
RpcPacketId = packetId;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05a91745dd829d043aadf391ac7b233e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/Helping/RpcLink.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace FishNet.Object.Helping
|
||||
{
|
||||
public enum RpcType : int
|
||||
{
|
||||
None = 0,
|
||||
Server = 1,
|
||||
Observers = 2,
|
||||
Target = 4,
|
||||
Replicate = 8,
|
||||
Reconcile = 16
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09f9e7236f988c64fad54645f4cc7f8b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/Helping/RpcType.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,44 @@
|
||||
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.IsServerStarted;
|
||||
}
|
||||
|
||||
/// <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.IsClientStarted;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 149cde0042627604d810c2c7fc0f9176
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/Helping/StaticShortcuts.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aed4a4fd57ff1f44abf1397c968a94ae
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,199 @@
|
||||
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>
|
||||
/// Type of logging to use when the IsServer check fails.
|
||||
/// </summary>
|
||||
public LoggingType Logging = LoggingType.Warning;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Type of logging to use when the IsServer check fails.
|
||||
/// </summary>
|
||||
public LoggingType Logging = LoggingType.Warning;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Type of logging to use when the IsServer check fails.
|
||||
/// </summary>
|
||||
public LoggingType Logging = LoggingType.Warning;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// False to prefer using networkObject.IsServer/ClientInitialized. True to use InstanceFinder.IsServer/ClientStarted.
|
||||
/// </summary>
|
||||
public bool UseIsStarted = false;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
/// <summary>
|
||||
/// False to prefer using networkObject.IsServer/ClientInitialized. True to use InstanceFinder.IsServer/ClientStarted.
|
||||
/// </summary>
|
||||
public bool UseIsStarted = 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>
|
||||
[Obsolete("This no longer functions. See console errors and Break Solutions in the documentation for resolution.")]
|
||||
[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>
|
||||
[Obsolete("This no longer functions. Use SyncVar<Type> instead. See console errors and Break Solutions in the documentation for resolution.")]
|
||||
[AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)]
|
||||
public class SyncVarAttribute : PropertyAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// How often values may update over the network.
|
||||
/// </summary>
|
||||
public float SendRate = 0.1f;
|
||||
/// <summary>
|
||||
/// Clients which may receive value updates.
|
||||
/// </summary>
|
||||
public ReadPermission ReadPermissions = ReadPermission.Observers;
|
||||
/// <summary>
|
||||
/// Network roles which may update values.
|
||||
/// </summary>
|
||||
public WritePermission WritePermissions = WritePermission.ServerOnly;
|
||||
/// <summary>
|
||||
/// Channel to use. Unreliable SyncVars will use eventual consistency.
|
||||
/// </summary>
|
||||
public Channel Channel;
|
||||
/// <summary>
|
||||
/// Method which will be called on the server and clients when the value changes.
|
||||
/// </summary>
|
||||
public string OnChange;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2c79ec60813585469c43b4539e3d0c5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/NetworkBehaviour/Attributes.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,16 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Utility;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo(UtilityConstants.CODEGEN_ASSEMBLY_NAME)]
|
||||
|
||||
namespace FishNet.Object.Delegating
|
||||
{
|
||||
public delegate void ServerRpcDelegate(PooledReader reader, Channel channel, NetworkConnection sender);
|
||||
|
||||
public delegate void ClientRpcDelegate(PooledReader reader, Channel channel);
|
||||
|
||||
public delegate bool SyncVarReadDelegate(PooledReader reader, byte index, bool asServer);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5dbd9cdda4843f34ab416273d80f83c8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/NetworkBehaviour/Delegates.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace FishNet.Object
|
||||
{
|
||||
/// <summary>
|
||||
/// This may be added at runtime to find objects without any network scripts, beneath a NetworkObject.
|
||||
/// </summary>
|
||||
public class EmptyNetworkBehaviour : NetworkBehaviour { }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8a6a39c46bf52104ba8efe3100bce3f7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/NetworkBehaviour/EmptyNetworkBehaviour.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,211 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Object.Synchronizing.Internal;
|
||||
using FishNet.Serializing;
|
||||
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
|
||||
|
||||
/* Payloads are written and read immediatley after the header containing the target NetworkObject/Behaviour. */
|
||||
/// <summary>
|
||||
/// Called when writing a spawn. This may be used to deliver information for predicted spawning, or simply have values set before initialization without depending on SyncTypes.
|
||||
/// </summary>
|
||||
/// <param name = "connection">Connection receiving the payload. When sending to the server connection.IsValid will return false.</param>
|
||||
public virtual void WritePayload(NetworkConnection connection, Writer writer) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called before network start callbacks, but after the object is initialized with network values. This may be used to read information from predicted spawning, or simply have values set before initialization without depending on SyncTypes.
|
||||
/// </summary>
|
||||
/// <param name = "connection">Connection sending the payload. When coming from the server connection.IsValid will return false.</param>
|
||||
public virtual void ReadPayload(NetworkConnection connection, Reader reader) { }
|
||||
|
||||
/// <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 _syncTypes.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)
|
||||
{
|
||||
// if (_syncTypes == null)
|
||||
// return;
|
||||
foreach (SyncBase item in _syncTypes.Values)
|
||||
item.OnStopCallback(asServer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes the OnStart/StopNetwork.
|
||||
/// </summary>
|
||||
internal void InvokeOnNetwork_Internal(bool start)
|
||||
{
|
||||
if (start)
|
||||
{
|
||||
if (_onStartNetworkCalled)
|
||||
return;
|
||||
|
||||
if (!gameObject.activeInHierarchy)
|
||||
{
|
||||
NetworkInitialize___Early();
|
||||
NetworkInitialize___Late();
|
||||
}
|
||||
OnStartNetwork_Internal();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_onStopNetworkCalled)
|
||||
return;
|
||||
OnStopNetwork_Internal();
|
||||
}
|
||||
}
|
||||
|
||||
internal virtual 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() { }
|
||||
|
||||
internal virtual 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() { }
|
||||
|
||||
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() { }
|
||||
|
||||
internal void OnStopServer_Internal()
|
||||
{
|
||||
OnStartServerCalled = false;
|
||||
ReturnRpcLinks();
|
||||
OnStopServer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on the server before deinitializing this object.
|
||||
/// </summary>
|
||||
public virtual void OnStopServer() { }
|
||||
|
||||
internal void OnOwnershipServer_Internal(NetworkConnection prevOwner)
|
||||
{
|
||||
ResetState_Prediction(true);
|
||||
OnOwnershipServer(prevOwner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on the server after ownership has changed.
|
||||
/// </summary>
|
||||
/// <param name = "prevOwner">Previous owner of this object.</param>
|
||||
public virtual void OnOwnershipServer(NetworkConnection prevOwner) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called on the server after a spawn message for this object has been sent to clients.
|
||||
/// Useful for sending remote calls or data to clients.
|
||||
/// </summary>
|
||||
/// <param name = "connection">Connection the object is being spawned for.</param>
|
||||
public virtual void OnSpawnServer(NetworkConnection connection) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called on the server before a despawn message for this object has been sent to connection.
|
||||
/// Useful for sending remote calls or actions to clients.
|
||||
/// </summary>
|
||||
public virtual void OnDespawnServer(NetworkConnection connection) { }
|
||||
|
||||
internal void OnStartClient_Internal()
|
||||
{
|
||||
OnStartClientCalled = true;
|
||||
OnStartClient();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on the client after initializing this object.
|
||||
/// </summary>
|
||||
public virtual void OnStartClient() { }
|
||||
|
||||
internal void OnStopClient_Internal()
|
||||
{
|
||||
OnStartClientCalled = false;
|
||||
OnStopClient();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on the client before deinitializing this object.
|
||||
/// </summary>
|
||||
public virtual void OnStopClient() { }
|
||||
|
||||
internal void OnOwnershipClient_Internal(NetworkConnection prevOwner)
|
||||
{
|
||||
// If losing or gaining ownership then clear replicate cache.
|
||||
if (IsOwner || prevOwner == LocalConnection)
|
||||
{
|
||||
ResetState_Prediction(false);
|
||||
}
|
||||
|
||||
OnOwnershipClient(prevOwner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called on the client after gaining or losing ownership.
|
||||
/// </summary>
|
||||
/// <param name = "prevOwner">Previous owner of this object.</param>
|
||||
public virtual void OnOwnershipClient(NetworkConnection prevOwner) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9ddaf08801752b49bcfe720217df74a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.Callbacks.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,19 @@
|
||||
using FishNet.Managing;
|
||||
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.CanLog(loggingType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ad4dd2795a9a00e4d814892ac1a67157
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.Logging.cs
|
||||
uploadId: 866910
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 074dac4dd3f9f6a4d8c1eb1191334472
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.Prediction.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,346 @@
|
||||
using FishNet.CodeAnalysis.Annotations;
|
||||
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 System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
public abstract partial class NetworkBehaviour : MonoBehaviour
|
||||
{
|
||||
#region Public.
|
||||
#region Obsoletes
|
||||
// Remove on v5
|
||||
[Obsolete("Use IsClientOnlyInitialized. Note the difference between IsClientOnlyInitialized and IsClientOnlyStarted.")]
|
||||
public bool IsClientOnly => IsClientOnlyInitialized;
|
||||
[Obsolete("Use IsServerOnlyInitialized. Note the difference between IsServerOnlyInitialized and IsServerOnlyStarted.")]
|
||||
public bool IsServerOnly => IsServerOnlyInitialized;
|
||||
[Obsolete("Use IsHostInitialized. Note the difference between IsHostInitialized and IsHostStarted.")]
|
||||
public bool IsHost => IsHostInitialized;
|
||||
[Obsolete("Use IsClientInitialized. Note the difference between IsClientInitialized and IsClientStarted.")]
|
||||
public bool IsClient => IsClientInitialized;
|
||||
[Obsolete("Use IsServerInitialized. Note the difference between IsServerInitialized and IsServerStarted.")]
|
||||
public bool IsServer => IsServerInitialized;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Gets this instance as the containing NetworkObject.
|
||||
/// </summary>
|
||||
public static implicit operator NetworkObject(NetworkBehaviour nb) => nb._networkObjectCache;
|
||||
|
||||
/// <summary>
|
||||
/// True if the NetworkObject for this NetworkBehaviour is deinitializing.
|
||||
/// </summary>
|
||||
public bool IsDeinitializing => _networkObjectCache.IsDeinitializing;
|
||||
/// <summary>
|
||||
/// NetworkManager for this object.
|
||||
/// </summary>
|
||||
public NetworkManager NetworkManager => _networkObjectCache.NetworkManager;
|
||||
/// <summary>
|
||||
/// ServerManager for this object.
|
||||
/// </summary>
|
||||
public ServerManager ServerManager => _networkObjectCache.ServerManager;
|
||||
/// <summary>
|
||||
/// ClientManager for this object.
|
||||
/// </summary>
|
||||
public ClientManager ClientManager => _networkObjectCache.ClientManager;
|
||||
/// <summary>
|
||||
/// ObserverManager for this object.
|
||||
/// </summary>
|
||||
public ObserverManager ObserverManager => _networkObjectCache.ObserverManager;
|
||||
/// <summary>
|
||||
/// TransportManager for this object.
|
||||
/// </summary>
|
||||
public TransportManager TransportManager => _networkObjectCache.TransportManager;
|
||||
/// <summary>
|
||||
/// TimeManager for this object.
|
||||
/// </summary>
|
||||
public TimeManager TimeManager => _networkObjectCache.TimeManager;
|
||||
/// <summary>
|
||||
/// SceneManager for this object.
|
||||
/// </summary>
|
||||
public SceneManager SceneManager => _networkObjectCache.SceneManager;
|
||||
/// <summary>
|
||||
/// PredictionManager for this object.
|
||||
/// </summary>
|
||||
public PredictionManager PredictionManager => _networkObjectCache.PredictionManager;
|
||||
/// <summary>
|
||||
/// RollbackManager for this object.
|
||||
/// </summary>
|
||||
public RollbackManager RollbackManager => _networkObjectCache.RollbackManager;
|
||||
/// <summary>
|
||||
/// NetworkObserver on this object.
|
||||
/// </summary>
|
||||
public NetworkObserver NetworkObserver => _networkObjectCache.NetworkObserver;
|
||||
/// <summary>
|
||||
/// True if this object has been initialized on the client side.
|
||||
/// This is set true right before client start callbacks and after stop callbacks.
|
||||
/// </summary>
|
||||
public bool IsClientInitialized => _networkObjectCache.IsClientInitialized;
|
||||
/// <summary>
|
||||
/// True if the client is started and authenticated.
|
||||
/// </summary>
|
||||
public bool IsClientStarted => _networkObjectCache.IsClientStarted;
|
||||
/// <summary>
|
||||
/// True if this object has been initialized only on the client side.
|
||||
/// This is set true right before server start callbacks and after stop callbacks.
|
||||
public bool IsClientOnlyInitialized => _networkObjectCache.IsClientOnlyInitialized;
|
||||
/// <summary>
|
||||
/// True if only the client is started and authenticated.
|
||||
/// </summary>
|
||||
public bool IsClientOnlyStarted => _networkObjectCache.IsClientOnlyStarted;
|
||||
/// <summary>
|
||||
/// True if this object has been initialized on the server side.
|
||||
/// This is set true right before server start callbacks and after stop callbacks.
|
||||
/// </summary>
|
||||
public bool IsServerInitialized => _networkObjectCache.IsServerInitialized;
|
||||
/// <summary>
|
||||
/// True if server is started.
|
||||
/// </summary>
|
||||
public bool IsServerStarted => _networkObjectCache.IsServerStarted;
|
||||
/// <summary>
|
||||
/// True if this object has been initialized only on the server side.
|
||||
/// This is set true right before server start callbacks and after stop callbacks.
|
||||
public bool IsServerOnlyInitialized => _networkObjectCache.IsServerOnlyInitialized;
|
||||
/// <summary>
|
||||
/// True if only the server is started.
|
||||
/// </summary>
|
||||
public bool IsServerOnlyStarted => _networkObjectCache.IsServerOnlyStarted;
|
||||
/// <summary>
|
||||
/// True if this object has been initialized on the server and client side.
|
||||
/// </summary>
|
||||
public bool IsHostInitialized => _networkObjectCache.IsHostInitialized;
|
||||
/// <summary>
|
||||
/// True if client and server are started.
|
||||
/// </summary>
|
||||
public bool IsHostStarted => _networkObjectCache.IsHostStarted;
|
||||
/// <summary>
|
||||
/// True if client nor server are started.
|
||||
/// </summary>
|
||||
public bool IsOffline => _networkObjectCache.IsOffline;
|
||||
/// <summary>
|
||||
/// True if the object will always initialize as a networked object. When false the object will not automatically initialize over the network. Using Spawn() on an object will always set that instance as networked.
|
||||
/// To check if server or client has been initialized on this object use IsXYZInitialized.
|
||||
/// </summary>
|
||||
[Obsolete("Use GetIsNetworked.")] // Remove on V5.
|
||||
public bool IsNetworked => GetIsNetworked();
|
||||
|
||||
/// <summary>
|
||||
/// True if the object will always initialize as a networked object. When false the object will not automatically initialize over the network. Using Spawn() on an object will always set that instance as networked.
|
||||
/// To check if server or client has been initialized on this object use IsXYZInitialized.
|
||||
/// </summary>
|
||||
public bool GetIsNetworked() => _networkObjectCache.GetIsNetworked();
|
||||
|
||||
/// <summary>
|
||||
/// Sets IsNetworked value. This method must be called before Start.
|
||||
/// </summary>
|
||||
/// <param name = "value">New IsNetworked value.</param>
|
||||
public void SetIsNetworked(bool value) => _networkObjectCache.SetIsNetworked(value);
|
||||
|
||||
/// <summary>
|
||||
/// True if a reconcile is occuring on the PredictionManager. Note the difference between this and IsBehaviourReconciling.
|
||||
/// </summary>
|
||||
public bool IsManagerReconciling => _networkObjectCache.IsManagerReconciling;
|
||||
/// <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>
|
||||
[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", "")]
|
||||
public bool IsOwner => _networkObjectCache.IsOwner;
|
||||
/// <summary>
|
||||
/// True if IsOwner, or if IsServerInitialized with no Owner.
|
||||
/// </summary>
|
||||
[PreventUsageInside("global::FishNet.Object.NetworkBehaviour", "OnStartServer", "")]
|
||||
[PreventUsageInside("global::FishNet.Object.NetworkBehaviour", "OnStartNetwork", " Use (base.Owner.IsLocalClient || (base.IsServerInitialized && !Owner.Isvalid) instead.")]
|
||||
[PreventUsageInside("global::FishNet.Object.NetworkBehaviour", "Awake", "")]
|
||||
[PreventUsageInside("global::FishNet.Object.NetworkBehaviour", "Start", "")]
|
||||
public bool IsController => _networkObjectCache.IsOwner || (_networkObjectCache.IsServerInitialized && !_networkObjectCache.Owner.IsValid);
|
||||
[Obsolete("Use IsController.")]
|
||||
public bool HasAuthority => IsController;
|
||||
/// <summary>
|
||||
/// Owner of this object.
|
||||
/// </summary>
|
||||
public NetworkConnection Owner
|
||||
{
|
||||
get
|
||||
{
|
||||
// Ensures a null Owner is never returned.
|
||||
if (_networkObjectCache == null)
|
||||
return 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;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Returns if a connection is the owner of this object.
|
||||
/// </summary>
|
||||
/// <param name = "connection"></param>
|
||||
/// <returns></returns>
|
||||
public bool OwnerMatches(NetworkConnection connection)
|
||||
{
|
||||
return _networkObjectCache.Owner == connection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns a GameObject. Only call from the server.
|
||||
/// </summary>
|
||||
/// <param name = "go">GameObject to despawn.</param>
|
||||
/// <param name = "despawnType">What happens to the object after being despawned.</param>
|
||||
public void Despawn(GameObject go, DespawnType? despawnType = null)
|
||||
{
|
||||
if (!IsNetworkObjectNull(true))
|
||||
_networkObjectCache.Despawn(go, despawnType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns a NetworkObject. Only call from the server.
|
||||
/// </summary>
|
||||
/// <param name = "nob">NetworkObject to despawn.</param>
|
||||
/// <param name = "despawnType">What happens to the object after being despawned.</param>
|
||||
public void Despawn(NetworkObject nob, DespawnType? despawnType = null)
|
||||
{
|
||||
if (!IsNetworkObjectNull(true))
|
||||
_networkObjectCache.Despawn(nob, despawnType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Despawns this _networkObjectCache. Can only be called on the server.
|
||||
/// </summary>
|
||||
/// <param name = "despawnType">What happens to the object after being despawned.</param>
|
||||
public void Despawn(DespawnType? despawnType = null)
|
||||
{
|
||||
if (!IsNetworkObjectNull(true))
|
||||
_networkObjectCache.Despawn(despawnType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns an object over the network. Can only be called on the server.
|
||||
/// </summary>
|
||||
/// <param name = "go">GameObject instance to spawn.</param>
|
||||
/// <param name = "ownerConnection">Connection to give ownership to.</param>
|
||||
public void Spawn(GameObject go, NetworkConnection ownerConnection = null, UnityEngine.SceneManagement.Scene scene = default)
|
||||
{
|
||||
if (IsNetworkObjectNull(true))
|
||||
return;
|
||||
_networkObjectCache.Spawn(go, ownerConnection, scene);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns an object over the network. Can only be called on the server.
|
||||
/// </summary>
|
||||
/// <param name = "nob">GameObject instance to spawn.</param>
|
||||
/// <param name = "ownerConnection">Connection to give ownership to.</param>
|
||||
public void Spawn(NetworkObject nob, NetworkConnection ownerConnection = null, UnityEngine.SceneManagement.Scene scene = default)
|
||||
{
|
||||
if (IsNetworkObjectNull(true))
|
||||
return;
|
||||
_networkObjectCache.Spawn(nob, ownerConnection, scene);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if NetworkObject is null.
|
||||
/// </summary>
|
||||
/// <param name = "warn">True to throw a warning if null.</param>
|
||||
/// <returns></returns>
|
||||
private bool IsNetworkObjectNull(bool warn)
|
||||
{
|
||||
bool isNull = _networkObjectCache == null;
|
||||
if (isNull && warn)
|
||||
NetworkManager.LogWarning($"NetworkObject is null. This can occur if this object is not spawned, or initialized yet.");
|
||||
|
||||
return isNull;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes ownership from all clients.
|
||||
/// </summary>
|
||||
public void RemoveOwnership() => _networkObjectCache.RemoveOwnership();
|
||||
|
||||
/// <summary>
|
||||
/// Gives ownership to newOwner.
|
||||
/// </summary>
|
||||
public void GiveOwnership(NetworkConnection newOwner) => _networkObjectCache.GiveOwnership(newOwner, asServer: true, recursive: false);
|
||||
|
||||
/// <summary>
|
||||
/// Gives ownership to newOwner.
|
||||
/// </summary>
|
||||
public void GiveOwnership(NetworkConnection newOwner, bool includeNested) => _networkObjectCache.GiveOwnership(newOwner, asServer: true, includeNested);
|
||||
|
||||
#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(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(component);
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a component from this NetworkManager.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T">Type to unregister.</typeparam>
|
||||
public void UnregisterInstance<T>() where T : UnityEngine.Component => _networkObjectCache.UnregisterInstance<T>();
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d7aef532208d06c4880973c51b1906f5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.QOL.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,166 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
#define DEVELOPMENT
|
||||
#endif
|
||||
using FishNet.Managing.Server;
|
||||
using FishNet.Object.Helping;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
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();
|
||||
#endregion
|
||||
|
||||
#region Consts.
|
||||
/// <summary>
|
||||
/// Number of bytes written for each RPCLinks.
|
||||
/// </summary>
|
||||
internal const int RPCLINK_RESERVED_BYTES = 2;
|
||||
#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.
|
||||
if (_observersRpcDelegates != null)
|
||||
{
|
||||
foreach (uint rpcHash in _observersRpcDelegates.Keys)
|
||||
{
|
||||
if (!MakeLink(rpcHash, PacketId.ObserversRpc))
|
||||
return;
|
||||
}
|
||||
}
|
||||
// TargetRpcs.
|
||||
if (_targetRpcDelegates != null)
|
||||
{
|
||||
foreach (uint rpcHash in _targetRpcDelegates.Keys)
|
||||
{
|
||||
if (!MakeLink(rpcHash, PacketId.TargetRpc))
|
||||
return;
|
||||
}
|
||||
}
|
||||
//ReconcileRpcs.
|
||||
if (_reconcileRpcDelegates != null)
|
||||
{
|
||||
foreach (uint rpcHash in _reconcileRpcDelegates.Keys)
|
||||
{
|
||||
if (!MakeLink(rpcHash, PacketId.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, PacketId packetId)
|
||||
{
|
||||
if (serverManager.GetRpcLink(out ushort linkIndex))
|
||||
{
|
||||
_rpcLinks[rpcHash] = new(rpcHash, packetId, linkIndex);
|
||||
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>
|
||||
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.LinkPacketId);
|
||||
|
||||
#if DEVELOPMENT
|
||||
int written = WriteDebugForValidateRpc(writer, link.RpcPacketId, link.RpcHash);
|
||||
#endif
|
||||
|
||||
//Write length only if reliable.
|
||||
if (channel == Channel.Reliable)
|
||||
writer.WriteInt32(methodWriter.Length);
|
||||
//Data.
|
||||
writer.WriteArraySegment(methodWriter.GetArraySegment());
|
||||
|
||||
#if DEVELOPMENT
|
||||
WriteDebugLengthForValidateRpc(writer, written);
|
||||
#endif
|
||||
|
||||
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)
|
||||
{
|
||||
int rpcLinksCount = _rpcLinks.Count;
|
||||
if (rpcLinksCount == 0)
|
||||
return;
|
||||
|
||||
writer.WriteNetworkBehaviourId(this);
|
||||
writer.WriteUInt16((ushort)rpcLinksCount);
|
||||
|
||||
foreach (KeyValuePair<uint, RpcLinkType> item in _rpcLinks)
|
||||
{
|
||||
//RpcLink index.
|
||||
writer.WriteUInt16Unpacked(item.Value.LinkPacketId);
|
||||
//Hash.
|
||||
writer.WriteUInt16Unpacked((ushort)item.Key);
|
||||
//True/false if observersRpc.
|
||||
writer.WriteUInt16Unpacked((ushort)item.Value.RpcPacketId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7136e9ee3f7eee44abab09285ab7b939
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.RPCLinks.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,642 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
#define DEVELOPMENT
|
||||
#endif
|
||||
using System;
|
||||
using FishNet.CodeGenerating;
|
||||
using FishNet.Connection;
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Managing.Transporting;
|
||||
using FishNet.Object.Delegating;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using FishNet.Managing.Statistic;
|
||||
using FishNet.Serializing.Helping;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
public abstract partial class NetworkBehaviour : MonoBehaviour
|
||||
{
|
||||
#region Types.
|
||||
private struct BufferedRpc
|
||||
{
|
||||
/// <summary>
|
||||
/// Writer containing the full RPC.
|
||||
/// </summary>
|
||||
public PooledWriter Writer;
|
||||
/// <summary>
|
||||
/// Which order to send the data in relation to other packets.
|
||||
/// </summary>
|
||||
public DataOrderType OrderType;
|
||||
/// <summary>
|
||||
/// True if owner should be excluded.
|
||||
/// </summary>
|
||||
public bool ExcludeOwner;
|
||||
|
||||
public BufferedRpc(PooledWriter writer, DataOrderType orderType, bool excludeOwner)
|
||||
{
|
||||
Writer = writer;
|
||||
OrderType = orderType;
|
||||
ExcludeOwner = excludeOwner;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// Used to fetch RPC names for debug.
|
||||
/// </summary>
|
||||
private Dictionary<uint, string> _rpcNames;
|
||||
#endif
|
||||
/// <summary>
|
||||
/// Registered ServerRpc methods.
|
||||
/// </summary>
|
||||
private readonly Dictionary<uint, ServerRpcDelegate> _serverRpcDelegates = new();
|
||||
/// <summary>
|
||||
/// Registered ObserversRpc methods.
|
||||
/// </summary>
|
||||
private readonly Dictionary<uint, ClientRpcDelegate> _observersRpcDelegates = new();
|
||||
/// <summary>
|
||||
/// Registered TargetRpc methods.
|
||||
/// </summary>
|
||||
private readonly Dictionary<uint, ClientRpcDelegate> _targetRpcDelegates = new();
|
||||
/// <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 readonly Dictionary<uint, BufferedRpc> _bufferedRpcs = new();
|
||||
/// <summary>
|
||||
/// Connections to exclude from RPCs, such as ExcludeOwner or ExcludeServer.
|
||||
/// </summary>
|
||||
private readonly HashSet<NetworkConnection> _networkConnectionCache = new();
|
||||
/// <summary>
|
||||
/// Used for debug output.
|
||||
/// </summary>
|
||||
private static StringBuilder _stringBuilder = new();
|
||||
#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;
|
||||
#if DEVELOPMENT
|
||||
/// <summary>
|
||||
/// Bytes used to write length for validating Rpc length.
|
||||
/// </summary>
|
||||
private const int VALIDATE_RPC_LENGTH_BYTES = 4;
|
||||
#endif
|
||||
#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)
|
||||
{
|
||||
if (bRpc.ExcludeOwner && conn == Owner)
|
||||
continue;
|
||||
|
||||
|
||||
tm.SendToClient((byte)Channel.Reliable, bRpc.Writer.GetArraySegment(), conn, true, bRpc.OrderType);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a RPC method.
|
||||
/// </summary>
|
||||
/// <param name = "hash"></param>
|
||||
/// <param name = "del"></param>
|
||||
[APIExclude]
|
||||
[MakePublic]
|
||||
internal void RegisterServerRpc(uint hash, ServerRpcDelegate del)
|
||||
{
|
||||
AddRpcName(PacketId.ServerRpc, hash, del.Method.Name);
|
||||
|
||||
if (_serverRpcDelegates.TryAdd(hash, del))
|
||||
IncreaseRpcMethodCount();
|
||||
else
|
||||
NetworkManager.LogError($"ServerRpc key {hash} has already been added for {GetType().FullName} on {gameObject.name}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a RPC method.
|
||||
/// </summary>
|
||||
/// <param name = "hash"></param>
|
||||
/// <param name = "del"></param>
|
||||
[APIExclude]
|
||||
[MakePublic]
|
||||
internal void RegisterObserversRpc(uint hash, ClientRpcDelegate del)
|
||||
{
|
||||
AddRpcName(PacketId.ObserversRpc, hash, del.Method.Name);
|
||||
|
||||
if (_observersRpcDelegates.TryAdd(hash, del))
|
||||
IncreaseRpcMethodCount();
|
||||
else
|
||||
NetworkManager.LogError($"ObserversRpc key {hash} has already been added for {GetType().FullName} on {gameObject.name}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a RPC method.
|
||||
/// </summary>
|
||||
/// <param name = "hash"></param>
|
||||
/// <param name = "del"></param>
|
||||
[APIExclude]
|
||||
[MakePublic]
|
||||
internal void RegisterTargetRpc(uint hash, ClientRpcDelegate del)
|
||||
{
|
||||
AddRpcName(PacketId.TargetRpc, hash, del.Method.Name);
|
||||
|
||||
if (_targetRpcDelegates.TryAdd(hash, del))
|
||||
IncreaseRpcMethodCount();
|
||||
else
|
||||
NetworkManager.LogError($"TargetRpc key {hash} has already been added for {GetType().FullName} on {gameObject.name}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a RpcName for hash.
|
||||
/// </summary>
|
||||
private void AddRpcName(PacketId packetId, uint hash, string methodName)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
/* Maximum Rpc hash will be ushort.maxValue, and packetId will be
|
||||
* well below that. Multiple packetId by ushort.MaxValue and add on
|
||||
* hash. This is an inexpensive and quick way to put all hashes in one
|
||||
* collection. */
|
||||
uint value = (uint)((ushort)packetId * ushort.MaxValue) + hash;
|
||||
|
||||
if (_rpcNames == null)
|
||||
_rpcNames = new();
|
||||
|
||||
_stringBuilder.Clear();
|
||||
/* This parsing to get the name originally is kind of dirty
|
||||
* but its only done by editor and won't cause issues
|
||||
* if not found. */
|
||||
const string indicator = "___";
|
||||
|
||||
int indicatorIndex = methodName.IndexOf(indicator, StringComparison.CurrentCultureIgnoreCase);
|
||||
if (indicatorIndex < 0)
|
||||
return;
|
||||
|
||||
// Trim start of first indicator.
|
||||
methodName = methodName.Substring(indicatorIndex + indicator.Length);
|
||||
|
||||
indicatorIndex = methodName.IndexOf(indicator, StringComparison.CurrentCultureIgnoreCase);
|
||||
if (indicatorIndex < 1)
|
||||
return;
|
||||
|
||||
// Trim end of last indicator.
|
||||
methodName = methodName.Substring(0, indicatorIndex);
|
||||
|
||||
_rpcNames[value] = methodName;
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a RpcName for hash.
|
||||
/// </summary>
|
||||
private string GetRpcName(PacketId packetId, uint hash)
|
||||
{
|
||||
string result;
|
||||
#if UNITY_EDITOR
|
||||
if (_rpcNames == null)
|
||||
return string.Empty;
|
||||
|
||||
// Set SetRpcName for why this is done.
|
||||
uint value = (uint)((ushort)packetId * ushort.MaxValue) + hash;
|
||||
|
||||
_rpcNames.TryGetValueIL2CPP(value, out result);
|
||||
#else
|
||||
result = string.Empty;
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <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.ReadUInt8Unpacked();
|
||||
else
|
||||
return reader.ReadUInt16();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a ServerRpc is received.
|
||||
/// </summary>
|
||||
internal void ReadServerRpc(int readerPositionAfterDebug, bool fromRpcLink, uint hash, PooledReader reader, NetworkConnection sendingClient, Channel channel)
|
||||
{
|
||||
if (!fromRpcLink)
|
||||
hash = ReadRpcHash(reader);
|
||||
|
||||
if (sendingClient == null)
|
||||
{
|
||||
_networkObjectCache.NetworkManager.LogError($"NetworkConnection is null. ServerRpc {hash} on object {gameObject.name} [id {ObjectId}] will not complete. Remainder of packet may become corrupt.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_serverRpcDelegates.TryGetValueIL2CPP(hash, out ServerRpcDelegate data))
|
||||
data.Invoke(reader, channel, sendingClient);
|
||||
else
|
||||
_networkObjectCache.NetworkManager.LogError($"ServerRpc not found for hash {hash} on object {gameObject.name} [id {ObjectId}]. Remainder of packet may become corrupt.");
|
||||
|
||||
#if !UNITY_SERVER
|
||||
if (_networkTrafficStatistics != null)
|
||||
_networkTrafficStatistics.AddInboundPacketIdData(PacketId.ServerRpc, GetRpcName(PacketId.ServerRpc, hash), reader.Position - readerPositionAfterDebug + TransportManager.PACKETID_LENGTH, gameObject, asServer: true);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when an ObserversRpc is received.
|
||||
/// </summary>
|
||||
internal void ReadObserversRpc(int readerPositionAfterDebug, bool fromRpcLink, uint hash, PooledReader reader, Channel channel)
|
||||
{
|
||||
if (!fromRpcLink)
|
||||
hash = ReadRpcHash(reader);
|
||||
|
||||
if (_observersRpcDelegates.TryGetValueIL2CPP(hash, out ClientRpcDelegate del))
|
||||
del.Invoke(reader, channel);
|
||||
else
|
||||
_networkObjectCache.NetworkManager.LogError($"ObserversRpc not found for hash {hash} on object {gameObject.name} [id {ObjectId}] . Remainder of packet may become corrupt.");
|
||||
|
||||
#if !UNITY_SERVER
|
||||
if (_networkTrafficStatistics != null)
|
||||
_networkTrafficStatistics.AddInboundPacketIdData(PacketId.ObserversRpc, GetRpcName(PacketId.ObserversRpc, hash), reader.Position - readerPositionAfterDebug + TransportManager.PACKETID_LENGTH, gameObject, asServer: false);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when an TargetRpc is received.
|
||||
/// </summary>
|
||||
internal void ReadTargetRpc(int readerPositionAfterDebug, bool fromRpcLink, uint hash, PooledReader reader, Channel channel)
|
||||
{
|
||||
if (!fromRpcLink)
|
||||
hash = ReadRpcHash(reader);
|
||||
|
||||
if (_targetRpcDelegates.TryGetValueIL2CPP(hash, out ClientRpcDelegate del))
|
||||
del.Invoke(reader, channel);
|
||||
else
|
||||
_networkObjectCache.NetworkManager.LogError($"TargetRpc not found for hash [{hash}] on gameObject [{gameObject.name}] ObjectId [{ObjectId}] NetworkBehaviour [{this.GetType().Name}]. The remainder of the packet may become corrupt.");
|
||||
|
||||
#if !UNITY_SERVER
|
||||
if (_networkTrafficStatistics != null)
|
||||
_networkTrafficStatistics.AddInboundPacketIdData(PacketId.TargetRpc, GetRpcName(PacketId.TargetRpc, hash), reader.Position - readerPositionAfterDebug + TransportManager.PACKETID_LENGTH, gameObject, asServer: false);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a RPC to server.
|
||||
/// </summary>
|
||||
/// <param name = "hash"></param>
|
||||
/// <param name = "methodWriter"></param>
|
||||
/// <param name = "channel"></param>
|
||||
[MakePublic]
|
||||
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);
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
if (_networkTrafficStatistics != null)
|
||||
_networkTrafficStatistics.AddOutboundPacketIdData(PacketId.ServerRpc, GetRpcName(PacketId.ServerRpc, hash), writer.Length, gameObject, asServer: false);
|
||||
#endif
|
||||
|
||||
_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]
|
||||
[MakePublic]
|
||||
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 = lCreateRpc(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();
|
||||
|
||||
/* If sent on unreliable the RPC has to be rebuilt for
|
||||
* reliable headers since buffered RPCs always send reliably
|
||||
* to new connections. */
|
||||
if (channel == Channel.Unreliable)
|
||||
{
|
||||
writer.StoreLength();
|
||||
writer = lCreateRpc(Channel.Reliable);
|
||||
}
|
||||
_bufferedRpcs[hash] = new(writer, orderType, excludeOwner);
|
||||
}
|
||||
// If not buffered then dispose immediately.
|
||||
else
|
||||
{
|
||||
writer.StoreLength();
|
||||
}
|
||||
|
||||
PooledWriter lCreateRpc(Channel c)
|
||||
{
|
||||
#if DEVELOPMENT
|
||||
if (!NetworkManager.DebugManager.DisableObserversRpcLinks && _rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
|
||||
#else
|
||||
if (_rpcLinks.TryGetValueIL2CPP(hash, out RpcLinkType link))
|
||||
#endif
|
||||
writer = CreateLinkedRpc(link, methodWriter, c);
|
||||
else
|
||||
writer = CreateRpc(hash, methodWriter, PacketId.ObserversRpc, c);
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
if (_networkTrafficStatistics != null)
|
||||
{
|
||||
int written = writer.Length * _networkObjectCache.Observers.Count;
|
||||
_networkTrafficStatistics.AddOutboundPacketIdData(PacketId.ObserversRpc, GetRpcName(PacketId.ObserversRpc, hash), written, gameObject, asServer: true);
|
||||
}
|
||||
#endif
|
||||
|
||||
return writer;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sends a RPC to target.
|
||||
/// </summary>
|
||||
[MakePublic]
|
||||
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 DEVELOPMENT
|
||||
if (!NetworkManager.DebugManager.DisableTargetRpcLinks && _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);
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
if (_networkTrafficStatistics != null)
|
||||
_networkTrafficStatistics.AddOutboundPacketIdData(PacketId.TargetRpc, GetRpcName(PacketId.TargetRpc, hash), writer.Length, gameObject, asServer: true);
|
||||
#endif
|
||||
|
||||
_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 && IsClientStarted)
|
||||
_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 = 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>
|
||||
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.WritePacketIdUnpacked(packetId);
|
||||
|
||||
#if DEVELOPMENT
|
||||
int written = WriteDebugForValidateRpc(writer, packetId, hash);
|
||||
#endif
|
||||
|
||||
writer.WriteNetworkBehaviour(this);
|
||||
|
||||
// Only write length if reliable.
|
||||
if (channel == Channel.Reliable)
|
||||
writer.WriteInt32(methodWriterLength + _rpcHashSize);
|
||||
|
||||
// Hash and data.
|
||||
WriteRpcHash(hash, writer);
|
||||
writer.WriteArraySegment(methodWriter.GetArraySegment());
|
||||
|
||||
#if DEVELOPMENT
|
||||
WriteDebugLengthForValidateRpc(writer, written);
|
||||
#endif
|
||||
|
||||
return writer;
|
||||
}
|
||||
|
||||
#if DEVELOPMENT
|
||||
/// <summary>
|
||||
/// Gets the method name for a Rpc using packetId and Rpc hash.
|
||||
/// </summary>
|
||||
private string GetRpcMethodName(PacketId packetId, uint hash)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (packetId == PacketId.ObserversRpc)
|
||||
return _observersRpcDelegates[hash].Method.Name;
|
||||
else if (packetId == PacketId.TargetRpc)
|
||||
return _targetRpcDelegates[hash].Method.Name;
|
||||
else if (packetId == PacketId.ServerRpc)
|
||||
return _serverRpcDelegates[hash].Method.Name;
|
||||
else if (packetId == PacketId.Replicate)
|
||||
return _replicateRpcDelegates[hash].Method.Name;
|
||||
else if (packetId == PacketId.Reconcile)
|
||||
return _reconcileRpcDelegates[hash].Method.Name;
|
||||
else
|
||||
_networkObjectCache.NetworkManager.LogError($"Unhandled packetId of {packetId} for hash {hash}.");
|
||||
}
|
||||
// This should not ever happen.
|
||||
catch
|
||||
{
|
||||
_networkObjectCache.NetworkManager.LogError($"Rpc method name not found for packetId {packetId}, hash {hash}.");
|
||||
}
|
||||
|
||||
return "Error";
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Writes rpcHash to writer.
|
||||
/// </summary>
|
||||
/// <param name = "hash"></param>
|
||||
/// <param name = "writer"></param>
|
||||
private void WriteRpcHash(uint hash, PooledWriter writer)
|
||||
{
|
||||
if (_rpcHashSize == 1)
|
||||
writer.WriteUInt8Unpacked((byte)hash);
|
||||
else
|
||||
writer.WriteUInt16((byte)hash);
|
||||
}
|
||||
|
||||
#if DEVELOPMENT
|
||||
private int WriteDebugForValidateRpc(Writer writer, PacketId packetId, uint hash)
|
||||
{
|
||||
if (!_networkObjectCache.NetworkManager.DebugManager.ValidateRpcLengths)
|
||||
return -1;
|
||||
|
||||
writer.Skip(VALIDATE_RPC_LENGTH_BYTES);
|
||||
int positionStart = writer.Position;
|
||||
|
||||
string txt = $"NetworkObject Details: {_networkObjectCache.ToString()}. NetworkBehaviour Details: Name [{GetType().Name}]. Rpc Details: Name [{GetRpcMethodName(packetId, hash)}] PacketId [{packetId}] Hash [{hash}]";
|
||||
writer.WriteString(txt);
|
||||
|
||||
return positionStart;
|
||||
}
|
||||
|
||||
private void WriteDebugLengthForValidateRpc(Writer writer, int positionStart)
|
||||
{
|
||||
if (!_networkObjectCache.NetworkManager.DebugManager.ValidateRpcLengths)
|
||||
return;
|
||||
|
||||
// Write length.
|
||||
int writtenLength = writer.Position - positionStart;
|
||||
writer.InsertInt32Unpacked(writtenLength, positionStart - VALIDATE_RPC_LENGTH_BYTES);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses written data used to validate a Rpc packet.
|
||||
/// </summary>
|
||||
internal static void ReadDebugForValidatedRpc(NetworkManager manager, PooledReader reader, out int readerRemainingAfterLength, out string rpcInformation, out uint expectedReadAmount)
|
||||
{
|
||||
rpcInformation = null;
|
||||
expectedReadAmount = 0;
|
||||
readerRemainingAfterLength = 0;
|
||||
|
||||
if (!manager.DebugManager.ValidateRpcLengths)
|
||||
return;
|
||||
|
||||
expectedReadAmount = (uint)reader.ReadInt32Unpacked();
|
||||
readerRemainingAfterLength = reader.Remaining;
|
||||
|
||||
rpcInformation = reader.ReadStringAllocated();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prints an error if an Rpc packet did not validate correctly.
|
||||
/// </summary>
|
||||
/// <returns>True if an error occurred.</returns>
|
||||
internal static bool TryPrintDebugForValidatedRpc(bool fromRpcLink, NetworkManager manager, PooledReader reader, int startReaderRemaining, string rpcInformation, uint expectedReadAmount, Channel channel)
|
||||
{
|
||||
if (!manager.DebugManager.ValidateRpcLengths)
|
||||
return false;
|
||||
|
||||
int readAmount = startReaderRemaining - reader.Remaining;
|
||||
if (readAmount != expectedReadAmount)
|
||||
{
|
||||
string src = fromRpcLink ? "RpcLink" : "Rpc";
|
||||
string msg = $"A {src} read an incorrect amount of data on channel {channel}. Read length was {readAmount}, expected length is {expectedReadAmount}. {rpcInformation}." + $" {manager.PacketIdHistory.GetReceivedPacketIds(packetsFromServer: reader.Source == Reader.DataSource.Server)}.";
|
||||
manager.LogError(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 938eacb83fa7d0046bd769b31dac7e80
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.RPCs.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,554 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
#define DEVELOPMENT
|
||||
#endif
|
||||
using FishNet.CodeGenerating;
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Managing.Transporting;
|
||||
using FishNet.Object.Synchronizing;
|
||||
using FishNet.Object.Synchronizing.Internal;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using FishNet.Serializing.Helping;
|
||||
using FishNet.Utility.Extension;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
public abstract partial class NetworkBehaviour : MonoBehaviour
|
||||
{
|
||||
#region Types.
|
||||
/// <summary>
|
||||
/// Used to generate data sent from synctypes.
|
||||
/// </summary>
|
||||
private struct SyncTypeWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// Writers for each channel.
|
||||
/// </summary>
|
||||
public List<PooledWriter> Writers;
|
||||
|
||||
/// <summary>
|
||||
/// Resets Writers.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
if (Writers == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < Writers.Count; i++)
|
||||
Writers[i].Clear();
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
Writers = CollectionCaches<PooledWriter>.RetrieveList();
|
||||
for (int i = 0; i < TransportManager.CHANNEL_COUNT; i++)
|
||||
Writers.Add(WriterPool.Retrieve());
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Writers for syncTypes. A writer will exist for every ReadPermission type.
|
||||
/// </summary>
|
||||
private static Dictionary<ReadPermission, SyncTypeWriter> _syncTypeWriters = new();
|
||||
/// <summary>
|
||||
/// SyncTypes within this NetworkBehaviour.
|
||||
/// </summary>
|
||||
private Dictionary<uint, SyncBase> _syncTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_syncTypesCache == null)
|
||||
_syncTypesCache = CollectionCaches<uint, SyncBase>.RetrieveDictionary();
|
||||
|
||||
return _syncTypesCache;
|
||||
}
|
||||
}
|
||||
private Dictionary<uint, SyncBase> _syncTypesCache;
|
||||
/// <summary>
|
||||
/// True if at least one syncType is dirty.
|
||||
/// </summary>
|
||||
internal bool SyncTypeDirty;
|
||||
/// <summary>
|
||||
/// All ReadPermission values.
|
||||
/// This is used to build SyncTypeWriters on initialization.
|
||||
/// </summary>
|
||||
private static List<ReadPermission> _readPermissions;
|
||||
#endregion
|
||||
|
||||
#region Consts.
|
||||
/// <summary>
|
||||
/// Bytes to reserve for writing SyncType headers.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal const byte SYNCTYPE_RESERVE_BYTES = 4;
|
||||
/// <summary>
|
||||
/// Bytes to reserve for writing payload headers.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal const byte PAYLOAD_RESERVE_BYTES = 4;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Registers a SyncType.
|
||||
/// </summary>
|
||||
/// <param name = "sb"></param>
|
||||
/// <param name = "index"></param>
|
||||
internal void RegisterSyncType(SyncBase sb, uint index)
|
||||
{
|
||||
if (!_syncTypes.TryAdd(index, sb))
|
||||
NetworkManager.LogError($"SyncType key {index} has already been added for {GetType().FullName} on {gameObject.name}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a SyncType as dirty.
|
||||
/// </summary>
|
||||
/// <returns>True if able to dirty SyncType.</returns>
|
||||
internal bool DirtySyncType()
|
||||
{
|
||||
if (!IsServerStarted)
|
||||
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. Clients which become observers
|
||||
* will get the latest values in the spawn message, which is separate
|
||||
* from writing dirty syncTypes. */
|
||||
if (_networkObjectCache.Observers.Count == 0 && !_networkObjectCache.PredictedSpawner.IsValid)
|
||||
return false;
|
||||
if (!SyncTypeDirty)
|
||||
_networkObjectCache.NetworkManager.ServerManager.Objects.SetDirtySyncType(this);
|
||||
|
||||
SyncTypeDirty = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes SyncTypes. This will only call once even as host.
|
||||
/// </summary>
|
||||
private void SyncTypes_Preinitialize(bool asServer)
|
||||
{
|
||||
if (_networkObjectCache.DoubleLogic(asServer))
|
||||
return;
|
||||
|
||||
// This only runs once since SyncTypeWriters are static.
|
||||
if (_syncTypeWriters.Count == 0)
|
||||
{
|
||||
List<ReadPermission> readPermissions = new();
|
||||
System.Array arr = System.Enum.GetValues(typeof(ReadPermission));
|
||||
foreach (ReadPermission rp in arr)
|
||||
readPermissions.Add(rp);
|
||||
|
||||
foreach (ReadPermission rp in readPermissions)
|
||||
{
|
||||
SyncTypeWriter syncTypeWriter = new();
|
||||
syncTypeWriter.Initialize();
|
||||
_syncTypeWriters[rp] = syncTypeWriter;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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 _syncTypes.Values)
|
||||
sb.PreInitialize(_networkObjectCache.NetworkManager, asServer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a SyncType.
|
||||
/// </summary>
|
||||
internal void ReadSyncType(int readerPositionAfterDebug, PooledReader reader, int writtenLength, bool asServer = false)
|
||||
{
|
||||
int endPosition = reader.Position + writtenLength;
|
||||
while (reader.Position < endPosition)
|
||||
{
|
||||
byte syncTypeId = reader.ReadUInt8Unpacked();
|
||||
if (_syncTypes.TryGetValueIL2CPP(syncTypeId, out SyncBase sb))
|
||||
sb.Read(reader, asServer);
|
||||
else
|
||||
NetworkManager.LogError($"SyncType not found for index {syncTypeId} on {transform.name}, component {GetType().FullName}. The remainder of the packet will become corrupt likely resulting in unforeseen issues for this tick, such as data missing or objects not spawning.");
|
||||
}
|
||||
|
||||
if (reader.Position > endPosition)
|
||||
{
|
||||
NetworkManager.LogError($"Remaining bytes in SyncType reader are less than expected. Something did not serialize or deserialize properly which will likely result in a SyncType being incorrect.");
|
||||
// Fix position.
|
||||
reader.Position = endPosition;
|
||||
}
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
if (_networkTrafficStatistics != null)
|
||||
_networkTrafficStatistics.AddOutboundPacketIdData(PacketId.SyncType, _typeName, reader.Position - readerPositionAfterDebug, gameObject, asServer: false);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes only dirty SyncTypes.
|
||||
/// </summary>
|
||||
/// <returns>True if there are no pending dirty sync types.</returns>
|
||||
internal bool WriteDirtySyncTypes(SyncTypeWriteFlag flags)
|
||||
{
|
||||
// /* IsSpawned 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.
|
||||
// *
|
||||
// * No observers can occur when the server changes a syncType
|
||||
// * value but gained no observers in the same tick. We still
|
||||
// * want to mark a syncType as dirty in this situation because
|
||||
// * it needs to write in a despawn message in the scenario the object
|
||||
// * is spawned (no observers), synctype changed, then despawned immediately
|
||||
// * after.
|
||||
// */
|
||||
// if (!IsSpawned || _networkObjectCache.Observers.Count == 0)
|
||||
// {
|
||||
// ResetState_SyncTypes(asServer: true);
|
||||
// return true;
|
||||
// }
|
||||
|
||||
/* IsSpawned 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)
|
||||
{
|
||||
ResetState_SyncTypes(asServer: true);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Additional checks need to appear below the reset check
|
||||
* above. Resets should place priority as this method was called
|
||||
* when it should not have been, such as during a despawn. */
|
||||
|
||||
// None dirty or no synctypes.
|
||||
if (!SyncTypeDirty || _syncTypes.Count == 0)
|
||||
return true;
|
||||
|
||||
// Number of syncTypes which are/were dirty.
|
||||
int dirtyCount = 0;
|
||||
// Number of syncTypes which were written.
|
||||
int writtenCount = 0;
|
||||
|
||||
// Flags as boolean.
|
||||
bool ignoreInterval = flags.FastContains(SyncTypeWriteFlag.IgnoreInterval);
|
||||
bool forceReliable = flags.FastContains(SyncTypeWriteFlag.ForceReliable);
|
||||
|
||||
uint tick = _networkObjectCache.NetworkManager.TimeManager.Tick;
|
||||
bool ownerIsActive = _networkObjectCache.Owner.IsActive;
|
||||
|
||||
// Reset syncTypeWriters.
|
||||
foreach (SyncTypeWriter stw in _syncTypeWriters.Values)
|
||||
stw.Reset();
|
||||
|
||||
HashSet<ReadPermission> writtenReadPermissions = CollectionCaches<ReadPermission>.RetrieveHashSet();
|
||||
|
||||
foreach (SyncBase sb in _syncTypes.Values)
|
||||
{
|
||||
// This entry is not dirty.
|
||||
if (!sb.IsDirty)
|
||||
continue;
|
||||
|
||||
/* Mark that at least one is still dirty.
|
||||
* This does not mean that anything was written
|
||||
* as there are still blocks to bypass. */
|
||||
dirtyCount++;
|
||||
|
||||
// Interval not yet met.
|
||||
if (!ignoreInterval && !sb.IsNextSyncTimeMet(tick))
|
||||
continue;
|
||||
|
||||
// Unset that SyncType is dirty as it will be written now.
|
||||
sb.ResetDirty();
|
||||
|
||||
/* SyncType is for owner only but the owner is not valid, therefor
|
||||
* nothing can be written. It's possible for a SyncType to be dirty
|
||||
* and owner only, with no owner, if the owner dropped after the syncType
|
||||
* was dirtied. */
|
||||
ReadPermission rp = sb.Settings.ReadPermission;
|
||||
// If ReadPermission is owner but no owner skip this syncType write.
|
||||
if (!ownerIsActive && rp == ReadPermission.OwnerOnly)
|
||||
continue;
|
||||
|
||||
writtenCount++;
|
||||
|
||||
if (forceReliable)
|
||||
sb.SetCurrentChannel(Channel.Reliable);
|
||||
|
||||
// Get channel
|
||||
byte channel = (byte)sb.Channel;
|
||||
|
||||
/* Writer can be obtained quickly by using the readPermission byte value.
|
||||
* Byte values are in order starting at 0. */
|
||||
|
||||
|
||||
// Find writer to use. Should never fail.
|
||||
if (!_syncTypeWriters.TryGetValueIL2CPP(rp, out SyncTypeWriter stw))
|
||||
continue;
|
||||
|
||||
/* Channel for syncType is beyond available channels in transport.
|
||||
* Use default reliable. */
|
||||
if (channel >= TransportManager.CHANNEL_COUNT)
|
||||
channel = (byte)Channel.Reliable;
|
||||
|
||||
writtenReadPermissions.Add(rp);
|
||||
|
||||
sb.WriteDelta(stw.Writers[channel]);
|
||||
}
|
||||
|
||||
// If no dirty were found.
|
||||
if (dirtyCount == 0)
|
||||
{
|
||||
SyncTypeDirty = false;
|
||||
CollectionCaches<ReadPermission>.Store(writtenReadPermissions);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Nothing was written, but some are still dirty.
|
||||
if (writtenReadPermissions.Count == 0)
|
||||
{
|
||||
CollectionCaches<ReadPermission>.Store(writtenReadPermissions);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If here something was written. */
|
||||
|
||||
PooledWriter fullWriter = WriterPool.Retrieve();
|
||||
TransportManager tm = _networkObjectCache.NetworkManager.TransportManager;
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
int totalBytesWritten = 0;
|
||||
#endif
|
||||
|
||||
foreach (ReadPermission rp in writtenReadPermissions)
|
||||
{
|
||||
// Find writer to use. Should never fail.
|
||||
if (!_syncTypeWriters.TryGetValueIL2CPP(rp, out SyncTypeWriter stw))
|
||||
continue;
|
||||
|
||||
for (int i = 0; i < stw.Writers.Count; i++)
|
||||
{
|
||||
PooledWriter writer = stw.Writers[i];
|
||||
// None written for this channel.
|
||||
if (writer.Length == 0)
|
||||
continue;
|
||||
|
||||
CompleteSyncTypePacket(fullWriter, writer);
|
||||
writer.Clear();
|
||||
|
||||
// Should not be the case but check for safety.
|
||||
if (fullWriter.Length == 0)
|
||||
continue;
|
||||
|
||||
byte channel = (byte)i;
|
||||
|
||||
switch (rp)
|
||||
{
|
||||
// Send to everyone or excludeOwner.
|
||||
case ReadPermission.Observers:
|
||||
tm.SendToClients(channel, fullWriter.GetArraySegment(), _networkObjectCache.Observers);
|
||||
break;
|
||||
// Everyone but owner.
|
||||
case ReadPermission.ExcludeOwner:
|
||||
_networkConnectionCache.Clear();
|
||||
if (ownerIsActive)
|
||||
_networkConnectionCache.Add(_networkObjectCache.Owner);
|
||||
tm.SendToClients(channel, fullWriter.GetArraySegment(), _networkObjectCache.Observers, _networkConnectionCache);
|
||||
break;
|
||||
// Owner only. Owner will always be valid if here.
|
||||
case ReadPermission.OwnerOnly:
|
||||
tm.SendToClient(channel, fullWriter.GetArraySegment(), _networkObjectCache.Owner);
|
||||
break;
|
||||
}
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
totalBytesWritten += fullWriter.Length;
|
||||
#endif
|
||||
|
||||
fullWriter.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
if (_networkTrafficStatistics != null)
|
||||
_networkTrafficStatistics.AddOutboundPacketIdData(PacketId.SyncType, _typeName, totalBytesWritten, gameObject, asServer: true);
|
||||
#endif
|
||||
fullWriter.Store();
|
||||
CollectionCaches<ReadPermission>.Store(writtenReadPermissions);
|
||||
|
||||
// Return if all dirty were written.
|
||||
bool allDirtyWritten = dirtyCount == writtenCount;
|
||||
if (allDirtyWritten)
|
||||
SyncTypeDirty = false;
|
||||
|
||||
return allDirtyWritten;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes all SyncTypes for a connection if readPermissions match.
|
||||
/// </summary>
|
||||
internal void WriteSyncTypesForConnection(NetworkConnection conn, ReadPermission readPermissions)
|
||||
{
|
||||
// There are no syncTypes.
|
||||
if (_syncTypes.Count == 0)
|
||||
return;
|
||||
|
||||
// It will always exist but we need to out anyway.
|
||||
if (!_syncTypeWriters.TryGetValueIL2CPP(readPermissions, out SyncTypeWriter stw))
|
||||
return;
|
||||
|
||||
// Reset syncTypeWriters.
|
||||
stw.Reset();
|
||||
|
||||
PooledWriter fullWriter = WriterPool.Retrieve();
|
||||
|
||||
foreach (SyncBase sb in _syncTypes.Values)
|
||||
{
|
||||
if (sb.Settings.ReadPermission != readPermissions)
|
||||
continue;
|
||||
|
||||
PooledWriter writer = stw.Writers[(byte)sb.Settings.Channel];
|
||||
sb.WriteFull(writer);
|
||||
}
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
int totalBytesWritten = 0;
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < stw.Writers.Count; i++)
|
||||
{
|
||||
PooledWriter writer = stw.Writers[i];
|
||||
CompleteSyncTypePacket(fullWriter, writer);
|
||||
writer.Clear();
|
||||
|
||||
byte channel = (byte)Channel.Reliable;
|
||||
_networkObjectCache.NetworkManager.TransportManager.SendToClient(channel, fullWriter.GetArraySegment(), conn);
|
||||
}
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
if (_networkTrafficStatistics != null)
|
||||
_networkTrafficStatistics.AddOutboundPacketIdData(PacketId.SyncType, _typeName, totalBytesWritten, gameObject, asServer: true);
|
||||
#endif
|
||||
|
||||
fullWriter.Store();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Completes the writing of a SyncType by writing the header and serialized values.
|
||||
/// </summary>
|
||||
private void CompleteSyncTypePacket(PooledWriter fullWriter, PooledWriter syncTypeWriter)
|
||||
{
|
||||
// None written for this writer.
|
||||
if (syncTypeWriter.Length == 0)
|
||||
return;
|
||||
|
||||
fullWriter.Clear();
|
||||
fullWriter.WritePacketIdUnpacked(PacketId.SyncType);
|
||||
fullWriter.WriteNetworkBehaviour(this);
|
||||
|
||||
ReservedLengthWriter reservedWriter = ReservedWritersExtensions.Retrieve();
|
||||
reservedWriter.Initialize(fullWriter, SYNCTYPE_RESERVE_BYTES);
|
||||
|
||||
fullWriter.WriteArraySegment(syncTypeWriter.GetArraySegment());
|
||||
|
||||
reservedWriter.WriteLength();
|
||||
reservedWriter.Store();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes syncTypes for a spawn message.
|
||||
/// </summary>
|
||||
/// <param name = "conn">Connection SyncTypes are being written for.</param>
|
||||
internal void WriteSyncTypesForSpawn(PooledWriter writer, NetworkConnection conn)
|
||||
{
|
||||
// There are no syncTypes.
|
||||
if (_syncTypes.Count == 0)
|
||||
return;
|
||||
|
||||
// True if connection passed in is the owner of this object.
|
||||
bool connIsOwner = conn == _networkObjectCache.Owner;
|
||||
|
||||
// Reserved bytes for componentIndex and amount written.
|
||||
const byte reservedBytes = 2;
|
||||
writer.Skip(reservedBytes);
|
||||
int positionAfterReserve = writer.Position;
|
||||
|
||||
byte written = 0;
|
||||
|
||||
foreach (SyncBase sb in _syncTypes.Values)
|
||||
{
|
||||
ReadPermission rp = sb.Settings.ReadPermission;
|
||||
bool canWrite = rp == ReadPermission.Observers || (rp == ReadPermission.ExcludeOwner && !connIsOwner) || (rp == ReadPermission.OwnerOnly && connIsOwner);
|
||||
|
||||
if (!canWrite)
|
||||
continue;
|
||||
|
||||
int startWriterPosition = writer.Position;
|
||||
sb.WriteFull(writer);
|
||||
if (writer.Position != startWriterPosition)
|
||||
written++;
|
||||
}
|
||||
|
||||
// If any where written.
|
||||
if (positionAfterReserve != writer.Position)
|
||||
{
|
||||
int insertPosition = positionAfterReserve - reservedBytes;
|
||||
writer.InsertUInt8Unpacked(ComponentIndex, insertPosition++);
|
||||
writer.InsertUInt8Unpacked(written, insertPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.Remove(reservedBytes);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a SyncType for spawn.
|
||||
/// </summary>
|
||||
internal void ReadSyncTypesForSpawn(PooledReader reader)
|
||||
{
|
||||
byte written = reader.ReadUInt8Unpacked();
|
||||
for (int i = 0; i < written; i++)
|
||||
{
|
||||
byte syncTypeId = reader.ReadUInt8Unpacked();
|
||||
|
||||
if (_syncTypes.TryGetValueIL2CPP(syncTypeId, out SyncBase sb))
|
||||
sb.Read(reader, asServer: false);
|
||||
else
|
||||
NetworkManager.LogWarning($"SyncType not found for index {syncTypeId} on {transform.name}, component {GetType().FullName}. Remainder of packet may become corrupt.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets all SyncTypes for this NetworkBehaviour for server or client.
|
||||
/// </summary>
|
||||
internal void ResetState_SyncTypes(bool asServer)
|
||||
{
|
||||
if (_syncTypes != null)
|
||||
{
|
||||
foreach (SyncBase item in _syncTypes.Values)
|
||||
item.ResetState(asServer);
|
||||
}
|
||||
|
||||
if (_syncTypeWriters != null)
|
||||
{
|
||||
foreach (SyncTypeWriter syncTypeWriter in _syncTypeWriters.Values)
|
||||
syncTypeWriter.Reset();
|
||||
}
|
||||
|
||||
if (asServer)
|
||||
SyncTypeDirty = false;
|
||||
}
|
||||
|
||||
private void SyncTypes_OnDestroy()
|
||||
{
|
||||
CollectionCaches<uint, SyncBase>.StoreAndDefault(ref _syncTypesCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e56d5389eb07aa040b8a9ec8b0d7c597
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.SyncTypes.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,280 @@
|
||||
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||
#define DEVELOPMENT
|
||||
#endif
|
||||
using FishNet.CodeGenerating;
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Managing.Transporting;
|
||||
using FishNet.Serializing.Helping;
|
||||
using FishNet.Utility;
|
||||
using System.Runtime.CompilerServices;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Managing.Statistic;
|
||||
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>
|
||||
[ExcludeSerialization]
|
||||
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 = UNSET_NETWORKBEHAVIOUR_ID;
|
||||
/// <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>
|
||||
#pragma warning disable CS0414 // Field is assigned but its value is never used
|
||||
private bool _initializedOnceServer;
|
||||
/// <summary>
|
||||
/// True if initialized at some point not asServer.
|
||||
/// </summary>
|
||||
private bool _initializedOnceClient;
|
||||
#pragma warning restore CS0414 // Field is assigned but its value is never used
|
||||
#if !UNITY_SERVER
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
private NetworkTrafficStatistics _networkTrafficStatistics;
|
||||
/// <summary>
|
||||
/// Name of this NetworkBehaviour.
|
||||
/// </summary>
|
||||
private string _typeName = string.Empty;
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
#region Consts.
|
||||
/// <summary>
|
||||
/// Maximum number of allowed added NetworkBehaviours.
|
||||
/// </summary>
|
||||
public const byte MAXIMUM_NETWORKBEHAVIOURS = UNSET_NETWORKBEHAVIOUR_ID - 1;
|
||||
/// <summary>
|
||||
/// Id for when a NetworkBehaviour is not valid.
|
||||
/// </summary>
|
||||
public const byte UNSET_NETWORKBEHAVIOUR_ID = byte.MaxValue;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Outputs data about this NetworkBehaviour to string.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Name [{gameObject.name}] ComponentId [{ComponentIndex}] NetworkObject Name [{_networkObjectCache.name}] NetworkObject Id [{_networkObjectCache.ObjectId}]";
|
||||
}
|
||||
|
||||
[MakePublic]
|
||||
internal virtual void NetworkInitialize___Early() { }
|
||||
|
||||
[MakePublic]
|
||||
internal virtual void NetworkInitialize___Late() { }
|
||||
|
||||
/// <summary>
|
||||
/// Preinitializes this script for the network.
|
||||
/// </summary>
|
||||
internal void InitializeEarly(NetworkObject nob, bool asServer)
|
||||
{
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
if (_typeName == string.Empty)
|
||||
_typeName = GetType().Name;
|
||||
#endif
|
||||
|
||||
_transportManagerCache = nob.TransportManager;
|
||||
SyncTypes_Preinitialize(asServer);
|
||||
|
||||
#if DEVELOPMENT && !UNITY_SERVER
|
||||
nob.NetworkManager.StatisticsManager.TryGetNetworkTrafficStatistics(out _networkTrafficStatistics);
|
||||
#endif
|
||||
|
||||
if (asServer)
|
||||
{
|
||||
InitializeRpcLinks();
|
||||
_initializedOnceServer = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_initializedOnceClient = true;
|
||||
}
|
||||
}
|
||||
|
||||
internal void Deinitialize(bool asServer)
|
||||
{
|
||||
ResetState_SyncTypes(asServer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called by the NetworkObject when this object is destroyed.
|
||||
/// </summary>
|
||||
internal void NetworkBehaviour_OnDestroy()
|
||||
{
|
||||
SyncTypes_OnDestroy();
|
||||
}
|
||||
|
||||
/// <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>
|
||||
[MakePublic]
|
||||
[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>
|
||||
public virtual void ResetState(bool asServer)
|
||||
{
|
||||
ResetState_SyncTypes(asServer);
|
||||
ResetState_Prediction(asServer);
|
||||
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(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>();
|
||||
NetworkManagerExtensions.Log($"Script {GetType().Name} on object {gameObject.name} added a NetworkObject component to {transform.root.name}.");
|
||||
}
|
||||
|
||||
AlertToDuplicateNetworkObjects(_addedNetworkObject.transform);
|
||||
return _addedNetworkObject;
|
||||
|
||||
// Removes duplicate network objects from t.
|
||||
void AlertToDuplicateNetworkObjects(Transform t)
|
||||
{
|
||||
NetworkObject[] nobs = t.GetComponents<NetworkObject>();
|
||||
// This shouldn't be possible but does occur sometimes; maybe a unity bug?
|
||||
if (nobs.Length > 1)
|
||||
{
|
||||
// Update added to first entryt.
|
||||
_addedNetworkObject = nobs[0];
|
||||
|
||||
string useMenu = " You may also use the Fish-Networking menu to automatically remove duplicate NetworkObjects.";
|
||||
string sceneName = t.gameObject.scene.name;
|
||||
if (string.IsNullOrEmpty(sceneName))
|
||||
Debug.LogError($"Prefab {t.name} has multiple NetworkObject components. Please remove the extra component(s) to prevent errors.{useMenu}");
|
||||
else
|
||||
Debug.LogError($"Object {t.name} in scene {sceneName} has multiple NetworkObject components. Please remove the extra component(s) to prevent errors.{useMenu}");
|
||||
}
|
||||
}
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2230f9cdb1ffc9489b53875c963342d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/NetworkBehaviour/NetworkBehaviour.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,28 @@
|
||||
using FishNet.Object.Helping;
|
||||
using FishNet.Transporting;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
internal struct RpcLinkType
|
||||
{
|
||||
/// <summary>
|
||||
/// Hash for the Rpc.
|
||||
/// </summary>
|
||||
public readonly uint RpcHash;
|
||||
/// <summary>
|
||||
/// PacketId used for the Rpc type when not using links.
|
||||
/// </summary>
|
||||
public readonly PacketId RpcPacketId;
|
||||
/// <summary>
|
||||
/// PacketId sent for the RpcLink.
|
||||
/// </summary>
|
||||
public readonly ushort LinkPacketId;
|
||||
|
||||
public RpcLinkType(uint rpcHash, PacketId packetId, ushort linkPacketId)
|
||||
{
|
||||
RpcHash = rpcHash;
|
||||
RpcPacketId = packetId;
|
||||
LinkPacketId = linkPacketId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4fa68ca6a21d08f42980dcf68f984d53
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/NetworkBehaviour/RpcLinkType.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,13 @@
|
||||
// namespace FishNet.Object // Remove V5
|
||||
// {
|
||||
//
|
||||
// internal enum SyncTypeWriteType
|
||||
// {
|
||||
// Observers = 0,
|
||||
// Owner = 1,
|
||||
// All = 2,
|
||||
// }
|
||||
//
|
||||
//
|
||||
// }
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e6406cc7d5fe47c44a26298145f54b00
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/NetworkBehaviour/SyncTypeWriteType.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a70645f2a323cb648b815b5b79b56419
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,28 @@
|
||||
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.LogWarning($"Cannot send broadcast from {gameObject.name}, NetworkManager reference is null. This may occur if the object is not spawned or initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
NetworkManager.ServerManager.Broadcast(Observers, message, requireAuthenticated, channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 55d793117b52da549affcc9ec30b05c0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.Broadcast.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,182 @@
|
||||
using FishNet.Connection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using FishNet.Serializing;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
public partial class NetworkObject : MonoBehaviour
|
||||
{
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// True if OnStartServer was called.
|
||||
/// </summary>
|
||||
private bool _onStartServerCalled;
|
||||
/// <summary>
|
||||
/// True if OnStartClient was called.
|
||||
/// </summary>
|
||||
private bool _onStartClientCalled;
|
||||
#endregion
|
||||
|
||||
// ReSharper disable Unity.PerformanceAnalysis
|
||||
/// <summary>
|
||||
/// Called after all data is synchronized with this NetworkObject.
|
||||
/// </summary>
|
||||
private void InvokeStartCallbacks(bool asServer, bool invokeSyncTypeCallbacks)
|
||||
{
|
||||
/* Note: When invoking OnOwnership here previous owner will
|
||||
* always be an empty connection, since the object is just
|
||||
* now initializing. */
|
||||
|
||||
// Invoke OnStartNetwork.
|
||||
bool invokeOnNetwork = asServer || IsServerOnlyStarted || IsClientOnlyInitialized;
|
||||
if (invokeOnNetwork)
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Count; i++)
|
||||
NetworkBehaviours[i].InvokeOnNetwork_Internal(start: true);
|
||||
}
|
||||
|
||||
//As server.
|
||||
if (asServer)
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Count; i++)
|
||||
NetworkBehaviours[i].OnStartServer_Internal();
|
||||
_onStartServerCalled = true;
|
||||
for (int i = 0; i < NetworkBehaviours.Count; i++)
|
||||
NetworkBehaviours[i].OnOwnershipServer_Internal(Managing.NetworkManager.EmptyConnection);
|
||||
}
|
||||
//As client.
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Count; i++)
|
||||
NetworkBehaviours[i].OnStartClient_Internal();
|
||||
_onStartClientCalled = true;
|
||||
for (int i = 0; i < NetworkBehaviours.Count; i++)
|
||||
NetworkBehaviours[i].OnOwnershipClient_Internal(Managing.NetworkManager.EmptyConnection);
|
||||
}
|
||||
|
||||
if (invokeSyncTypeCallbacks)
|
||||
InvokeOnStartSyncTypeCallbacks(true);
|
||||
|
||||
InvokeStartCallbacks_Prediction(asServer);
|
||||
}
|
||||
|
||||
/// <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.Count; 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.Count; 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.Count; i++)
|
||||
NetworkBehaviours[i].SendBufferedRpcs(conn);
|
||||
|
||||
for (int i = 0; i < NetworkBehaviours.Count; 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.Count; i++)
|
||||
NetworkBehaviours[i].OnDespawnServer(conn);
|
||||
}
|
||||
|
||||
// ReSharper disable Unity.PerformanceAnalysis
|
||||
/// <summary>
|
||||
/// Invokes OnStop callbacks.
|
||||
/// </summary>
|
||||
internal void InvokeStopCallbacks(bool asServer, bool invokeSyncTypeCallbacks)
|
||||
{
|
||||
InvokeStopCallbacks_Prediction(asServer);
|
||||
|
||||
if (invokeSyncTypeCallbacks)
|
||||
InvokeOnStopSyncTypeCallbacks(asServer);
|
||||
|
||||
if (asServer && _onStartServerCalled)
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Count; i++)
|
||||
NetworkBehaviours[i].OnStopServer_Internal();
|
||||
|
||||
if (!_onStartClientCalled)
|
||||
InvokeOnNetwork();
|
||||
|
||||
_onStartServerCalled = false;
|
||||
}
|
||||
else if (!asServer && _onStartClientCalled)
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Count; i++)
|
||||
NetworkBehaviours[i].OnStopClient_Internal();
|
||||
|
||||
/* Only invoke OnNetwork if server start isn't called, otherwise
|
||||
* that means this is still intialized on the server. This would
|
||||
* happen if the object despawned for the clientHost but not on the
|
||||
* server. */
|
||||
if (!_onStartServerCalled)
|
||||
InvokeOnNetwork();
|
||||
|
||||
_onStartClientCalled = false;
|
||||
}
|
||||
|
||||
void InvokeOnNetwork()
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Count; i++)
|
||||
NetworkBehaviours[i].InvokeOnNetwork_Internal(start: false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes OnOwnership callbacks when ownership changes.
|
||||
/// This is not to be called when assigning ownership during a spawn message.
|
||||
/// </summary>
|
||||
private void InvokeManualOwnershipChange(NetworkConnection prevOwner, bool asServer)
|
||||
{
|
||||
if (asServer)
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Count; i++)
|
||||
NetworkBehaviours[i].OnOwnershipServer_Internal(prevOwner);
|
||||
|
||||
WriteSyncTypesForManualOwnershipChange(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 && !IsServerStarted && prevOwner == Owner;
|
||||
if (!blockInvoke)
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Count; i++)
|
||||
NetworkBehaviours[i].OnOwnershipClient_Internal(prevOwner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8e9fbf0d6eb10e94d892dd4e817030bc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.Callbacks.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,296 @@
|
||||
using FishNet.Component.Observing;
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Managing.Timing;
|
||||
using FishNet.Observing;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
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();
|
||||
#endregion
|
||||
|
||||
#region Internal.
|
||||
/// <summary>
|
||||
/// Current HashGrid entry this belongs to.
|
||||
/// </summary>
|
||||
internal GridEntry HashGridEntry;
|
||||
/// <summary>
|
||||
/// Last tick an observer was added.
|
||||
/// </summary>
|
||||
internal uint ObserverAddedTick = TimeManager.UNSET_TICK;
|
||||
#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>
|
||||
[NonSerialized]
|
||||
private List<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>
|
||||
public void UpdateRenderers(bool updateVisibility = true)
|
||||
{
|
||||
InitializeRendererCollection(force: true, 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>
|
||||
public void SetRenderersVisible(bool visible, bool force = false)
|
||||
{
|
||||
if (!force && !NetworkObserver.UpdateHostVisibility)
|
||||
return;
|
||||
|
||||
UpdateRenderVisibility(visible);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates visibilites on renders without checks.
|
||||
/// </summary>
|
||||
/// <param name = "visible"></param>
|
||||
private void UpdateRenderVisibility(bool visible)
|
||||
{
|
||||
InitializeRendererCollection(force: false, updateVisibility: false);
|
||||
|
||||
List<Renderer> rs = _renderers;
|
||||
for (int i = 0; i < rs.Count; i++)
|
||||
{
|
||||
Renderer r = rs[i];
|
||||
if (r == null)
|
||||
{
|
||||
_renderers.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
else
|
||||
{
|
||||
r.enabled = visible;
|
||||
}
|
||||
}
|
||||
|
||||
if (OnHostVisibilityUpdated != null)
|
||||
OnHostVisibilityUpdated.Invoke(_lastClientHostVisibility, visible);
|
||||
_lastClientHostVisibility = visible;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If needed Renderers collection is initialized and populated.
|
||||
/// </summary>
|
||||
private void InitializeRendererCollection(bool force, bool updateVisibility)
|
||||
{
|
||||
if (!force && _renderersPopulated)
|
||||
return;
|
||||
|
||||
List<Renderer> cache = CollectionCaches<Renderer>.RetrieveList();
|
||||
GetComponentsInChildren<Renderer>(includeInactive: true, cache);
|
||||
|
||||
_renderers = new();
|
||||
|
||||
foreach (Renderer r in cache)
|
||||
{
|
||||
if (r.enabled)
|
||||
_renderers.Add(r);
|
||||
}
|
||||
|
||||
CollectionCaches<Renderer>.Store(cache);
|
||||
|
||||
/* Intentionally set before event call. This is to prevent
|
||||
* a potential endless loop should the user make another call
|
||||
* to this objects renderer API from the event, resulting in
|
||||
* the population repeating. */
|
||||
_renderersPopulated = true;
|
||||
|
||||
if (updateVisibility)
|
||||
UpdateRenderVisibility(_lastClientHostVisibility);
|
||||
}
|
||||
|
||||
/// <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.
|
||||
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;
|
||||
}
|
||||
|
||||
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 (TimeManager != null)
|
||||
ObserverAddedTick = TimeManager.LocalTick;
|
||||
|
||||
if (OnObserversActive != null)
|
||||
{
|
||||
if ((Observers.Count > 0 && startCount == 0) || (Observers.Count == 0 && startCount > 0))
|
||||
OnObserversActive.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets this object to starting values.
|
||||
/// </summary>
|
||||
private void ResetState_Observers(bool asServer)
|
||||
{
|
||||
// As server or client it's safe to reset this value.
|
||||
ObserverAddedTick = TimeManager.UNSET_TICK;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 816dbea70a70ab949a44f485155f0087
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.Observers.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,692 @@
|
||||
#define NEW_RECONCILE_TEST
|
||||
using System;
|
||||
using FishNet.Component.Prediction;
|
||||
using FishNet.Component.Transforming;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Managing.Timing;
|
||||
using FishNet.Object.Prediction;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using FishNet.Connection;
|
||||
using FishNet.Managing.Server;
|
||||
using Unity.Profiling;
|
||||
using UnityEngine;
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace FishNet.Object
|
||||
{
|
||||
public partial class NetworkObject
|
||||
{
|
||||
#region Types.
|
||||
/// <summary>
|
||||
/// Type of prediction movement being used.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
internal enum PredictionType : byte
|
||||
{
|
||||
Other = 0,
|
||||
Rigidbody = 1,
|
||||
Rigidbody2D = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How to correct, or reset a rigidbody transform after a reconcile when the reconcile state is local, and the rigidbody has near nil differences from when the reconcile started.
|
||||
/// </summary>
|
||||
/// <remarks>Due to physics not being deterministic a reconcile can cause a rigidbody to finish with different results than what it started it, even if the rigidbody did not experience any difference in forces. These options allow FishNet to reset the rigidbody to as it were before the reconcile if the differences are minor enough. By resetting values de-synchronization and subtly observed shaking can be prevented or significantly reduced.</remarks>
|
||||
[Serializable]
|
||||
internal enum RigidbodyLocalReconcileCorrectionType : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Do not make corrections.
|
||||
/// </summary>
|
||||
Disabled = 0,
|
||||
/// <summary>
|
||||
/// Only reset the transform.
|
||||
/// </summary>
|
||||
TransformOnly = 1,
|
||||
/* Velocities support will be available next release.
|
||||
* To support velocities as well PreReconcilingTransformProperties must
|
||||
* also store each rigidbody associated with the transform. This should not
|
||||
* be too difficult given we already check for a rb to exist before adding
|
||||
* the transform.
|
||||
*
|
||||
* When adding velocities support only add velocity data if feature
|
||||
* it set to reset velocities; same applies when comparing and resetting.
|
||||
* */
|
||||
/// <summary>
|
||||
/// Reset the transform and rigidbody velocities.
|
||||
/// </summary>
|
||||
/// <remarks>This setting is included even though it is not yet functional so that it becomes effective immediately on availability should it be the selected option.</remarks>
|
||||
TransformAndVelocities = 2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Properties of a Transform and properties associated with it.
|
||||
/// </summary>
|
||||
internal class PreReconcilingTransformProperties : IResettable
|
||||
{
|
||||
/// <summary>
|
||||
/// NetworkBehaviours that are predicted on the transform.
|
||||
/// </summary>
|
||||
public readonly List<NetworkBehaviour> NetworkBehaviours = new();
|
||||
/// <summary>
|
||||
/// Transform on the first added NetworkBehaviour.
|
||||
/// </summary>
|
||||
public Transform Transform { get; private set; }
|
||||
/// <summary>
|
||||
/// Properties of the transform during PreReconcile.
|
||||
/// </summary>
|
||||
public TransformProperties Properties;
|
||||
|
||||
// ReSharper disable once EmptyConstructor
|
||||
public PreReconcilingTransformProperties() { }
|
||||
|
||||
public void AddNetworkBehaviour(NetworkBehaviour networkBehaviour)
|
||||
{
|
||||
NetworkBehaviours.Add(networkBehaviour);
|
||||
|
||||
if (Transform == null)
|
||||
Transform = networkBehaviour.transform;
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
NetworkBehaviours.Clear();
|
||||
Transform = null;
|
||||
}
|
||||
|
||||
public void InitializeState() { }
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// True if a reconcile is occuring on any NetworkBehaviour that is on or nested of this NetworkObject. Runtime NetworkBehaviours are not included, such as if you child a NetworkObject to another at runtime.
|
||||
/// </summary>
|
||||
public bool IsObjectReconciling { get; internal set; }
|
||||
/// <summary>
|
||||
/// Graphical smoother to use when using set for owner.
|
||||
/// </summary>
|
||||
[Obsolete("This field will be removed in v5. Instead reference NetworkTickSmoother on each graphical object used.")]
|
||||
public TransformTickSmoother PredictionSmoother { get; private set; }
|
||||
#endregion
|
||||
|
||||
#region Internal.
|
||||
/// <summary>
|
||||
/// Pauses and unpauses rigidbodies when they do not have data to reconcile to.
|
||||
/// </summary>
|
||||
public RigidbodyPauser RigidbodyPauser => _rigidbodyPauser;
|
||||
private RigidbodyPauser _rigidbodyPauser;
|
||||
/// <summary>
|
||||
/// True if PredictionType is set to a rigidbody value.
|
||||
/// </summary>
|
||||
internal bool IsRigidbodyPredictionType;
|
||||
#endregion
|
||||
|
||||
#region Serialized.
|
||||
/// <summary>
|
||||
/// True if this object uses prediciton methods.
|
||||
/// </summary>
|
||||
public bool EnablePrediction => _enablePrediction;
|
||||
[Tooltip("True if this object uses prediction methods.")]
|
||||
[SerializeField]
|
||||
private bool _enablePrediction;
|
||||
/// <summary>
|
||||
/// What type of component is being used for prediction? If not using rigidbodies set to other.
|
||||
/// </summary>
|
||||
[Tooltip("What type of component is being used for prediction? If not using rigidbodies set to other.")]
|
||||
[SerializeField]
|
||||
private PredictionType _predictionType = PredictionType.Other;
|
||||
/// <summary>
|
||||
/// Object state corrections to apply after replaying from a local state when non-deterministic physics have possibly provided a different result under the same conditions.
|
||||
/// </summary>
|
||||
[Tooltip("Object state corrections to apply after replaying from a local state when non-deterministic physics have possibly provided a different result under the same conditions.")]
|
||||
[SerializeField]
|
||||
private RigidbodyLocalReconcileCorrectionType _localReconcileCorrectionType = RigidbodyLocalReconcileCorrectionType.TransformAndVelocities;
|
||||
/// <summary>
|
||||
/// Object containing graphics when using prediction. This should be child of the predicted root.
|
||||
/// </summary>
|
||||
[Tooltip("Object containing graphics when using prediction. This should be child of the predicted root.")]
|
||||
[SerializeField]
|
||||
private Transform _graphicalObject;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current graphical object for prediction.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Transform GetGraphicalObject() => _graphicalObject;
|
||||
|
||||
/// <summary>
|
||||
/// Sets a new graphical object for prediction.
|
||||
/// </summary>
|
||||
/// <param name = "t"></param>
|
||||
public void SetGraphicalObject(Transform t)
|
||||
{
|
||||
_graphicalObject = t;
|
||||
InitializeTickSmoother();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True to detach and re-attach the graphical object at runtime when the client initializes/deinitializes the item.
|
||||
/// This can resolve camera jitter or be helpful objects child of the graphical which do not handle reconiliation well, such as certain animation rigs.
|
||||
/// Transform is detached after OnStartClient, and reattached before OnStopClient.
|
||||
/// </summary>
|
||||
[Tooltip("True to detach and re-attach the graphical object at runtime when the client initializes/deinitializes the item. This can resolve camera jitter or be helpful objects child of the graphical which do not handle reconiliation well, such as certain animation rigs. Transform is detached after OnStartClient, and reattached before OnStopClient.")]
|
||||
[SerializeField]
|
||||
private bool _detachGraphicalObject;
|
||||
/// <summary>
|
||||
/// True to forward replicate and reconcile states to all clients. This is ideal with games where you want all clients and server to run the same inputs. False to only use prediction on the owner, and synchronize to spectators using other means such as a NetworkTransform.
|
||||
/// </summary>
|
||||
public bool EnableStateForwarding => _enablePrediction && _enableStateForwarding;
|
||||
[Tooltip("True to forward replicate and reconcile states to all clients. This is ideal with games where you want all clients and server to run the same inputs. False to only use prediction on the owner, and synchronize to spectators using other means such as a NetworkTransform.")]
|
||||
[SerializeField]
|
||||
private bool _enableStateForwarding = true;
|
||||
/// <summary>
|
||||
/// NetworkTransform to configure for prediction. Specifying this is optional.
|
||||
/// </summary>
|
||||
[Tooltip("NetworkTransform to configure for prediction. Specifying this is optional.")]
|
||||
[SerializeField]
|
||||
private NetworkTransform _networkTransform;
|
||||
internal NetworkTransform PredictionNetworkTransform => _networkTransform;
|
||||
/// <summary>
|
||||
/// How many ticks to interpolate graphics on objects owned by the client. Typically low as 1 can be used to smooth over the frames between ticks.
|
||||
/// </summary>
|
||||
[Tooltip("How many ticks to interpolate graphics on objects owned by the client. Typically low as 1 can be used to smooth over the frames between ticks.")]
|
||||
[Range(1, byte.MaxValue)]
|
||||
[SerializeField]
|
||||
private byte _ownerInterpolation = 1;
|
||||
/// <summary>
|
||||
/// Properties of the graphicalObject to smooth when owned.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
private TransformPropertiesFlag _ownerSmoothedProperties = (TransformPropertiesFlag)~(-1 << 8);
|
||||
/// <summary>
|
||||
/// Interpolation amount of adaptive interpolation to use on non-owned objects. Higher levels result in more interpolation. When off spectatorInterpolation is used; when on interpolation based on strength and local client latency is used.
|
||||
/// </summary>
|
||||
[Tooltip("Interpolation amount of adaptive interpolation to use on non-owned objects. Higher levels result in more interpolation. When off spectatorInterpolation is used; when on interpolation based on strength and local client latency is used.")]
|
||||
[SerializeField]
|
||||
private AdaptiveInterpolationType _adaptiveInterpolation = AdaptiveInterpolationType.Low;
|
||||
/// <summary>
|
||||
/// Properties of the graphicalObject to smooth when the object is spectated.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
private TransformPropertiesFlag _spectatorSmoothedProperties = (TransformPropertiesFlag)~(-1 << 8);
|
||||
/// <summary>
|
||||
/// How many ticks to interpolate graphics on objects when not owned by the client.
|
||||
/// </summary>
|
||||
[Tooltip("How many ticks to interpolate graphics on objects when not owned by the client.")]
|
||||
[Range(1, byte.MaxValue)]
|
||||
[SerializeField]
|
||||
private byte _spectatorInterpolation = 2;
|
||||
/// <summary>
|
||||
/// True to enable teleport threshhold.
|
||||
/// </summary>
|
||||
[Tooltip("True to enable teleport threshhold.")]
|
||||
[SerializeField]
|
||||
private bool _enableTeleport;
|
||||
/// <summary>
|
||||
/// Distance the graphical object must move between ticks to teleport the transform properties.
|
||||
/// </summary>
|
||||
[Tooltip("Distance the graphical object must move between ticks to teleport the transform properties.")]
|
||||
[Range(0.001f, ushort.MaxValue)]
|
||||
[SerializeField]
|
||||
private float _teleportThreshold = 1f;
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// True if prediction behaviours have already been registered.
|
||||
/// </summary>
|
||||
private bool _predictionBehavioursRegistered;
|
||||
/// <summary>
|
||||
/// NetworkBehaviours which use prediction.
|
||||
/// </summary>
|
||||
private HashSet<NetworkBehaviour> _predictionBehaviours;
|
||||
/// <summary>
|
||||
/// Properties of a transform before reconcile when the transform may be affected by a rigidbody.
|
||||
/// </summary>
|
||||
private Dictionary<Transform, PreReconcilingTransformProperties> _rigidbodyTransformsPreReconcileProperties;
|
||||
/// <summary>
|
||||
/// Values which were updated within <see cref="_rigidbodyTransformsPreReconcileProperties"/> during preReconcile.
|
||||
/// </summary>
|
||||
private List<PreReconcilingTransformProperties> _updatedPreReconcilingTransformProperties;
|
||||
#endregion
|
||||
|
||||
#region Private Profiler Markers
|
||||
private static readonly ProfilerMarker _pm_OnPreTick = new("NetworkObject.TimeManager_OnPreTick()");
|
||||
private static readonly ProfilerMarker _pm_OnPostReplicateReplay = new("NetworkObject.PredictionManager_OnPostReplicateReplay(uint, uint)");
|
||||
private static readonly ProfilerMarker _pm_OnPostTick = new("NetworkObject.TimeManager_OnPostTick()");
|
||||
private static readonly ProfilerMarker _pm_OnPreReconcile = new("NetworkObject.PredictionManager_OnPreReconcile(uint, uint)");
|
||||
private static readonly ProfilerMarker _pm_OnReconcile = new("NetworkObject.PredictionManager_OnReconcile(uint, uint)");
|
||||
private static readonly ProfilerMarker _pm_OnPostReconcile = new("NetworkObject.PredictionManager_OnPostReconcile(uint, uint)");
|
||||
private static readonly ProfilerMarker _pm_OnReplicateReplay = new("NetworkObject.PredictionManager_OnReplicateReplay(uint, uint)");
|
||||
#endregion
|
||||
|
||||
private void TimeManager_OnUpdate_Prediction()
|
||||
{
|
||||
if (!_enablePrediction)
|
||||
return;
|
||||
|
||||
if (PredictionSmoother != null)
|
||||
PredictionSmoother.OnUpdate();
|
||||
}
|
||||
|
||||
private void InitializeEarly_Prediction(NetworkManager manager, bool asServer)
|
||||
{
|
||||
if (!_enablePrediction)
|
||||
return;
|
||||
|
||||
if (!_enableStateForwarding && _networkTransform != null)
|
||||
_networkTransform.ConfigureForPrediction(_predictionType);
|
||||
|
||||
IsRigidbodyPredictionType = _predictionType == PredictionType.Rigidbody || _predictionType == PredictionType.Rigidbody2D;
|
||||
|
||||
if (!_predictionBehavioursRegistered)
|
||||
{
|
||||
foreach (NetworkBehaviour behaviour in NetworkBehaviours)
|
||||
{
|
||||
TryRegisterPredictionBehaviour(behaviour);
|
||||
RegisterPredictionRigidbodyTransform(behaviour);
|
||||
}
|
||||
|
||||
_predictionBehavioursRegistered = true;
|
||||
}
|
||||
|
||||
if (!asServer)
|
||||
InitializeSmoothers();
|
||||
|
||||
ChangePredictionSubscriptions(true, manager, asServer);
|
||||
}
|
||||
|
||||
private void Deinitialize_Prediction(bool asServer)
|
||||
{
|
||||
if (!_enablePrediction)
|
||||
return;
|
||||
|
||||
DeinitializeSmoothers();
|
||||
ChangePredictionSubscriptions(subscribe: false, NetworkManager, asServer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes subscriptions to use callbacks for prediction.
|
||||
/// </summary>
|
||||
private void ChangePredictionSubscriptions(bool subscribe, NetworkManager manager, bool asServer)
|
||||
{
|
||||
/* Only the client needs to unsubscribe from these but
|
||||
* asServer may not invoke as false if the client is suddenly
|
||||
* dropping their connection. */
|
||||
if (asServer && subscribe)
|
||||
return;
|
||||
|
||||
if (manager == null)
|
||||
return;
|
||||
|
||||
if (_predictionBehaviours.Count == 0)
|
||||
return;
|
||||
|
||||
if (subscribe)
|
||||
{
|
||||
manager.PredictionManager.OnPreReconcile += PredictionManager_OnPreReconcile;
|
||||
manager.PredictionManager.OnReconcile += PredictionManager_OnReconcile;
|
||||
manager.PredictionManager.OnReplicateReplay += PredictionManager_OnReplicateReplay;
|
||||
manager.PredictionManager.OnPostReplicateReplay += PredictionManager_OnPostReplicateReplay;
|
||||
manager.PredictionManager.OnPostReconcile += PredictionManager_OnPostReconcile;
|
||||
manager.TimeManager.OnPreTick += TimeManager_OnPreTick;
|
||||
manager.TimeManager.OnPostTick += TimeManager_OnPostTick;
|
||||
}
|
||||
else
|
||||
{
|
||||
manager.PredictionManager.OnPreReconcile -= PredictionManager_OnPreReconcile;
|
||||
manager.PredictionManager.OnReconcile -= PredictionManager_OnReconcile;
|
||||
manager.PredictionManager.OnReplicateReplay -= PredictionManager_OnReplicateReplay;
|
||||
manager.PredictionManager.OnPostReplicateReplay -= PredictionManager_OnPostReplicateReplay;
|
||||
manager.PredictionManager.OnPostReconcile -= PredictionManager_OnPostReconcile;
|
||||
manager.TimeManager.OnPreTick -= TimeManager_OnPreTick;
|
||||
manager.TimeManager.OnPostTick -= TimeManager_OnPostTick;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes tick smoothing.
|
||||
/// </summary>
|
||||
private void InitializeSmoothers()
|
||||
{
|
||||
if (IsRigidbodyPredictionType)
|
||||
{
|
||||
_rigidbodyPauser = ResettableObjectCaches<RigidbodyPauser>.Retrieve();
|
||||
RigidbodyType rbType = _predictionType == PredictionType.Rigidbody ? RigidbodyType.Rigidbody : RigidbodyType.Rigidbody2D;
|
||||
_rigidbodyPauser.UpdateRigidbodies(transform, rbType, getInChildren: true);
|
||||
}
|
||||
|
||||
if (_graphicalObject == null)
|
||||
{
|
||||
NetworkManager.Log($"GraphicalObject is null on {gameObject.name}. This may be intentional, and acceptable, if you are smoothing between ticks yourself. Otherwise consider assigning the GraphicalObject field.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (PredictionSmoother == null)
|
||||
PredictionSmoother = ResettableObjectCaches<TransformTickSmoother>.Retrieve();
|
||||
|
||||
InitializeTickSmoother();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the tick smoother.
|
||||
/// </summary>
|
||||
private void InitializeTickSmoother()
|
||||
{
|
||||
if (PredictionSmoother == null)
|
||||
return;
|
||||
|
||||
float teleportT = _enableTeleport ? _teleportThreshold : MoveRates.UNSET_VALUE;
|
||||
PredictionSmoother.InitializeNetworked(this, _graphicalObject, _detachGraphicalObject, teleportT, (float)TimeManager.TickDelta, _ownerInterpolation, _ownerSmoothedProperties, _spectatorInterpolation, _spectatorSmoothedProperties, _adaptiveInterpolation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes tick smoothing.
|
||||
/// </summary>
|
||||
private void DeinitializeSmoothers()
|
||||
{
|
||||
if (PredictionSmoother != null)
|
||||
{
|
||||
PredictionSmoother.Deinitialize();
|
||||
ResettableObjectCaches<TransformTickSmoother>.Store(PredictionSmoother);
|
||||
PredictionSmoother = null;
|
||||
ResettableObjectCaches<RigidbodyPauser>.StoreAndDefault(ref _rigidbodyPauser);
|
||||
}
|
||||
}
|
||||
|
||||
private void InvokeStartCallbacks_Prediction(bool asServer)
|
||||
{
|
||||
if (!asServer)
|
||||
{
|
||||
TimeManager.OnUpdate += TimeManager_Update;
|
||||
|
||||
if (PredictionSmoother != null)
|
||||
PredictionSmoother.OnStartClient();
|
||||
}
|
||||
}
|
||||
|
||||
private void InvokeStopCallbacks_Prediction(bool asServer)
|
||||
{
|
||||
if (!asServer)
|
||||
return;
|
||||
|
||||
if (TimeManager != null)
|
||||
TimeManager.OnUpdate -= TimeManager_Update;
|
||||
|
||||
if (PredictionSmoother != null)
|
||||
PredictionSmoother.OnStopClient();
|
||||
}
|
||||
|
||||
private void TimeManager_OnPreTick()
|
||||
{
|
||||
using (_pm_OnPreTick.Auto())
|
||||
{
|
||||
if (PredictionSmoother != null)
|
||||
PredictionSmoother.OnPreTick();
|
||||
}
|
||||
}
|
||||
|
||||
private void PredictionManager_OnPostReplicateReplay(uint clientTick, uint serverTick)
|
||||
{
|
||||
using (_pm_OnPostReplicateReplay.Auto())
|
||||
{
|
||||
if (PredictionSmoother != null)
|
||||
PredictionSmoother.OnPostReplicateReplay(clientTick);
|
||||
}
|
||||
}
|
||||
|
||||
private void TimeManager_OnPostTick()
|
||||
{
|
||||
using (_pm_OnPostTick.Auto())
|
||||
{
|
||||
if (PredictionSmoother != null)
|
||||
PredictionSmoother.OnPostTick(NetworkManager.TimeManager.LocalTick);
|
||||
}
|
||||
}
|
||||
|
||||
private void PredictionManager_OnPreReconcile(uint clientTick, uint serverTick)
|
||||
{
|
||||
using (_pm_OnPreReconcile.Auto())
|
||||
{
|
||||
if (IsClientInitialized)
|
||||
{
|
||||
/* Always call clear. It's cheap and will prevent possible issues
|
||||
* should users be toggling related settings during testing. */
|
||||
_updatedPreReconcilingTransformProperties.Clear();
|
||||
|
||||
//Rigidbody corrections.
|
||||
if (_localReconcileCorrectionType != RigidbodyLocalReconcileCorrectionType.Disabled)
|
||||
{
|
||||
foreach (KeyValuePair<Transform, PreReconcilingTransformProperties> kvp in _rigidbodyTransformsPreReconcileProperties)
|
||||
{
|
||||
PreReconcilingTransformProperties tpc = kvp.Value;
|
||||
bool addedEntry = false;
|
||||
|
||||
foreach (NetworkBehaviour nb in tpc.NetworkBehaviours)
|
||||
{
|
||||
//Only update transform data if reconciling using local data.
|
||||
if (nb.IsBehaviourReconciling && !nb.IsReconcileRemote)
|
||||
{
|
||||
tpc.Properties.Update(kvp.Key);
|
||||
_updatedPreReconcilingTransformProperties.Add(tpc);
|
||||
|
||||
addedEntry = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//Can exit after updating when any NetworkBehaviour is reconciling for the Transform.
|
||||
if (addedEntry)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (PredictionSmoother != null)
|
||||
PredictionSmoother.OnPreReconcile();
|
||||
}
|
||||
}
|
||||
|
||||
private void PredictionManager_OnReconcile(uint clientReconcileTick, uint serverReconcileTick)
|
||||
{
|
||||
using (_pm_OnReconcile.Auto())
|
||||
{
|
||||
if (!IsClientInitialized)
|
||||
return;
|
||||
|
||||
/* Tell all prediction behaviours to set/validate their
|
||||
* reconcile data now. This will use reconciles from the server
|
||||
* whenever possible, and local reconciles if a server reconcile
|
||||
* is not available. */
|
||||
foreach (NetworkBehaviour networkBehaviour in _predictionBehaviours)
|
||||
networkBehaviour.Reconcile_Client_Start();
|
||||
|
||||
/* If still not reconciling then pause rigidbody.
|
||||
* This shouldn't happen unless the user is not calling
|
||||
* reconcile at all. */
|
||||
if (!IsObjectReconciling)
|
||||
{
|
||||
if (_rigidbodyPauser != null)
|
||||
_rigidbodyPauser.Pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PredictionManager_OnPostReconcile(uint clientReconcileTick, uint serverReconcileTick)
|
||||
{
|
||||
foreach (NetworkBehaviour nbb in _predictionBehaviours)
|
||||
nbb.IsReconcileRemote = false;
|
||||
|
||||
using (_pm_OnPostReconcile.Auto())
|
||||
{
|
||||
if (!IsClientInitialized)
|
||||
return;
|
||||
|
||||
if (_localReconcileCorrectionType != RigidbodyLocalReconcileCorrectionType.Disabled)
|
||||
{
|
||||
/* Check changes in transform for every transform
|
||||
* which utilizes prediction and a rigidbody, and
|
||||
* may have changed since preReconcile. */
|
||||
foreach (PreReconcilingTransformProperties prtp in _updatedPreReconcilingTransformProperties)
|
||||
{
|
||||
/* If transform has not changed enough to matter
|
||||
* then reset values as they were before the reconcile. */
|
||||
if (!LHasTransformChanged())
|
||||
prtp.Properties.SetWorldProperties(prtp.Transform);
|
||||
|
||||
bool LHasTransformChanged()
|
||||
{
|
||||
const float v3Distance = 0.000025f;
|
||||
const float angleDistance = 0.2f;
|
||||
|
||||
bool hasChanged = (transform.position - prtp.Properties.Position).sqrMagnitude >= v3Distance;
|
||||
if (!hasChanged)
|
||||
hasChanged = transform.rotation.Angle(prtp.Properties.Rotation, precise: true) >= angleDistance;
|
||||
|
||||
return hasChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//This is cleared before the reconcile as well, but no point to keep behaviours in memory if not needed.
|
||||
/* Always call clear. It's cheap and will prevent possible issues
|
||||
* should users be toggling related settings during testing. */
|
||||
_updatedPreReconcilingTransformProperties.Clear();
|
||||
|
||||
foreach (NetworkBehaviour networkBehaviour in _predictionBehaviours)
|
||||
networkBehaviour.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. */
|
||||
if (_rigidbodyPauser != null)
|
||||
_rigidbodyPauser.Unpause();
|
||||
|
||||
IsObjectReconciling = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void PredictionManager_OnReplicateReplay(uint clientTick, uint serverTick)
|
||||
{
|
||||
using (_pm_OnReplicateReplay.Auto())
|
||||
{
|
||||
if (!IsClientInitialized)
|
||||
return;
|
||||
|
||||
uint replayTick = IsOwner ? clientTick : serverTick;
|
||||
|
||||
foreach (NetworkBehaviour networkBehaviour in _predictionBehaviours)
|
||||
networkBehaviour.Replicate_Replay_Start(replayTick);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a NetworkBehaviour if it uses prediction.
|
||||
/// </summary>
|
||||
/// <returns>True if behavior was registered or already registered.</returns>
|
||||
// ReSharper disable once UnusedMethodReturnValue.Local
|
||||
private bool TryRegisterPredictionBehaviour(NetworkBehaviour nb)
|
||||
{
|
||||
if (!nb.UsesPrediction)
|
||||
return false;
|
||||
|
||||
_predictionBehaviours.Add(nb);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a NetworkBehaviour's Transform if the behaviour uses prediction and has a rigidbody on it.
|
||||
/// </summary>
|
||||
/// <returns>True if behavior was just registered, or already registered.</returns>
|
||||
private void RegisterPredictionRigidbodyTransform(NetworkBehaviour nb)
|
||||
{
|
||||
if (!nb.UsesPrediction)
|
||||
return;
|
||||
|
||||
Transform t = nb.transform;
|
||||
|
||||
/* Check if the transform is already registered. This will prevent
|
||||
* checking for rigidbodies multiple times on the same transform if more
|
||||
* than one prediction script exist on the same transform. */
|
||||
if (!_rigidbodyTransformsPreReconcileProperties.TryGetValueIL2CPP(t, out PreReconcilingTransformProperties prtp))
|
||||
{
|
||||
prtp = ResettableObjectCaches<PreReconcilingTransformProperties>.Retrieve();
|
||||
_rigidbodyTransformsPreReconcileProperties[t] = prtp;
|
||||
}
|
||||
|
||||
//Only transforms with rigidbodies need to be registered.
|
||||
if (t.TryGetComponent(out Rigidbody _) || t.TryGetComponent(out Rigidbody2D _))
|
||||
prtp.AddNetworkBehaviour(nb);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears replication queue inserting them into the past replicates history when possible.
|
||||
/// This should only be called when client only.
|
||||
/// </summary>
|
||||
internal void EmptyReplicatesQueueIntoHistory()
|
||||
{
|
||||
foreach (NetworkBehaviour networkBehaviour in _predictionBehaviours)
|
||||
networkBehaviour.EmptyReplicatesQueueIntoHistory_Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the last tick a NetworkBehaviour replicated with.
|
||||
/// </summary>
|
||||
/// <param name = "setUnordered">True to set unordered value, false to set ordered.</param>
|
||||
internal void SetReplicateTick(uint value, bool createdReplicate)
|
||||
{
|
||||
if (createdReplicate && Owner.IsValid)
|
||||
// ReSharper disable once RedundantArgumentDefaultValue
|
||||
Owner.ReplicateTick.Update(NetworkManager.TimeManager, value, EstimatedTick.OldTickOption.Discard);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Place this component on your NetworkManager object to remove ownership of objects for a disconnecting client.
|
||||
/// This prevents any owned object from being despawned when the owner disconnects.
|
||||
/// </summary>
|
||||
public class GlobalPreserveOwnedObjects : MonoBehaviour
|
||||
{
|
||||
private void Awake()
|
||||
{
|
||||
ServerManager sm = GetComponent<ServerManager>();
|
||||
sm.Objects.OnPreDestroyClientObjects += Objects_OnPreDestroyClientObjects;
|
||||
}
|
||||
|
||||
protected virtual void Objects_OnPreDestroyClientObjects(NetworkConnection conn)
|
||||
{
|
||||
foreach (NetworkObject networkObject in conn.Objects)
|
||||
networkObject.RemoveOwnership();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Place this component on NetworkObjects you wish to remove ownership on for a disconnecting owner.
|
||||
/// This prevents the object from being despawned when the owner disconnects.
|
||||
/// </summary>
|
||||
public class NetworkPreserveOwnedObjects : NetworkBehaviour
|
||||
{
|
||||
public override void OnStartServer()
|
||||
{
|
||||
ServerManager.Objects.OnPreDestroyClientObjects += OnPreDestroyClientObjects;
|
||||
}
|
||||
|
||||
public override void OnStopServer()
|
||||
{
|
||||
if (ServerManager != null)
|
||||
ServerManager.Objects.OnPreDestroyClientObjects -= OnPreDestroyClientObjects;
|
||||
}
|
||||
|
||||
private void OnPreDestroyClientObjects(NetworkConnection conn)
|
||||
{
|
||||
if (conn == Owner)
|
||||
RemoveOwnership();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 29401c9ff84500647a0d8718a39f28d4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.Prediction.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,402 @@
|
||||
using FishNet.CodeAnalysis.Annotations;
|
||||
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 System.Collections.Generic;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
public partial class NetworkObject : MonoBehaviour
|
||||
{
|
||||
#region Public.
|
||||
#region Obsoletes
|
||||
[Obsolete("Use IsClientOnlyInitialized. Note the difference between IsClientOnlyInitialized and IsClientOnlyStarted.")]
|
||||
public bool IsClientOnly => IsClientOnlyInitialized;
|
||||
[Obsolete("Use IsServerOnlyInitialized. Note the difference between IsServerOnlyInitialized and IsServerOnlyStarted.")]
|
||||
public bool IsServerOnly => IsServerOnlyInitialized;
|
||||
[Obsolete("Use IsHostInitialized. Note the difference between IsHostInitialized and IsHostStarted.")]
|
||||
public bool IsHost => IsHostInitialized;
|
||||
[Obsolete("Use IsClientInitialized. Note the difference between IsClientInitialized and IsClientStarted.")]
|
||||
public bool IsClient => IsClientInitialized;
|
||||
[Obsolete("Use IsServerInitialized. Note the difference between IsServerInitialized and IsServerStarted.")]
|
||||
public bool IsServer => IsServerInitialized;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// True if despawning without object pooling, or if OnDestroy was invoked on this NetworkObject. As clientHost this value becomes true when previous criteria are met and server begins to deinitialize the object.
|
||||
/// </summary>
|
||||
/// <remarks>This can be useful for checking if you wish to perform certain actions within OnStopNetwork based on destroying status.</remarks>
|
||||
public bool IsDestroying { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets IsDestroying to true if DespawnType is not pooled. When DespawnType is not specified default DespawnType is checked.
|
||||
/// </summary>
|
||||
internal void SetIsDestroying(DespawnType? despawnType = null)
|
||||
{
|
||||
if (despawnType.HasValue)
|
||||
{
|
||||
if (despawnType.Value == DespawnType.Destroy)
|
||||
IsDestroying = true;
|
||||
}
|
||||
else if (GetDefaultDespawnType() == DespawnType.Destroy)
|
||||
{
|
||||
IsDestroying = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 if this object has been initialized on the client side.
|
||||
/// This is set true right before client start callbacks and after stop callbacks.
|
||||
/// </summary>
|
||||
public bool IsClientInitialized { get; private set; }
|
||||
/// <summary>
|
||||
/// True if the client is started and authenticated. This will return true on clientHost even if the object has not initialized yet for the client.
|
||||
/// To check if this object has been initialized for the client use IsClientInitialized.
|
||||
/// </summary>
|
||||
public bool IsClientStarted => NetworkManager == null ? false : NetworkManager.IsClientStarted;
|
||||
/// <summary>
|
||||
/// True if this object has been initialized only on the server side.
|
||||
/// This is set true right before server start callbacks and after stop callbacks.
|
||||
/// </summary>
|
||||
public bool IsClientOnlyInitialized => !IsServerInitialized && IsClientInitialized;
|
||||
/// <summary>
|
||||
/// True if only the client is started and authenticated.
|
||||
/// </summary>
|
||||
public bool IsClientOnlyStarted => IsClientStarted && !IsServerStarted;
|
||||
/// <summary>
|
||||
/// True if this object has been initialized on the server side.
|
||||
/// This is set true right before server start callbacks and after stop callbacks.
|
||||
/// </summary>
|
||||
public bool IsServerInitialized { get; private set; }
|
||||
/// <summary>
|
||||
/// True if the server is active. This will return true on clientHost even if the object is being deinitialized on the server.
|
||||
/// To check if this object has been initialized for the server use IsServerInitialized.
|
||||
/// </summary>
|
||||
public bool IsServerStarted => NetworkManager == null ? false : NetworkManager.IsServerStarted;
|
||||
/// <summary>
|
||||
/// True if this object has been initialized only on the server side.
|
||||
/// This is set true right before server start callbacks and after stop callbacks.
|
||||
/// </summary>
|
||||
public bool IsServerOnlyInitialized => IsServerInitialized && !IsClientInitialized;
|
||||
/// <summary>
|
||||
/// True if only the server is started.
|
||||
/// </summary>
|
||||
public bool IsServerOnlyStarted => IsServerStarted && !IsClientStarted;
|
||||
/// <summary>
|
||||
/// True if client and server are started.
|
||||
/// </summary>
|
||||
public bool IsHostStarted => IsClientStarted && IsServerStarted;
|
||||
/// <summary>
|
||||
/// True if this object has been initialized on the server and client side.
|
||||
/// </summary>
|
||||
public bool IsHostInitialized => IsClientInitialized && IsServerInitialized;
|
||||
/// <summary>
|
||||
/// True if client nor server are started.
|
||||
/// </summary>
|
||||
public bool IsOffline => !IsClientStarted && !IsServerStarted;
|
||||
/// <summary>
|
||||
/// True if a reconcile is occuring on the PredictionManager. Note the difference between this and IsBehaviourReconciling.
|
||||
/// </summary>
|
||||
public bool IsManagerReconciling => PredictionManager.IsReconciling;
|
||||
/// <summary>
|
||||
/// True if the local client is currently using a PredictedOwner component on this object to take ownership.
|
||||
/// </summary>
|
||||
public bool IsTakingOwnership => PredictedOwner != null && PredictedOwner.TakingOwnership;
|
||||
/// <summary>
|
||||
/// True if the local client is the owner of this object.
|
||||
/// This will only return true if IsClientInitialized is also true. You may check ownership status regardless of client initialized state by using Owner.IsLocalClient.
|
||||
/// </summary>
|
||||
[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", "")]
|
||||
public bool IsOwner
|
||||
{
|
||||
get
|
||||
{
|
||||
/* ClientInitialized becomes true when this
|
||||
* NetworkObject has been initialized on the client side.
|
||||
*
|
||||
* This value is used to prevent IsOwner from returning true
|
||||
* when running as host; primarily in Update or Tick callbacks
|
||||
* where IsOwner would be true as host but OnStartClient has
|
||||
* not called yet.
|
||||
*
|
||||
* EG: server will set owner when it spawns the object.
|
||||
* If IsOwner is checked before the object spawns on the
|
||||
* client-host then it would also return true, since the
|
||||
* Owner reference would be the same as what was set by server.
|
||||
*
|
||||
* This is however bad when the client hasn't initialized the object
|
||||
* yet because it gives a false sense of execution order.
|
||||
* As a result, Update or Ticks may return IsOwner as true well before OnStartClient
|
||||
* is called. Many users rightfully create code with the assumption the client has been
|
||||
* initialized by the time IsOwner is true.
|
||||
*
|
||||
* This is a double edged sword though because now IsOwner would return true
|
||||
* within OnStartNetwork for clients only, but not for host given the client
|
||||
* side won't be initialized yet as host. As a work around CodeAnalysis will
|
||||
* inform users to instead use base.Owner.IsLocalClient within OnStartNetwork. */
|
||||
if (!IsClientInitialized)
|
||||
return false;
|
||||
|
||||
return Owner.IsLocalClient;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// True if IsOwner, or if IsServerInitialized with no Owner.
|
||||
/// </summary>
|
||||
[PreventUsageInside("global::FishNet.Object.NetworkBehaviour", "OnStartServer", "")]
|
||||
[PreventUsageInside("global::FishNet.Object.NetworkBehaviour", "OnStartNetwork", " Use (base.Owner.IsLocalClient || (base.IsServerInitialized && !Owner.Isvalid) instead.")]
|
||||
[PreventUsageInside("global::FishNet.Object.NetworkBehaviour", "Awake", "")]
|
||||
[PreventUsageInside("global::FishNet.Object.NetworkBehaviour", "Start", "")]
|
||||
public bool IsController => IsOwner || (IsServerInitialized && !Owner.IsValid);
|
||||
[Obsolete("Use IsController.")]
|
||||
public bool HasAuthority => IsController;
|
||||
/// <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 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 != UNSET_OBJECTID_VALUE;
|
||||
/// <summary>
|
||||
/// The local connection of the client calling this method.
|
||||
/// </summary>
|
||||
public NetworkConnection LocalConnection => NetworkManager == null ? new() : 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.Count)
|
||||
{
|
||||
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.";
|
||||
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)
|
||||
{
|
||||
if (NetworkManager != 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)
|
||||
{
|
||||
if (NetworkManager != 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;
|
||||
if (NetworkManager != null)
|
||||
NetworkManager.ServerManager.Despawn(nob, despawnType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns an object over the network. Only call from the server.
|
||||
/// </summary>
|
||||
public void Spawn(GameObject go, NetworkConnection ownerConnection = null, UnityEngine.SceneManagement.Scene scene = default)
|
||||
{
|
||||
if (NetworkManager != null)
|
||||
NetworkManager.ServerManager.Spawn(go, ownerConnection, scene);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Spawns an object over the network. Only call from the server.
|
||||
/// </summary>
|
||||
public void Spawn(NetworkObject nob, NetworkConnection ownerConnection = null, UnityEngine.SceneManagement.Scene scene = default)
|
||||
{
|
||||
if (NetworkManager != null)
|
||||
NetworkManager.ServerManager.Spawn(nob, ownerConnection, scene);
|
||||
}
|
||||
|
||||
[Obsolete("Use SetLocalOwnership(NetworkConnection, bool).")]
|
||||
public void SetLocalOwnership(NetworkConnection caller) => SetLocalOwnership(caller, recursive: false);
|
||||
|
||||
/// <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, bool recursive)
|
||||
{
|
||||
NetworkConnection prevOwner = Owner;
|
||||
SetOwner(caller);
|
||||
|
||||
int count;
|
||||
count = NetworkBehaviours.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
NetworkBehaviours[i].OnOwnershipClient_Internal(prevOwner);
|
||||
|
||||
if (recursive)
|
||||
{
|
||||
List<NetworkObject> allNested = GetNetworkObjects(GetNetworkObjectOption.AllNestedRecursive);
|
||||
|
||||
foreach (NetworkObject nob in allNested)
|
||||
nob.SetLocalOwnership(caller, recursive: true);
|
||||
|
||||
CollectionCaches<NetworkObject>.Store(allNested);
|
||||
}
|
||||
}
|
||||
|
||||
#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(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(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(out component);
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a component from this NetworkManager.
|
||||
/// </summary>
|
||||
/// <typeparam name = "T">Type to unregister.</typeparam>
|
||||
public void UnregisterInstance<T>() where T : UnityEngine.Component => NetworkManager.UnregisterInstance<T>();
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd30b4b61d50d01499c94a63a6eeb863
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.QOL.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,2 @@
|
||||
// Remove in V5
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: be0a4b0a32b02f64495ba3b1d22f89c4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.ReferenceIds.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,32 @@
|
||||
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()
|
||||
{
|
||||
// if (NetworkManager != null)
|
||||
NetworkManager.ClientManager.Objects.RemoveLinkIndexes(_rpcLinkIndexes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b2f6927cf3ef254d91b89e5f99a92b9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.RpcLinks.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,232 @@
|
||||
// This file contains values serialized in editor or once at runtime.
|
||||
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Utility.Extension;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor.Experimental.SceneManagement;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
public partial class NetworkObject : MonoBehaviour
|
||||
{
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// Networked PrefabId assigned to this Prefab.
|
||||
/// </summary>
|
||||
[field: SerializeField]
|
||||
[field: HideInInspector]
|
||||
public ushort PrefabId { get; internal set; } = UNSET_PREFABID_VALUE;
|
||||
/// <summary>
|
||||
/// Spawn collection to use assigned to this Prefab.
|
||||
/// </summary>
|
||||
[field: SerializeField]
|
||||
[field: HideInInspector]
|
||||
public ushort SpawnableCollectionId { get; internal set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Sets SceneId value. This is not synchronized automatically.
|
||||
/// </summary>
|
||||
/// <param name = "sceneId"></param>
|
||||
public void SetSceneId(ulong sceneId) => SceneId = sceneId;
|
||||
|
||||
/// <summary>
|
||||
/// Hash for the path which this asset resides. This value is set during edit time.
|
||||
/// </summary>
|
||||
[field: SerializeField]
|
||||
[field: 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
|
||||
|
||||
#region Internal.
|
||||
/// <summary>
|
||||
/// NetworkId for this scene object.
|
||||
/// </summary>
|
||||
[field: SerializeField]
|
||||
[field: HideInInspector]
|
||||
internal ulong SceneId;
|
||||
/// <summary>
|
||||
/// Local properties of the transform during serialization.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
internal TransformProperties SerializedTransformProperties = new();
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Last time sceneIds were built automatically.
|
||||
/// </summary>
|
||||
[NonSerialized]
|
||||
private static double _lastSceneIdAutomaticRebuildTime;
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Removes SceneObject state.
|
||||
/// This may only be called at runtime.
|
||||
/// </summary>
|
||||
internal void ClearRuntimeSceneObject()
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
NetworkManager.LogError($"ClearRuntimeSceneObject may only be called at runtime.");
|
||||
return;
|
||||
}
|
||||
|
||||
SceneId = UNSET_SCENEID_VALUE;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private void OnApplicationQuit()
|
||||
{
|
||||
_lastSceneIdAutomaticRebuildTime = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to generate a SceneIds for NetworkObjects in a scene.
|
||||
/// </summary>
|
||||
internal static List<NetworkObject> CreateSceneId(UnityEngine.SceneManagement.Scene scene, bool force, out int changed)
|
||||
{
|
||||
changed = 0;
|
||||
|
||||
if (Application.isPlaying)
|
||||
return new();
|
||||
if (!scene.IsValid())
|
||||
return new();
|
||||
if (!scene.isLoaded)
|
||||
return new();
|
||||
|
||||
HashSet<ulong> setIds = new();
|
||||
uint scenePathHash = scene.path.GetStableHashU32();
|
||||
List<NetworkObject> sceneNobs = new();
|
||||
|
||||
Scenes.GetSceneNetworkObjects(scene, firstOnly: false, errorOnDuplicates: false, ignoreUnsetSceneIds: false, ref sceneNobs);
|
||||
System.Random rnd = new();
|
||||
|
||||
// NetworkObjects which need their Ids rebuilt.
|
||||
List<NetworkObject> rebuildingNobs = new();
|
||||
|
||||
foreach (NetworkObject item in sceneNobs)
|
||||
{
|
||||
bool canGenerate = !item.IsSceneObject || !setIds.Add(item.SceneId);
|
||||
/* If an Id has not been generated yet or if it
|
||||
* already exist then rebuild for this object. */
|
||||
if (force || canGenerate)
|
||||
{
|
||||
item.SceneId = UNSET_SCENEID_VALUE;
|
||||
rebuildingNobs.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (NetworkObject item in rebuildingNobs)
|
||||
{
|
||||
ulong nextSceneId = UNSET_SCENEID_VALUE;
|
||||
while (nextSceneId == UNSET_SCENEID_VALUE || setIds.Contains(nextSceneId))
|
||||
{
|
||||
uint rndId = (uint)(rnd.Next(int.MinValue, int.MaxValue) + int.MaxValue);
|
||||
nextSceneId = CombineHashes(scenePathHash, rndId);
|
||||
}
|
||||
|
||||
ulong CombineHashes(uint a, uint b)
|
||||
{
|
||||
return b | a;
|
||||
}
|
||||
|
||||
setIds.Add(nextSceneId);
|
||||
changed++;
|
||||
item.SceneId = nextSceneId;
|
||||
EditorUtility.SetDirty(item);
|
||||
}
|
||||
|
||||
return sceneNobs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to generate a SceneId.
|
||||
/// </summary>
|
||||
private void CreateSceneId(bool force)
|
||||
{
|
||||
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 = UNSET_SCENEID_VALUE;
|
||||
return;
|
||||
}
|
||||
|
||||
/* If building then only check if
|
||||
* scene networkobjects have their sceneIds
|
||||
* missing. */
|
||||
if (BuildPipeline.isBuildingPlayer)
|
||||
{
|
||||
// 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))
|
||||
// If here this is a sceneObject, but sceneId is not set.
|
||||
if (!IsSceneObject)
|
||||
throw new InvalidOperationException($"Networked GameObject {gameObject.name} in scene {gameObject.scene.path} is missing a SceneId. Use the Fish-Networking menu -> Utility -> Reserialize NetworkObjects > Reserialize Scenes. 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.");
|
||||
}
|
||||
// If not building check to rebuild sceneIds this for object and the scene its in.
|
||||
else
|
||||
{
|
||||
double realtime = EditorApplication.timeSinceStartup;
|
||||
// Only do this once every Xms to prevent excessive rebiulds.
|
||||
if (realtime - _lastSceneIdAutomaticRebuildTime < 0.250d)
|
||||
return;
|
||||
|
||||
// Not in a scene, another prefab check.
|
||||
// Stored on disk, so is a prefab. Somehow prefabutility missed it.
|
||||
if (PrefabUtility.IsPartOfPrefabAsset(this) || IsEditingInPrefabMode() || !gameObject.scene.IsValid() || EditorUtility.IsPersistent(this))
|
||||
return;
|
||||
|
||||
_lastSceneIdAutomaticRebuildTime = realtime;
|
||||
|
||||
CreateSceneId(gameObject.scene, force, out _);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsEditingInPrefabMode()
|
||||
{
|
||||
// if the game object is stored on disk, it is a prefab of some kind, despite not returning true for IsPartOfPrefabAsset =/
|
||||
if (EditorUtility.IsPersistent(this))
|
||||
return true;
|
||||
|
||||
// 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)
|
||||
{
|
||||
PrefabStage prefabStage = PrefabStageUtility.GetPrefabStage(gameObject);
|
||||
if (prefabStage != null)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ReferenceIds_Reset()
|
||||
{
|
||||
CreateSceneId(force: false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d0b7f2fb91aa9243819ba0c5f783dd5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.Serialized.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using FishNet.Connection;
|
||||
using FishNet.Object.Synchronizing;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Serializing.Helping;
|
||||
using FishNet.Transporting;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object
|
||||
{
|
||||
public partial class NetworkObject : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes SyncTypes for previous and new owner where permissions apply.
|
||||
/// </summary>
|
||||
private void WriteSyncTypesForManualOwnershipChange(NetworkConnection prevOwner)
|
||||
{
|
||||
if (prevOwner.IsActive)
|
||||
WriteForConnection(prevOwner, ReadPermission.ExcludeOwner);
|
||||
if (Owner.IsActive)
|
||||
WriteForConnection(Owner, ReadPermission.OwnerOnly);
|
||||
|
||||
void WriteForConnection(NetworkConnection conn, ReadPermission permission)
|
||||
{
|
||||
for (int i = 0; i < NetworkBehaviours.Count; i++)
|
||||
NetworkBehaviours[i].WriteSyncTypesForConnection(conn, permission);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aacec7a84fa0ebe45a6a385a33863782
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.SyncTypes.cs
|
||||
uploadId: 866910
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 26b716c41e9b56b4baafaf13a523ba2e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: bf9191e2e07d29749bca3a1ae44e4bc8, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/NetworkObject/NetworkObject.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,75 @@
|
||||
namespace FishNet.Object
|
||||
{
|
||||
/// <summary>
|
||||
/// Action to take when despawning a NetworkObject.
|
||||
/// </summary>
|
||||
public enum DespawnType : byte
|
||||
{
|
||||
Destroy = 0,
|
||||
Pool = 1
|
||||
}
|
||||
|
||||
/// <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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options on retrieving nested NetworkObjects.
|
||||
/// </summary>
|
||||
[System.Flags]
|
||||
internal enum GetNetworkObjectOption : int
|
||||
{
|
||||
/// <summary>
|
||||
/// Include NetworkObject which nested are being returned for.
|
||||
/// </summary>
|
||||
Self = 1 << 0,
|
||||
/// <summary>
|
||||
/// Include initialize nested.
|
||||
/// </summary>
|
||||
InitializedNested = 1 << 1,
|
||||
/// <summary>
|
||||
/// Include runtime nested.
|
||||
/// </summary>
|
||||
RuntimeNested = 1 << 2,
|
||||
/// <summary>
|
||||
/// Recursively iterate nested includes.
|
||||
/// </summary>
|
||||
/// <remarks>This only functions if Initialized or Runtime is flagged.</remarks>
|
||||
Recursive = 1 << 3,
|
||||
/// <summary>
|
||||
/// Uses InitializedNested and RuntimeNested flags.
|
||||
/// </summary>
|
||||
AllNested = InitializedNested | RuntimeNested,
|
||||
/// <summary>
|
||||
/// Uses InitializedNested, RuntimeNested, and Recursive flags.
|
||||
/// </summary>
|
||||
AllNestedRecursive = InitializedNested | RuntimeNested | Recursive,
|
||||
/// <summary>
|
||||
/// Sets all flags.
|
||||
/// </summary>
|
||||
All = ~0
|
||||
}
|
||||
|
||||
internal static class GetNetworkObjectOptionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// True if whole contains part.
|
||||
/// </summary>
|
||||
public static bool FastContains(this GetNetworkObjectOption whole, GetNetworkObjectOption part) => (whole & part) == part;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 536f137f11dc6654eab9fbe94ca14cd8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/NetworkObject/NetworkObjectData.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 46773e0f27a85d643867f04d902fa007
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
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>
|
||||
/// 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 { }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b082d36535ce0404d8438bc1b0499e53
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/Prediction/Attributes.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,23 @@
|
||||
using FishNet.Connection;
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using FishNet.Utility;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo(UtilityConstants.CODEGEN_ASSEMBLY_NAME)]
|
||||
|
||||
namespace FishNet.Object.Prediction.Delegating
|
||||
{
|
||||
[APIExclude]
|
||||
public delegate void ReplicateRpcDelegate(PooledReader reader, NetworkConnection sender, Channel channel);
|
||||
|
||||
[APIExclude]
|
||||
public delegate void ReconcileRpcDelegate(PooledReader reader, Channel channel);
|
||||
|
||||
[APIExclude]
|
||||
public delegate void ReplicateUserLogicDelegate<T>(T data, ReplicateState state, Channel channel);
|
||||
|
||||
[APIExclude]
|
||||
public delegate void ReconcileUserLogicDelegate<T>(T data, Channel channel);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c9904192dacd41a4ba7b29bc3199ec3a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/Prediction/Delegates.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,42 @@
|
||||
namespace FishNet.Object.Prediction
|
||||
{
|
||||
public interface IReplicateData
|
||||
{
|
||||
/// <summary>
|
||||
/// Local tick when the data was created.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
uint GetTick();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the local tick when data was created.
|
||||
/// </summary>
|
||||
/// <param name = "value"></param>
|
||||
void SetTick(uint value);
|
||||
|
||||
/// <summary>
|
||||
/// Allows for any cleanup when the data is being discarded.
|
||||
/// </summary>
|
||||
void Dispose();
|
||||
}
|
||||
|
||||
public interface IReconcileData
|
||||
{
|
||||
/// <summary>
|
||||
/// Local tick when the data was created.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
uint GetTick();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the local tick when data was created.
|
||||
/// </summary>
|
||||
/// <param name = "value"></param>
|
||||
void SetTick(uint value);
|
||||
|
||||
/// <summary>
|
||||
/// Allows for any cleanup when the data is being discarded.
|
||||
/// </summary>
|
||||
void Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 515754257f85574438408c7f5b268590
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/Prediction/Interfaces.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,45 @@
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Serializing;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object.Prediction
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to store reconciles locally.
|
||||
/// </summary>
|
||||
/// <remarks>This is for internal use only.</remarks>
|
||||
[APIExclude]
|
||||
public struct LocalReconcile<T> where T : IReconcileData
|
||||
{
|
||||
/// <summary>
|
||||
/// Tick for reconcile.
|
||||
/// </summary>
|
||||
public uint Tick;
|
||||
/// <summary>
|
||||
/// Writer reconcile was written to.
|
||||
/// </summary>
|
||||
public PooledWriter Writer;
|
||||
/// <summary>
|
||||
/// Data inside writer.
|
||||
/// </summary>
|
||||
public T Data;
|
||||
|
||||
public void Initialize(uint tick, T data)
|
||||
{
|
||||
Tick = tick;
|
||||
Data = data;
|
||||
Writer = WriterPool.Retrieve();
|
||||
Writer.Write(data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of used data.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Data.Dispose();
|
||||
if (Writer != null)
|
||||
WriterPool.Store(Writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb90cfdc07524be40a9d4d11ae7c35e6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/Prediction/LocalReconcile.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,467 @@
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using Unity.Profiling;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Scripting;
|
||||
|
||||
namespace FishNet.Object.Prediction
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to make calculations and perform actions in moving transforms over time.
|
||||
/// </summary>
|
||||
[Preserve]
|
||||
public struct MoveRates
|
||||
{
|
||||
/// <summary>
|
||||
/// Rate at which to move Position.
|
||||
/// </summary>
|
||||
public float Position;
|
||||
/// <summary>
|
||||
/// Rate at which to move Rotation.
|
||||
/// </summary>
|
||||
public float Rotation;
|
||||
/// <summary>
|
||||
/// Rate at which to move Scale.
|
||||
/// </summary>
|
||||
public float Scale;
|
||||
/// <summary>
|
||||
/// Time remaining until the move is complete.
|
||||
/// </summary>
|
||||
public float TimeRemaining;
|
||||
/// <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>
|
||||
/// True if any data is set. Once set, this will remain true until ResetState is called.
|
||||
/// </summary>
|
||||
public bool IsValid { get; private set; }
|
||||
|
||||
public MoveRates(float value) : this()
|
||||
{
|
||||
Position = value;
|
||||
Rotation = value;
|
||||
Scale = value;
|
||||
|
||||
IsValid = true;
|
||||
}
|
||||
|
||||
public MoveRates(float position, float rotation) : this()
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
Scale = INSTANT_VALUE;
|
||||
|
||||
IsValid = true;
|
||||
}
|
||||
|
||||
public MoveRates(float position, float rotation, float scale) : this()
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
Scale = scale;
|
||||
|
||||
IsValid = true;
|
||||
}
|
||||
|
||||
public MoveRates(float position, float rotation, float scale, float timeRemaining)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
Scale = scale;
|
||||
TimeRemaining = timeRemaining;
|
||||
|
||||
IsValid = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if a positional move rate is set.
|
||||
/// </summary>
|
||||
public bool IsPositionSet => Position != UNSET_VALUE;
|
||||
/// <summary>
|
||||
/// True if rotation move rate is set.
|
||||
/// </summary>
|
||||
public bool IsRotationSet => Rotation != UNSET_VALUE;
|
||||
/// <summary>
|
||||
/// True if a scale move rate is set.
|
||||
/// </summary>
|
||||
public bool IsScaleSet => Scale != UNSET_VALUE;
|
||||
/// <summary>
|
||||
/// True if position move rate should be instant.
|
||||
/// </summary>
|
||||
public bool IsPositionInstantValue => Position == INSTANT_VALUE;
|
||||
/// <summary>
|
||||
/// True if rotation move rate should be instant.
|
||||
/// </summary>
|
||||
public bool IsRotationInstantValue => Rotation == INSTANT_VALUE;
|
||||
/// <summary>
|
||||
/// True if scale move rate should be instant.
|
||||
/// </summary>
|
||||
public bool IsScaleInstantValue => Scale == INSTANT_VALUE;
|
||||
|
||||
#region Private Profiler Markers
|
||||
private static readonly ProfilerMarker _pm_GetMoveRatesFull = new("MoveRates.GetMoveRates(Vector3, Vector3, Quaternion, Quaternion, Vector3, Vector3, float, float)");
|
||||
private static readonly ProfilerMarker _pm_GetMoveRatesVec = new("MoveRates.GetMoveRates(Vector3, Vector3, float, float)");
|
||||
private static readonly ProfilerMarker _pm_Move = new("MoveRates.Move(Transform, TransformPropertiesFlag, Vector3, float, Quaternion, float, Vector3, float, float, bool)");
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Sets all rates to instant.
|
||||
/// </summary>
|
||||
public void SetInstantRates() => Update(INSTANT_VALUE);
|
||||
|
||||
/// <summary>
|
||||
/// Sets all rates to the same value.
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
IsValid = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets rates for each property.
|
||||
/// </summary>
|
||||
public void Update(float position, float rotation, float scale, float timeRemaining)
|
||||
{
|
||||
Position = position;
|
||||
Rotation = rotation;
|
||||
Scale = scale;
|
||||
TimeRemaining = timeRemaining;
|
||||
|
||||
IsValid = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates to new values.
|
||||
/// </summary>
|
||||
public void Update(MoveRates moveRates) => Update(moveRates.Position, moveRates.Rotation, moveRates.Scale, moveRates.TimeRemaining);
|
||||
|
||||
/// <summary>
|
||||
/// Updates to new values.
|
||||
/// </summary>
|
||||
public void Update(MoveRatesCls moveRates) => Update(moveRates.Position, moveRates.Rotation, moveRates.Scale, moveRates.TimeRemaining);
|
||||
|
||||
/// <summary>
|
||||
/// Resets to unset values.
|
||||
/// </summary>
|
||||
public void ResetState()
|
||||
{
|
||||
Update(UNSET_VALUE, UNSET_VALUE, UNSET_VALUE, timeRemaining: 0f);
|
||||
|
||||
IsValid = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new MoveRates based on previous values, and a transforms current position.
|
||||
/// </summary>
|
||||
public static MoveRates GetWorldMoveRates(Transform from, Transform to, float duration, float teleportThreshold)
|
||||
{
|
||||
return GetMoveRates(from.position, to.position, from.rotation, to.rotation, from.localScale, to.localScale, duration, teleportThreshold);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new MoveRates based on previous values, and a transforms current position.
|
||||
/// </summary>
|
||||
public static MoveRates GetLocalMoveRates(Transform from, Transform to, float duration, float teleportThreshold)
|
||||
{
|
||||
return GetMoveRates(from.localPosition, to.localPosition, from.localRotation, to.localRotation, from.localScale, to.localScale, duration, teleportThreshold);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new MoveRates based on previous values, and a transforms current position.
|
||||
/// </summary>
|
||||
public static MoveRates GetWorldMoveRates(TransformProperties prevValues, Transform t, float duration, float teleportThreshold)
|
||||
{
|
||||
return GetMoveRates(prevValues.Position, t.position, prevValues.Rotation, t.rotation, prevValues.Scale, t.localScale, duration, teleportThreshold);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new MoveRates based on previous values, and a transforms current position.
|
||||
/// </summary>
|
||||
public static MoveRates GetLocalMoveRates(TransformProperties prevValues, Transform t, float duration, float teleportThreshold)
|
||||
{
|
||||
return GetMoveRates(prevValues.Position, t.localPosition, prevValues.Rotation, t.localRotation, prevValues.Scale, t.localScale, duration, teleportThreshold);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new MoveRates based on previous values, and a transforms current position.
|
||||
/// </summary>
|
||||
public static MoveRates GetMoveRates(TransformProperties prevValues, TransformProperties nextValues, float duration, float teleportThreshold)
|
||||
{
|
||||
return GetMoveRates(prevValues.Position, nextValues.Position, prevValues.Rotation, nextValues.Rotation, prevValues.Scale, nextValues.Scale, duration, teleportThreshold);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new MoveRates based on previous values, and a transforms current position.
|
||||
/// </summary>
|
||||
public static MoveRates GetMoveRates(Vector3 fromPosition, Vector3 toPosition, Quaternion fromRotation, Quaternion toRotation, Vector3 fromScale, Vector3 toScale, float duration, float teleportThreshold)
|
||||
{
|
||||
using (_pm_GetMoveRatesFull.Auto())
|
||||
{
|
||||
float rate;
|
||||
|
||||
/* Position. */
|
||||
rate = toPosition.GetRate(fromPosition, duration, out float distance);
|
||||
// Basic teleport check.
|
||||
if (teleportThreshold != UNSET_VALUE && distance > teleportThreshold)
|
||||
return new(INSTANT_VALUE, INSTANT_VALUE, INSTANT_VALUE, duration);
|
||||
|
||||
//Smoothing.
|
||||
float positionRate = rate.SetIfUnderTolerance(0.0001f, INSTANT_VALUE);
|
||||
rate = toRotation.GetRate(fromRotation, duration, out _);
|
||||
float rotationRate = rate.SetIfUnderTolerance(0.2f, INSTANT_VALUE);
|
||||
rate = toScale.GetRate(fromScale, duration, out _);
|
||||
float scaleRate = rate.SetIfUnderTolerance(0.0001f, INSTANT_VALUE);
|
||||
|
||||
return new(positionRate, rotationRate, scaleRate, duration);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a move rate for two Vector3s.
|
||||
/// </summary>
|
||||
public static float GetMoveRate(Vector3 fromPosition, Vector3 toPosition, float duration, float teleportThreshold)
|
||||
{
|
||||
using (_pm_GetMoveRatesVec.Auto())
|
||||
{
|
||||
float rate;
|
||||
float distance;
|
||||
|
||||
/* Position. */
|
||||
rate = toPosition.GetRate(fromPosition, duration, out distance);
|
||||
//Basic teleport check.
|
||||
if (teleportThreshold != UNSET_VALUE && distance > teleportThreshold)
|
||||
{
|
||||
return INSTANT_VALUE;
|
||||
}
|
||||
//Smoothing.
|
||||
else
|
||||
{
|
||||
float positionRate = rate.SetIfUnderTolerance(0.0001f, INSTANT_VALUE);
|
||||
return positionRate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a move rate for two Quaternions.
|
||||
/// </summary>
|
||||
public static float GetMoveRate(Quaternion fromRotation, Quaternion toRotation, float duration)
|
||||
{
|
||||
float rate = toRotation.GetRate(fromRotation, duration, out _);
|
||||
float rotationRate = rate.SetIfUnderTolerance(0.2f, INSTANT_VALUE);
|
||||
return rotationRate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves transform to target values.
|
||||
/// </summary>
|
||||
public void Move(Transform movingTransform, TransformProperties goalProperties, float delta, bool useWorldSpace)
|
||||
{
|
||||
if (!IsValid)
|
||||
return;
|
||||
|
||||
Move(movingTransform, TransformPropertiesFlag.Everything, goalProperties.Position, Position, goalProperties.Rotation, Rotation, goalProperties.Scale, Scale, delta, useWorldSpace);
|
||||
TimeRemaining -= delta;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves transform to target values.
|
||||
/// </summary>
|
||||
public void Move(Transform movingTransform, TransformProperties goalProperties, TransformPropertiesFlag movedProperties, float delta, bool useWorldSpace)
|
||||
{
|
||||
if (!IsValid)
|
||||
return;
|
||||
|
||||
Move(movingTransform, movedProperties, goalProperties.Position, Position, goalProperties.Rotation, Rotation, goalProperties.Scale, Scale, delta, useWorldSpace);
|
||||
TimeRemaining -= delta;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves transform to target values.
|
||||
/// </summary>
|
||||
public static void Move(Transform movingTransform, TransformPropertiesFlag movedProperties, Vector3 posGoal, float posRate, Quaternion rotGoal, float rotRate, Vector3 scaleGoal, float scaleRate, float delta, bool useWorldSpace)
|
||||
{
|
||||
using (_pm_Move.Auto())
|
||||
{
|
||||
Transform t = movingTransform;
|
||||
|
||||
bool containsPosition = movedProperties.FastContains(TransformPropertiesFlag.Position);
|
||||
bool containsRotation = movedProperties.FastContains(TransformPropertiesFlag.Rotation);
|
||||
bool containsScale = movedProperties.FastContains(TransformPropertiesFlag.Scale);
|
||||
|
||||
//World space.
|
||||
if (useWorldSpace)
|
||||
{
|
||||
if (containsPosition)
|
||||
{
|
||||
if (posRate == INSTANT_VALUE)
|
||||
{
|
||||
t.position = posGoal;
|
||||
}
|
||||
else if (posRate == UNSET_VALUE) { }
|
||||
else
|
||||
{
|
||||
t.position = Vector3.MoveTowards(t.position, posGoal, posRate * delta);
|
||||
}
|
||||
}
|
||||
|
||||
if (containsRotation)
|
||||
{
|
||||
if (rotRate == INSTANT_VALUE)
|
||||
{
|
||||
t.rotation = rotGoal;
|
||||
}
|
||||
else if (rotRate == UNSET_VALUE) { }
|
||||
else
|
||||
{
|
||||
t.rotation = Quaternion.RotateTowards(t.rotation, rotGoal, rotRate * delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
//Local space.
|
||||
else
|
||||
{
|
||||
if (containsPosition)
|
||||
{
|
||||
if (posRate == INSTANT_VALUE)
|
||||
{
|
||||
t.localPosition = posGoal;
|
||||
}
|
||||
else if (posRate == UNSET_VALUE) { }
|
||||
else
|
||||
{
|
||||
t.localPosition = Vector3.MoveTowards(t.localPosition, posGoal, posRate * delta);
|
||||
}
|
||||
}
|
||||
|
||||
if (containsRotation)
|
||||
{
|
||||
if (rotRate == INSTANT_VALUE)
|
||||
{
|
||||
t.localRotation = rotGoal;
|
||||
}
|
||||
else if (rotRate == UNSET_VALUE) { }
|
||||
else
|
||||
{
|
||||
t.localRotation = Quaternion.RotateTowards(t.localRotation, rotGoal, rotRate * delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Scale always uses local.
|
||||
if (containsScale)
|
||||
{
|
||||
if (scaleRate == INSTANT_VALUE)
|
||||
{
|
||||
t.localScale = scaleGoal;
|
||||
}
|
||||
else if (scaleRate == UNSET_VALUE) { }
|
||||
else
|
||||
{
|
||||
t.localScale = Vector3.MoveTowards(t.localScale, scaleGoal, scaleRate * delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to make calculations and perform actions in moving transforms over time.
|
||||
/// </summary>
|
||||
/// <remarks>This acts as a wrapper for MoveRates struct.</remarks>
|
||||
public class MoveRatesCls : IResettable
|
||||
{
|
||||
/// <summary>
|
||||
/// Container of all move rate information.
|
||||
/// </summary>
|
||||
private MoveRates _moveRates = new();
|
||||
/// <summary>
|
||||
/// Rate at which to move Position.
|
||||
/// </summary>
|
||||
public float Position => _moveRates.Position;
|
||||
/// <summary>
|
||||
/// Rate at which to move Rotation.
|
||||
/// </summary>
|
||||
public float Rotation => _moveRates.Rotation;
|
||||
/// <summary>
|
||||
/// Rate at which to move Scale.
|
||||
/// </summary>
|
||||
public float Scale => _moveRates.Scale;
|
||||
/// <summary>
|
||||
/// Time remaining until the move is complete.
|
||||
/// </summary>
|
||||
public float TimeRemaining => _moveRates.TimeRemaining;
|
||||
/// <summary>
|
||||
/// True if position move rate should be instant.
|
||||
/// </summary>
|
||||
public bool IsPositionInstantValue => _moveRates.IsPositionInstantValue;
|
||||
/// <summary>
|
||||
/// True if rotation move rate should be instant.
|
||||
/// </summary>
|
||||
public bool IsRotationInstantValue => _moveRates.IsRotationInstantValue;
|
||||
/// <summary>
|
||||
/// True if scale move rate should be instant.
|
||||
/// </summary>
|
||||
public bool IsScaleInstantValue => _moveRates.IsScaleInstantValue;
|
||||
/// <summary>
|
||||
/// True if any data is set.
|
||||
/// </summary>
|
||||
public bool IsValid => _moveRates.IsValid;
|
||||
public MoveRatesCls(float value) => _moveRates = new(value);
|
||||
public MoveRatesCls(float position, float rotation) => _moveRates = new(position, rotation);
|
||||
public MoveRatesCls(float position, float rotation, float scale) => _moveRates = new(position, rotation, scale);
|
||||
public MoveRatesCls(float position, float rotation, float scale, float timeRemaining) => _moveRates = new(position, rotation, scale, timeRemaining);
|
||||
public MoveRatesCls() => _moveRates.ResetState();
|
||||
|
||||
/// <summary>
|
||||
/// Sets all rates to instant.
|
||||
/// </summary>
|
||||
public void SetInstantRates() => _moveRates.SetInstantRates();
|
||||
|
||||
/// <summary>
|
||||
/// Sets all rates to the same value.
|
||||
/// </summary>
|
||||
public void Update(float value) => _moveRates.Update(value);
|
||||
|
||||
/// <summary>
|
||||
/// Updates values.
|
||||
/// </summary>
|
||||
public void Update(float position, float rotation, float scale) => _moveRates.Update(position, rotation, scale);
|
||||
|
||||
/// <summary>
|
||||
/// Updates values.
|
||||
/// </summary>
|
||||
public void Update(float position, float rotation, float scale, float timeRemaining) => _moveRates.Update(position, rotation, scale, timeRemaining);
|
||||
|
||||
/// <summary>
|
||||
/// Updaes values.
|
||||
/// </summary>
|
||||
public void Update(MoveRatesCls mr) => _moveRates.Update(mr.Position, mr.Rotation, mr.Scale);
|
||||
|
||||
/// <summary>
|
||||
/// Moves transform to target values.
|
||||
/// </summary>
|
||||
public void Move(Transform movingTransform, TransformProperties goalProperties, float delta, bool useWorldSpace) => _moveRates.Move(movingTransform, goalProperties, delta, useWorldSpace);
|
||||
|
||||
/// <summary>
|
||||
/// Moves transform to target values.
|
||||
/// </summary>
|
||||
public void Move(Transform movingTransform, TransformProperties goalProperties, TransformPropertiesFlag movedProperties, float delta, bool useWorldSpace) => _moveRates.Move(movingTransform, goalProperties, movedProperties, delta, useWorldSpace);
|
||||
|
||||
public void ResetState() => _moveRates.ResetState();
|
||||
public void InitializeState() { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1bddf57861232884ca21f7e97a6d662d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/Prediction/MoveRates.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,528 @@
|
||||
using FishNet.CodeGenerating;
|
||||
using FishNet.Component.Prediction;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Serializing;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Scripting;
|
||||
|
||||
namespace FishNet.Object.Prediction
|
||||
{
|
||||
[Preserve]
|
||||
[DefaultWriter]
|
||||
public static class PredictionRigidbodySerializers
|
||||
{
|
||||
[DefaultWriter]
|
||||
public static void WriteEntryData(this Writer w, PredictionRigidbody.EntryData value)
|
||||
{
|
||||
PredictionRigidbody.ForceApplicationType appType = value.Type;
|
||||
w.WriteUInt8Unpacked((byte)appType);
|
||||
PredictionRigidbody.AllForceData data = value.Data;
|
||||
|
||||
switch (appType)
|
||||
{
|
||||
case PredictionRigidbody.ForceApplicationType.AddTorque:
|
||||
case PredictionRigidbody.ForceApplicationType.AddForce:
|
||||
case PredictionRigidbody.ForceApplicationType.AddRelativeTorque:
|
||||
case PredictionRigidbody.ForceApplicationType.AddRelativeForce:
|
||||
w.WriteVector3(data.Vector3Force);
|
||||
w.WriteInt32((byte)data.Mode);
|
||||
break;
|
||||
case PredictionRigidbody.ForceApplicationType.AddExplosiveForce:
|
||||
w.WriteSingle(data.FloatForce);
|
||||
w.WriteVector3(data.Position);
|
||||
w.WriteSingle(data.Radius);
|
||||
w.WriteSingle(data.UpwardsModifier);
|
||||
w.WriteInt32((byte)data.Mode);
|
||||
break;
|
||||
case PredictionRigidbody.ForceApplicationType.AddForceAtPosition:
|
||||
w.WriteVector3(data.Vector3Force);
|
||||
w.WriteVector3(data.Position);
|
||||
w.WriteInt32((byte)data.Mode);
|
||||
break;
|
||||
case PredictionRigidbody.ForceApplicationType.MovePosition:
|
||||
w.WriteVector3(data.Position);
|
||||
break;
|
||||
case PredictionRigidbody.ForceApplicationType.MoveRotation:
|
||||
w.WriteUInt8Unpacked((byte)data.RotationPacking);
|
||||
w.WriteQuaternion(data.Rotation, data.RotationPacking);
|
||||
break;
|
||||
default:
|
||||
w.NetworkManager.LogError($"ForceApplicationType of {appType} is not supported.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[DefaultReader]
|
||||
public static PredictionRigidbody.EntryData ReadEntryData(this Reader r)
|
||||
{
|
||||
PredictionRigidbody.EntryData fd = new();
|
||||
|
||||
PredictionRigidbody.ForceApplicationType appType = (PredictionRigidbody.ForceApplicationType)r.ReadUInt8Unpacked();
|
||||
fd.Type = appType;
|
||||
|
||||
PredictionRigidbody.AllForceData data = new();
|
||||
|
||||
switch (appType)
|
||||
{
|
||||
case PredictionRigidbody.ForceApplicationType.AddTorque:
|
||||
case PredictionRigidbody.ForceApplicationType.AddForce:
|
||||
case PredictionRigidbody.ForceApplicationType.AddRelativeTorque:
|
||||
case PredictionRigidbody.ForceApplicationType.AddRelativeForce:
|
||||
data.Vector3Force = r.ReadVector3();
|
||||
data.Mode = (ForceMode)r.ReadInt32();
|
||||
break;
|
||||
case PredictionRigidbody.ForceApplicationType.AddExplosiveForce:
|
||||
data.FloatForce = r.ReadSingle();
|
||||
data.Position = r.ReadVector3();
|
||||
data.Radius = r.ReadSingle();
|
||||
data.UpwardsModifier = r.ReadSingle();
|
||||
data.Mode = (ForceMode)r.ReadInt32();
|
||||
break;
|
||||
case PredictionRigidbody.ForceApplicationType.AddForceAtPosition:
|
||||
data.Vector3Force = r.ReadVector3();
|
||||
data.Position = r.ReadVector3();
|
||||
data.Mode = (ForceMode)r.ReadInt32();
|
||||
break;
|
||||
case PredictionRigidbody.ForceApplicationType.MovePosition:
|
||||
data.Position = r.ReadVector3();
|
||||
break;
|
||||
case PredictionRigidbody.ForceApplicationType.MoveRotation:
|
||||
AutoPackType apt = (AutoPackType)r.ReadUInt8Unpacked();
|
||||
data.Rotation = r.ReadQuaternion(apt);
|
||||
break;
|
||||
default:
|
||||
r.NetworkManager.LogError($"ForceApplicationType of {appType} is not supported.");
|
||||
break;
|
||||
}
|
||||
|
||||
fd.Data = data;
|
||||
return fd;
|
||||
}
|
||||
|
||||
[DefaultWriter]
|
||||
public static void WritePredictionRigidbody(this Writer w, PredictionRigidbody pr)
|
||||
{
|
||||
w.Write(pr.Rigidbody.GetState(pr.RotationPacking));
|
||||
w.WriteList(pr.GetPendingForces());
|
||||
}
|
||||
|
||||
[DefaultReader]
|
||||
public static PredictionRigidbody ReadPredictionRigidbody(this Reader r)
|
||||
{
|
||||
List<PredictionRigidbody.EntryData> lst = CollectionCaches<PredictionRigidbody.EntryData>.RetrieveList();
|
||||
|
||||
RigidbodyState rs = r.Read<RigidbodyState>();
|
||||
r.ReadList(ref lst);
|
||||
PredictionRigidbody pr = ResettableObjectCaches<PredictionRigidbody>.Retrieve();
|
||||
|
||||
pr.SetReconcileData(rs, lst);
|
||||
return pr;
|
||||
}
|
||||
|
||||
[DefaultDeltaWriter]
|
||||
public static bool WriteDeltaEntryData(this Writer w, PredictionRigidbody.EntryData value)
|
||||
{
|
||||
w.WriteEntryData(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
[DefaultDeltaReader]
|
||||
public static PredictionRigidbody.EntryData ReadDeltaEntryData(this Reader r) => r.ReadEntryData();
|
||||
|
||||
[DefaultDeltaWriter]
|
||||
public static bool WriteDeltaPredictionRigidbody(this Writer w, PredictionRigidbody pr)
|
||||
{
|
||||
w.WritePredictionRigidbody(pr);
|
||||
return true;
|
||||
}
|
||||
|
||||
[DefaultDeltaReader]
|
||||
public static PredictionRigidbody ReadDeltaPredictionRigidbody(this Reader r) => r.ReadPredictionRigidbody();
|
||||
}
|
||||
|
||||
[UseGlobalCustomSerializer]
|
||||
[Preserve]
|
||||
public class PredictionRigidbody : IResettable
|
||||
{
|
||||
#region Types.
|
||||
public struct AllForceData
|
||||
{
|
||||
public ForceMode Mode;
|
||||
public Vector3 Vector3Force;
|
||||
public Vector3 Position;
|
||||
public Quaternion Rotation;
|
||||
[ExcludeSerialization]
|
||||
public readonly AutoPackType RotationPacking;
|
||||
public float FloatForce;
|
||||
public float Radius;
|
||||
public float UpwardsModifier;
|
||||
|
||||
/// <summary>
|
||||
/// Used for MovePosition.
|
||||
/// </summary>
|
||||
public AllForceData(Vector3 position) : this()
|
||||
{
|
||||
Position = position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for MoveRotation.
|
||||
/// </summary>
|
||||
public AllForceData(Quaternion rotation, AutoPackType rotationPacking) : this()
|
||||
{
|
||||
Rotation = rotation;
|
||||
RotationPacking = rotationPacking;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for Force and Torque.
|
||||
/// </summary>
|
||||
public AllForceData(Vector3 force, ForceMode mode) : this()
|
||||
{
|
||||
Vector3Force = force;
|
||||
Mode = mode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for Position.
|
||||
/// </summary>
|
||||
public AllForceData(Vector3 force, Vector3 position, ForceMode mode) : this()
|
||||
{
|
||||
Vector3Force = force;
|
||||
Position = position;
|
||||
Mode = mode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used for Explosive.
|
||||
/// </summary>
|
||||
/// <param name = "force"></param>
|
||||
/// <param name = "position"></param>
|
||||
/// <param name = "radius"></param>
|
||||
/// <param name = "upwardsModifier"></param>
|
||||
/// <param name = "mode"></param>
|
||||
public AllForceData(float force, Vector3 position, float radius, float upwardsModifier, ForceMode mode) : this()
|
||||
{
|
||||
FloatForce = force;
|
||||
Position = position;
|
||||
Radius = radius;
|
||||
UpwardsModifier = upwardsModifier;
|
||||
Mode = mode;
|
||||
}
|
||||
}
|
||||
|
||||
public interface IForceData { }
|
||||
|
||||
// How the force was applied.
|
||||
[System.Flags]
|
||||
public enum ForceApplicationType : byte
|
||||
{
|
||||
AddForceAtPosition = 1 << 0,
|
||||
AddExplosiveForce = 1 << 1,
|
||||
AddForce = 1 << 2,
|
||||
AddRelativeForce = 1 << 3,
|
||||
AddTorque = 1 << 4,
|
||||
AddRelativeTorque = 1 << 5,
|
||||
MovePosition = 1 << 6,
|
||||
MoveRotation = 1 << 7,
|
||||
}
|
||||
|
||||
[UseGlobalCustomSerializer]
|
||||
public struct EntryData
|
||||
{
|
||||
public ForceApplicationType Type;
|
||||
public AllForceData Data;
|
||||
|
||||
public EntryData(ForceApplicationType type, AllForceData data)
|
||||
{
|
||||
Type = type;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public EntryData(EntryData fd)
|
||||
{
|
||||
Type = fd.Type;
|
||||
Data = fd.Data;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// Rigidbody which force is applied.
|
||||
/// </summary>
|
||||
public Rigidbody Rigidbody { get; private set; }
|
||||
/// <summary>
|
||||
/// Returns if there are any pending forces.
|
||||
/// </summary>
|
||||
public bool HasPendingForces => _pendingForces != null && _pendingForces.Count > 0;
|
||||
#endregion
|
||||
|
||||
#region Internal.
|
||||
/// <summary>
|
||||
/// RigidbodyState set only as reconcile data.
|
||||
/// </summary>
|
||||
[System.NonSerialized]
|
||||
internal RigidbodyState RigidbodyState;
|
||||
/// <summary>
|
||||
/// How much to pack rotation.
|
||||
/// </summary>
|
||||
[ExcludeSerialization]
|
||||
internal AutoPackType RotationPacking = AutoPackType.Packed;
|
||||
#endregion
|
||||
|
||||
#region Private
|
||||
/// <summary>
|
||||
/// Forces waiting to be applied.
|
||||
/// </summary>
|
||||
[ExcludeSerialization]
|
||||
private List<EntryData> _pendingForces;
|
||||
|
||||
/// <summary>
|
||||
/// Returns current pending forces.
|
||||
/// Modifying this collection could cause undesirable results.
|
||||
/// </summary>
|
||||
public List<EntryData> GetPendingForces() => _pendingForces;
|
||||
#endregion
|
||||
|
||||
~PredictionRigidbody()
|
||||
{
|
||||
if (_pendingForces != null)
|
||||
CollectionCaches<EntryData>.StoreAndDefault(ref _pendingForces);
|
||||
|
||||
Rigidbody = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rigidbody which force is applied.
|
||||
/// </summary>
|
||||
/// <param name = "rb"></param>
|
||||
public void Initialize(Rigidbody rb, AutoPackType rotationPacking = AutoPackType.Packed)
|
||||
{
|
||||
Rigidbody = rb;
|
||||
RotationPacking = rotationPacking;
|
||||
|
||||
if (_pendingForces == null)
|
||||
_pendingForces = CollectionCaches<EntryData>.RetrieveList();
|
||||
else
|
||||
_pendingForces.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds Velocity force to the Rigidbody.
|
||||
/// </summary>
|
||||
public void AddForce(Vector3 force, ForceMode mode = ForceMode.Force)
|
||||
{
|
||||
EntryData fd = new(ForceApplicationType.AddForce, new(force, mode));
|
||||
_pendingForces.Add(fd);
|
||||
}
|
||||
|
||||
public void AddRelativeForce(Vector3 force, ForceMode mode = ForceMode.Force)
|
||||
{
|
||||
EntryData fd = new(ForceApplicationType.AddRelativeForce, new(force, mode));
|
||||
_pendingForces.Add(fd);
|
||||
}
|
||||
|
||||
public void AddTorque(Vector3 force, ForceMode mode = ForceMode.Force)
|
||||
{
|
||||
EntryData fd = new(ForceApplicationType.AddTorque, new(force, mode));
|
||||
_pendingForces.Add(fd);
|
||||
}
|
||||
|
||||
public void AddRelativeTorque(Vector3 force, ForceMode mode = ForceMode.Force)
|
||||
{
|
||||
EntryData fd = new(ForceApplicationType.AddRelativeTorque, new(force, mode));
|
||||
_pendingForces.Add(fd);
|
||||
}
|
||||
|
||||
public void AddExplosiveForce(float force, Vector3 position, float radius, float upwardsModifier = 0f, ForceMode mode = ForceMode.Force)
|
||||
{
|
||||
EntryData fd = new(ForceApplicationType.AddExplosiveForce, new(force, position, radius, upwardsModifier, mode));
|
||||
_pendingForces.Add(fd);
|
||||
}
|
||||
|
||||
public void AddForceAtPosition(Vector3 force, Vector3 position, ForceMode mode = ForceMode.Force)
|
||||
{
|
||||
EntryData fd = new(ForceApplicationType.AddForceAtPosition, new(force, position, mode));
|
||||
_pendingForces.Add(fd);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets velocity while clearing pending forces.
|
||||
/// Simulate should still be called normally.
|
||||
/// </summary>
|
||||
public void Velocity(Vector3 force)
|
||||
{
|
||||
#if UNITY_6000_1_OR_NEWER
|
||||
Rigidbody.linearVelocity = force;
|
||||
#else
|
||||
Rigidbody.velocity = force;
|
||||
#endif
|
||||
RemoveForces(nonAngular: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets angularVelocity while clearing pending forces.
|
||||
/// Simulate should still be called normally.
|
||||
/// </summary>
|
||||
public void AngularVelocity(Vector3 force)
|
||||
{
|
||||
Rigidbody.angularVelocity = force;
|
||||
RemoveForces(nonAngular: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the kinematic Rigidbody towards position.
|
||||
/// </summary>
|
||||
/// <param name="position">Next position.</param>
|
||||
public void MovePosition(Vector3 position)
|
||||
{
|
||||
EntryData fd = new(ForceApplicationType.MovePosition, new(position));
|
||||
_pendingForces.Add(fd);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the kinematic Rigidbody towards rotation.
|
||||
/// </summary>
|
||||
/// <param name="position">Next position.</param>
|
||||
public void MoveRotation(Quaternion rotation)
|
||||
{
|
||||
EntryData fd = new(ForceApplicationType.MoveRotation, new(rotation, RotationPacking));
|
||||
_pendingForces.Add(fd);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies pending forces to rigidbody in the order they were added.
|
||||
/// </summary>
|
||||
public void Simulate()
|
||||
{
|
||||
foreach (EntryData item in _pendingForces)
|
||||
{
|
||||
AllForceData data = item.Data;
|
||||
switch (item.Type)
|
||||
{
|
||||
case ForceApplicationType.AddTorque:
|
||||
Rigidbody.AddTorque(data.Vector3Force, data.Mode);
|
||||
break;
|
||||
case ForceApplicationType.AddForce:
|
||||
Rigidbody.AddForce(data.Vector3Force, data.Mode);
|
||||
break;
|
||||
case ForceApplicationType.AddRelativeTorque:
|
||||
Rigidbody.AddRelativeTorque(data.Vector3Force, data.Mode);
|
||||
break;
|
||||
case ForceApplicationType.AddRelativeForce:
|
||||
Rigidbody.AddRelativeForce(data.Vector3Force, data.Mode);
|
||||
break;
|
||||
case ForceApplicationType.AddExplosiveForce:
|
||||
Rigidbody.AddExplosionForce(data.FloatForce, data.Position, data.Radius, data.UpwardsModifier, data.Mode);
|
||||
break;
|
||||
case ForceApplicationType.AddForceAtPosition:
|
||||
Rigidbody.AddForceAtPosition(data.Vector3Force, data.Position, data.Mode);
|
||||
break;
|
||||
case ForceApplicationType.MovePosition:
|
||||
Rigidbody.MovePosition(data.Position);
|
||||
break;
|
||||
case ForceApplicationType.MoveRotation:
|
||||
Rigidbody.MoveRotation(data.Rotation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
_pendingForces.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears current and pending forces for velocity and angularVelocity.
|
||||
/// </summary>
|
||||
public void ClearVelocities()
|
||||
{
|
||||
Velocity(Vector3.zero);
|
||||
AngularVelocity(Vector3.zero);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears pending forces for velocity, or angular velocity.
|
||||
/// </summary>
|
||||
/// <param name = "nonAngular">True to clear pending velocity forces, false to clear pending angularVelocity forces.</param>
|
||||
public void ClearPendingForces(bool nonAngular)
|
||||
{
|
||||
RemoveForces(nonAngular);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears pending forces for velocity and angularVelocity.
|
||||
/// </summary>
|
||||
public void ClearPendingForces()
|
||||
{
|
||||
_pendingForces.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reconciles to a state.
|
||||
/// </summary>
|
||||
public void Reconcile(PredictionRigidbody pr)
|
||||
{
|
||||
_pendingForces.Clear();
|
||||
|
||||
if (pr._pendingForces != null)
|
||||
{
|
||||
foreach (EntryData item in pr._pendingForces)
|
||||
_pendingForces.Add(new(item));
|
||||
}
|
||||
|
||||
// Set state.
|
||||
Rigidbody.SetState(pr.RigidbodyState);
|
||||
|
||||
ResettableObjectCaches<PredictionRigidbody>.Store(pr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes forces from pendingForces.
|
||||
/// </summary>
|
||||
/// <param name = "nonAngular">True to remove if velocity, false if to remove angular velocity.</param>
|
||||
private void RemoveForces(bool nonAngular)
|
||||
{
|
||||
if (_pendingForces.Count > 0)
|
||||
{
|
||||
ForceApplicationType velocityApplicationTypes = ForceApplicationType.AddRelativeForce | ForceApplicationType.AddForce | ForceApplicationType.AddExplosiveForce;
|
||||
|
||||
List<EntryData> datasToKeep = CollectionCaches<EntryData>.RetrieveList();
|
||||
foreach (EntryData item in _pendingForces)
|
||||
{
|
||||
if (VelocityApplicationTypesContains(item.Type) == !nonAngular || item.Type == ForceApplicationType.MovePosition || item.Type == ForceApplicationType.MoveRotation)
|
||||
datasToKeep.Add(item);
|
||||
}
|
||||
// Add back to _pendingForces if changed.
|
||||
if (datasToKeep.Count != _pendingForces.Count)
|
||||
{
|
||||
_pendingForces.Clear();
|
||||
|
||||
foreach (EntryData item in datasToKeep)
|
||||
_pendingForces.Add(item);
|
||||
}
|
||||
|
||||
CollectionCaches<EntryData>.Store(datasToKeep);
|
||||
|
||||
bool VelocityApplicationTypesContains(ForceApplicationType apt)
|
||||
{
|
||||
return (velocityApplicationTypes & apt) == apt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void SetReconcileData(RigidbodyState rs, List<EntryData> lst)
|
||||
{
|
||||
RigidbodyState = rs;
|
||||
_pendingForces = lst;
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
CollectionCaches<EntryData>.StoreAndDefault(ref _pendingForces);
|
||||
Rigidbody = null;
|
||||
}
|
||||
|
||||
public void InitializeState() { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c964c0b90f389c4899a8120cc19d8b0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/Prediction/PredictionRigidbody.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,445 @@
|
||||
using FishNet.CodeGenerating;
|
||||
using FishNet.Component.Prediction;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Serializing;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Scripting;
|
||||
|
||||
namespace FishNet.Object.Prediction
|
||||
{
|
||||
public static class PredictionRigidbody2DSerializers
|
||||
{
|
||||
public static void WriteForceData(this Writer w, PredictionRigidbody2D.EntryData value)
|
||||
{
|
||||
PredictionRigidbody2D.ForceApplicationType appType = value.Type;
|
||||
w.WriteUInt8Unpacked((byte)appType);
|
||||
PredictionRigidbody2D.AllForceData data = value.Data;
|
||||
|
||||
switch (appType)
|
||||
{
|
||||
case PredictionRigidbody2D.ForceApplicationType.AddForce:
|
||||
case PredictionRigidbody2D.ForceApplicationType.AddRelativeForce:
|
||||
w.WriteVector3(data.Vector3Force);
|
||||
w.WriteInt32((byte)data.Mode);
|
||||
break;
|
||||
case PredictionRigidbody2D.ForceApplicationType.AddTorque:
|
||||
w.WriteSingle(data.FloatForce);
|
||||
w.WriteInt32((byte)data.Mode);
|
||||
break;
|
||||
case PredictionRigidbody2D.ForceApplicationType.AddForceAtPosition:
|
||||
w.WriteVector3(data.Vector3Force);
|
||||
w.WriteVector3(data.Position);
|
||||
w.WriteInt32((byte)data.Mode);
|
||||
break;
|
||||
case PredictionRigidbody2D.ForceApplicationType.MovePosition:
|
||||
w.WriteVector3(data.Position);
|
||||
break;
|
||||
case PredictionRigidbody2D.ForceApplicationType.MoveRotation:
|
||||
w.WriteUInt8Unpacked((byte)data.RotationPacking);
|
||||
w.WriteQuaternion(data.Rotation, data.RotationPacking);
|
||||
break;
|
||||
default:
|
||||
w.NetworkManager.LogError($"ForceApplicationType of {appType} is not supported.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static PredictionRigidbody2D.EntryData ReadForceData(this Reader r)
|
||||
{
|
||||
PredictionRigidbody2D.EntryData fd = new();
|
||||
|
||||
PredictionRigidbody2D.ForceApplicationType appType = (PredictionRigidbody2D.ForceApplicationType)r.ReadUInt8Unpacked();
|
||||
fd.Type = appType;
|
||||
|
||||
PredictionRigidbody2D.AllForceData data = new();
|
||||
|
||||
switch (appType)
|
||||
{
|
||||
case PredictionRigidbody2D.ForceApplicationType.AddForce:
|
||||
case PredictionRigidbody2D.ForceApplicationType.AddRelativeForce:
|
||||
data.Vector3Force = r.ReadVector3();
|
||||
data.Mode = (ForceMode2D)r.ReadUInt8Unpacked();
|
||||
break;
|
||||
case PredictionRigidbody2D.ForceApplicationType.AddTorque:
|
||||
data.FloatForce = r.ReadSingle();
|
||||
data.Mode = (ForceMode2D)r.ReadUInt8Unpacked();
|
||||
break;
|
||||
case PredictionRigidbody2D.ForceApplicationType.AddForceAtPosition:
|
||||
data.Vector3Force = r.ReadVector3();
|
||||
data.Position = r.ReadVector3();
|
||||
data.Mode = (ForceMode2D)r.ReadUInt8Unpacked();
|
||||
break;
|
||||
case PredictionRigidbody2D.ForceApplicationType.MovePosition:
|
||||
data.Position = r.ReadVector3();
|
||||
break;
|
||||
case PredictionRigidbody2D.ForceApplicationType.MoveRotation:
|
||||
AutoPackType apt = (AutoPackType)r.ReadUInt8Unpacked();
|
||||
data.Rotation = r.ReadQuaternion(apt);
|
||||
break;
|
||||
default:
|
||||
r.NetworkManager.LogError($"ForceApplicationType of {appType} is not supported.");
|
||||
break;
|
||||
}
|
||||
|
||||
fd.Data = data;
|
||||
return fd;
|
||||
}
|
||||
|
||||
public static void WritePredictionRigidbody2D(this Writer w, PredictionRigidbody2D pr)
|
||||
{
|
||||
w.Write(pr.Rigidbody2D.GetState(pr.RotationPacking));
|
||||
w.WriteList(pr.GetPendingForces());
|
||||
}
|
||||
|
||||
public static PredictionRigidbody2D ReadPredictionRigidbody2D(this Reader r)
|
||||
{
|
||||
Rigidbody2DState rs = r.Read<Rigidbody2DState>();
|
||||
|
||||
List<PredictionRigidbody2D.EntryData> lst = CollectionCaches<PredictionRigidbody2D.EntryData>.RetrieveList();
|
||||
r.ReadList(ref lst);
|
||||
|
||||
PredictionRigidbody2D pr = ResettableObjectCaches<PredictionRigidbody2D>.Retrieve();
|
||||
pr.SetReconcileData(rs, lst);
|
||||
pr.SetPendingForces(lst);
|
||||
|
||||
return pr;
|
||||
}
|
||||
}
|
||||
|
||||
[UseGlobalCustomSerializer]
|
||||
[Preserve]
|
||||
public class PredictionRigidbody2D : IResettable
|
||||
{
|
||||
#region Types.
|
||||
// How the force was applied.
|
||||
[System.Flags]
|
||||
public enum ForceApplicationType : byte
|
||||
{
|
||||
AddForceAtPosition = 1 << 0,
|
||||
AddForce = 1 << 1,
|
||||
AddRelativeForce = 1 << 2,
|
||||
AddTorque = 1 << 3,
|
||||
MovePosition = 1 << 4,
|
||||
MoveRotation = 1 << 5,
|
||||
}
|
||||
|
||||
public struct AllForceData
|
||||
{
|
||||
public Vector3 Vector3Force;
|
||||
public float FloatForce;
|
||||
public Vector3 Position;
|
||||
public Quaternion Rotation;
|
||||
[ExcludeSerialization]
|
||||
public readonly AutoPackType RotationPacking;
|
||||
public ForceMode2D Mode;
|
||||
|
||||
public AllForceData(Vector3 position) : this()
|
||||
{
|
||||
Position = position;
|
||||
}
|
||||
|
||||
public AllForceData(Quaternion rotation, AutoPackType rotationPacking) : this()
|
||||
{
|
||||
Rotation = rotation;
|
||||
RotationPacking = rotationPacking;
|
||||
}
|
||||
|
||||
public AllForceData(Vector3 force, ForceMode2D mode) : this()
|
||||
{
|
||||
Vector3Force = force;
|
||||
Mode = mode;
|
||||
}
|
||||
|
||||
public AllForceData(float force, ForceMode2D mode) : this()
|
||||
{
|
||||
FloatForce = force;
|
||||
Mode = mode;
|
||||
}
|
||||
|
||||
public AllForceData(Vector3 force, Vector3 position, ForceMode2D mode) : this()
|
||||
{
|
||||
Vector3Force = force;
|
||||
Position = position;
|
||||
Mode = mode;
|
||||
}
|
||||
}
|
||||
|
||||
[UseGlobalCustomSerializer]
|
||||
public struct EntryData
|
||||
{
|
||||
public ForceApplicationType Type;
|
||||
public AllForceData Data;
|
||||
|
||||
public EntryData(ForceApplicationType type, AllForceData data)
|
||||
{
|
||||
Type = type;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
public EntryData(EntryData fd)
|
||||
{
|
||||
Type = fd.Type;
|
||||
Data = fd.Data;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Internal.
|
||||
/// <summary>
|
||||
/// Rigidbody2DState set only as reconcile data.
|
||||
/// </summary>
|
||||
[System.NonSerialized]
|
||||
internal Rigidbody2DState Rigidbody2DState;
|
||||
/// <summary>
|
||||
/// How much to pack rotation.
|
||||
/// </summary>
|
||||
[ExcludeSerialization]
|
||||
internal AutoPackType RotationPacking = AutoPackType.Packed;
|
||||
#endregion
|
||||
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// Rigidbody which force is applied.
|
||||
/// </summary>
|
||||
public Rigidbody2D Rigidbody2D { get; private set; }
|
||||
/// <summary>
|
||||
/// Returns if there are any pending forces.
|
||||
/// </summary>
|
||||
public bool HasPendingForces => _pendingForces != null && _pendingForces.Count > 0;
|
||||
#endregion
|
||||
|
||||
#region Private
|
||||
/// <summary>
|
||||
/// Forces waiting to be applied.
|
||||
/// </summary>
|
||||
[ExcludeSerialization]
|
||||
private List<EntryData> _pendingForces;
|
||||
|
||||
/// <summary>
|
||||
/// Returns current pending forces.
|
||||
/// Modifying this collection could cause undesirable results.
|
||||
/// </summary>
|
||||
public List<EntryData> GetPendingForces() => _pendingForces;
|
||||
#endregion
|
||||
|
||||
~PredictionRigidbody2D()
|
||||
{
|
||||
if (_pendingForces != null)
|
||||
CollectionCaches<EntryData>.StoreAndDefault(ref _pendingForces);
|
||||
|
||||
Rigidbody2D = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rigidbody which force is applied.
|
||||
/// </summary>
|
||||
/// <param name = "rb"></param>
|
||||
public void Initialize(Rigidbody2D rb, AutoPackType rotationPacking = AutoPackType.Packed)
|
||||
{
|
||||
Rigidbody2D = rb;
|
||||
RotationPacking = rotationPacking;
|
||||
|
||||
if (_pendingForces == null)
|
||||
_pendingForces = CollectionCaches<EntryData>.RetrieveList();
|
||||
else
|
||||
_pendingForces.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds Velocity force to the Rigidbody.
|
||||
/// </summary>
|
||||
public void AddForce(Vector3 force, ForceMode2D mode = ForceMode2D.Force)
|
||||
{
|
||||
EntryData fd = new(ForceApplicationType.AddForce, new(force, mode));
|
||||
_pendingForces.Add(fd);
|
||||
}
|
||||
|
||||
public void AddRelativeForce(Vector3 force, ForceMode2D mode = ForceMode2D.Force)
|
||||
{
|
||||
EntryData fd = new(ForceApplicationType.AddRelativeForce, new(force, mode));
|
||||
_pendingForces.Add(fd);
|
||||
}
|
||||
|
||||
public void AddTorque(float force, ForceMode2D mode = ForceMode2D.Force)
|
||||
{
|
||||
EntryData fd = new(ForceApplicationType.AddTorque, new(force, mode));
|
||||
_pendingForces.Add(fd);
|
||||
}
|
||||
|
||||
public void AddForceAtPosition(Vector3 force, Vector3 position, ForceMode2D mode = ForceMode2D.Force)
|
||||
{
|
||||
EntryData fd = new(ForceApplicationType.AddForceAtPosition, new(force, position, mode));
|
||||
_pendingForces.Add(fd);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets velocity while clearing pending forces.
|
||||
/// Simulate should still be called normally.
|
||||
/// </summary>
|
||||
public void Velocity(Vector3 force)
|
||||
{
|
||||
#if UNITY_6000_1_OR_NEWER
|
||||
Rigidbody2D.linearVelocity = force;
|
||||
#else
|
||||
Rigidbody2D.velocity = force;
|
||||
#endif
|
||||
RemoveForces(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets angularVelocity while clearning pending forces.
|
||||
/// Simulate should still be called normally.
|
||||
/// </summary>
|
||||
public void AngularVelocity(float force)
|
||||
{
|
||||
Rigidbody2D.angularVelocity = force;
|
||||
RemoveForces(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the kinematic Rigidbody towards position.
|
||||
/// </summary>
|
||||
/// <param name="position">Next position.</param>
|
||||
public void MovePosition(Vector3 position)
|
||||
{
|
||||
EntryData fd = new(ForceApplicationType.MovePosition, new(position));
|
||||
_pendingForces.Add(fd);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves the kinematic Rigidbody towards rotation.
|
||||
/// </summary>
|
||||
/// <param name="position">Next position.</param>
|
||||
public void MoveRotation(Quaternion rotation)
|
||||
{
|
||||
EntryData fd = new(ForceApplicationType.MoveRotation, new(rotation, RotationPacking));
|
||||
_pendingForces.Add(fd);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applies pending forces to rigidbody in the order they were added.
|
||||
/// </summary>
|
||||
public void Simulate()
|
||||
{
|
||||
foreach (EntryData item in _pendingForces)
|
||||
{
|
||||
AllForceData data = item.Data;
|
||||
switch (item.Type)
|
||||
{
|
||||
case ForceApplicationType.AddTorque:
|
||||
Rigidbody2D.AddTorque(data.FloatForce, data.Mode);
|
||||
break;
|
||||
case ForceApplicationType.AddForce:
|
||||
Rigidbody2D.AddForce(data.Vector3Force, data.Mode);
|
||||
break;
|
||||
case ForceApplicationType.AddRelativeForce:
|
||||
Rigidbody2D.AddRelativeForce(data.Vector3Force, data.Mode);
|
||||
break;
|
||||
case ForceApplicationType.AddForceAtPosition:
|
||||
Rigidbody2D.AddForceAtPosition(data.Vector3Force, data.Position, data.Mode);
|
||||
break;
|
||||
case ForceApplicationType.MovePosition:
|
||||
Rigidbody2D.MovePosition(data.Position);
|
||||
break;
|
||||
case ForceApplicationType.MoveRotation:
|
||||
Rigidbody2D.MoveRotation(data.Rotation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
_pendingForces.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears current and pending forces for velocity and angularVelocity.
|
||||
/// </summary>
|
||||
public void ClearVelocities()
|
||||
{
|
||||
Velocity(Vector3.zero);
|
||||
AngularVelocity(0f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears pending forces for velocity, or angular velocity.
|
||||
/// </summary>
|
||||
/// <param name = "nonRotational">True to clear velocities, false to clear angular velocities.</param>
|
||||
public void ClearPendingForces(bool nonRotational)
|
||||
{
|
||||
RemoveForces(nonRotational);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears pending forces for velocity and angularVelocity.
|
||||
/// </summary>
|
||||
public void ClearPendingForces()
|
||||
{
|
||||
_pendingForces.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reconciles to a state.
|
||||
/// </summary>
|
||||
public void Reconcile(PredictionRigidbody2D pr)
|
||||
{
|
||||
_pendingForces.Clear();
|
||||
if (pr._pendingForces != null)
|
||||
{
|
||||
foreach (EntryData item in pr._pendingForces)
|
||||
_pendingForces.Add(new(item));
|
||||
}
|
||||
Rigidbody2D.SetState(pr.Rigidbody2DState);
|
||||
|
||||
ResettableObjectCaches<PredictionRigidbody2D>.Store(pr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes forces from pendingForces.
|
||||
/// </summary>
|
||||
/// <param name = "nonAngular">True to remove if velocity, false if to remove angular velocity.</param>
|
||||
private void RemoveForces(bool nonAngular)
|
||||
{
|
||||
if (_pendingForces.Count > 0)
|
||||
{
|
||||
ForceApplicationType velocityApplicationTypes = ForceApplicationType.AddRelativeForce | ForceApplicationType.AddForce;
|
||||
|
||||
List<EntryData> datasToKeep = CollectionCaches<EntryData>.RetrieveList();
|
||||
foreach (EntryData item in _pendingForces)
|
||||
{
|
||||
if (VelocityApplicationTypesContains(item.Type) == !nonAngular || item.Type == ForceApplicationType.MovePosition || item.Type == ForceApplicationType.MoveRotation)
|
||||
datasToKeep.Add(item);
|
||||
}
|
||||
// Add back to _pendingForces if changed.
|
||||
if (datasToKeep.Count != _pendingForces.Count)
|
||||
{
|
||||
_pendingForces.Clear();
|
||||
|
||||
foreach (EntryData item in datasToKeep)
|
||||
_pendingForces.Add(item);
|
||||
}
|
||||
CollectionCaches<EntryData>.Store(datasToKeep);
|
||||
|
||||
bool VelocityApplicationTypesContains(ForceApplicationType apt)
|
||||
{
|
||||
return (velocityApplicationTypes & apt) == apt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void SetPendingForces(List<EntryData> lst) => _pendingForces = lst;
|
||||
|
||||
internal void SetReconcileData(Rigidbody2DState rs, List<EntryData> lst)
|
||||
{
|
||||
Rigidbody2DState = rs;
|
||||
_pendingForces = lst;
|
||||
}
|
||||
|
||||
public void ResetState()
|
||||
{
|
||||
CollectionCaches<EntryData>.StoreAndDefault(ref _pendingForces);
|
||||
Rigidbody2D = null;
|
||||
}
|
||||
|
||||
public void InitializeState() { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1b6a1cc418198134180faa3438517b52
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/Prediction/PredictionRigidbody2D.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,98 @@
|
||||
using FishNet.Utility;
|
||||
using System.Runtime.CompilerServices;
|
||||
using FishNet.CodeGenerating;
|
||||
using FishNet.Transporting;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
|
||||
[assembly: InternalsVisibleTo(UtilityConstants.CODEGEN_ASSEMBLY_NAME)]
|
||||
|
||||
namespace FishNet.Object.Prediction
|
||||
{
|
||||
[MakePublic]
|
||||
internal struct ReplicateDataContainer<T> where T : IReplicateData, new()
|
||||
{
|
||||
#region Types
|
||||
private enum DataCachingType
|
||||
{
|
||||
Unset,
|
||||
ValueType,
|
||||
IResettableReferenceType,
|
||||
ReferenceType
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Replicate data.
|
||||
/// </summary>
|
||||
public T Data;
|
||||
/// <summary>
|
||||
/// True if the data was created locally or came through the network as created.
|
||||
/// </summary>
|
||||
public bool IsCreated;
|
||||
/// <summary>
|
||||
/// Channel the data came in on.
|
||||
/// </summary>
|
||||
public readonly Channel Channel;
|
||||
/// <summary>
|
||||
/// True if populated.
|
||||
/// </summary>
|
||||
public bool IsValid { get; private set; }
|
||||
/// <summary>
|
||||
/// How data should be cached and retrieved when not set.
|
||||
/// </summary>
|
||||
private static DataCachingType _dataCachingType = DataCachingType.Unset;
|
||||
public ReplicateDataContainer(T data, Channel channel) : this(data, channel, tick: 0, isCreated: false) { }
|
||||
public ReplicateDataContainer(T data, Channel channel, bool isCreated) : this(data, channel, tick: 0, isCreated) { }
|
||||
|
||||
public ReplicateDataContainer(T data, Channel channel, uint tick, bool isCreated = false)
|
||||
{
|
||||
Data = data;
|
||||
Channel = channel;
|
||||
IsCreated = isCreated;
|
||||
IsValid = true;
|
||||
|
||||
SetDataTick(tick);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A shortcut to calling Data.SetTick.
|
||||
/// </summary>
|
||||
public void SetDataTick(uint tick)
|
||||
{
|
||||
SetDataIfNull(ref Data);
|
||||
Data.SetTick(tick);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets data to new() if is nullable type, and is null.
|
||||
/// </summary>
|
||||
/// <param name = "data"></param>
|
||||
private void SetDataIfNull(ref T data)
|
||||
{
|
||||
// Only figure out data caching type once to save perf.
|
||||
if (_dataCachingType == DataCachingType.Unset)
|
||||
{
|
||||
if (typeof(T).IsValueType)
|
||||
_dataCachingType = DataCachingType.ValueType;
|
||||
else if (typeof(IResettable).IsAssignableFrom(typeof(T)))
|
||||
_dataCachingType = DataCachingType.IResettableReferenceType;
|
||||
else
|
||||
_dataCachingType = DataCachingType.ReferenceType;
|
||||
}
|
||||
|
||||
if (_dataCachingType != DataCachingType.ValueType && data == null)
|
||||
data = ObjectCaches<T>.Retrieve();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Data != null)
|
||||
Data.Dispose();
|
||||
|
||||
IsValid = false;
|
||||
}
|
||||
|
||||
public static ReplicateDataContainer<T> GetDefault(uint tick) => new(default, Channel.Unreliable, tick);
|
||||
public static ReplicateDataContainer<T> GetDefault() => GetDefault(tick: 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e45211dba582c146993b54bf316d8fc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/Prediction/ReplicateDataContainer.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using FishNet.Utility;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo(UtilityConstants.CODEGEN_ASSEMBLY_NAME)]
|
||||
|
||||
namespace FishNet.Object.Prediction
|
||||
{
|
||||
[Flags]
|
||||
public enum ReplicateState : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// The default value of this state.
|
||||
/// This value should never occur when a replicate runs.
|
||||
/// </summary>
|
||||
Invalid = 0,
|
||||
/// <summary>
|
||||
/// Server and clients use this flag.
|
||||
/// Flag will be set if data tick has run outside a reconcile, such as from user code within OnTick.
|
||||
/// </summary>
|
||||
Ticked = 1 << 0, // 1
|
||||
/// <summary>
|
||||
/// Only client will use this flag.
|
||||
/// Flag is set if data is being run during a reconcile.
|
||||
/// </summary>
|
||||
Replayed = 1 << 1, // 2
|
||||
/// <summary>
|
||||
/// Server and client use this flag.
|
||||
/// Data has been created by the server or client.
|
||||
/// This indicates that data is known and was intentionally sent.
|
||||
/// </summary>
|
||||
Created = 1 << 2 // 4
|
||||
}
|
||||
|
||||
public static class ReplicateStateExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns if value is valid.
|
||||
/// This should never be false.
|
||||
/// </summary>
|
||||
public static bool IsValid(this ReplicateState value) => value != ReplicateState.Invalid;
|
||||
|
||||
/// <summary>
|
||||
/// Returns if value contains ReplicateState.Ticked.
|
||||
/// </summary>
|
||||
public static bool ContainsTicked(this ReplicateState value) => value.FastContains(ReplicateState.Ticked);
|
||||
|
||||
/// <summary>
|
||||
/// Returns if value contains ReplicateState.Created.
|
||||
/// </summary>
|
||||
public static bool ContainsCreated(this ReplicateState value) => value.FastContains(ReplicateState.Created);
|
||||
|
||||
/// <summary>
|
||||
/// Returns if value contains ReplicateState.Replayed.
|
||||
/// </summary>
|
||||
public static bool ContainsReplayed(this ReplicateState value) => value.FastContains(ReplicateState.Replayed);
|
||||
|
||||
[Obsolete("Use ContainsReplayed.")]
|
||||
public static bool IsReplayed(this ReplicateState value) => value.ContainsReplayed();
|
||||
|
||||
/// <summary>
|
||||
/// Returns if value is (ReplicateState.Ticked | ReplicateState.Created).
|
||||
/// </summary>
|
||||
public static bool IsTickedCreated(this ReplicateState value) => value == (ReplicateState.Ticked | ReplicateState.Created);
|
||||
|
||||
/// <summary>
|
||||
/// Returns if value equals ReplicateState.Ticked.
|
||||
/// </summary>
|
||||
public static bool IsTickedNonCreated(this ReplicateState value) => value == ReplicateState.Ticked;
|
||||
|
||||
/// <summary>
|
||||
/// Returns if value is (ReplicateState.Replayed | ReplicateState.Ticked | ReplicateState.Created).
|
||||
/// </summary>
|
||||
public static bool IsReplayedCreated(this ReplicateState value) => value == (ReplicateState.Replayed | ReplicateState.Created);
|
||||
|
||||
/// <summary>
|
||||
/// Returns if value is ReplicateState.Replayed without ReplicateState.Ticked nor ReplicateState.Created.
|
||||
/// </summary>
|
||||
public static bool IsFuture(this ReplicateState value) => value == ReplicateState.Replayed;
|
||||
|
||||
[Obsolete("Use ContainsCreated.")]
|
||||
public static bool IsCreated(this ReplicateState value) => value.ContainsCreated();
|
||||
|
||||
/// <summary>
|
||||
/// True if part is containined within whole.
|
||||
/// </summary>
|
||||
public static bool FastContains(this ReplicateState whole, ReplicateState part) => (whole & part) == part;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f8deb74673db557489cf93509af3cb21
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/Prediction/ReplicateState.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5429986c563a23a4c86f7b6724e41a70
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace FishNet.Object.Synchronizing
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom SyncObjects must inherit from SyncBase and implement this interface.
|
||||
/// </summary>
|
||||
public interface ICustomSync
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the serialized type.
|
||||
/// This must return the value type you are synchronizing, for example a struct or class.
|
||||
/// If you are not synchronizing a particular value but instead of supported values such as int, bool, ect, then you may return null on this method.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
object GetSerializedType();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2024b0be0cd1cc744a442f3e2e6ba483
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/Synchronizing/ICustomSync.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,173 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object.Synchronizing
|
||||
{
|
||||
/// <summary>
|
||||
/// Implements features specific for a typed SyncVar.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class FloatSyncVar : SyncVar<float>, ICustomSync
|
||||
{
|
||||
public object GetSerializedType() => typeof(float);
|
||||
protected override float Interpolate(float previous, float current, float percent) => Mathf.Lerp(previous, current, percent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements features specific for a typed SyncVar.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class DoubleSyncVar : SyncVar<double>, ICustomSync
|
||||
{
|
||||
public object GetSerializedType() => typeof(double);
|
||||
|
||||
protected override double Interpolate(double previous, double current, float percent)
|
||||
{
|
||||
float a = (float)previous;
|
||||
float b = (float)current;
|
||||
return Mathf.Lerp(a, b, percent);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements features specific for a typed SyncVar.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class SbyteSyncVar : SyncVar<sbyte>, ICustomSync
|
||||
{
|
||||
public object GetSerializedType() => typeof(sbyte);
|
||||
protected override sbyte Interpolate(sbyte previous, sbyte current, float percent) => (sbyte)Mathf.Lerp(previous, current, percent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements features specific for a typed SyncVar.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class ByteSyncVar : SyncVar<byte>, ICustomSync
|
||||
{
|
||||
public object GetSerializedType() => typeof(byte);
|
||||
protected override byte Interpolate(byte previous, byte current, float percent) => (byte)Mathf.Lerp(previous, current, percent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements features specific for a typed SyncVar.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class ShortSyncVar : SyncVar<short>, ICustomSync
|
||||
{
|
||||
public object GetSerializedType() => typeof(short);
|
||||
protected override short Interpolate(short previous, short current, float percent) => (short)Mathf.Lerp(previous, current, percent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements features specific for a typed SyncVar.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class UShortSyncVar : SyncVar<ushort>, ICustomSync
|
||||
{
|
||||
public object GetSerializedType() => typeof(ushort);
|
||||
protected override ushort Interpolate(ushort previous, ushort current, float percent) => (ushort)Mathf.Lerp(previous, current, percent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements features specific for a typed SyncVar.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class IntSyncVar : SyncVar<int>, ICustomSync
|
||||
{
|
||||
public object GetSerializedType() => typeof(int);
|
||||
protected override int Interpolate(int previous, int current, float percent) => (int)Mathf.Lerp(previous, current, percent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements features specific for a typed SyncVar.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class UIntSyncVar : SyncVar<uint>, ICustomSync
|
||||
{
|
||||
public object GetSerializedType() => typeof(uint);
|
||||
protected override uint Interpolate(uint previous, uint current, float percent) => (uint)Mathf.Lerp(previous, current, percent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements features specific for a typed SyncVar.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class LongSyncVar : SyncVar<long>, ICustomSync
|
||||
{
|
||||
public object GetSerializedType() => typeof(long);
|
||||
protected override long Interpolate(long previous, long current, float percent) => (long)Mathf.Lerp(previous, current, percent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements features specific for a typed SyncVar.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class ULongSyncVar : SyncVar<ulong>, ICustomSync
|
||||
{
|
||||
public object GetSerializedType() => typeof(ulong);
|
||||
protected override ulong Interpolate(ulong previous, ulong current, float percent) => (ulong)Mathf.Lerp(previous, current, percent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements features specific for a typed SyncVar.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class Vector2SyncVar : SyncVar<Vector2>, ICustomSync
|
||||
{
|
||||
public object GetSerializedType() => typeof(Vector2);
|
||||
protected override Vector2 Interpolate(Vector2 previous, Vector2 current, float percent) => Vector2.Lerp(previous, current, percent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements features specific for a typed SyncVar.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class Vector3SyncVar : SyncVar<Vector3>, ICustomSync
|
||||
{
|
||||
public object GetSerializedType() => typeof(Vector3);
|
||||
protected override Vector3 Interpolate(Vector3 previous, Vector3 current, float percent) => Vector3.Lerp(previous, current, percent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements features specific for a typed SyncVar.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class Vector4SyncVar : SyncVar<Vector4>, ICustomSync
|
||||
{
|
||||
public object GetSerializedType() => typeof(Vector4);
|
||||
protected override Vector4 Interpolate(Vector4 previous, Vector4 current, float percent) => Vector4.Lerp(previous, current, percent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements features specific for a typed SyncVar.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class Vector2IntSyncVar : SyncVar<Vector2Int>, ICustomSync
|
||||
{
|
||||
public object GetSerializedType() => typeof(Vector2);
|
||||
|
||||
protected override Vector2Int Interpolate(Vector2Int previous, Vector2Int current, float percent)
|
||||
{
|
||||
int x = (int)Mathf.Lerp(previous.x, current.x, percent);
|
||||
int y = (int)Mathf.Lerp(previous.y, current.y, percent);
|
||||
return new(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements features specific for a typed SyncVar.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class Vector3IntSyncVar : SyncVar<Vector3Int>, ICustomSync
|
||||
{
|
||||
public object GetSerializedType() => typeof(Vector3Int);
|
||||
|
||||
protected override Vector3Int Interpolate(Vector3Int previous, Vector3Int current, float percent)
|
||||
{
|
||||
int x = (int)Mathf.Lerp(previous.x, current.x, percent);
|
||||
int y = (int)Mathf.Lerp(previous.y, current.y, percent);
|
||||
int z = (int)Mathf.Lerp(previous.z, current.z, percent);
|
||||
return new(x, y, z);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 727355d27ffb19747a43beb6299f7b98
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/Synchronizing/InterpolatedSyncVars.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace FishNet.Object
|
||||
{
|
||||
internal enum MissingObjectPacketLength : int
|
||||
{
|
||||
Reliable = -1,
|
||||
PurgeRemaiming = -2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3d177496f9519e246b8e3ef199d83437
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/Synchronizing/MissingObjectPacketLength.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace FishNet.Object.Synchronizing
|
||||
{
|
||||
/// <summary>
|
||||
/// Which clients may receive synchronization updates.
|
||||
/// </summary>
|
||||
/// // Remove on V5. Just rename file to ReadPermission.cs, do not remove.
|
||||
public enum ReadPermission : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// All observers will receive updates.
|
||||
/// </summary>
|
||||
Observers = 0,
|
||||
/// <summary>
|
||||
/// Only owner will receive updates.
|
||||
/// </summary>
|
||||
OwnerOnly = 1,
|
||||
/// <summary>
|
||||
/// Send to all observers except owner.
|
||||
/// </summary>
|
||||
ExcludeOwner = 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8050ef114e01f74409d8e29b821b6fc0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/Synchronizing/ReadPermissions.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,543 @@
|
||||
using System;
|
||||
using FishNet.CodeGenerating;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Managing.Timing;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Transporting;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.Object.Synchronizing.Internal
|
||||
{
|
||||
public class SyncBase
|
||||
{
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// True if this SyncBase has been initialized on its NetworkBehaviour.
|
||||
/// Being true does not mean that the NetworkBehaviour has been initialized on the network, but rather that this SyncBase has been configured with the basics to be networked.
|
||||
/// </summary>
|
||||
public bool IsInitialized { get; private set; }
|
||||
/// <summary>
|
||||
/// True if the object for which this SyncType is for has been initialized for the network.
|
||||
/// </summary>
|
||||
public bool IsNetworkInitialized => IsInitialized && (NetworkBehaviour.IsServerInitialized || NetworkBehaviour.IsClientInitialized);
|
||||
/// <summary>
|
||||
/// True if a SyncObject, false if a SyncVar.
|
||||
/// </summary>
|
||||
public bool IsSyncObject { get; private set; }
|
||||
/// <summary>
|
||||
/// The settings for this SyncVar.
|
||||
/// </summary>
|
||||
[MakePublic]
|
||||
internal SyncTypeSettings Settings;
|
||||
/// <summary>
|
||||
/// How often updates may send.
|
||||
/// </summary>
|
||||
[MakePublic]
|
||||
internal 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>
|
||||
[MakePublic]
|
||||
internal 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;
|
||||
|
||||
/// <summary>
|
||||
/// Sets a new currentChannel.
|
||||
/// </summary>
|
||||
/// <param name = "channel"></param>
|
||||
internal void SetCurrentChannel(Channel channel) => _currentChannel = channel;
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// Sync interval converted to ticks.
|
||||
/// </summary>
|
||||
private uint _timeToTicks;
|
||||
/// <summary>
|
||||
/// Channel to use for next write. To ensure eventual consistency this eventually changes to reliable when Settings are unreliable.
|
||||
/// </summary>
|
||||
private Channel _currentChannel;
|
||||
/// <summary>
|
||||
/// Last changerId read from sender.
|
||||
/// </summary>
|
||||
private ushort _lastReadChangeId = UNSET_CHANGE_ID;
|
||||
/// <summary>
|
||||
/// Last changeId that was sent to receivers.
|
||||
/// </summary>
|
||||
private ushort _lastWrittenChangeId = UNSET_CHANGE_ID;
|
||||
#endregion
|
||||
|
||||
#region Consts.
|
||||
/// <summary>
|
||||
/// Value to use when readId is unset.
|
||||
/// </summary>
|
||||
private const ushort UNSET_CHANGE_ID = 0;
|
||||
/// <summary>
|
||||
/// Maximum value readId can be before resetting to the beginning.
|
||||
/// </summary>
|
||||
private const ushort MAXIMUM_CHANGE_ID = ushort.MaxValue;
|
||||
#endregion
|
||||
|
||||
#region Constructors
|
||||
public SyncBase() : this(new()) { }
|
||||
|
||||
public SyncBase(SyncTypeSettings settings)
|
||||
{
|
||||
Settings = settings;
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Updates settings with new values.
|
||||
/// </summary>
|
||||
public void UpdateSettings(SyncTypeSettings settings)
|
||||
{
|
||||
Settings = settings;
|
||||
SetTimeToTicks();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates settings with new values.
|
||||
/// </summary>
|
||||
public void UpdatePermissions(WritePermission writePermissions, ReadPermission readPermissions)
|
||||
{
|
||||
UpdatePermissions(writePermissions);
|
||||
UpdatePermissions(readPermissions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates settings with new values.
|
||||
/// </summary>
|
||||
public void UpdatePermissions(WritePermission writePermissions) => Settings.WritePermission = writePermissions;
|
||||
|
||||
/// <summary>
|
||||
/// Updates settings with new values.
|
||||
/// </summary>
|
||||
public void UpdatePermissions(ReadPermission readPermissions) => Settings.ReadPermission = readPermissions;
|
||||
|
||||
/// <summary>
|
||||
/// Updates settings with new values.
|
||||
/// </summary>
|
||||
public void UpdateSendRate(float sendRate)
|
||||
{
|
||||
Settings.SendRate = sendRate;
|
||||
SetTimeToTicks();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates settings with new values.
|
||||
/// </summary>
|
||||
public void UpdateSettings(Channel channel)
|
||||
{
|
||||
CheckChannel(ref channel);
|
||||
_currentChannel = channel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates settings with new values.
|
||||
/// </summary>
|
||||
public void UpdateSettings(WritePermission writePermissions, ReadPermission readPermissions, float sendRate, Channel channel)
|
||||
{
|
||||
CheckChannel(ref channel);
|
||||
_currentChannel = channel;
|
||||
Settings = new(writePermissions, readPermissions, sendRate, channel);
|
||||
SetTimeToTicks();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks channel and corrects if not valid.
|
||||
/// </summary>
|
||||
/// <param name = "c"></param>
|
||||
private void CheckChannel(ref Channel c)
|
||||
{
|
||||
if (c == Channel.Unreliable && IsSyncObject)
|
||||
{
|
||||
c = Channel.Reliable;
|
||||
string warning = $"Channel cannot be unreliable for SyncObjects. Channel has been changed to reliable.";
|
||||
NetworkManager.LogWarning(warning);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes this SyncBase before user Awake code.
|
||||
/// </summary>
|
||||
[MakePublic]
|
||||
internal void InitializeEarly(NetworkBehaviour nb, uint syncIndex, bool isSyncObject)
|
||||
{
|
||||
NetworkBehaviour = nb;
|
||||
SyncIndex = syncIndex;
|
||||
IsSyncObject = isSyncObject;
|
||||
|
||||
NetworkBehaviour.RegisterSyncType(this, SyncIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called during InitializeLate in NetworkBehaviours to indicate user Awake code has executed.
|
||||
/// </summary>
|
||||
[MakePublic]
|
||||
internal void InitializeLate()
|
||||
{
|
||||
Initialized();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the SyncType has been registered, but not yet initialized over the network.
|
||||
/// </summary>
|
||||
protected virtual void Initialized()
|
||||
{
|
||||
IsInitialized = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PreInitializes this for use with the network.
|
||||
/// </summary>
|
||||
[MakePublic]
|
||||
protected internal void PreInitialize(NetworkManager networkManager, bool asServer)
|
||||
{
|
||||
NetworkManager = networkManager;
|
||||
|
||||
if (Settings.IsDefault())
|
||||
{
|
||||
float sendRate = Mathf.Max(networkManager.ServerManager.GetSyncTypeRate(), (float)networkManager.TimeManager.TickDelta);
|
||||
Settings = new(sendRate);
|
||||
}
|
||||
|
||||
SetTimeToTicks();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets ticks needed to pass for send rate.
|
||||
/// </summary>
|
||||
private void SetTimeToTicks()
|
||||
{
|
||||
if (NetworkManager == null)
|
||||
return;
|
||||
_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>
|
||||
[MakePublic]
|
||||
protected internal 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>
|
||||
[MakePublic]
|
||||
protected internal virtual void OnStopCallback(bool asServer)
|
||||
{
|
||||
if (asServer)
|
||||
OnStartServerCalled = false;
|
||||
else
|
||||
OnStartClientCalled = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// True if can set values and send them over the network.
|
||||
/// </summary>
|
||||
/// <param name = "log"></param>
|
||||
/// <returns></returns>
|
||||
protected bool CanNetworkSetValues(bool log = 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 (!IsInitialized)
|
||||
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.IsServerStarted)
|
||||
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 && log)
|
||||
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>
|
||||
/// <param name = "sendRpc">True to send current dirtied values immediately as a RPC. When this occurs values will arrive in the order they are sent and interval is ignored.</param>
|
||||
protected bool Dirty() // bool sendRpc = false)
|
||||
{
|
||||
// if (sendRpc)
|
||||
// NextSyncTick = 0;
|
||||
/* 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();
|
||||
IsDirty |= canDirty;
|
||||
|
||||
return canDirty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if callbacks can be invoked with asServer ture.
|
||||
/// This is typically used when the value is changing through user code, causing supplier to be unknown.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected bool CanInvokeCallbackAsServer() => !IsNetworkInitialized || NetworkBehaviour.IsServerStarted;
|
||||
|
||||
/// <summary>
|
||||
/// Reads a change Id and returns true if the change is new.
|
||||
/// </summary>
|
||||
/// <remarks>This method is currently under evaluation and may change at any time.</remarks>
|
||||
protected virtual bool ReadChangeId(Reader reader)
|
||||
{
|
||||
if (NetworkManager == null)
|
||||
{
|
||||
NetworkManager.LogWarning($"NetworkManager is unexpectedly null during a SyncType read.");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool rolledOver = reader.ReadBoolean();
|
||||
ushort id = reader.ReadUInt16();
|
||||
|
||||
// Only check lastReadId if its not unset.
|
||||
if (_lastReadChangeId != UNSET_CHANGE_ID)
|
||||
{
|
||||
/* If not rolledOver then Id should always be larger
|
||||
* than the last read. If it's not then the data is
|
||||
* old.
|
||||
*
|
||||
* If Id is smaller then rolledOver should be normal,
|
||||
* as rolling over means to restart the Id from the lowest
|
||||
* value. */
|
||||
if (rolledOver)
|
||||
{
|
||||
if (id >= _lastReadChangeId)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (id <= _lastReadChangeId)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_lastReadChangeId = id;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the readId for a change.
|
||||
/// </summary>
|
||||
/// <remarks>This method is currently under evaluation and may change at any time.</remarks>
|
||||
protected virtual void WriteChangeId(PooledWriter writer)
|
||||
{
|
||||
bool rollOver;
|
||||
if (_lastWrittenChangeId >= MAXIMUM_CHANGE_ID)
|
||||
{
|
||||
rollOver = true;
|
||||
_lastWrittenChangeId = UNSET_CHANGE_ID;
|
||||
}
|
||||
else
|
||||
{
|
||||
rollOver = false;
|
||||
}
|
||||
|
||||
_lastWrittenChangeId++;
|
||||
writer.WriteBoolean(rollOver);
|
||||
writer.WriteUInt16(_lastWrittenChangeId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if values are being read as clientHost.
|
||||
/// </summary>
|
||||
/// <param name = "asServer">True if reading as server.</param>
|
||||
/// <remarks>This method is currently under evaluation and may change at any time.</remarks>
|
||||
protected bool IsReadAsClientHost(bool asServer) => !asServer && NetworkManager.IsServerStarted;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if values are being read as clientHost.
|
||||
/// </summary>
|
||||
/// <param name = "asServer">True if reading as server.</param>
|
||||
/// <remarks>This method is currently under evaluation and may change at any time.</remarks>
|
||||
protected bool CanReset(bool asServer)
|
||||
{
|
||||
bool clientStarted = IsNetworkInitialized && NetworkManager.IsClientStarted;
|
||||
return (asServer && !clientStarted) || (!asServer && NetworkBehaviour.IsDeinitializing);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Outputs values which may be helpful on how to process a read operation.
|
||||
/// </summary>
|
||||
/// <param name = "newChangeId">True if the changeId read is not old data.</param>
|
||||
/// <param name = "asClientHost">True if being read as clientHost.</param>
|
||||
/// <param name = "canModifyValues">True if can modify values from the read, typically when asServer or not asServer and not clientHost.</param>
|
||||
/// <remarks>This method is currently under evaluation and may change at any time.</remarks>
|
||||
protected void SetReadArguments(PooledReader reader, bool asServer, out bool newChangeId, out bool asClientHost, out bool canModifyValues)
|
||||
{
|
||||
newChangeId = ReadChangeId(reader);
|
||||
asClientHost = IsReadAsClientHost(asServer);
|
||||
canModifyValues = newChangeId && !asClientHost;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
internal bool IsNextSyncTimeMet(uint tick) => IsDirty && tick >= NextSyncTick;
|
||||
|
||||
[Obsolete("Use IsNextSyncTimeMet.")] //Remove on V5
|
||||
internal bool SyncTimeMet(uint tick) => IsNextSyncTimeMet(tick);
|
||||
|
||||
/// <summary>
|
||||
/// Writes current value.
|
||||
/// </summary>
|
||||
/// <param name = "resetSyncTick">True to set the next time data may sync.</param>
|
||||
[MakePublic]
|
||||
protected internal virtual void WriteDelta(PooledWriter writer, bool resetSyncTick = true)
|
||||
{
|
||||
WriteHeader(writer, resetSyncTick);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the header for this SyncType.
|
||||
/// </summary>
|
||||
protected virtual void WriteHeader(PooledWriter writer, bool resetSyncTick = true)
|
||||
{
|
||||
if (resetSyncTick)
|
||||
NextSyncTick = NetworkManager.TimeManager.LocalTick + _timeToTicks;
|
||||
|
||||
writer.WriteUInt8Unpacked((byte)SyncIndex);
|
||||
WriteChangeId(writer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that a full write has occurred.
|
||||
/// This is called from WriteFull, or can be called manually.
|
||||
/// </summary>
|
||||
[Obsolete("This method no longer functions. You may remove it from your code.")] //Remove on V5.
|
||||
protected void FullWritten() { }
|
||||
|
||||
/// <summary>
|
||||
/// Writes all values for the SyncType.
|
||||
/// </summary>
|
||||
[MakePublic]
|
||||
protected internal virtual void WriteFull(PooledWriter writer) { }
|
||||
|
||||
/// <summary>
|
||||
/// Sets current value as server or client through deserialization.
|
||||
/// </summary>
|
||||
[MakePublic]
|
||||
protected internal virtual void Read(PooledReader reader, bool asServer) { }
|
||||
|
||||
/// <summary>
|
||||
/// Resets initialized values for server and client.
|
||||
/// </summary>
|
||||
protected internal virtual void ResetState()
|
||||
{
|
||||
ResetState(true);
|
||||
ResetState(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets initialized values for server or client.
|
||||
/// </summary>
|
||||
[MakePublic]
|
||||
protected internal virtual void ResetState(bool asServer)
|
||||
{
|
||||
if (asServer)
|
||||
{
|
||||
NextSyncTick = 0;
|
||||
SetCurrentChannel(Settings.Channel);
|
||||
IsDirty = false;
|
||||
}
|
||||
|
||||
/* This only needs to be reset for clients, since
|
||||
* it only applies to clients. But if the server is resetting
|
||||
* that means the object is deinitializing, and won't have any
|
||||
* client observers anyway. Because of this it's safe to reset
|
||||
* with asServer true, or false.
|
||||
*
|
||||
* This change is made to resolve a bug where asServer:false
|
||||
* sometimes does not invoke when stopping clientHost while not
|
||||
* also stopping play mode. */
|
||||
_lastReadChangeId = UNSET_CHANGE_ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1a6f26e3f8016cc499b3fa99e7368fbc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/Synchronizing/SyncBase.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,680 @@
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Object.Synchronizing.Internal;
|
||||
using FishNet.Serializing;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace FishNet.Object.Synchronizing
|
||||
{
|
||||
[System.Serializable]
|
||||
public class SyncDictionary<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;
|
||||
internal readonly int CollectionCountAfterChange;
|
||||
|
||||
public ChangeData(SyncDictionaryOperation operation, TKey key, TValue value, int collectionCountAfterChange)
|
||||
{
|
||||
Operation = operation;
|
||||
Key = key;
|
||||
Value = value;
|
||||
CollectionCountAfterChange = collectionCountAfterChange;
|
||||
}
|
||||
}
|
||||
#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 Dictionary<TKey, TValue> Collection;
|
||||
/// <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 Dictionary<TKey, TValue> _initialValues = new();
|
||||
/// <summary>
|
||||
/// Changed data which will be sent next tick.
|
||||
/// </summary>
|
||||
private List<ChangeData> _changed = new();
|
||||
/// <summary>
|
||||
/// Server OnChange events waiting for start callbacks.
|
||||
/// </summary>
|
||||
private List<CachedOnChange> _serverOnChanges = new();
|
||||
/// <summary>
|
||||
/// Client OnChange events waiting for start callbacks.
|
||||
/// </summary>
|
||||
private List<CachedOnChange> _clientOnChanges = new();
|
||||
/// <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
|
||||
|
||||
#region Constructors.
|
||||
public SyncDictionary(SyncTypeSettings settings = new()) : this(CollectionCaches<TKey, TValue>.RetrieveDictionary(), settings) { }
|
||||
|
||||
public SyncDictionary(Dictionary<TKey, TValue> collection, SyncTypeSettings settings = new()) : base(settings)
|
||||
{
|
||||
Collection = collection == null ? CollectionCaches<TKey, TValue>.RetrieveDictionary() : collection;
|
||||
_initialValues = CollectionCaches<TKey, TValue>.RetrieveDictionary();
|
||||
_changed = CollectionCaches<ChangeData>.RetrieveList();
|
||||
_serverOnChanges = CollectionCaches<CachedOnChange>.RetrieveList();
|
||||
_clientOnChanges = CollectionCaches<CachedOnChange>.RetrieveList();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Deconstructor.
|
||||
~SyncDictionary()
|
||||
{
|
||||
CollectionCaches<TKey, TValue>.StoreAndDefault(ref Collection);
|
||||
CollectionCaches<TKey, TValue>.StoreAndDefault(ref _initialValues);
|
||||
CollectionCaches<ChangeData>.StoreAndDefault(ref _changed);
|
||||
CollectionCaches<CachedOnChange>.StoreAndDefault(ref _serverOnChanges);
|
||||
CollectionCaches<CachedOnChange>.StoreAndDefault(ref _clientOnChanges);
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <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)
|
||||
{
|
||||
return Collection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the SyncType has been registered, but not yet initialized over the network.
|
||||
/// </summary>
|
||||
protected override void Initialized()
|
||||
{
|
||||
base.Initialized();
|
||||
|
||||
// Initialize collections if needed. OdinInspector can cause them to become deinitialized.
|
||||
#if ODIN_INSPECTOR
|
||||
if (_initialValues == null)
|
||||
_initialValues = new();
|
||||
if (_changed == null)
|
||||
_changed = new();
|
||||
if (_serverOnChanges == null)
|
||||
_serverOnChanges = new();
|
||||
if (_clientOnChanges == null)
|
||||
_clientOnChanges = new();
|
||||
#endif
|
||||
|
||||
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]
|
||||
private void AddOperation(SyncDictionaryOperation operation, TKey key, TValue value, int collectionCountAfterChange)
|
||||
{
|
||||
if (!IsInitialized)
|
||||
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 = !IsNetworkInitialized || NetworkBehaviour.IsServerStarted;
|
||||
|
||||
if (asServerInvoke)
|
||||
{
|
||||
_valuesChanged = true;
|
||||
if (base.Dirty())
|
||||
{
|
||||
ChangeData change = new(operation, key, value, collectionCountAfterChange);
|
||||
_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>
|
||||
protected internal 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 an operation and data required by all operations.
|
||||
/// </summary>
|
||||
private void WriteOperationHeader(PooledWriter writer, SyncDictionaryOperation operation, int collectionCountAfterChange)
|
||||
{
|
||||
writer.WriteUInt8Unpacked((byte)operation);
|
||||
writer.WriteInt32(collectionCountAfterChange);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an operation and data required by all operations.
|
||||
/// </summary>
|
||||
private void ReadOperationHeader(PooledReader reader, out SyncDictionaryOperation operation, out int collectionCountAfterChange)
|
||||
{
|
||||
operation = (SyncDictionaryOperation)reader.ReadUInt8Unpacked();
|
||||
collectionCountAfterChange = reader.ReadInt32();
|
||||
}
|
||||
|
||||
/// <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]
|
||||
protected internal override void WriteDelta(PooledWriter writer, bool resetSyncTick = true)
|
||||
{
|
||||
// If sending all then clear changed and write full.
|
||||
if (_sendAll)
|
||||
{
|
||||
_sendAll = false;
|
||||
_changed.Clear();
|
||||
WriteFull(writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.WriteDelta(writer, resetSyncTick);
|
||||
|
||||
// False for not full write.
|
||||
writer.WriteBoolean(false);
|
||||
|
||||
writer.WriteInt32(_changed.Count);
|
||||
|
||||
for (int i = 0; i < _changed.Count; i++)
|
||||
{
|
||||
ChangeData change = _changed[i];
|
||||
|
||||
WriteOperationHeader(writer, change.Operation, change.CollectionCountAfterChange);
|
||||
|
||||
// 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]
|
||||
protected internal override void WriteFull(PooledWriter writer)
|
||||
{
|
||||
if (!_valuesChanged)
|
||||
return;
|
||||
|
||||
base.WriteHeader(writer, false);
|
||||
|
||||
// True for full write.
|
||||
writer.WriteBoolean(true);
|
||||
|
||||
writer.WriteInt32(Collection.Count);
|
||||
|
||||
int iteration = 0;
|
||||
foreach (KeyValuePair<TKey, TValue> item in Collection)
|
||||
{
|
||||
WriteOperationHeader(writer, SyncDictionaryOperation.Add, iteration + 1);
|
||||
writer.Write(item.Key);
|
||||
writer.Write(item.Value);
|
||||
|
||||
iteration++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and sets the current values for server or client.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
protected internal override void Read(PooledReader reader, bool asServer)
|
||||
{
|
||||
SetReadArguments(reader, asServer, out bool newChangeId, out bool asClientHost, out bool canModifyValues);
|
||||
|
||||
// True to warn if this object was deinitialized on the server.
|
||||
bool deinitialized = asClientHost && !OnStartServerCalled;
|
||||
if (deinitialized)
|
||||
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 = Collection;
|
||||
|
||||
bool fullWrite = reader.ReadBoolean();
|
||||
|
||||
// Clear collection since it's a full write.
|
||||
if (canModifyValues && fullWrite)
|
||||
collection.Clear();
|
||||
|
||||
int changes = reader.ReadInt32();
|
||||
for (int i = 0; i < changes; i++)
|
||||
{
|
||||
ReadOperationHeader(reader, out SyncDictionaryOperation operation, out int collectionCountAfterChange);
|
||||
|
||||
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)
|
||||
{
|
||||
/* If a set then the collection count should remain the same.
|
||||
* Otherwise, the count should increase by 1. */
|
||||
int sizeExpectedAfterChange = operation == SyncDictionaryOperation.Add ? collection.Count + 1 : collection.Count;
|
||||
|
||||
key = reader.Read<TKey>();
|
||||
value = reader.Read<TValue>();
|
||||
|
||||
if (canModifyValues)
|
||||
{
|
||||
// Integrity validation.
|
||||
if (sizeExpectedAfterChange == collectionCountAfterChange)
|
||||
collection[key] = value;
|
||||
}
|
||||
}
|
||||
// Clear.
|
||||
else if (operation == SyncDictionaryOperation.Clear)
|
||||
{
|
||||
if (canModifyValues)
|
||||
{
|
||||
// No integrity validation needed.
|
||||
collection.Clear();
|
||||
}
|
||||
}
|
||||
//Remove.
|
||||
else if (operation == SyncDictionaryOperation.Remove)
|
||||
{
|
||||
key = reader.Read<TKey>();
|
||||
|
||||
if (canModifyValues)
|
||||
{
|
||||
//Integrity validation.
|
||||
if (collection.Count - 1 == collectionCountAfterChange)
|
||||
collection.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (newChangeId)
|
||||
InvokeOnChange(operation, key, value, false);
|
||||
}
|
||||
|
||||
//If changes were made invoke complete after all have been read.
|
||||
if (newChangeId && 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 (NetworkBehaviour.OnStartServerCalled)
|
||||
OnChange?.Invoke(operation, key, value, asServer);
|
||||
else
|
||||
_serverOnChanges.Add(new(operation, key, value));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (NetworkBehaviour.OnStartClientCalled)
|
||||
OnChange?.Invoke(operation, key, value, asServer);
|
||||
else
|
||||
_clientOnChanges.Add(new(operation, key, value));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets to initialized values.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
protected internal override void ResetState(bool asServer)
|
||||
{
|
||||
base.ResetState(asServer);
|
||||
|
||||
if (CanReset(asServer))
|
||||
{
|
||||
_sendAll = false;
|
||||
_changed.Clear();
|
||||
Collection.Clear();
|
||||
_valuesChanged = false;
|
||||
|
||||
foreach (KeyValuePair<TKey, TValue> item in _initialValues)
|
||||
Collection[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 (!CanNetworkSetValues(true))
|
||||
return;
|
||||
|
||||
Collection.Add(key, value);
|
||||
/* We can perform add operation without checks, as Add would have failed above
|
||||
* if entry already existed. */
|
||||
if (asServer)
|
||||
AddOperation(SyncDictionaryOperation.Add, key, value, Collection.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all values.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
Clear(true);
|
||||
}
|
||||
|
||||
private void Clear(bool asServer)
|
||||
{
|
||||
if (!CanNetworkSetValues(true))
|
||||
return;
|
||||
|
||||
Collection.Clear();
|
||||
if (asServer)
|
||||
AddOperation(SyncDictionaryOperation.Clear, default, default, Collection.Count);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
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)
|
||||
{
|
||||
NetworkManager.LogError($"Index is out of range.");
|
||||
return;
|
||||
}
|
||||
|
||||
int remaining = array.Length - offset;
|
||||
if (remaining < Count)
|
||||
{
|
||||
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 (!CanNetworkSetValues(true))
|
||||
return false;
|
||||
|
||||
if (Collection.Remove(key))
|
||||
{
|
||||
AddOperation(SyncDictionaryOperation.Remove, key, default, Collection.Count);
|
||||
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>
|
||||
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 (!CanNetworkSetValues(true))
|
||||
return;
|
||||
|
||||
/* Change to Add if entry does not exist yet. */
|
||||
SyncDictionaryOperation operation = Collection.ContainsKey(key) ? SyncDictionaryOperation.Set : SyncDictionaryOperation.Add;
|
||||
|
||||
Collection[key] = value;
|
||||
|
||||
AddOperation(operation, key, value, Collection.Count);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dirties the entire collection forcing a full send.
|
||||
/// </summary>
|
||||
public void DirtyAll()
|
||||
{
|
||||
if (!IsInitialized)
|
||||
return;
|
||||
if (!CanNetworkSetValues(log: 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 (!IsInitialized)
|
||||
return;
|
||||
if (!CanNetworkSetValues(true))
|
||||
return;
|
||||
|
||||
if (Collection.TryGetValueIL2CPP(key, out TValue value))
|
||||
AddOperation(SyncDictionaryOperation.Set, key, value, Collection.Count);
|
||||
}
|
||||
|
||||
/// <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 (!IsInitialized)
|
||||
return false;
|
||||
if (!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, Collection.Count);
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 79196d6c6e862da499491d106f66ad72
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/Synchronizing/SyncDictionary.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,29 @@
|
||||
using FishNet.Documenting;
|
||||
|
||||
namespace FishNet.Object.Synchronizing
|
||||
{
|
||||
[APIExclude]
|
||||
public enum SyncDictionaryOperation : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// A key and value have been added to the collection.
|
||||
/// </summary>
|
||||
Add,
|
||||
/// <summary>
|
||||
/// Collection has been cleared.
|
||||
/// </summary>
|
||||
Clear,
|
||||
/// <summary>
|
||||
/// A key was removed from the collection.
|
||||
/// </summary>
|
||||
Remove,
|
||||
/// <summary>
|
||||
/// A value has been set for a key in the collection.
|
||||
/// </summary>
|
||||
Set,
|
||||
/// <summary>
|
||||
/// All operations for the tick have been processed.
|
||||
/// </summary>
|
||||
Complete
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5d6ed9db47a8224fa9ed4d2ff54586f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/Synchronizing/SyncDictionaryOperation.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,29 @@
|
||||
using FishNet.Documenting;
|
||||
|
||||
namespace FishNet.Object.Synchronizing
|
||||
{
|
||||
[APIExclude]
|
||||
public enum SyncHashSetOperation : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// An item is added to the collection.
|
||||
/// </summary>
|
||||
Add,
|
||||
/// <summary>
|
||||
/// An item is removed from the collection.
|
||||
/// </summary>
|
||||
Remove,
|
||||
/// <summary>
|
||||
/// Collection is cleared.
|
||||
/// </summary>
|
||||
Clear,
|
||||
/// <summary>
|
||||
/// An item has been updated within the collection. This is generally used when modifying data within a container.
|
||||
/// </summary>
|
||||
Set,
|
||||
/// <summary>
|
||||
/// All operations for the tick have been processed. This only occurs on clients as the server is unable to be aware of when the user is done modifying the list.
|
||||
/// </summary>
|
||||
Complete
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 914089f5707003340a68fd6cd718e4c4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/Synchronizing/SyncHashSetOperation.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,667 @@
|
||||
using FishNet.Documenting;
|
||||
using FishNet.Managing;
|
||||
using FishNet.Object.Synchronizing.Internal;
|
||||
using FishNet.Serializing;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FishNet.Object.Synchronizing
|
||||
{
|
||||
[System.Serializable]
|
||||
public class SyncHashSet<T> : SyncBase, ISet<T>
|
||||
{
|
||||
#region Types.
|
||||
/// <summary>
|
||||
/// Information needed to invoke a callback.
|
||||
/// </summary>
|
||||
private struct CachedOnChange
|
||||
{
|
||||
internal readonly SyncHashSetOperation Operation;
|
||||
internal readonly T Item;
|
||||
|
||||
public CachedOnChange(SyncHashSetOperation operation, T item)
|
||||
{
|
||||
Operation = operation;
|
||||
Item = item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Information about how the collection has changed.
|
||||
/// </summary>
|
||||
private struct ChangeData
|
||||
{
|
||||
internal readonly SyncHashSetOperation Operation;
|
||||
internal readonly T Item;
|
||||
internal readonly int CollectionCountAfterChange;
|
||||
|
||||
public ChangeData(SyncHashSetOperation operation, T item, int collectionCountAfterChange)
|
||||
{
|
||||
Operation = operation;
|
||||
Item = item;
|
||||
CollectionCountAfterChange = collectionCountAfterChange;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public.
|
||||
/// <summary>
|
||||
/// Implementation from List<T>. Not used.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate signature for when SyncList changes.
|
||||
/// </summary>
|
||||
/// <param name = "op">Type of change.</param>
|
||||
/// <param name = "item">Item which was modified.</param>
|
||||
/// <param name = "asServer">True if callback is occuring on the server.</param>
|
||||
[APIExclude]
|
||||
public delegate void SyncHashSetChanged(SyncHashSetOperation op, T item, bool asServer);
|
||||
|
||||
/// <summary>
|
||||
/// Called when the SyncList changes.
|
||||
/// </summary>
|
||||
public event SyncHashSetChanged OnChange;
|
||||
/// <summary>
|
||||
/// Collection of objects.
|
||||
/// </summary>
|
||||
public HashSet<T> Collection;
|
||||
/// <summary>
|
||||
/// Number of objects in the collection.
|
||||
/// </summary>
|
||||
public int Count => Collection.Count;
|
||||
#endregion
|
||||
|
||||
#region Private.
|
||||
/// <summary>
|
||||
/// ListCache for comparing.
|
||||
/// </summary>
|
||||
private static List<T> _cache = new();
|
||||
/// <summary>
|
||||
/// Values upon initialization.
|
||||
/// </summary>
|
||||
private HashSet<T> _initialValues;
|
||||
/// <summary>
|
||||
/// Changed data which will be sent next tick.
|
||||
/// </summary>
|
||||
private List<ChangeData> _changed;
|
||||
/// <summary>
|
||||
/// Server OnChange events waiting for start callbacks.
|
||||
/// </summary>
|
||||
private List<CachedOnChange> _serverOnChanges;
|
||||
/// <summary>
|
||||
/// Client OnChange events waiting for start callbacks.
|
||||
/// </summary>
|
||||
private List<CachedOnChange> _clientOnChanges;
|
||||
/// <summary>
|
||||
/// Comparer to see if entries change when calling public methods.
|
||||
/// // Not used right now.
|
||||
/// </summary>
|
||||
private readonly IEqualityComparer<T> _comparer;
|
||||
/// <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
|
||||
|
||||
#region Constructors.
|
||||
public SyncHashSet(SyncTypeSettings settings = new()) : this(CollectionCaches<T>.RetrieveHashSet(), EqualityComparer<T>.Default, settings) { }
|
||||
public SyncHashSet(IEqualityComparer<T> comparer, SyncTypeSettings settings = new()) : this(CollectionCaches<T>.RetrieveHashSet(), comparer == null ? EqualityComparer<T>.Default : comparer, settings) { }
|
||||
|
||||
public SyncHashSet(HashSet<T> collection, IEqualityComparer<T> comparer = null, SyncTypeSettings settings = new()) : base(settings)
|
||||
{
|
||||
_comparer = comparer == null ? EqualityComparer<T>.Default : comparer;
|
||||
Collection = collection == null ? CollectionCaches<T>.RetrieveHashSet() : collection;
|
||||
|
||||
_initialValues = CollectionCaches<T>.RetrieveHashSet();
|
||||
_changed = CollectionCaches<ChangeData>.RetrieveList();
|
||||
_serverOnChanges = CollectionCaches<CachedOnChange>.RetrieveList();
|
||||
_clientOnChanges = CollectionCaches<CachedOnChange>.RetrieveList();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Deconstructor.
|
||||
~SyncHashSet()
|
||||
{
|
||||
CollectionCaches<T>.StoreAndDefault(ref Collection);
|
||||
CollectionCaches<T>.StoreAndDefault(ref _initialValues);
|
||||
CollectionCaches<ChangeData>.StoreAndDefault(ref _changed);
|
||||
CollectionCaches<CachedOnChange>.StoreAndDefault(ref _serverOnChanges);
|
||||
CollectionCaches<CachedOnChange>.StoreAndDefault(ref _clientOnChanges);
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Called when the SyncType has been registered, but not yet initialized over the network.
|
||||
/// </summary>
|
||||
protected override void Initialized()
|
||||
{
|
||||
base.Initialized();
|
||||
|
||||
// Initialize collections if needed. OdinInspector can cause them to become deinitialized.
|
||||
#if ODIN_INSPECTOR
|
||||
if (_initialValues == null)
|
||||
_initialValues = new();
|
||||
if (_changed == null)
|
||||
_changed = new();
|
||||
if (_serverOnChanges == null)
|
||||
_serverOnChanges = new();
|
||||
if (_clientOnChanges == null)
|
||||
_clientOnChanges = new();
|
||||
#endif
|
||||
foreach (T item in Collection)
|
||||
_initialValues.Add(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection being used within this SyncList.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public HashSet<T> GetCollection(bool asServer)
|
||||
{
|
||||
return Collection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an operation and invokes locally.
|
||||
/// </summary>
|
||||
private void AddOperation(SyncHashSetOperation operation, T item, int collectionCountAfterChange)
|
||||
{
|
||||
if (!IsInitialized)
|
||||
return;
|
||||
|
||||
bool asServerInvoke = !IsNetworkInitialized || NetworkBehaviour.IsServerStarted;
|
||||
|
||||
if (asServerInvoke)
|
||||
{
|
||||
_valuesChanged = true;
|
||||
if (base.Dirty())
|
||||
{
|
||||
ChangeData change = new(operation, item, collectionCountAfterChange);
|
||||
_changed.Add(change);
|
||||
}
|
||||
}
|
||||
|
||||
InvokeOnChange(operation, item, asServerInvoke);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called after OnStartXXXX has occurred.
|
||||
/// </summary>
|
||||
/// <param name = "asServer">True if OnStartServer was called, false if OnStartClient.</param>
|
||||
protected internal override void OnStartCallback(bool asServer)
|
||||
{
|
||||
base.OnStartCallback(asServer);
|
||||
List<CachedOnChange> collection = asServer ? _serverOnChanges : _clientOnChanges;
|
||||
if (OnChange != null)
|
||||
{
|
||||
foreach (CachedOnChange item in collection)
|
||||
OnChange.Invoke(item.Operation, item.Item, asServer);
|
||||
}
|
||||
|
||||
collection.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes an operation and data required by all operations.
|
||||
/// </summary>
|
||||
private void WriteOperationHeader(PooledWriter writer, SyncHashSetOperation operation, int collectionCountAfterChange)
|
||||
{
|
||||
writer.WriteUInt8Unpacked((byte)operation);
|
||||
writer.WriteInt32(collectionCountAfterChange);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an operation and data required by all operations.
|
||||
/// </summary>
|
||||
private void ReadOperationHeader(PooledReader reader, out SyncHashSetOperation operation, out int collectionCountAfterChange)
|
||||
{
|
||||
operation = (SyncHashSetOperation)reader.ReadUInt8Unpacked();
|
||||
collectionCountAfterChange = reader.ReadInt32();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes all changed values.
|
||||
/// </summary>
|
||||
/// <param name = "writer"></param>
|
||||
/// <param name = "resetSyncTick">True to set the next time data may sync.</param>
|
||||
protected internal override void WriteDelta(PooledWriter writer, bool resetSyncTick = true)
|
||||
{
|
||||
// If sending all then clear changed and write full.
|
||||
if (_sendAll)
|
||||
{
|
||||
_sendAll = false;
|
||||
_changed.Clear();
|
||||
WriteFull(writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.WriteDelta(writer, resetSyncTick);
|
||||
|
||||
// False for not full write.
|
||||
writer.WriteBoolean(false);
|
||||
|
||||
writer.WriteInt32(_changed.Count);
|
||||
|
||||
for (int i = 0; i < _changed.Count; i++)
|
||||
{
|
||||
ChangeData change = _changed[i];
|
||||
|
||||
WriteOperationHeader(writer, change.Operation, change.CollectionCountAfterChange);
|
||||
|
||||
// Clear does not need to write anymore data so it is not included in checks.
|
||||
if (change.Operation == SyncHashSetOperation.Add || change.Operation == SyncHashSetOperation.Remove || change.Operation == SyncHashSetOperation.Set)
|
||||
writer.Write(change.Item);
|
||||
}
|
||||
|
||||
_changed.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes all values if not initial values.
|
||||
/// </summary>
|
||||
/// <param name = "writer"></param>
|
||||
protected internal override void WriteFull(PooledWriter writer)
|
||||
{
|
||||
if (!_valuesChanged)
|
||||
return;
|
||||
|
||||
base.WriteHeader(writer, false);
|
||||
// True for full write.
|
||||
writer.WriteBoolean(true);
|
||||
|
||||
int count = Collection.Count;
|
||||
writer.WriteInt32(count);
|
||||
|
||||
int iteration = 0;
|
||||
foreach (T item in Collection)
|
||||
{
|
||||
WriteOperationHeader(writer, SyncHashSetOperation.Add, collectionCountAfterChange: iteration + 1);
|
||||
writer.Write(item);
|
||||
|
||||
iteration++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads and sets the current values for server or client.
|
||||
/// </summary>
|
||||
[APIExclude]
|
||||
protected internal override void Read(PooledReader reader, bool asServer)
|
||||
{
|
||||
SetReadArguments(reader, asServer, out bool newChangeId, out bool asClientHost, out bool canModifyValues);
|
||||
|
||||
// True to warn if this object was deinitialized on the server.
|
||||
bool deinitialized = asClientHost && !OnStartServerCalled;
|
||||
if (deinitialized)
|
||||
NetworkManager.LogWarning($"SyncType {GetType().Name} received a Read but was deinitialized on the server. Client callback values may be incorrect. This is a ClientHost limitation.");
|
||||
|
||||
ISet<T> collection = Collection;
|
||||
|
||||
bool fullWrite = reader.ReadBoolean();
|
||||
|
||||
// Clear collection since it's a full write.
|
||||
if (canModifyValues && fullWrite)
|
||||
collection.Clear();
|
||||
|
||||
int changes = reader.ReadInt32();
|
||||
for (int i = 0; i < changes; i++)
|
||||
{
|
||||
ReadOperationHeader(reader, out SyncHashSetOperation operation, out int collectionCountAfterChange);
|
||||
|
||||
T next = default;
|
||||
|
||||
// Add.
|
||||
if (operation == SyncHashSetOperation.Add)
|
||||
{
|
||||
next = reader.Read<T>();
|
||||
|
||||
if (canModifyValues)
|
||||
{
|
||||
// Integrity validation.
|
||||
if (collection.Count + 1 == collectionCountAfterChange)
|
||||
collection.Add(next);
|
||||
}
|
||||
}
|
||||
// Clear.
|
||||
else if (operation == SyncHashSetOperation.Clear)
|
||||
{
|
||||
if (canModifyValues)
|
||||
{
|
||||
// No integrity validation needed.
|
||||
collection.Clear();
|
||||
}
|
||||
}
|
||||
// Remove.
|
||||
else if (operation == SyncHashSetOperation.Remove)
|
||||
{
|
||||
next = reader.Read<T>();
|
||||
|
||||
if (canModifyValues)
|
||||
{
|
||||
// Integrity validation.
|
||||
if (collection.Count - 1 == collectionCountAfterChange)
|
||||
collection.Remove(next);
|
||||
}
|
||||
}
|
||||
// Set.
|
||||
else if (operation == SyncHashSetOperation.Set)
|
||||
{
|
||||
next = reader.Read<T>();
|
||||
|
||||
if (canModifyValues)
|
||||
{
|
||||
// Integrity validation.
|
||||
if (collection.Count == collectionCountAfterChange)
|
||||
{
|
||||
collection.Remove(next);
|
||||
collection.Add(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (newChangeId)
|
||||
InvokeOnChange(operation, next, false);
|
||||
}
|
||||
|
||||
// If changes were made invoke complete after all have been read.
|
||||
if (newChangeId && changes > 0)
|
||||
InvokeOnChange(SyncHashSetOperation.Complete, default, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invokes OnChanged callback.
|
||||
/// </summary>
|
||||
private void InvokeOnChange(SyncHashSetOperation operation, T item, bool asServer)
|
||||
{
|
||||
if (asServer)
|
||||
{
|
||||
if (NetworkBehaviour.OnStartServerCalled)
|
||||
OnChange?.Invoke(operation, item, asServer);
|
||||
else
|
||||
_serverOnChanges.Add(new(operation, item));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (NetworkBehaviour.OnStartClientCalled)
|
||||
OnChange?.Invoke(operation, item, asServer);
|
||||
else
|
||||
_clientOnChanges.Add(new(operation, item));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets to initialized values.
|
||||
/// </summary>
|
||||
protected internal override void ResetState(bool asServer)
|
||||
{
|
||||
base.ResetState(asServer);
|
||||
|
||||
if (CanReset(asServer))
|
||||
{
|
||||
_sendAll = false;
|
||||
_changed.Clear();
|
||||
Collection.Clear();
|
||||
|
||||
foreach (T item in _initialValues)
|
||||
Collection.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds value.
|
||||
/// </summary>
|
||||
/// <param name = "item"></param>
|
||||
public bool Add(T item)
|
||||
{
|
||||
return Add(item, true);
|
||||
}
|
||||
|
||||
private bool Add(T item, bool asServer)
|
||||
{
|
||||
if (!CanNetworkSetValues(true))
|
||||
return false;
|
||||
|
||||
bool result = Collection.Add(item);
|
||||
// Only process if add was successful.
|
||||
if (result && asServer)
|
||||
AddOperation(SyncHashSetOperation.Add, item, Collection.Count);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a range of values.
|
||||
/// </summary>
|
||||
/// <param name = "range"></param>
|
||||
public void AddRange(IEnumerable<T> range)
|
||||
{
|
||||
foreach (T entry in range)
|
||||
Add(entry, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all values.
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
Clear(true);
|
||||
}
|
||||
|
||||
private void Clear(bool asServer)
|
||||
{
|
||||
if (!CanNetworkSetValues(true))
|
||||
return;
|
||||
|
||||
Collection.Clear();
|
||||
if (asServer)
|
||||
AddOperation(SyncHashSetOperation.Clear, default, Collection.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if value exist.
|
||||
/// </summary>
|
||||
/// <param name = "item"></param>
|
||||
/// <returns></returns>
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return Collection.Contains(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a value.
|
||||
/// </summary>
|
||||
/// <param name = "item"></param>
|
||||
/// <returns></returns>
|
||||
public bool Remove(T item)
|
||||
{
|
||||
return Remove(item, true);
|
||||
}
|
||||
|
||||
private bool Remove(T item, bool asServer)
|
||||
{
|
||||
if (!CanNetworkSetValues(true))
|
||||
return false;
|
||||
|
||||
bool result = Collection.Remove(item);
|
||||
// Only process if remove was successful.
|
||||
if (result && asServer)
|
||||
AddOperation(SyncHashSetOperation.Remove, item, Collection.Count);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dirties the entire collection forcing a full send.
|
||||
/// </summary>
|
||||
public void DirtyAll()
|
||||
{
|
||||
if (!IsInitialized)
|
||||
return;
|
||||
if (!CanNetworkSetValues(log: true))
|
||||
return;
|
||||
|
||||
if (base.Dirty())
|
||||
_sendAll = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up obj in Collection and if found marks it's index as dirty.
|
||||
/// This operation can be very expensive, will cause allocations, and may fail if your value cannot be compared.
|
||||
/// </summary>
|
||||
/// <param name = "obj">Object to lookup.</param>
|
||||
public void Dirty(T obj)
|
||||
{
|
||||
if (!IsInitialized)
|
||||
return;
|
||||
if (!CanNetworkSetValues(true))
|
||||
return;
|
||||
|
||||
foreach (T item in Collection)
|
||||
{
|
||||
if (item.Equals(obj))
|
||||
{
|
||||
AddOperation(SyncHashSetOperation.Set, obj, Collection.Count);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Not found.
|
||||
NetworkManager.LogError($"Could not find object within SyncHashSet, dirty will not be set.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns Enumerator for collection.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IEnumerator GetEnumerator() => Collection.GetEnumerator();
|
||||
|
||||
[APIExclude]
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator() => Collection.GetEnumerator();
|
||||
|
||||
[APIExclude]
|
||||
IEnumerator IEnumerable.GetEnumerator() => Collection.GetEnumerator();
|
||||
|
||||
public void ExceptWith(IEnumerable<T> other)
|
||||
{
|
||||
// Again, removing from self is a clear.
|
||||
if (other == Collection)
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (T item in other)
|
||||
Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void IntersectWith(IEnumerable<T> other)
|
||||
{
|
||||
ISet<T> set;
|
||||
if (other is ISet<T> setA)
|
||||
set = setA;
|
||||
else
|
||||
set = new HashSet<T>(other);
|
||||
|
||||
IntersectWith(set);
|
||||
}
|
||||
|
||||
private void IntersectWith(ISet<T> other)
|
||||
{
|
||||
_cache.AddRange(Collection);
|
||||
|
||||
int count = _cache.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
T entry = _cache[i];
|
||||
if (!other.Contains(entry))
|
||||
Remove(entry);
|
||||
}
|
||||
|
||||
_cache.Clear();
|
||||
}
|
||||
|
||||
public bool IsProperSubsetOf(IEnumerable<T> other)
|
||||
{
|
||||
return Collection.IsProperSubsetOf(other);
|
||||
}
|
||||
|
||||
public bool IsProperSupersetOf(IEnumerable<T> other)
|
||||
{
|
||||
return Collection.IsProperSupersetOf(other);
|
||||
}
|
||||
|
||||
public bool IsSubsetOf(IEnumerable<T> other)
|
||||
{
|
||||
return Collection.IsSubsetOf(other);
|
||||
}
|
||||
|
||||
public bool IsSupersetOf(IEnumerable<T> other)
|
||||
{
|
||||
return Collection.IsSupersetOf(other);
|
||||
}
|
||||
|
||||
public bool Overlaps(IEnumerable<T> other)
|
||||
{
|
||||
bool result = Collection.Overlaps(other);
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool SetEquals(IEnumerable<T> other)
|
||||
{
|
||||
return Collection.SetEquals(other);
|
||||
}
|
||||
|
||||
public void SymmetricExceptWith(IEnumerable<T> other)
|
||||
{
|
||||
// If calling except on self then that is the same as a clear.
|
||||
if (other == Collection)
|
||||
{
|
||||
Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (T item in other)
|
||||
Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void UnionWith(IEnumerable<T> other)
|
||||
{
|
||||
if (other == Collection)
|
||||
return;
|
||||
|
||||
foreach (T item in other)
|
||||
Add(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an item.
|
||||
/// </summary>
|
||||
/// <param name = "item"></param>
|
||||
void ICollection<T>.Add(T item)
|
||||
{
|
||||
Add(item, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies values to an array.
|
||||
/// </summary>
|
||||
/// <param name = "array"></param>
|
||||
/// <param name = "index"></param>
|
||||
public void CopyTo(T[] array, int index)
|
||||
{
|
||||
Collection.CopyTo(array, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b8627bf3171f6274790bc7e60e471260
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/Runtime/Object/Synchronizing/SyncHashset.cs
|
||||
uploadId: 866910
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user