[Add] FishNet

This commit is contained in:
2026-03-30 20:11:57 +07:00
parent ee793a3361
commit c22c08753a
1797 changed files with 197950 additions and 1 deletions
@@ -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