The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). ## [1.0.0-pre.6] - 2022-03-02 ### Added - NetworkAnimator now properly synchrhonizes all animation layers as well as runtime-adjusted weighting between them (#1765) - Added first set of tests for NetworkAnimator - parameter syncing, trigger set / reset, override network animator (#1735) ### Changed ### Fixed - Fixed an issue where sometimes the first client to connect to the server could see messages from the server as coming from itself. (#1683) - Fixed an issue where clients seemed to be able to send messages to ClientId 1, but these messages would actually still go to the server (id 0) instead of that client. (#1683) - Improved clarity of error messaging when a client attempts to send a message to a destination other than the server, which isn't allowed. (#1683) - Disallowed async keyword in RPCs (#1681) - Fixed an issue where Alpha release versions of Unity (version 2022.2.0a5 and later) will not compile due to the UNet Transport no longer existing (#1678) - Fixed messages larger than 64k being written with incorrectly truncated message size in header (#1686) (credit: @kaen) - Fixed overloading RPC methods causing collisions and failing on IL2CPP targets. (#1694) - Fixed spawn flow to propagate `IsSceneObject` down to children NetworkObjects, decouple implicit relationship between object spawning & `IsSceneObject` flag (#1685) - Fixed error when serializing ConnectionApprovalMessage with scene management disabled when one or more objects is hidden via the CheckObjectVisibility delegate (#1720) - Fixed CheckObjectVisibility delegate not being properly invoked for connecting clients when Scene Management is enabled. (#1680) - Fixed NetworkList to properly call INetworkSerializable's NetworkSerialize() method (#1682) - Fixed NetworkVariables containing more than 1300 bytes of data (such as large NetworkLists) no longer cause an OverflowException (the limit on data size is now whatever limit the chosen transport imposes on fragmented NetworkDelivery mechanisms) (#1725) - Fixed ServerRpcParams and ClientRpcParams must be the last parameter of an RPC in order to function properly. Added a compile-time check to ensure this is the case and trigger an error if they're placed elsewhere (#1721) - Fixed FastBufferReader being created with a length of 1 if provided an input of length 0 (#1724) - Fixed The NetworkConfig's checksum hash includes the NetworkTick so that clients with a different tickrate than the server are identified and not allowed to connect (#1728) - Fixed OwnedObjects not being properly modified when using ChangeOwnership (#1731) - Improved performance in NetworkAnimator (#1735) - Removed the "always sync" network animator (aka "autosend") parameters (#1746)
699 lines
28 KiB
C#
699 lines
28 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEngine;
|
|
using System.Reflection;
|
|
using Unity.Collections;
|
|
|
|
namespace Unity.Netcode
|
|
{
|
|
/// <summary>
|
|
/// The base class to override to write network code. Inherits MonoBehaviour
|
|
/// </summary>
|
|
public abstract class NetworkBehaviour : MonoBehaviour
|
|
{
|
|
#pragma warning disable IDE1006 // disable naming rule violation check
|
|
// RuntimeAccessModifiersILPP will make this `protected`
|
|
internal enum __RpcExecStage
|
|
{
|
|
None = 0,
|
|
Server = 1,
|
|
Client = 2
|
|
}
|
|
|
|
// NetworkBehaviourILPP will override this in derived classes to return the name of the concrete type
|
|
internal virtual string __getTypeName() => nameof(NetworkBehaviour);
|
|
|
|
[NonSerialized]
|
|
// RuntimeAccessModifiersILPP will make this `protected`
|
|
internal __RpcExecStage __rpc_exec_stage = __RpcExecStage.None;
|
|
#pragma warning restore IDE1006 // restore naming rule violation check
|
|
|
|
private const int k_RpcMessageDefaultSize = 1024; // 1k
|
|
private const int k_RpcMessageMaximumSize = 1024 * 64; // 64k
|
|
|
|
#pragma warning disable IDE1006 // disable naming rule violation check
|
|
// RuntimeAccessModifiersILPP will make this `protected`
|
|
internal FastBufferWriter __beginSendServerRpc(uint rpcMethodId, ServerRpcParams serverRpcParams, RpcDelivery rpcDelivery)
|
|
#pragma warning restore IDE1006 // restore naming rule violation check
|
|
{
|
|
return new FastBufferWriter(k_RpcMessageDefaultSize, Allocator.Temp, k_RpcMessageMaximumSize);
|
|
}
|
|
|
|
#pragma warning disable IDE1006 // disable naming rule violation check
|
|
// RuntimeAccessModifiersILPP will make this `protected`
|
|
internal void __endSendServerRpc(ref FastBufferWriter bufferWriter, uint rpcMethodId, ServerRpcParams serverRpcParams, RpcDelivery rpcDelivery)
|
|
#pragma warning restore IDE1006 // restore naming rule violation check
|
|
{
|
|
var serverRpcMessage = new ServerRpcMessage
|
|
{
|
|
Metadata = new RpcMetadata
|
|
{
|
|
NetworkObjectId = NetworkObjectId,
|
|
NetworkBehaviourId = NetworkBehaviourId,
|
|
NetworkRpcMethodId = rpcMethodId,
|
|
},
|
|
WriteBuffer = bufferWriter
|
|
};
|
|
|
|
NetworkDelivery networkDelivery;
|
|
switch (rpcDelivery)
|
|
{
|
|
default:
|
|
case RpcDelivery.Reliable:
|
|
networkDelivery = NetworkDelivery.ReliableFragmentedSequenced;
|
|
break;
|
|
case RpcDelivery.Unreliable:
|
|
if (bufferWriter.Length > MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE)
|
|
{
|
|
throw new OverflowException("RPC parameters are too large for unreliable delivery.");
|
|
}
|
|
networkDelivery = NetworkDelivery.Unreliable;
|
|
break;
|
|
}
|
|
|
|
var rpcWriteSize = 0;
|
|
|
|
// If we are a server/host then we just no op and send to ourself
|
|
if (IsHost || IsServer)
|
|
{
|
|
using var tempBuffer = new FastBufferReader(bufferWriter, Allocator.Temp);
|
|
var context = new NetworkContext
|
|
{
|
|
SenderId = NetworkManager.ServerClientId,
|
|
Timestamp = Time.realtimeSinceStartup,
|
|
SystemOwner = NetworkManager,
|
|
// header information isn't valid since it's not a real message.
|
|
// RpcMessage doesn't access this stuff so it's just left empty.
|
|
Header = new MessageHeader(),
|
|
SerializedHeaderSize = 0,
|
|
MessageSize = 0
|
|
};
|
|
serverRpcMessage.ReadBuffer = tempBuffer;
|
|
serverRpcMessage.Handle(ref context);
|
|
rpcWriteSize = tempBuffer.Length;
|
|
}
|
|
else
|
|
{
|
|
rpcWriteSize = NetworkManager.SendMessage(ref serverRpcMessage, networkDelivery, NetworkManager.ServerClientId);
|
|
}
|
|
|
|
bufferWriter.Dispose();
|
|
|
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
|
if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName))
|
|
{
|
|
NetworkManager.NetworkMetrics.TrackRpcSent(
|
|
NetworkManager.ServerClientId,
|
|
NetworkObject,
|
|
rpcMethodName,
|
|
__getTypeName(),
|
|
rpcWriteSize);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#pragma warning disable IDE1006 // disable naming rule violation check
|
|
// RuntimeAccessModifiersILPP will make this `protected`
|
|
internal FastBufferWriter __beginSendClientRpc(uint rpcMethodId, ClientRpcParams clientRpcParams, RpcDelivery rpcDelivery)
|
|
#pragma warning restore IDE1006 // restore naming rule violation check
|
|
{
|
|
return new FastBufferWriter(k_RpcMessageDefaultSize, Allocator.Temp, k_RpcMessageMaximumSize);
|
|
}
|
|
|
|
#pragma warning disable IDE1006 // disable naming rule violation check
|
|
// RuntimeAccessModifiersILPP will make this `protected`
|
|
internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMethodId, ClientRpcParams clientRpcParams, RpcDelivery rpcDelivery)
|
|
#pragma warning restore IDE1006 // restore naming rule violation check
|
|
{
|
|
var clientRpcMessage = new ClientRpcMessage
|
|
{
|
|
Metadata = new RpcMetadata
|
|
{
|
|
NetworkObjectId = NetworkObjectId,
|
|
NetworkBehaviourId = NetworkBehaviourId,
|
|
NetworkRpcMethodId = rpcMethodId,
|
|
},
|
|
WriteBuffer = bufferWriter
|
|
};
|
|
|
|
NetworkDelivery networkDelivery;
|
|
switch (rpcDelivery)
|
|
{
|
|
default:
|
|
case RpcDelivery.Reliable:
|
|
networkDelivery = NetworkDelivery.ReliableFragmentedSequenced;
|
|
break;
|
|
case RpcDelivery.Unreliable:
|
|
if (bufferWriter.Length > MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE)
|
|
{
|
|
throw new OverflowException("RPC parameters are too large for unreliable delivery.");
|
|
}
|
|
networkDelivery = NetworkDelivery.Unreliable;
|
|
break;
|
|
}
|
|
|
|
var rpcWriteSize = 0;
|
|
|
|
// We check to see if we need to shortcut for the case where we are the host/server and we can send a clientRPC
|
|
// to ourself. Sadly we have to figure that out from the list of clientIds :(
|
|
bool shouldSendToHost = false;
|
|
|
|
if (clientRpcParams.Send.TargetClientIds != null)
|
|
{
|
|
foreach (var clientId in clientRpcParams.Send.TargetClientIds)
|
|
{
|
|
if (clientId == NetworkManager.ServerClientId)
|
|
{
|
|
shouldSendToHost = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
rpcWriteSize = NetworkManager.SendMessage(ref clientRpcMessage, networkDelivery, in clientRpcParams.Send.TargetClientIds);
|
|
}
|
|
else if (clientRpcParams.Send.TargetClientIdsNativeArray != null)
|
|
{
|
|
foreach (var clientId in clientRpcParams.Send.TargetClientIdsNativeArray)
|
|
{
|
|
if (clientId == NetworkManager.ServerClientId)
|
|
{
|
|
shouldSendToHost = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
rpcWriteSize = NetworkManager.SendMessage(ref clientRpcMessage, networkDelivery, clientRpcParams.Send.TargetClientIdsNativeArray.Value);
|
|
}
|
|
else
|
|
{
|
|
shouldSendToHost = IsHost;
|
|
rpcWriteSize = NetworkManager.SendMessage(ref clientRpcMessage, networkDelivery, NetworkManager.ConnectedClientsIds);
|
|
}
|
|
|
|
// If we are a server/host then we just no op and send to ourself
|
|
if (shouldSendToHost)
|
|
{
|
|
using var tempBuffer = new FastBufferReader(bufferWriter, Allocator.Temp);
|
|
var context = new NetworkContext
|
|
{
|
|
SenderId = NetworkManager.ServerClientId,
|
|
Timestamp = Time.realtimeSinceStartup,
|
|
SystemOwner = NetworkManager,
|
|
// header information isn't valid since it's not a real message.
|
|
// RpcMessage doesn't access this stuff so it's just left empty.
|
|
Header = new MessageHeader(),
|
|
SerializedHeaderSize = 0,
|
|
MessageSize = 0
|
|
};
|
|
clientRpcMessage.ReadBuffer = tempBuffer;
|
|
clientRpcMessage.Handle(ref context);
|
|
}
|
|
|
|
bufferWriter.Dispose();
|
|
|
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
|
if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName))
|
|
{
|
|
foreach (var client in NetworkManager.ConnectedClients)
|
|
{
|
|
NetworkManager.NetworkMetrics.TrackRpcSent(
|
|
client.Key,
|
|
NetworkObject,
|
|
rpcMethodName,
|
|
__getTypeName(),
|
|
rpcWriteSize);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the NetworkManager that owns this NetworkBehaviour instance
|
|
/// See note around `NetworkObject` for how there is a chicken / egg problem when we are not initialized
|
|
/// </summary>
|
|
public NetworkManager NetworkManager => NetworkObject.NetworkManager;
|
|
|
|
/// <summary>
|
|
/// Gets if the object is the the personal clients player object
|
|
/// </summary>
|
|
public bool IsLocalPlayer => NetworkObject.IsLocalPlayer;
|
|
|
|
/// <summary>
|
|
/// Gets if the object is owned by the local player or if the object is the local player object
|
|
/// </summary>
|
|
public bool IsOwner => NetworkObject.IsOwner;
|
|
|
|
/// <summary>
|
|
/// Gets if we are executing as server
|
|
/// </summary>
|
|
protected bool IsServer => IsRunning && NetworkManager.IsServer;
|
|
|
|
/// <summary>
|
|
/// Gets if we are executing as client
|
|
/// </summary>
|
|
protected bool IsClient => IsRunning && NetworkManager.IsClient;
|
|
|
|
/// <summary>
|
|
/// Gets if we are executing as Host, I.E Server and Client
|
|
/// </summary>
|
|
protected bool IsHost => IsRunning && NetworkManager.IsHost;
|
|
|
|
private bool IsRunning => NetworkManager && NetworkManager.IsListening;
|
|
|
|
/// <summary>
|
|
/// Gets Whether or not the object has a owner
|
|
/// </summary>
|
|
public bool IsOwnedByServer => NetworkObject.IsOwnedByServer;
|
|
|
|
/// <summary>
|
|
/// Used to determine if it is safe to access NetworkObject and NetworkManager from within a NetworkBehaviour component
|
|
/// Primarily useful when checking NetworkObject/NetworkManager properties within FixedUpate
|
|
/// </summary>
|
|
public bool IsSpawned => HasNetworkObject ? NetworkObject.IsSpawned : false;
|
|
|
|
internal bool IsBehaviourEditable()
|
|
{
|
|
// Only server can MODIFY. So allow modification if network is either not running or we are server
|
|
return !m_NetworkObject ||
|
|
m_NetworkObject.NetworkManager == null ||
|
|
m_NetworkObject.NetworkManager.IsListening == false ||
|
|
m_NetworkObject.NetworkManager.IsServer;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the NetworkObject that owns this NetworkBehaviour instance
|
|
/// TODO: this needs an overhaul. It's expensive, it's ja little naive in how it looks for networkObject in
|
|
/// its parent and worst, it creates a puzzle if you are a NetworkBehaviour wanting to see if you're live or not
|
|
/// (e.g. editor code). All you want to do is find out if NetworkManager is null, but to do that you
|
|
/// need NetworkObject, but if you try and grab NetworkObject and NetworkManager isn't up you'll get
|
|
/// the warning below. This is why IsBehaviourEditable had to be created. Matt was going to re-do
|
|
/// how NetworkObject works but it was close to the release and too risky to change
|
|
///
|
|
/// </summary>
|
|
public NetworkObject NetworkObject
|
|
{
|
|
get
|
|
{
|
|
if (m_NetworkObject == null)
|
|
{
|
|
m_NetworkObject = GetComponentInParent<NetworkObject>();
|
|
}
|
|
|
|
// ShutdownInProgress check:
|
|
// This prevents an edge case scenario where the NetworkManager is shutting down but user code
|
|
// in Update and/or in FixedUpdate could still be checking NetworkBehaviour.NetworkObject directly (i.e. does it exist?)
|
|
// or NetworkBehaviour.IsSpawned (i.e. to early exit if not spawned) which, in turn, could generate several Warning messages
|
|
// per spawned NetworkObject. Checking for ShutdownInProgress prevents these unnecessary LogWarning messages.
|
|
if (m_NetworkObject == null && (NetworkManager.Singleton == null || !NetworkManager.Singleton.ShutdownInProgress))
|
|
{
|
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
|
{
|
|
NetworkLog.LogWarning($"Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?");
|
|
}
|
|
}
|
|
|
|
return m_NetworkObject;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets whether or not this NetworkBehaviour instance has a NetworkObject owner.
|
|
/// </summary>
|
|
public bool HasNetworkObject => NetworkObject != null;
|
|
|
|
private NetworkObject m_NetworkObject = null;
|
|
|
|
/// <summary>
|
|
/// Gets the NetworkId of the NetworkObject that owns this NetworkBehaviour
|
|
/// </summary>
|
|
public ulong NetworkObjectId => NetworkObject.NetworkObjectId;
|
|
|
|
/// <summary>
|
|
/// Gets NetworkId for this NetworkBehaviour from the owner NetworkObject
|
|
/// </summary>
|
|
public ushort NetworkBehaviourId => NetworkObject.GetNetworkBehaviourOrderIndex(this);
|
|
|
|
/// <summary>
|
|
/// Internally caches the Id of this behaviour in a NetworkObject. Makes look-up faster
|
|
/// </summary>
|
|
internal ushort NetworkBehaviourIdCache = 0;
|
|
|
|
/// <summary>
|
|
/// Returns a the NetworkBehaviour with a given BehaviourId for the current NetworkObject
|
|
/// </summary>
|
|
/// <param name="behaviourId">The behaviourId to return</param>
|
|
/// <returns>Returns NetworkBehaviour with given behaviourId</returns>
|
|
protected NetworkBehaviour GetNetworkBehaviour(ushort behaviourId)
|
|
{
|
|
return NetworkObject.GetNetworkBehaviourAtOrderIndex(behaviourId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the ClientId that owns the NetworkObject
|
|
/// </summary>
|
|
public ulong OwnerClientId => NetworkObject.OwnerClientId;
|
|
|
|
/// <summary>
|
|
/// Gets called when the <see cref="NetworkObject"/> gets spawned, message handlers are ready to be registered and the network is setup.
|
|
/// </summary>
|
|
public virtual void OnNetworkSpawn() { }
|
|
|
|
/// <summary>
|
|
/// Gets called when the <see cref="NetworkObject"/> gets despawned. Is called both on the server and clients.
|
|
/// </summary>
|
|
public virtual void OnNetworkDespawn() { }
|
|
|
|
internal void InternalOnNetworkSpawn()
|
|
{
|
|
InitializeVariables();
|
|
}
|
|
|
|
internal void InternalOnNetworkDespawn() { }
|
|
|
|
/// <summary>
|
|
/// Gets called when the local client gains ownership of this object
|
|
/// </summary>
|
|
public virtual void OnGainedOwnership() { }
|
|
|
|
/// <summary>
|
|
/// Gets called when we loose ownership of this object
|
|
/// </summary>
|
|
public virtual void OnLostOwnership() { }
|
|
|
|
/// <summary>
|
|
/// Gets called when the parent NetworkObject of this NetworkBehaviour's NetworkObject has changed
|
|
/// </summary>
|
|
public virtual void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) { }
|
|
|
|
private bool m_VarInit = false;
|
|
|
|
private readonly List<HashSet<int>> m_DeliveryMappedNetworkVariableIndices = new List<HashSet<int>>();
|
|
private readonly List<NetworkDelivery> m_DeliveryTypesForNetworkVariableGroups = new List<NetworkDelivery>();
|
|
internal readonly List<NetworkVariableBase> NetworkVariableFields = new List<NetworkVariableBase>();
|
|
|
|
private static Dictionary<Type, FieldInfo[]> s_FieldTypes = new Dictionary<Type, FieldInfo[]>();
|
|
|
|
private static FieldInfo[] GetFieldInfoForType(Type type)
|
|
{
|
|
if (!s_FieldTypes.ContainsKey(type))
|
|
{
|
|
s_FieldTypes.Add(type, GetFieldInfoForTypeRecursive(type));
|
|
}
|
|
|
|
return s_FieldTypes[type];
|
|
}
|
|
|
|
private static FieldInfo[] GetFieldInfoForTypeRecursive(Type type, List<FieldInfo> list = null)
|
|
{
|
|
if (list == null)
|
|
{
|
|
list = new List<FieldInfo>();
|
|
list.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
|
|
}
|
|
else
|
|
{
|
|
list.AddRange(type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance));
|
|
}
|
|
|
|
if (type.BaseType != null && type.BaseType != typeof(NetworkBehaviour))
|
|
{
|
|
return GetFieldInfoForTypeRecursive(type.BaseType, list);
|
|
}
|
|
|
|
return list.OrderBy(x => x.Name, StringComparer.Ordinal).ToArray();
|
|
}
|
|
|
|
internal void InitializeVariables()
|
|
{
|
|
if (m_VarInit)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_VarInit = true;
|
|
|
|
FieldInfo[] sortedFields = GetFieldInfoForType(GetType());
|
|
|
|
for (int i = 0; i < sortedFields.Length; i++)
|
|
{
|
|
Type fieldType = sortedFields[i].FieldType;
|
|
|
|
if (fieldType.IsSubclassOf(typeof(NetworkVariableBase)))
|
|
{
|
|
var instance = (NetworkVariableBase)sortedFields[i].GetValue(this);
|
|
|
|
if (instance == null)
|
|
{
|
|
throw new Exception($"{GetType().FullName}.{sortedFields[i].Name} cannot be null. All {nameof(NetworkVariableBase)} instances must be initialized.");
|
|
}
|
|
|
|
instance.Initialize(this);
|
|
|
|
var instanceNameProperty = fieldType.GetProperty(nameof(NetworkVariableBase.Name));
|
|
var sanitizedVariableName = sortedFields[i].Name.Replace("<", string.Empty).Replace(">k__BackingField", string.Empty);
|
|
instanceNameProperty?.SetValue(instance, sanitizedVariableName);
|
|
|
|
NetworkVariableFields.Add(instance);
|
|
}
|
|
}
|
|
|
|
{
|
|
// Create index map for delivery types
|
|
var firstLevelIndex = new Dictionary<NetworkDelivery, int>();
|
|
int secondLevelCounter = 0;
|
|
|
|
for (int i = 0; i < NetworkVariableFields.Count; i++)
|
|
{
|
|
var networkDelivery = NetworkVariableBase.Delivery;
|
|
if (!firstLevelIndex.ContainsKey(networkDelivery))
|
|
{
|
|
firstLevelIndex.Add(networkDelivery, secondLevelCounter);
|
|
m_DeliveryTypesForNetworkVariableGroups.Add(networkDelivery);
|
|
secondLevelCounter++;
|
|
}
|
|
|
|
if (firstLevelIndex[networkDelivery] >= m_DeliveryMappedNetworkVariableIndices.Count)
|
|
{
|
|
m_DeliveryMappedNetworkVariableIndices.Add(new HashSet<int>());
|
|
}
|
|
|
|
m_DeliveryMappedNetworkVariableIndices[firstLevelIndex[networkDelivery]].Add(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void PreNetworkVariableWrite()
|
|
{
|
|
// reset our "which variables got written" data
|
|
NetworkVariableIndexesToReset.Clear();
|
|
NetworkVariableIndexesToResetSet.Clear();
|
|
}
|
|
|
|
internal void PostNetworkVariableWrite()
|
|
{
|
|
// mark any variables we wrote as no longer dirty
|
|
for (int i = 0; i < NetworkVariableIndexesToReset.Count; i++)
|
|
{
|
|
NetworkVariableFields[NetworkVariableIndexesToReset[i]].ResetDirty();
|
|
}
|
|
}
|
|
|
|
internal void VariableUpdate(ulong clientId)
|
|
{
|
|
if (!m_VarInit)
|
|
{
|
|
InitializeVariables();
|
|
}
|
|
|
|
PreNetworkVariableWrite();
|
|
NetworkVariableUpdate(clientId, NetworkBehaviourId);
|
|
}
|
|
|
|
internal readonly List<int> NetworkVariableIndexesToReset = new List<int>();
|
|
internal readonly HashSet<int> NetworkVariableIndexesToResetSet = new HashSet<int>();
|
|
|
|
private void NetworkVariableUpdate(ulong clientId, int behaviourIndex)
|
|
{
|
|
if (!CouldHaveDirtyNetworkVariables())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (NetworkManager.NetworkConfig.UseSnapshotDelta)
|
|
{
|
|
for (int k = 0; k < NetworkVariableFields.Count; k++)
|
|
{
|
|
NetworkManager.SnapshotSystem.Store(NetworkObjectId, behaviourIndex, k, NetworkVariableFields[k]);
|
|
}
|
|
}
|
|
|
|
if (!NetworkManager.NetworkConfig.UseSnapshotDelta)
|
|
{
|
|
for (int j = 0; j < m_DeliveryMappedNetworkVariableIndices.Count; j++)
|
|
{
|
|
var shouldSend = false;
|
|
for (int k = 0; k < NetworkVariableFields.Count; k++)
|
|
{
|
|
if (NetworkVariableFields[k].ShouldWrite(clientId, IsServer))
|
|
{
|
|
shouldSend = true;
|
|
}
|
|
}
|
|
|
|
if (shouldSend)
|
|
{
|
|
var message = new NetworkVariableDeltaMessage
|
|
{
|
|
NetworkObjectId = NetworkObjectId,
|
|
NetworkBehaviourIndex = NetworkObject.GetNetworkBehaviourOrderIndex(this),
|
|
NetworkBehaviour = this,
|
|
ClientId = clientId,
|
|
DeliveryMappedNetworkVariableIndex = m_DeliveryMappedNetworkVariableIndices[j]
|
|
};
|
|
// TODO: Serialization is where the IsDirty flag gets changed.
|
|
// Messages don't get sent from the server to itself, so if we're host and sending to ourselves,
|
|
// we still have to actually serialize the message even though we're not sending it, otherwise
|
|
// the dirty flag doesn't change properly. These two pieces should be decoupled at some point
|
|
// so we don't have to do this serialization work if we're not going to use the result.
|
|
if (IsServer && clientId == NetworkManager.ServerClientId)
|
|
{
|
|
var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, MessagingSystem.FRAGMENTED_MESSAGE_MAX_SIZE);
|
|
using (tmpWriter)
|
|
{
|
|
message.Serialize(tmpWriter);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NetworkManager.SendMessage(ref message, m_DeliveryTypesForNetworkVariableGroups[j], clientId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool CouldHaveDirtyNetworkVariables()
|
|
{
|
|
// TODO: There should be a better way by reading one dirty variable vs. 'n'
|
|
for (int i = 0; i < NetworkVariableFields.Count; i++)
|
|
{
|
|
if (NetworkVariableFields[i].IsDirty())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
internal void MarkVariablesDirty()
|
|
{
|
|
for (int j = 0; j < NetworkVariableFields.Count; j++)
|
|
{
|
|
NetworkVariableFields[j].SetDirty(true);
|
|
}
|
|
}
|
|
|
|
internal void WriteNetworkVariableData(FastBufferWriter writer, ulong clientId)
|
|
{
|
|
if (NetworkVariableFields.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int j = 0; j < NetworkVariableFields.Count; j++)
|
|
{
|
|
bool canClientRead = NetworkVariableFields[j].CanClientRead(clientId);
|
|
|
|
if (canClientRead)
|
|
{
|
|
var writePos = writer.Position;
|
|
writer.WriteValueSafe((ushort)0);
|
|
var startPos = writer.Position;
|
|
NetworkVariableFields[j].WriteField(writer);
|
|
var size = writer.Position - startPos;
|
|
writer.Seek(writePos);
|
|
writer.WriteValueSafe((ushort)size);
|
|
writer.Seek(startPos + size);
|
|
}
|
|
else
|
|
{
|
|
writer.WriteValueSafe((ushort)0);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void SetNetworkVariableData(FastBufferReader reader)
|
|
{
|
|
if (NetworkVariableFields.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int j = 0; j < NetworkVariableFields.Count; j++)
|
|
{
|
|
reader.ReadValueSafe(out ushort varSize);
|
|
if (varSize == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var readStartPos = reader.Position;
|
|
NetworkVariableFields[j].ReadField(reader);
|
|
|
|
if (NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
|
{
|
|
if (reader.Position > (readStartPos + varSize))
|
|
{
|
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
|
{
|
|
NetworkLog.LogWarning($"Var data read too far. {reader.Position - (readStartPos + varSize)} bytes.");
|
|
}
|
|
|
|
reader.Seek(readStartPos + varSize);
|
|
}
|
|
else if (reader.Position < (readStartPos + varSize))
|
|
{
|
|
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
|
{
|
|
NetworkLog.LogWarning($"Var data read too little. {(readStartPos + varSize) - reader.Position} bytes.");
|
|
}
|
|
|
|
reader.Seek(readStartPos + varSize);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the local instance of a object with a given NetworkId
|
|
/// </summary>
|
|
/// <param name="networkId"></param>
|
|
/// <returns></returns>
|
|
protected NetworkObject GetNetworkObject(ulong networkId)
|
|
{
|
|
return NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(networkId, out NetworkObject networkObject) ? networkObject : null;
|
|
}
|
|
|
|
public virtual void OnDestroy()
|
|
{
|
|
// this seems odd to do here, but in fact especially in tests we can find ourselves
|
|
// here without having called InitializedVariables, which causes problems if any
|
|
// of those variables use native containers (e.g. NetworkList) as they won't be
|
|
// registered here and therefore won't be cleaned up.
|
|
//
|
|
// we should study to understand the initialization patterns
|
|
if (!m_VarInit)
|
|
{
|
|
InitializeVariables();
|
|
}
|
|
|
|
for (int i = 0; i < NetworkVariableFields.Count; i++)
|
|
{
|
|
NetworkVariableFields[i].Dispose();
|
|
}
|
|
}
|
|
}
|
|
}
|