com.unity.netcode.gameobjects@1.0.0-pre.6

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)
This commit is contained in:
Unity Technologies
2022-03-02 00:00:00 +00:00
parent 4818405514
commit 5b4aaa8b59
205 changed files with 6971 additions and 2722 deletions

View File

@@ -6,6 +6,34 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
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)
## [1.0.0-pre.5] - 2022-01-26
### Added
@@ -15,12 +43,15 @@ Additional documentation and release notes are available at [Multiplayer Documen
### Changed
- NetworkManager's GameObject is no longer allowed to be nested under one or more GameObject(s).(#1484)
- NetworkManager DontDestroy property was removed and now NetworkManager always is migrated into the DontDestroyOnLoad scene. (#1484)
- NetworkManager DontDestroy property was removed and now NetworkManager always is migrated into the DontDestroyOnLoad scene. (#1484)'
### Fixed
- Fixed network tick value sometimes being duplicated or skipped. (#1614)
- Fixed The ClientNetworkTransform sample script to allow for owner changes at runtime. (#1606)
- Fixed When the LogLevel is set to developer NetworkBehaviour generates warning messages when it should not (#1631)
- Fixed NetworkTransport Initialize now can receive the associated NetworkManager instance to avoid using NetworkManager.Singleton in transport layer (#1677)
- Fixed a bug where NetworkList.Contains value was inverted (#1363)
## [1.0.0-pre.4] - 2021-01-04

View File

@@ -69,6 +69,13 @@ namespace Unity.Netcode
private bool InvalidState => m_Buffer.Count == 0 && m_LifetimeConsumedCount == 0;
public void Clear()
{
m_Buffer.Clear();
m_EndTimeConsumed = 0.0d;
m_StartTimeConsumed = 0.0d;
}
public void ResetTo(T targetValue, double serverTime)
{
m_LifetimeConsumedCount = 1;

View File

@@ -1,6 +1,5 @@
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
namespace Unity.Netcode.Components
@@ -14,24 +13,19 @@ namespace Unity.Netcode.Components
{
internal struct AnimationMessage : INetworkSerializable
{
public int StateHash; // if non-zero, then Play() this animation, skipping transitions
// state hash per layer. if non-zero, then Play() this animation, skipping transitions
public int StateHash;
public float NormalizedTime;
public int Layer;
public float Weight;
public byte[] Parameters;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref StateHash);
serializer.SerializeValue(ref NormalizedTime);
serializer.SerializeValue(ref Parameters);
}
}
internal struct AnimationParametersMessage : INetworkSerializable
{
public byte[] Parameters;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref Layer);
serializer.SerializeValue(ref Weight);
serializer.SerializeValue(ref Parameters);
}
}
@@ -49,8 +43,6 @@ namespace Unity.Netcode.Components
}
[SerializeField] private Animator m_Animator;
[SerializeField] private uint m_ParameterSendBits;
[SerializeField] private float m_SendRate = 0.1f;
public Animator Animator
{
@@ -58,43 +50,17 @@ namespace Unity.Netcode.Components
set
{
m_Animator = value;
ResetParameterOptions();
}
}
/*
* AutoSend is the ability to select which parameters linked to this animator
* get replicated on a regular basis regardless of a state change. The thinking
* behind this is that many of the parameters people use are usually booleans
* which result in a state change and thus would cause a full sync of state.
* Thus if you really care about a parameter syncing then you need to be explict
* by selecting it in the inspector when an NetworkAnimator is selected.
*/
public void SetParameterAutoSend(int index, bool value)
{
if (value)
{
m_ParameterSendBits |= (uint)(1 << index);
}
else
{
m_ParameterSendBits &= (uint)(~(1 << index));
}
}
public bool GetParameterAutoSend(int index)
{
return (m_ParameterSendBits & (uint)(1 << index)) != 0;
}
private bool m_SendMessagesAllowed = false;
// Animators only support up to 32 params
public static int K_MaxAnimationParams = 32;
private int m_TransitionHash;
private double m_NextSendTime = 0.0f;
private int m_AnimationHash;
public int AnimationHash { get => m_AnimationHash; }
private int[] m_TransitionHash;
private int[] m_AnimationHash;
private float[] m_LayerWeights;
private unsafe struct AnimatorParamCache
{
@@ -103,11 +69,11 @@ namespace Unity.Netcode.Components
public fixed byte Value[4]; // this is a max size of 4 bytes
}
// 128bytes per Animator
// 128 bytes per Animator
private FastBufferWriter m_ParameterWriter = new FastBufferWriter(K_MaxAnimationParams * sizeof(float), Allocator.Persistent);
private NativeArray<AnimatorParamCache> m_CachedAnimatorParameters;
// We cache these values because UnsafeUtility.EnumToInt use direct IL that allows a nonboxing conversion
// We cache these values because UnsafeUtility.EnumToInt uses direct IL that allows a non-boxing conversion
private struct AnimationParamEnumWrapper
{
public static readonly int AnimatorControllerParameterInt;
@@ -122,25 +88,6 @@ namespace Unity.Netcode.Components
}
}
internal void ResetParameterOptions()
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfoServer("ResetParameterOptions");
}
m_ParameterSendBits = 0;
}
private bool sendMessagesAllowed
{
get
{
return IsServer && NetworkObject.IsSpawned;
}
}
public override void OnDestroy()
{
if (m_CachedAnimatorParameters.IsCreated)
@@ -153,25 +100,36 @@ namespace Unity.Netcode.Components
public override void OnNetworkSpawn()
{
if (IsServer)
{
m_SendMessagesAllowed = true;
int layers = m_Animator.layerCount;
m_TransitionHash = new int[layers];
m_AnimationHash = new int[layers];
m_LayerWeights = new float[layers];
}
var parameters = m_Animator.parameters;
m_CachedAnimatorParameters = new NativeArray<AnimatorParamCache>(parameters.Length, Allocator.Persistent);
m_AnimationHash = -1;
for (var i = 0; i < parameters.Length; i++)
{
var parameter = parameters[i];
if (m_Animator.IsParameterControlledByCurve(parameter.nameHash))
{
//we are ignoring parameters that are controlled by animation curves - syncing the layer states indirectly syncs the values that are driven by the animation curves
// we are ignoring parameters that are controlled by animation curves - syncing the layer
// states indirectly syncs the values that are driven by the animation curves
continue;
}
var cacheParam = new AnimatorParamCache();
var cacheParam = new AnimatorParamCache
{
Type = UnsafeUtility.EnumToInt(parameter.type),
Hash = parameter.nameHash
};
cacheParam.Type = UnsafeUtility.EnumToInt(parameter.type);
cacheParam.Hash = parameter.nameHash;
unsafe
{
switch (parameter.type)
@@ -199,93 +157,88 @@ namespace Unity.Netcode.Components
}
}
private void FixedUpdate()
public override void OnNetworkDespawn()
{
if (!sendMessagesAllowed)
{
return;
}
int stateHash;
float normalizedTime;
if (!CheckAnimStateChanged(out stateHash, out normalizedTime))
{
// We only want to check and send if we don't have any other state to since
// as we will sync all params as part of the state sync
CheckAndSend();
return;
}
var animMsg = new AnimationMessage();
animMsg.StateHash = stateHash;
animMsg.NormalizedTime = normalizedTime;
m_ParameterWriter.Seek(0);
m_ParameterWriter.Truncate();
WriteParameters(m_ParameterWriter, false);
animMsg.Parameters = m_ParameterWriter.ToArray();
SendAnimStateClientRpc(animMsg);
m_SendMessagesAllowed = false;
}
private void CheckAndSend()
private void FixedUpdate()
{
var networkTime = NetworkManager.ServerTime.Time;
if (sendMessagesAllowed && m_SendRate != 0 && m_NextSendTime < networkTime)
if (!m_SendMessagesAllowed || !m_Animator || !m_Animator.enabled)
{
m_NextSendTime = networkTime + m_SendRate;
return;
}
for (int layer = 0; layer < m_Animator.layerCount; layer++)
{
int stateHash;
float normalizedTime;
if (!CheckAnimStateChanged(out stateHash, out normalizedTime, layer))
{
continue;
}
var animMsg = new AnimationMessage
{
StateHash = stateHash,
NormalizedTime = normalizedTime,
Layer = layer,
Weight = m_LayerWeights[layer]
};
m_ParameterWriter.Seek(0);
m_ParameterWriter.Truncate();
if (WriteParameters(m_ParameterWriter, true))
{
// we then sync the params we care about
var animMsg = new AnimationParametersMessage()
{
Parameters = m_ParameterWriter.ToArray()
};
WriteParameters(m_ParameterWriter);
animMsg.Parameters = m_ParameterWriter.ToArray();
SendParamsClientRpc(animMsg);
}
SendAnimStateClientRpc(animMsg);
}
}
private bool CheckAnimStateChanged(out int stateHash, out float normalizedTime)
private bool CheckAnimStateChanged(out int stateHash, out float normalizedTime, int layer)
{
bool shouldUpdate = false;
stateHash = 0;
normalizedTime = 0;
if (m_Animator.IsInTransition(0))
float layerWeightNow = m_Animator.GetLayerWeight(layer);
if (!Mathf.Approximately(layerWeightNow, m_LayerWeights[layer]))
{
AnimatorTransitionInfo tt = m_Animator.GetAnimatorTransitionInfo(0);
if (tt.fullPathHash != m_TransitionHash)
m_LayerWeights[layer] = layerWeightNow;
shouldUpdate = true;
}
if (m_Animator.IsInTransition(layer))
{
AnimatorTransitionInfo tt = m_Animator.GetAnimatorTransitionInfo(layer);
if (tt.fullPathHash != m_TransitionHash[layer])
{
// first time in this transition
m_TransitionHash = tt.fullPathHash;
m_AnimationHash = 0;
return true;
// first time in this transition for this layer
m_TransitionHash[layer] = tt.fullPathHash;
m_AnimationHash[layer] = 0;
shouldUpdate = true;
}
}
else
{
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(layer);
if (st.fullPathHash != m_AnimationHash[layer])
{
// first time in this animation state
if (m_AnimationHash[layer] != 0)
{
// came from another animation directly - from Play()
stateHash = st.fullPathHash;
normalizedTime = st.normalizedTime;
}
m_TransitionHash[layer] = 0;
m_AnimationHash[layer] = st.fullPathHash;
shouldUpdate = true;
}
return false;
}
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(0);
if (st.fullPathHash != m_AnimationHash)
{
// first time in this animation state
if (m_AnimationHash != 0)
{
// came from another animation directly - from Play()
stateHash = st.fullPathHash;
normalizedTime = st.normalizedTime;
}
m_TransitionHash = 0;
m_AnimationHash = st.fullPathHash;
return true;
}
return false;
return shouldUpdate;
}
/* $AS TODO: Right now we are not checking for changed values this is because
@@ -294,20 +247,10 @@ namespace Unity.Netcode.Components
there needs to be logic to track which indexes changed in order for there
to be proper value change checking. Will revist in 1.1.0.
*/
private unsafe bool WriteParameters(FastBufferWriter writer, bool autoSend)
private unsafe void WriteParameters(FastBufferWriter writer)
{
if (m_CachedAnimatorParameters == null)
{
return false;
}
for (int i = 0; i < m_CachedAnimatorParameters.Length; i++)
{
if (autoSend && !GetParameterAutoSend(i))
{
continue;
}
ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef<AnimatorParamCache>(m_CachedAnimatorParameters.GetUnsafePtr(), i);
var hash = cacheValue.Hash;
@@ -340,24 +283,12 @@ namespace Unity.Netcode.Components
}
}
}
// If we do not write any values to the writer then we should not send any data
return writer.Length > 0;
}
private unsafe void ReadParameters(FastBufferReader reader, bool autoSend)
private unsafe void ReadParameters(FastBufferReader reader)
{
if (m_CachedAnimatorParameters == null)
{
return;
}
for (int i = 0; i < m_CachedAnimatorParameters.Length; i++)
{
if (autoSend && !GetParameterAutoSend(i))
{
continue;
}
ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef<AnimatorParamCache>(m_CachedAnimatorParameters.GetUnsafePtr(), i);
var hash = cacheValue.Hash;
@@ -391,28 +322,20 @@ namespace Unity.Netcode.Components
}
}
[ClientRpc]
private unsafe void SendParamsClientRpc(AnimationParametersMessage animSnapshot, ClientRpcParams clientRpcParams = default)
{
if (animSnapshot.Parameters != null)
{
// We use a fixed value here to avoid the copy of data from the byte buffer since we own the data
fixed (byte* parameters = animSnapshot.Parameters)
{
var reader = new FastBufferReader(parameters, Allocator.None, animSnapshot.Parameters.Length);
ReadParameters(reader, true);
}
}
}
/// <summary>
/// Internally-called RPC client receiving function to update some animation parameters on a client when
/// the server wants to update them
/// </summary>
/// <param name="animSnapshot">the payload containing the parameters to apply</param>
/// <param name="clientRpcParams">unused</param>
[ClientRpc]
private unsafe void SendAnimStateClientRpc(AnimationMessage animSnapshot, ClientRpcParams clientRpcParams = default)
{
if (animSnapshot.StateHash != 0)
{
m_AnimationHash = animSnapshot.StateHash;
m_Animator.Play(animSnapshot.StateHash, 0, animSnapshot.NormalizedTime);
m_Animator.Play(animSnapshot.StateHash, animSnapshot.Layer, animSnapshot.NormalizedTime);
}
m_Animator.SetLayerWeight(animSnapshot.Layer, animSnapshot.Weight);
if (animSnapshot.Parameters != null && animSnapshot.Parameters.Length != 0)
{
@@ -420,11 +343,17 @@ namespace Unity.Netcode.Components
fixed (byte* parameters = animSnapshot.Parameters)
{
var reader = new FastBufferReader(parameters, Allocator.None, animSnapshot.Parameters.Length);
ReadParameters(reader, false);
ReadParameters(reader);
}
}
}
/// <summary>
/// Internally-called RPC client receiving function to update a trigger when the server wants to forward
/// a trigger for a client to play / reset
/// </summary>
/// <param name="animSnapshot">the payload containing the trigger data to apply</param>
/// <param name="clientRpcParams">unused</param>
[ClientRpc]
private void SendAnimTriggerClientRpc(AnimationTriggerMessage animSnapshot, ClientRpcParams clientRpcParams = default)
{
@@ -438,11 +367,24 @@ namespace Unity.Netcode.Components
}
}
/// <summary>
/// Sets the trigger for the associated animation
/// Note, triggers are special vs other kinds of parameters. For all the other parameters we watch for changes
/// in FixedUpdate and users can just set them normally off of Animator. But because triggers are transitory
/// and likely to come and go between FixedUpdate calls, we require users to set them here to guarantee us to
/// catch it...then we forward it to the Animator component
/// </summary>
/// <param name="triggerName">The string name of the trigger to activate</param>
public void SetTrigger(string triggerName)
{
SetTrigger(Animator.StringToHash(triggerName));
}
/// <summary>
/// Sets the trigger for the associated animation. See note for SetTrigger(string)
/// </summary>
/// <param name="hash">The hash for the trigger to activate</param>
/// <param name="reset">If true, resets the trigger</param>
public void SetTrigger(int hash, bool reset = false)
{
var animMsg = new AnimationTriggerMessage();
@@ -451,15 +393,38 @@ namespace Unity.Netcode.Components
if (IsServer)
{
// trigger the animation locally on the server...
if (reset)
{
m_Animator.ResetTrigger(hash);
}
else
{
m_Animator.SetTrigger(hash);
}
// ...then tell all the clients to do the same
SendAnimTriggerClientRpc(animMsg);
}
else
{
Debug.LogWarning("Trying to call NetworkAnimator.SetTrigger on a client...ignoring");
}
}
/// <summary>
/// Resets the trigger for the associated animation. See note for SetTrigger(string)
/// </summary>
/// <param name="triggerName">The string name of the trigger to reset</param>
public void ResetTrigger(string triggerName)
{
ResetTrigger(Animator.StringToHash(triggerName));
}
/// <summary>
/// Resets the trigger for the associated animation. See note for SetTrigger(string)
/// </summary>
/// <param name="hash">The hash for the trigger to reset</param>
public void ResetTrigger(int hash)
{
SetTrigger(hash, true);

View File

@@ -260,8 +260,10 @@ namespace Unity.Netcode.Components
/// </summary>
[Tooltip("Sets whether this transform should sync in local space or in world space")]
public bool InLocalSpace = false;
private bool m_LastInterpolateLocal = false; // was the last frame local
public bool Interpolate = true;
private bool m_LastInterpolate = true; // was the last frame interpolated
/// <summary>
/// Used to determine who can write to this transform. Server only for this transform.
@@ -367,7 +369,11 @@ namespace Unity.Netcode.Components
private void CommitLocallyAndReplicate(NetworkTransformState networkState)
{
m_ReplicatedNetworkState.Value = networkState;
AddInterpolatedState(networkState);
if (Interpolate)
{
AddInterpolatedState(networkState);
}
}
private void ResetInterpolatedStateToCurrentAuthoritativeState()
@@ -532,7 +538,12 @@ namespace Unity.Netcode.Components
// again, we should be using quats here
if (SyncRotAngleX || SyncRotAngleY || SyncRotAngleZ)
{
var eulerAngles = m_RotationInterpolator.GetInterpolatedValue().eulerAngles;
var eulerAngles = new Vector3();
if (Interpolate)
{
eulerAngles = m_RotationInterpolator.GetInterpolatedValue().eulerAngles;
}
if (SyncRotAngleX)
{
interpolatedRotAngles.x = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Rotation.x : eulerAngles.x;
@@ -603,10 +614,46 @@ namespace Unity.Netcode.Components
}
}
private void AddInterpolatedState(NetworkTransformState newState)
private void AddInterpolatedState(NetworkTransformState newState, bool reset = false)
{
var sentTime = newState.SentTime;
if (reset)
{
if (newState.HasPositionX)
{
m_PositionXInterpolator.ResetTo(newState.PositionX, sentTime);
}
if (newState.HasPositionY)
{
m_PositionYInterpolator.ResetTo(newState.PositionY, sentTime);
}
if (newState.HasPositionZ)
{
m_PositionZInterpolator.ResetTo(newState.PositionZ, sentTime);
}
m_RotationInterpolator.ResetTo(Quaternion.Euler(newState.Rotation), sentTime);
if (newState.HasScaleX)
{
m_ScaleXInterpolator.ResetTo(newState.ScaleX, sentTime);
}
if (newState.HasScaleY)
{
m_ScaleYInterpolator.ResetTo(newState.ScaleY, sentTime);
}
if (newState.HasScaleZ)
{
m_ScaleZInterpolator.ResetTo(newState.ScaleZ, sentTime);
}
return;
}
if (newState.HasPositionX)
{
m_PositionXInterpolator.AddMeasurement(newState.PositionX, sentTime);
@@ -656,7 +703,11 @@ namespace Unity.Netcode.Components
Debug.DrawLine(newState.Position, newState.Position + Vector3.up + Vector3.left, Color.green, 10, false);
AddInterpolatedState(newState);
if (Interpolate)
{
AddInterpolatedState(newState, (newState.InLocalSpace != m_LastInterpolateLocal));
}
m_LastInterpolateLocal = newState.InLocalSpace;
if (m_CachedNetworkManager.LogLevel == LogLevel.Developer)
{
@@ -812,6 +863,17 @@ namespace Unity.Netcode.Components
return;
}
if (!Interpolate && m_LastInterpolate)
{
// if we just stopped interpolating, let's clear the interpolators
foreach (var interpolator in m_AllFloatInterpolators)
{
interpolator.Clear();
}
}
m_LastInterpolate = Interpolate;
if (CanCommitToTransform)
{
if (m_CachedIsServer)
@@ -831,12 +893,15 @@ namespace Unity.Netcode.Components
var cachedServerTime = serverTime.Time;
var cachedRenderTime = serverTime.TimeTicksAgo(1).Time;
foreach (var interpolator in m_AllFloatInterpolators)
if (Interpolate)
{
interpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime);
}
foreach (var interpolator in m_AllFloatInterpolators)
{
interpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime);
}
m_RotationInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime);
m_RotationInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime);
}
if (!CanCommitToTransform)
{

View File

@@ -5,13 +5,5 @@
"Unity.Netcode.Runtime",
"Unity.Collections"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
"allowUnsafeCode": true
}

View File

@@ -22,6 +22,10 @@ namespace Unity.Netcode.Editor.CodeGen
public static readonly string ClientRpcAttribute_FullName = typeof(ClientRpcAttribute).FullName;
public static readonly string ServerRpcParams_FullName = typeof(ServerRpcParams).FullName;
public static readonly string ClientRpcParams_FullName = typeof(ClientRpcParams).FullName;
public static readonly string ClientRpcSendParams_FullName = typeof(ClientRpcSendParams).FullName;
public static readonly string ClientRpcReceiveParams_FullName = typeof(ClientRpcReceiveParams).FullName;
public static readonly string ServerRpcSendParams_FullName = typeof(ServerRpcSendParams).FullName;
public static readonly string ServerRpcReceiveParams_FullName = typeof(ServerRpcReceiveParams).FullName;
public static readonly string INetworkSerializable_FullName = typeof(INetworkSerializable).FullName;
public static readonly string UnityColor_FullName = typeof(Color).FullName;
public static readonly string UnityColor32_FullName = typeof(Color32).FullName;

View File

@@ -13,7 +13,6 @@ using MethodAttributes = Mono.Cecil.MethodAttributes;
namespace Unity.Netcode.Editor.CodeGen
{
internal sealed class INetworkMessageILPP : ILPPInterface
{
public override ILPPInterface GetInstance() => this;
@@ -31,7 +30,6 @@ namespace Unity.Netcode.Editor.CodeGen
return null;
}
m_Diagnostics.Clear();
// read
@@ -95,27 +93,23 @@ namespace Unity.Netcode.Editor.CodeGen
}
private TypeReference m_FastBufferReader_TypeRef;
private TypeReference m_NetworkContext_TypeRef;
private MethodReference m_MessagingSystem_ReceiveMessage_MethodRef;
private TypeReference m_MessagingSystem_MessageWithHandler_TypeRef;
private MethodReference m_MessagingSystem_MessageHandler_Constructor_TypeRef;
private FieldReference m_ILPPMessageProvider___network_message_types_FieldRef;
private FieldReference m_MessagingSystem_MessageWithHandler_MessageType_FieldRef;
private FieldReference m_MessagingSystem_MessageWithHandler_Handler_FieldRef;
private MethodReference m_Type_GetTypeFromHandle_MethodRef;
private MethodReference m_List_Add_MethodRef;
private const string k_ReceiveMessageName = nameof(MessagingSystem.ReceiveMessage);
private bool ImportReferences(ModuleDefinition moduleDefinition)
{
m_FastBufferReader_TypeRef = moduleDefinition.ImportReference(typeof(FastBufferReader));
m_NetworkContext_TypeRef = moduleDefinition.ImportReference(typeof(NetworkContext));
m_MessagingSystem_MessageHandler_Constructor_TypeRef =
moduleDefinition.ImportReference(typeof(MessagingSystem.MessageHandler).GetConstructors()[0]);
m_MessagingSystem_MessageHandler_Constructor_TypeRef = moduleDefinition.ImportReference(typeof(MessagingSystem.MessageHandler).GetConstructors()[0]);
var messageWithHandlerType = typeof(MessagingSystem.MessageWithHandler);
m_MessagingSystem_MessageWithHandler_TypeRef =
moduleDefinition.ImportReference(messageWithHandlerType);
m_MessagingSystem_MessageWithHandler_TypeRef = moduleDefinition.ImportReference(messageWithHandlerType);
foreach (var fieldInfo in messageWithHandlerType.GetFields())
{
switch (fieldInfo.Name)
@@ -162,38 +156,18 @@ namespace Unity.Netcode.Editor.CodeGen
}
}
return true;
}
private MethodReference GetNetworkMessageRecieveHandler(TypeDefinition typeDefinition)
{
SequencePoint typeSequence = null;
foreach (var method in typeDefinition.Methods)
var messagingSystemType = typeof(MessagingSystem);
foreach (var methodInfo in messagingSystemType.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public))
{
var resolved = method.Resolve();
var methodSequence = resolved.DebugInformation.SequencePoints.FirstOrDefault();
if (typeSequence == null || methodSequence.StartLine < typeSequence.StartLine)
switch (methodInfo.Name)
{
typeSequence = methodSequence;
}
if (resolved.IsStatic && resolved.IsPublic && resolved.Name == "Receive" && resolved.Parameters.Count == 2
&& !resolved.Parameters[0].IsIn
&& !resolved.Parameters[0].ParameterType.IsByReference
&& resolved.Parameters[0].ParameterType.Resolve() ==
m_FastBufferReader_TypeRef.Resolve()
&& resolved.Parameters[1].IsIn
&& resolved.Parameters[1].ParameterType.IsByReference
&& resolved.Parameters[1].ParameterType.GetElementType().Resolve() == m_NetworkContext_TypeRef.Resolve()
&& resolved.ReturnType == resolved.Module.TypeSystem.Void)
{
return method;
case k_ReceiveMessageName:
m_MessagingSystem_ReceiveMessage_MethodRef = moduleDefinition.ImportReference(methodInfo);
break;
}
}
m_Diagnostics.AddError(typeSequence, $"Class {typeDefinition.FullName} does not implement required method: `public static void Receive(FastBufferReader, in NetworkContext)`");
return null;
return true;
}
private MethodDefinition GetOrCreateStaticConstructor(TypeDefinition typeDefinition)
@@ -264,11 +238,8 @@ namespace Unity.Netcode.Editor.CodeGen
foreach (var type in networkMessageTypes)
{
var receiveMethod = GetNetworkMessageRecieveHandler(type);
if (receiveMethod == null)
{
continue;
}
var receiveMethod = new GenericInstanceMethod(m_MessagingSystem_ReceiveMessage_MethodRef);
receiveMethod.GenericArguments.Add(type);
CreateInstructionsToRegisterType(processor, instructions, type, receiveMethod);
}

View File

@@ -7,7 +7,6 @@ using System.Runtime.CompilerServices;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using Unity.Collections;
using Unity.CompilationPipeline.Common.Diagnostics;
using Unity.CompilationPipeline.Common.ILPostProcessing;
using UnityEngine;
@@ -17,7 +16,6 @@ using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostPr
namespace Unity.Netcode.Editor.CodeGen
{
internal sealed class NetworkBehaviourILPP : ILPPInterface
{
private const string k_ReadValueMethodName = nameof(FastBufferReader.ReadValueSafe);
@@ -25,7 +23,8 @@ namespace Unity.Netcode.Editor.CodeGen
public override ILPPInterface GetInstance() => this;
public override bool WillProcess(ICompiledAssembly compiledAssembly) => compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName);
public override bool WillProcess(ICompiledAssembly compiledAssembly) =>
compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName);
private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>();
@@ -109,8 +108,10 @@ namespace Unity.Netcode.Editor.CodeGen
private FieldReference m_NetworkManager_rpc_name_table_FieldRef;
private MethodReference m_NetworkManager_rpc_name_table_Add_MethodRef;
private TypeReference m_NetworkBehaviour_TypeRef;
private MethodReference m_NetworkBehaviour_SendServerRpc_MethodRef;
private MethodReference m_NetworkBehaviour_SendClientRpc_MethodRef;
private MethodReference m_NetworkBehaviour_beginSendServerRpc_MethodRef;
private MethodReference m_NetworkBehaviour_endSendServerRpc_MethodRef;
private MethodReference m_NetworkBehaviour_beginSendClientRpc_MethodRef;
private MethodReference m_NetworkBehaviour_endSendClientRpc_MethodRef;
private FieldReference m_NetworkBehaviour_rpc_exec_stage_FieldRef;
private MethodReference m_NetworkBehaviour_getNetworkManager_MethodRef;
private MethodReference m_NetworkBehaviour_getOwnerClientId_MethodRef;
@@ -124,8 +125,6 @@ namespace Unity.Netcode.Editor.CodeGen
private TypeReference m_ClientRpcParams_TypeRef;
private TypeReference m_FastBufferWriter_TypeRef;
private MethodReference m_FastBufferWriter_Constructor;
private MethodReference m_FastBufferWriter_Dispose;
private Dictionary<string, MethodReference> m_FastBufferWriter_WriteValue_MethodRefs = new Dictionary<string, MethodReference>();
private List<MethodReference> m_FastBufferWriter_ExtensionMethodRefs = new List<MethodReference>();
@@ -144,8 +143,10 @@ namespace Unity.Netcode.Editor.CodeGen
private const string k_NetworkManager_rpc_name_table = nameof(NetworkManager.__rpc_name_table);
private const string k_NetworkBehaviour_rpc_exec_stage = nameof(NetworkBehaviour.__rpc_exec_stage);
private const string k_NetworkBehaviour_SendServerRpc = nameof(NetworkBehaviour.__sendServerRpc);
private const string k_NetworkBehaviour_SendClientRpc = nameof(NetworkBehaviour.__sendClientRpc);
private const string k_NetworkBehaviour_beginSendServerRpc = nameof(NetworkBehaviour.__beginSendServerRpc);
private const string k_NetworkBehaviour_endSendServerRpc = nameof(NetworkBehaviour.__endSendServerRpc);
private const string k_NetworkBehaviour_beginSendClientRpc = nameof(NetworkBehaviour.__beginSendClientRpc);
private const string k_NetworkBehaviour_endSendClientRpc = nameof(NetworkBehaviour.__endSendClientRpc);
private const string k_NetworkBehaviour_NetworkManager = nameof(NetworkBehaviour.NetworkManager);
private const string k_NetworkBehaviour_OwnerClientId = nameof(NetworkBehaviour.OwnerClientId);
@@ -234,11 +235,17 @@ namespace Unity.Netcode.Editor.CodeGen
{
switch (methodInfo.Name)
{
case k_NetworkBehaviour_SendServerRpc:
m_NetworkBehaviour_SendServerRpc_MethodRef = moduleDefinition.ImportReference(methodInfo);
case k_NetworkBehaviour_beginSendServerRpc:
m_NetworkBehaviour_beginSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodInfo);
break;
case k_NetworkBehaviour_SendClientRpc:
m_NetworkBehaviour_SendClientRpc_MethodRef = moduleDefinition.ImportReference(methodInfo);
case k_NetworkBehaviour_endSendServerRpc:
m_NetworkBehaviour_endSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodInfo);
break;
case k_NetworkBehaviour_beginSendClientRpc:
m_NetworkBehaviour_beginSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodInfo);
break;
case k_NetworkBehaviour_endSendClientRpc:
m_NetworkBehaviour_endSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodInfo);
break;
}
}
@@ -299,17 +306,12 @@ namespace Unity.Netcode.Editor.CodeGen
var fastBufferWriterType = typeof(FastBufferWriter);
m_FastBufferWriter_TypeRef = moduleDefinition.ImportReference(fastBufferWriterType);
m_FastBufferWriter_Constructor = moduleDefinition.ImportReference(
fastBufferWriterType.GetConstructor(new[] { typeof(int), typeof(Allocator), typeof(int) }));
m_FastBufferWriter_Dispose = moduleDefinition.ImportReference(fastBufferWriterType.GetMethod("Dispose"));
var fastBufferReaderType = typeof(FastBufferReader);
m_FastBufferReader_TypeRef = moduleDefinition.ImportReference(fastBufferReaderType);
// Find all extension methods for FastBufferReader and FastBufferWriter to enable user-implemented
// methods to be called.
var assemblies = new List<AssemblyDefinition>();
assemblies.Add(m_MainModule.Assembly);
var assemblies = new List<AssemblyDefinition> { m_MainModule.Assembly };
foreach (var reference in m_MainModule.AssemblyReferences)
{
var assembly = m_AssemblyResolver.Resolve(reference);
@@ -319,8 +321,7 @@ namespace Unity.Netcode.Editor.CodeGen
}
}
var extensionConstructor =
moduleDefinition.ImportReference(typeof(ExtensionAttribute).GetConstructor(new Type[] { }));
var extensionConstructor = moduleDefinition.ImportReference(typeof(ExtensionAttribute).GetConstructor(new Type[] { }));
foreach (var assembly in assemblies)
{
foreach (var module in assembly.Modules)
@@ -332,6 +333,7 @@ namespace Unity.Netcode.Editor.CodeGen
{
continue;
}
foreach (var method in type.Methods)
{
if (!method.IsStatic)
@@ -356,13 +358,11 @@ namespace Unity.Netcode.Editor.CodeGen
var parameters = method.Parameters;
if (parameters.Count == 2
&& parameters[0].ParameterType.Resolve() == m_FastBufferWriter_TypeRef.MakeByReferenceType().Resolve())
if (parameters.Count == 2 && parameters[0].ParameterType.Resolve() == m_FastBufferWriter_TypeRef.MakeByReferenceType().Resolve())
{
m_FastBufferWriter_ExtensionMethodRefs.Add(m_MainModule.ImportReference(method));
}
else if (parameters.Count == 2
&& parameters[0].ParameterType.Resolve() == m_FastBufferReader_TypeRef.MakeByReferenceType().Resolve())
else if (parameters.Count == 2 && parameters[0].ParameterType.Resolve() == m_FastBufferReader_TypeRef.MakeByReferenceType().Resolve())
{
m_FastBufferReader_ExtensionMethodRefs.Add(m_MainModule.ImportReference(method));
}
@@ -395,9 +395,20 @@ namespace Unity.Netcode.Editor.CodeGen
continue;
}
if (methodDefinition.HasCustomAttributes)
{
foreach (var attribute in methodDefinition.CustomAttributes)
{
if (attribute.AttributeType.Name == nameof(AsyncStateMachineAttribute))
{
m_Diagnostics.AddError(methodDefinition, $"{methodDefinition.FullName}: RPCs cannot be 'async'");
}
}
}
InjectWriteAndCallBlocks(methodDefinition, rpcAttribute, rpcMethodId);
rpcHandlers.Add((rpcMethodId, GenerateStaticHandler(methodDefinition, rpcAttribute)));
rpcHandlers.Add((rpcMethodId, GenerateStaticHandler(methodDefinition, rpcAttribute, rpcMethodId)));
if (isEditorOrDevelopment)
{
@@ -477,7 +488,6 @@ namespace Unity.Netcode.Editor.CodeGen
private CustomAttribute CheckAndGetRpcAttribute(MethodDefinition methodDefinition)
{
CustomAttribute rpcAttribute = null;
bool isServerRpc = false;
foreach (var customAttribute in methodDefinition.CustomAttributes)
{
var customAttributeType_FullName = customAttribute.AttributeType.FullName;
@@ -521,7 +531,6 @@ namespace Unity.Netcode.Editor.CodeGen
if (isValid)
{
isServerRpc = customAttributeType_FullName == CodeGenHelpers.ServerRpcAttribute_FullName;
rpcAttribute = customAttribute;
}
}
@@ -569,12 +578,12 @@ namespace Unity.Netcode.Editor.CodeGen
checkType = paramType.GetElementType().Resolve();
}
if (
(parameters[0].ParameterType.Resolve() == checkType
|| (parameters[0].ParameterType.Resolve() == checkType.MakeByReferenceType().Resolve() && parameters[0].IsIn)))
if ((parameters[0].ParameterType.Resolve() == checkType ||
(parameters[0].ParameterType.Resolve() == checkType.MakeByReferenceType().Resolve() && parameters[0].IsIn)))
{
return method;
}
if (method.HasGenericParameters && method.GenericParameters.Count == 1)
{
if (method.GenericParameters[0].HasConstraints)
@@ -584,17 +593,15 @@ namespace Unity.Netcode.Editor.CodeGen
{
var resolvedConstraint = constraint.Resolve();
if (
(resolvedConstraint.IsInterface &&
!checkType.HasInterface(resolvedConstraint.FullName))
|| (resolvedConstraint.IsClass &&
!checkType.Resolve().IsSubclassOf(resolvedConstraint.FullName))
|| (resolvedConstraint.Name == "ValueType" && !checkType.IsValueType))
if ((resolvedConstraint.IsInterface && !checkType.HasInterface(resolvedConstraint.FullName)) ||
(resolvedConstraint.IsClass && !checkType.Resolve().IsSubclassOf(resolvedConstraint.FullName)) ||
(resolvedConstraint.Name == "ValueType" && !checkType.IsValueType))
{
meetsConstraints = false;
break;
}
}
if (meetsConstraints)
{
var instanceMethod = new GenericInstanceMethod(method);
@@ -624,8 +631,8 @@ namespace Unity.Netcode.Editor.CodeGen
{
if (parameters[1].IsIn)
{
if (parameters[1].ParameterType.Resolve() == paramType.MakeByReferenceType().Resolve()
&& ((ByReferenceType)parameters[1].ParameterType).ElementType.IsArray == paramType.IsArray)
if (parameters[1].ParameterType.Resolve() == paramType.MakeByReferenceType().Resolve() &&
((ByReferenceType)parameters[1].ParameterType).ElementType.IsArray == paramType.IsArray)
{
methodRef = method;
m_FastBufferWriter_WriteValue_MethodRefs[assemblyQualifiedName] = methodRef;
@@ -635,8 +642,8 @@ namespace Unity.Netcode.Editor.CodeGen
else
{
if (parameters[1].ParameterType.Resolve() == paramType.Resolve()
&& parameters[1].ParameterType.IsArray == paramType.IsArray)
if (parameters[1].ParameterType.Resolve() == paramType.Resolve() &&
parameters[1].ParameterType.IsArray == paramType.IsArray)
{
methodRef = method;
m_FastBufferWriter_WriteValue_MethodRefs[assemblyQualifiedName] = methodRef;
@@ -707,11 +714,8 @@ namespace Unity.Netcode.Editor.CodeGen
{
var resolvedConstraint = constraint.Resolve();
if (
(resolvedConstraint.IsInterface &&
checkType.HasInterface(resolvedConstraint.FullName))
|| (resolvedConstraint.IsClass &&
checkType.Resolve().IsSubclassOf(resolvedConstraint.FullName)))
if ((resolvedConstraint.IsInterface && checkType.HasInterface(resolvedConstraint.FullName)) ||
(resolvedConstraint.IsClass && checkType.Resolve().IsSubclassOf(resolvedConstraint.FullName)))
{
var instanceMethod = new GenericInstanceMethod(method);
instanceMethod.GenericArguments.Add(checkType);
@@ -736,11 +740,10 @@ namespace Unity.Netcode.Editor.CodeGen
foreach (var method in m_FastBufferReader_ExtensionMethodRefs)
{
var parameters = method.Resolve().Parameters;
if (
method.Name == k_ReadValueMethodName
&& parameters[1].IsOut
&& parameters[1].ParameterType.Resolve() == paramType.MakeByReferenceType().Resolve()
&& ((ByReferenceType)parameters[1].ParameterType).ElementType.IsArray == paramType.IsArray)
if (method.Name == k_ReadValueMethodName &&
parameters[1].IsOut &&
parameters[1].ParameterType.Resolve() == paramType.MakeByReferenceType().Resolve() &&
((ByReferenceType)parameters[1].ParameterType).ElementType.IsArray == paramType.IsArray)
{
methodRef = method;
m_FastBufferReader_ReadValue_MethodRefs[assemblyQualifiedName] = methodRef;
@@ -772,8 +775,8 @@ namespace Unity.Netcode.Editor.CodeGen
var instructions = new List<Instruction>();
var processor = methodDefinition.Body.GetILProcessor();
var isServerRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ServerRpcAttribute_FullName;
var requireOwnership = true; // default value MUST be = `ServerRpcAttribute.RequireOwnership`
var rpcDelivery = RpcDelivery.Reliable; // default value MUST be = `RpcAttribute.Delivery`
var requireOwnership = true; // default value MUST be == `ServerRpcAttribute.RequireOwnership`
var rpcDelivery = RpcDelivery.Reliable; // default value MUST be == `RpcAttribute.Delivery`
foreach (var attrField in rpcAttribute.Fields)
{
switch (attrField.Name)
@@ -797,9 +800,9 @@ namespace Unity.Netcode.Editor.CodeGen
// NetworkManager networkManager;
methodDefinition.Body.Variables.Add(new VariableDefinition(m_NetworkManager_TypeRef));
int netManLocIdx = methodDefinition.Body.Variables.Count - 1;
// NetworkSerializer serializer;
// FastBufferWriter bufferWriter;
methodDefinition.Body.Variables.Add(new VariableDefinition(m_FastBufferWriter_TypeRef));
int serializerLocIdx = methodDefinition.Body.Variables.Count - 1;
int bufWriterLocIdx = methodDefinition.Body.Variables.Count - 1;
// XXXRpcParams
if (!hasRpcParams)
@@ -854,6 +857,8 @@ namespace Unity.Netcode.Editor.CodeGen
instructions.Add(beginInstr);
// var bufferWriter = __beginSendServerRpc(rpcMethodId, serverRpcParams, rpcDelivery) -> ServerRpc
// var bufferWriter = __beginSendClientRpc(rpcMethodId, clientRpcParams, rpcDelivery) -> ClientRpc
if (isServerRpc)
{
// ServerRpc
@@ -867,8 +872,7 @@ namespace Unity.Netcode.Editor.CodeGen
instructions.Add(processor.Create(OpCodes.Ldarg_0));
instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_getOwnerClientId_MethodRef));
instructions.Add(processor.Create(OpCodes.Ldloc, netManLocIdx));
instructions.Add(
processor.Create(OpCodes.Callvirt, m_NetworkManager_getLocalClientId_MethodRef));
instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkManager_getLocalClientId_MethodRef));
instructions.Add(processor.Create(OpCodes.Ceq));
instructions.Add(processor.Create(OpCodes.Ldc_I4, 0));
instructions.Add(processor.Create(OpCodes.Ceq));
@@ -886,8 +890,7 @@ namespace Unity.Netcode.Editor.CodeGen
instructions.Add(processor.Create(OpCodes.Brfalse, logNextInstr));
// Debug.LogError(...);
instructions.Add(processor.Create(OpCodes.Ldstr,
"Only the owner can invoke a ServerRpc that requires ownership!"));
instructions.Add(processor.Create(OpCodes.Ldstr, "Only the owner can invoke a ServerRpc that requires ownership!"));
instructions.Add(processor.Create(OpCodes.Call, m_Debug_LogError_MethodRef));
instructions.Add(logNextInstr);
@@ -895,31 +898,86 @@ namespace Unity.Netcode.Editor.CodeGen
instructions.Add(roReturnInstr);
instructions.Add(roLastInstr);
}
// var bufferWriter = __beginSendServerRpc(rpcMethodId, serverRpcParams, rpcDelivery);
instructions.Add(processor.Create(OpCodes.Ldarg_0));
// rpcMethodId
instructions.Add(processor.Create(OpCodes.Ldc_I4, unchecked((int)rpcMethodId)));
// rpcParams
instructions.Add(hasRpcParams ? processor.Create(OpCodes.Ldarg, paramCount) : processor.Create(OpCodes.Ldloc, rpcParamsIdx));
// rpcDelivery
instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)rpcDelivery));
// __beginSendServerRpc
instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_beginSendServerRpc_MethodRef));
instructions.Add(processor.Create(OpCodes.Stloc, bufWriterLocIdx));
}
else
{
// ClientRpc
// var writer = new FastBufferWriter(1285, Allocator.Temp, 63985);
instructions.Add(processor.Create(OpCodes.Ldloca, serializerLocIdx));
instructions.Add(processor.Create(OpCodes.Ldc_I4, 1300 - sizeof(byte) - sizeof(ulong) - sizeof(uint) - sizeof(ushort)));
instructions.Add(processor.Create(OpCodes.Ldc_I4_2));
instructions.Add(processor.Create(OpCodes.Ldc_I4, 64000 - sizeof(byte) - sizeof(ulong) - sizeof(uint) - sizeof(ushort)));
instructions.Add(processor.Create(OpCodes.Call, m_FastBufferWriter_Constructor));
// var bufferWriter = __beginSendClientRpc(rpcMethodId, clientRpcParams, rpcDelivery);
instructions.Add(processor.Create(OpCodes.Ldarg_0));
var firstInstruction = processor.Create(OpCodes.Nop);
instructions.Add(firstInstruction);
// rpcMethodId
instructions.Add(processor.Create(OpCodes.Ldc_I4, unchecked((int)rpcMethodId)));
// rpcParams
instructions.Add(hasRpcParams ? processor.Create(OpCodes.Ldarg, paramCount) : processor.Create(OpCodes.Ldloc, rpcParamsIdx));
// rpcDelivery
instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)rpcDelivery));
// __beginSendClientRpc
instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_beginSendClientRpc_MethodRef));
instructions.Add(processor.Create(OpCodes.Stloc, bufWriterLocIdx));
}
// write method parameters into stream
for (int paramIndex = 0; paramIndex < paramCount; ++paramIndex)
{
var paramDef = methodDefinition.Parameters[paramIndex];
var paramType = paramDef.ParameterType;
// ServerRpcParams
if (paramType.FullName == CodeGenHelpers.ServerRpcParams_FullName && isServerRpc && paramIndex == paramCount - 1)
if (paramType.FullName == CodeGenHelpers.ClientRpcSendParams_FullName ||
paramType.FullName == CodeGenHelpers.ClientRpcReceiveParams_FullName)
{
m_Diagnostics.AddError($"Rpcs may not accept {paramType.FullName} as a parameter. Use {nameof(ClientRpcParams)} instead.");
continue;
}
if (paramType.FullName == CodeGenHelpers.ServerRpcSendParams_FullName ||
paramType.FullName == CodeGenHelpers.ServerRpcReceiveParams_FullName)
{
m_Diagnostics.AddError($"Rpcs may not accept {paramType.FullName} as a parameter. Use {nameof(ServerRpcParams)} instead.");
continue;
}
// ServerRpcParams
if (paramType.FullName == CodeGenHelpers.ServerRpcParams_FullName)
{
if (paramIndex != paramCount - 1)
{
m_Diagnostics.AddError(methodDefinition, $"{nameof(ServerRpcParams)} must be the last parameter in a ServerRpc.");
}
if (!isServerRpc)
{
m_Diagnostics.AddError($"ClientRpcs may not accept {nameof(ServerRpcParams)} as a parameter.");
}
continue;
}
// ClientRpcParams
if (paramType.FullName == CodeGenHelpers.ClientRpcParams_FullName && !isServerRpc && paramIndex == paramCount - 1)
if (paramType.FullName == CodeGenHelpers.ClientRpcParams_FullName)
{
if (paramIndex != paramCount - 1)
{
m_Diagnostics.AddError(methodDefinition, $"{nameof(ClientRpcParams)} must be the last parameter in a ClientRpc.");
}
if (isServerRpc)
{
m_Diagnostics.AddError($"ServerRpcs may not accept {nameof(ClientRpcParams)} as a parameter.");
}
continue;
}
@@ -942,8 +1000,8 @@ namespace Unity.Netcode.Editor.CodeGen
instructions.Add(processor.Create(OpCodes.Cgt_Un));
instructions.Add(processor.Create(OpCodes.Stloc, isSetLocalIndex));
// writer.WriteValueSafe(isSet);
instructions.Add(processor.Create(OpCodes.Ldloca, serializerLocIdx));
// bufferWriter.WriteValueSafe(isSet);
instructions.Add(processor.Create(OpCodes.Ldloca, bufWriterLocIdx));
instructions.Add(processor.Create(OpCodes.Ldloca, isSetLocalIndex));
instructions.Add(processor.Create(OpCodes.Call, boolMethodRef));
@@ -956,11 +1014,11 @@ namespace Unity.Netcode.Editor.CodeGen
var foundMethodRef = GetWriteMethodForParameter(paramType, out var methodRef);
if (foundMethodRef)
{
// writer.WriteNetworkSerializable(param) for INetworkSerializable, OR
// writer.WriteNetworkSerializable(param, -1, 0) for INetworkSerializable arrays, OR
// writer.WriteValueSafe(param) for value types, OR
// writer.WriteValueSafe(param, -1, 0) for arrays of value types, OR
// writer.WriteValueSafe(param, false) for strings
// bufferWriter.WriteNetworkSerializable(param) for INetworkSerializable, OR
// bufferWriter.WriteNetworkSerializable(param, -1, 0) for INetworkSerializable arrays, OR
// bufferWriter.WriteValueSafe(param) for value types, OR
// bufferWriter.WriteValueSafe(param, -1, 0) for arrays of value types, OR
// bufferWriter.WriteValueSafe(param, false) for strings
var method = methodRef.Resolve();
var checkParameter = method.Parameters[0];
var isExtensionMethod = false;
@@ -971,11 +1029,11 @@ namespace Unity.Netcode.Editor.CodeGen
}
if (!isExtensionMethod || method.Parameters[0].ParameterType.IsByReference)
{
instructions.Add(processor.Create(OpCodes.Ldloca, serializerLocIdx));
instructions.Add(processor.Create(OpCodes.Ldloca, bufWriterLocIdx));
}
else
{
instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx));
instructions.Add(processor.Create(OpCodes.Ldloc, bufWriterLocIdx));
}
if (checkParameter.IsIn || checkParameter.IsOut || checkParameter.ParameterType.IsByReference)
{
@@ -986,16 +1044,14 @@ namespace Unity.Netcode.Editor.CodeGen
instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1));
}
// Special handling for WriteValue() on arrays and strings since they have additional arguments.
if (paramType.IsArray
&& ((!isExtensionMethod && methodRef.Parameters.Count == 3)
|| (isExtensionMethod && methodRef.Parameters.Count == 4)))
if (paramType.IsArray && ((!isExtensionMethod && methodRef.Parameters.Count == 3) ||
(isExtensionMethod && methodRef.Parameters.Count == 4)))
{
instructions.Add(processor.Create(OpCodes.Ldc_I4_M1));
instructions.Add(processor.Create(OpCodes.Ldc_I4_0));
}
else if (paramType == typeSystem.String
&& ((!isExtensionMethod && methodRef.Parameters.Count == 2)
|| (isExtensionMethod && methodRef.Parameters.Count == 3)))
else if (paramType == typeSystem.String && ((!isExtensionMethod && methodRef.Parameters.Count == 2) ||
(isExtensionMethod && methodRef.Parameters.Count == 3)))
{
instructions.Add(processor.Create(OpCodes.Ldc_I4_0));
}
@@ -1015,20 +1071,20 @@ namespace Unity.Netcode.Editor.CodeGen
instructions.Add(endInstr);
// __sendServerRpc(ref serializer, rpcMethodId, serverRpcParams, rpcDelivery) -> ServerRpc
// __sendClientRpc(ref serializer, rpcMethodId, clientRpcParams, rpcDelivery) -> ClientRpc
// __endSendServerRpc(ref bufferWriter, rpcMethodId, serverRpcParams, rpcDelivery) -> ServerRpc
// __endSendClientRpc(ref bufferWriter, rpcMethodId, clientRpcParams, rpcDelivery) -> ClientRpc
if (isServerRpc)
{
// ServerRpc
// __sendServerRpc(ref serializer, rpcMethodId, serverRpcParams, rpcDelivery);
// __endSendServerRpc(ref bufferWriter, rpcMethodId, serverRpcParams, rpcDelivery);
instructions.Add(processor.Create(OpCodes.Ldarg_0));
// serializer
instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx));
// bufferWriter
instructions.Add(processor.Create(OpCodes.Ldloca, bufWriterLocIdx));
// rpcMethodId
instructions.Add(processor.Create(OpCodes.Ldc_I4, unchecked((int)rpcMethodId)));
if (hasRpcParams)
{
// rpcParams
@@ -1039,25 +1095,24 @@ namespace Unity.Netcode.Editor.CodeGen
// default
instructions.Add(processor.Create(OpCodes.Ldloc, rpcParamsIdx));
}
// rpcDelivery
instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)rpcDelivery));
// EndSendServerRpc
instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_SendServerRpc_MethodRef));
// __endSendServerRpc
instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_endSendServerRpc_MethodRef));
}
else
{
// ClientRpc
// __sendClientRpc(ref serializer, rpcMethodId, clientRpcParams, rpcDelivery);
// __endSendClientRpc(ref bufferWriter, rpcMethodId, clientRpcParams, rpcDelivery);
instructions.Add(processor.Create(OpCodes.Ldarg_0));
// serializer
instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx));
// bufferWriter
instructions.Add(processor.Create(OpCodes.Ldloca, bufWriterLocIdx));
// rpcMethodId
instructions.Add(processor.Create(OpCodes.Ldc_I4, unchecked((int)rpcMethodId)));
if (hasRpcParams)
{
// rpcParams
@@ -1068,36 +1123,11 @@ namespace Unity.Netcode.Editor.CodeGen
// default
instructions.Add(processor.Create(OpCodes.Ldloc, rpcParamsIdx));
}
// rpcDelivery
instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)rpcDelivery));
// EndSendClientRpc
instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_SendClientRpc_MethodRef));
}
{
// TODO: Figure out why try/catch here cause the try block not to execute at all.
// End try block
//instructions.Add(processor.Create(OpCodes.Leave, lastInstr));
// writer.Dispose();
var handlerFirst = processor.Create(OpCodes.Ldloca, serializerLocIdx);
instructions.Add(handlerFirst);
instructions.Add(processor.Create(OpCodes.Call, m_FastBufferWriter_Dispose));
// End finally block
//instructions.Add(processor.Create(OpCodes.Endfinally));
// try { ... serialization code ... } finally { writer.Dispose(); }
/*var handler = new ExceptionHandler(ExceptionHandlerType.Finally)
{
TryStart = firstInstruction,
TryEnd = handlerFirst,
HandlerStart = handlerFirst,
HandlerEnd = lastInstr
};
processor.Body.ExceptionHandlers.Add(handler);*/
// __endSendClientRpc
instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_endSendClientRpc_MethodRef));
}
instructions.Add(lastInstr);
@@ -1132,25 +1162,21 @@ namespace Unity.Netcode.Editor.CodeGen
instructions.ForEach(instruction => processor.Body.Instructions.Insert(0, instruction));
}
private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition, CustomAttribute rpcAttribute)
private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition, CustomAttribute rpcAttribute, uint rpcMethodId)
{
var typeSystem = methodDefinition.Module.TypeSystem;
var nhandler = new MethodDefinition(
$"{methodDefinition.Name}__nhandler",
var rpcHandler = new MethodDefinition(
$"__rpc_handler_{rpcMethodId}",
MethodAttributes.Private | MethodAttributes.Static | MethodAttributes.HideBySig,
methodDefinition.Module.TypeSystem.Void);
nhandler.Parameters.Add(new ParameterDefinition("target", ParameterAttributes.None, m_NetworkBehaviour_TypeRef));
nhandler.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, m_FastBufferReader_TypeRef));
nhandler.Parameters.Add(new ParameterDefinition("rpcParams", ParameterAttributes.None, m_RpcParams_TypeRef));
rpcHandler.Parameters.Add(new ParameterDefinition("target", ParameterAttributes.None, m_NetworkBehaviour_TypeRef));
rpcHandler.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, m_FastBufferReader_TypeRef));
rpcHandler.Parameters.Add(new ParameterDefinition("rpcParams", ParameterAttributes.None, m_RpcParams_TypeRef));
var processor = nhandler.Body.GetILProcessor();
// begin Try/Catch
var tryStart = processor.Create(OpCodes.Nop);
processor.Append(tryStart);
var processor = rpcHandler.Body.GetILProcessor();
var isServerRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ServerRpcAttribute_FullName;
var requireOwnership = true; // default value MUST be = `ServerRpcAttribute.RequireOwnership`
var requireOwnership = true; // default value MUST be == `ServerRpcAttribute.RequireOwnership`
foreach (var attrField in rpcAttribute.Fields)
{
switch (attrField.Name)
@@ -1161,10 +1187,10 @@ namespace Unity.Netcode.Editor.CodeGen
}
}
nhandler.Body.InitLocals = true;
rpcHandler.Body.InitLocals = true;
// NetworkManager networkManager;
nhandler.Body.Variables.Add(new VariableDefinition(m_NetworkManager_TypeRef));
int netManLocIdx = nhandler.Body.Variables.Count - 1;
rpcHandler.Body.Variables.Add(new VariableDefinition(m_NetworkManager_TypeRef));
int netManLocIdx = rpcHandler.Body.Variables.Count - 1;
{
var returnInstr = processor.Create(OpCodes.Ret);
@@ -1233,8 +1259,8 @@ namespace Unity.Netcode.Editor.CodeGen
var paramType = paramDef.ParameterType;
// local variable
nhandler.Body.Variables.Add(new VariableDefinition(paramType));
int localIndex = nhandler.Body.Variables.Count - 1;
rpcHandler.Body.Variables.Add(new VariableDefinition(paramType));
int localIndex = rpcHandler.Body.Variables.Count - 1;
paramLocalMap[paramIndex] = localIndex;
// ServerRpcParams, ClientRpcParams
@@ -1268,8 +1294,8 @@ namespace Unity.Netcode.Editor.CodeGen
}
// reader.ReadValueSafe(out bool isSet)
nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.Boolean));
int isSetLocalIndex = nhandler.Body.Variables.Count - 1;
rpcHandler.Body.Variables.Add(new VariableDefinition(typeSystem.Boolean));
int isSetLocalIndex = rpcHandler.Body.Variables.Count - 1;
processor.Emit(OpCodes.Ldarga, 1);
processor.Emit(OpCodes.Ldloca, isSetLocalIndex);
processor.Emit(OpCodes.Call, boolMethodRef);
@@ -1336,55 +1362,8 @@ namespace Unity.Netcode.Editor.CodeGen
processor.Emit(OpCodes.Ldc_I4, (int)NetworkBehaviour.__RpcExecStage.None);
processor.Emit(OpCodes.Stfld, m_NetworkBehaviour_rpc_exec_stage_FieldRef);
// pull in the Exception Module
var exception = m_MainModule.ImportReference(typeof(Exception));
// Get Exception.ToString()
var exp = m_MainModule.ImportReference(typeof(Exception).GetMethod("ToString", new Type[] { }));
// Get String.Format (This is equivalent to an interpolated string)
var stringFormat = m_MainModule.ImportReference(typeof(string).GetMethod("Format", new Type[] { typeof(string), typeof(object) }));
nhandler.Body.Variables.Add(new VariableDefinition(exception));
int exceptionVariableIndex = nhandler.Body.Variables.Count - 1;
//try ends/catch begins
var catchEnds = processor.Create(OpCodes.Nop);
processor.Emit(OpCodes.Leave, catchEnds);
// Load the Exception onto the stack
var catchStarts = processor.Create(OpCodes.Stloc, exceptionVariableIndex);
processor.Append(catchStarts);
// Load string for the error log that will be shown
processor.Emit(OpCodes.Ldstr, $"Unhandled RPC Exception:\n {{0}}");
processor.Emit(OpCodes.Ldloc, exceptionVariableIndex);
processor.Emit(OpCodes.Callvirt, exp);
processor.Emit(OpCodes.Call, stringFormat);
// Call Debug.LogError
processor.Emit(OpCodes.Call, m_Debug_LogError_MethodRef);
// reset NetworkBehaviour.__rpc_exec_stage = __RpcExecStage.None;
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Ldc_I4, (int)NetworkBehaviour.__RpcExecStage.None);
processor.Emit(OpCodes.Stfld, m_NetworkBehaviour_rpc_exec_stage_FieldRef);
// catch ends
processor.Append(catchEnds);
processor.Body.ExceptionHandlers.Add(new ExceptionHandler(ExceptionHandlerType.Catch)
{
CatchType = exception,
TryStart = tryStart,
TryEnd = catchStarts,
HandlerStart = catchStarts,
HandlerEnd = catchEnds
});
processor.Emit(OpCodes.Ret);
return nhandler;
return rpcHandler;
}
}
}

View File

@@ -134,8 +134,10 @@ namespace Unity.Netcode.Editor.CodeGen
foreach (var methodDefinition in typeDefinition.Methods)
{
if (methodDefinition.Name == nameof(NetworkBehaviour.__sendServerRpc)
|| methodDefinition.Name == nameof(NetworkBehaviour.__sendClientRpc))
if (methodDefinition.Name == nameof(NetworkBehaviour.__beginSendServerRpc) ||
methodDefinition.Name == nameof(NetworkBehaviour.__endSendServerRpc) ||
methodDefinition.Name == nameof(NetworkBehaviour.__beginSendClientRpc) ||
methodDefinition.Name == nameof(NetworkBehaviour.__endSendClientRpc))
{
methodDefinition.IsFamily = true;
}

View File

@@ -1,8 +0,0 @@
using System;
namespace Unity.Netcode.Editor
{
public class DontShowInTransportDropdownAttribute : Attribute
{
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 5f097067d4254dc7ad018d7ad90df7c3
timeCreated: 1620386886

View File

@@ -1,103 +1,23 @@
using System;
using Unity.Netcode.Components;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
namespace Unity.Netcode.Editor
{
public static class TextUtility
{
public static GUIContent TextContent(string name, string tooltip)
{
var newContent = new GUIContent(name);
newContent.tooltip = tooltip;
return newContent;
}
public static GUIContent TextContent(string name)
{
return new GUIContent(name);
}
}
[CustomEditor(typeof(NetworkAnimator), true)]
[CanEditMultipleObjects]
public class NetworkAnimatorEditor : UnityEditor.Editor
{
private NetworkAnimator m_AnimSync;
[NonSerialized] private bool m_Initialized;
private SerializedProperty m_AnimatorProperty;
private GUIContent m_AnimatorLabel;
private void Init()
{
if (m_Initialized)
{
return;
}
m_Initialized = true;
m_AnimSync = target as NetworkAnimator;
m_AnimatorProperty = serializedObject.FindProperty("m_Animator");
m_AnimatorLabel = TextUtility.TextContent("Animator", "The Animator component to synchronize.");
}
public override void OnInspectorGUI()
{
Init();
serializedObject.Update();
DrawControls();
serializedObject.ApplyModifiedProperties();
}
private void DrawControls()
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_AnimatorProperty, m_AnimatorLabel);
if (EditorGUI.EndChangeCheck())
{
m_AnimSync.ResetParameterOptions();
}
var label = new GUIContent("Animator", "The Animator component to synchronize");
EditorGUILayout.PropertyField(serializedObject.FindProperty("m_Animator"), label);
EditorGUI.EndChangeCheck();
if (m_AnimSync.Animator == null)
{
return;
}
var controller = m_AnimSync.Animator.runtimeAnimatorController as AnimatorController;
if (controller != null)
{
var showWarning = false;
EditorGUI.indentLevel += 1;
int i = 0;
foreach (var p in controller.parameters)
{
if (i >= NetworkAnimator.K_MaxAnimationParams)
{
showWarning = true;
break;
}
bool oldSend = m_AnimSync.GetParameterAutoSend(i);
bool send = EditorGUILayout.Toggle(p.name, oldSend);
if (send != oldSend)
{
m_AnimSync.SetParameterAutoSend(i, send);
EditorUtility.SetDirty(target);
}
i += 1;
}
if (showWarning)
{
EditorGUILayout.HelpBox($"NetworkAnimator can only select between the first {NetworkAnimator.K_MaxAnimationParams} parameters in a mecanim controller", MessageType.Warning);
}
EditorGUI.indentLevel -= 1;
}
serializedObject.ApplyModifiedProperties();
}
}
}

View File

@@ -57,7 +57,7 @@ namespace Unity.Netcode.Editor
foreach (var type in types)
{
if (type.IsSubclassOf(typeof(NetworkTransport)) && type.GetCustomAttributes(typeof(DontShowInTransportDropdownAttribute), true).Length == 0)
if (type.IsSubclassOf(typeof(NetworkTransport)) && !type.IsSubclassOf(typeof(TestingNetworkTransport)) && type != typeof(TestingNetworkTransport))
{
m_TransportTypes.Add(type);
}

View File

@@ -12,7 +12,7 @@ namespace Unity.Netcode.Editor
{
internal static NetworkManagerHelper Singleton;
// This is primarily to handle multiInstance scenarios where more than 1 NetworkManager could exist
// This is primarily to handle IntegrationTest scenarios where more than 1 NetworkManager could exist
private static Dictionary<NetworkManager, Transform> s_LastKnownNetworkManagerParents = new Dictionary<NetworkManager, Transform>();
/// <summary>

View File

@@ -8,18 +8,11 @@
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [
{
"name": "com.unity.multiplayer.tools",
"expression": "",
"define": "MULTIPLAYER_TOOLS"
}
],
"noEngineReferences": false
]
}

View File

@@ -1,3 +1,5 @@
# Netcode for GameObjects
[![Forums](https://img.shields.io/badge/unity--forums-multiplayer-blue)](https://forum.unity.com/forums/multiplayer.26/) [![Discord](https://img.shields.io/discord/449263083769036810.svg?label=discord&logo=discord&color=informational)](https://discord.gg/FM8SE9E)
[![Website](https://img.shields.io/badge/docs-website-informational.svg)](https://docs-multiplayer.unity3d.com/) [![Api](https://img.shields.io/badge/docs-api-informational.svg)](https://docs-multiplayer.unity3d.com/docs/mlapi-api/introduction)

View File

@@ -5,8 +5,10 @@ using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")]
[assembly: InternalsVisibleTo("Unity.Netcode.Editor")]
[assembly: InternalsVisibleTo("TestProject.EditorTests")]
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")]
[assembly: InternalsVisibleTo("TestProject.ToolsIntegration.RuntimeTests")]
#endif
[assembly: InternalsVisibleTo("TestProject.ToolsIntegration.RuntimeTests")]
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")]
[assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")]
[assembly: InternalsVisibleTo("Unity.Netcode.TestHelpers.Runtime")]
[assembly: InternalsVisibleTo("Unity.Netcode.Adapter.UTP")]

View File

@@ -239,6 +239,8 @@ namespace Unity.Netcode
writer.WriteValueSafe(sortedEntry.Key);
}
}
writer.WriteValueSafe(TickRate);
writer.WriteValueSafe(ConnectionApproval);
writer.WriteValueSafe(ForceSamePrefabs);
writer.WriteValueSafe(EnableSceneManagement);

View File

@@ -15,41 +15,56 @@ namespace Unity.Netcode
#pragma warning disable IDE1006 // disable naming rule violation check
// RuntimeAccessModifiersILPP will make this `protected`
internal enum __RpcExecStage
#pragma warning restore IDE1006 // restore naming rule violation check
{
None = 0,
Server = 1,
Client = 2
}
#pragma warning disable IDE1006 // disable naming rule violation check
// NetworkBehaviourILPP will override this in derived classes to return the name of the concrete type
internal virtual string __getTypeName() => nameof(NetworkBehaviour);
#pragma warning restore IDE1006 // restore naming rule violation check
#pragma warning disable 414 // disable assigned but its value is never used
#pragma warning disable IDE1006 // disable naming rule violation check
[NonSerialized]
// RuntimeAccessModifiersILPP will make this `protected`
internal __RpcExecStage __rpc_exec_stage = __RpcExecStage.None;
#pragma warning restore 414 // restore assigned but its value is never used
#pragma warning restore IDE1006 // restore naming rule violation check
#pragma warning disable 414 // disable assigned but its value is never used
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 void __sendServerRpc(FastBufferWriter writer, uint rpcMethodId, ServerRpcParams rpcParams, RpcDelivery delivery)
#pragma warning restore 414 // restore assigned but its value is never used
internal FastBufferWriter __beginSendServerRpc(uint rpcMethodId, ServerRpcParams serverRpcParams, RpcDelivery rpcDelivery)
#pragma warning restore IDE1006 // restore naming rule violation check
{
NetworkDelivery networkDelivery = NetworkDelivery.Reliable;
switch (delivery)
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 (writer.Length > MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE - sizeof(RpcMessage.RpcType) - sizeof(ulong) - sizeof(uint) - sizeof(ushort))
if (bufferWriter.Length > MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE)
{
throw new OverflowException("RPC parameters are too large for unreliable delivery.");
}
@@ -57,42 +72,33 @@ namespace Unity.Netcode
break;
}
var message = new RpcMessage
{
Header = new RpcMessage.HeaderData
{
Type = RpcMessage.RpcType.Server,
NetworkObjectId = NetworkObjectId,
NetworkBehaviourId = NetworkBehaviourId,
NetworkMethodId = rpcMethodId
},
RpcData = writer
};
var rpcMessageSize = 0;
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(writer, Allocator.Temp);
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.
// Passing false to canDefer prevents it being accessed.
// RpcMessage doesn't access this stuff so it's just left empty.
Header = new MessageHeader(),
SerializedHeaderSize = 0,
MessageSize = 0
};
message.Handle(tempBuffer, context, NetworkManager, NetworkManager.ServerClientId, false);
rpcMessageSize = tempBuffer.Length;
serverRpcMessage.ReadBuffer = tempBuffer;
serverRpcMessage.Handle(ref context);
rpcWriteSize = tempBuffer.Length;
}
else
{
rpcMessageSize = NetworkManager.SendMessage(message, networkDelivery, NetworkManager.ServerClientId);
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))
@@ -102,26 +108,44 @@ namespace Unity.Netcode
NetworkObject,
rpcMethodName,
__getTypeName(),
rpcMessageSize);
rpcWriteSize);
}
#endif
}
#pragma warning disable 414 // disable assigned but its value is never used
#pragma warning disable IDE1006 // disable naming rule violation check
// RuntimeAccessModifiersILPP will make this `protected`
internal unsafe void __sendClientRpc(FastBufferWriter writer, uint rpcMethodId, ClientRpcParams rpcParams, RpcDelivery delivery)
#pragma warning disable 414 // disable assigned but its value is never used
#pragma warning disable IDE1006 // disable naming rule violation check
internal FastBufferWriter __beginSendClientRpc(uint rpcMethodId, ClientRpcParams clientRpcParams, RpcDelivery rpcDelivery)
#pragma warning restore IDE1006 // restore naming rule violation check
{
NetworkDelivery networkDelivery = NetworkDelivery.Reliable;
switch (delivery)
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 (writer.Length > MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE - sizeof(RpcMessage.RpcType) - sizeof(ulong) - sizeof(uint) - sizeof(ushort))
if (bufferWriter.Length > MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE)
{
throw new OverflowException("RPC parameters are too large for unreliable delivery.");
}
@@ -129,26 +153,15 @@ namespace Unity.Netcode
break;
}
var message = new RpcMessage
{
Header = new RpcMessage.HeaderData
{
Type = RpcMessage.RpcType.Client,
NetworkObjectId = NetworkObjectId,
NetworkBehaviourId = NetworkBehaviourId,
NetworkMethodId = rpcMethodId
},
RpcData = writer
};
int messageSize;
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 (rpcParams.Send.TargetClientIds != null)
if (clientRpcParams.Send.TargetClientIds != null)
{
foreach (var clientId in rpcParams.Send.TargetClientIds)
foreach (var clientId in clientRpcParams.Send.TargetClientIds)
{
if (clientId == NetworkManager.ServerClientId)
{
@@ -157,11 +170,11 @@ namespace Unity.Netcode
}
}
messageSize = NetworkManager.SendMessage(message, networkDelivery, in rpcParams.Send.TargetClientIds);
rpcWriteSize = NetworkManager.SendMessage(ref clientRpcMessage, networkDelivery, in clientRpcParams.Send.TargetClientIds);
}
else if (rpcParams.Send.TargetClientIdsNativeArray != null)
else if (clientRpcParams.Send.TargetClientIdsNativeArray != null)
{
foreach (var clientId in rpcParams.Send.TargetClientIdsNativeArray)
foreach (var clientId in clientRpcParams.Send.TargetClientIdsNativeArray)
{
if (clientId == NetworkManager.ServerClientId)
{
@@ -170,32 +183,35 @@ namespace Unity.Netcode
}
}
messageSize = NetworkManager.SendMessage(message, networkDelivery, rpcParams.Send.TargetClientIdsNativeArray.Value);
rpcWriteSize = NetworkManager.SendMessage(ref clientRpcMessage, networkDelivery, clientRpcParams.Send.TargetClientIdsNativeArray.Value);
}
else
{
shouldSendToHost = IsHost;
messageSize = NetworkManager.SendMessage(message, networkDelivery, NetworkManager.ConnectedClientsIds);
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(writer, Allocator.Temp);
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.
// Passing false to canDefer prevents it being accessed.
// RpcMessage doesn't access this stuff so it's just left empty.
Header = new MessageHeader(),
SerializedHeaderSize = 0,
MessageSize = 0
};
message.Handle(tempBuffer, context, NetworkManager, NetworkManager.ServerClientId, false);
messageSize = tempBuffer.Length;
clientRpcMessage.ReadBuffer = tempBuffer;
clientRpcMessage.Handle(ref context);
}
bufferWriter.Dispose();
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName))
{
@@ -206,7 +222,7 @@ namespace Unity.Netcode
NetworkObject,
rpcMethodName,
__getTypeName(),
messageSize);
rpcWriteSize);
}
}
#endif
@@ -260,9 +276,9 @@ namespace Unity.Netcode
{
// 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 ||
m_NetworkObject.NetworkManager.IsServer);
m_NetworkObject.NetworkManager == null ||
m_NetworkObject.NetworkManager.IsListening == false ||
m_NetworkObject.NetworkManager.IsServer;
}
/// <summary>
@@ -284,10 +300,14 @@ namespace Unity.Netcode
m_NetworkObject = GetComponentInParent<NetworkObject>();
}
if (m_NetworkObject == null || NetworkManager.Singleton == null ||
(NetworkManager.Singleton != null && !NetworkManager.Singleton.ShutdownInProgress))
// 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)
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?");
}
@@ -349,10 +369,7 @@ namespace Unity.Netcode
InitializeVariables();
}
internal void InternalOnNetworkDespawn()
{
}
internal void InternalOnNetworkDespawn() { }
/// <summary>
/// Gets called when the local client gains ownership of this object
@@ -428,8 +445,7 @@ namespace Unity.Netcode
if (instance == null)
{
instance = (NetworkVariableBase)Activator.CreateInstance(fieldType, true);
sortedFields[i].SetValue(this, instance);
throw new Exception($"{GetType().FullName}.{sortedFields[i].Name} cannot be null. All {nameof(NetworkVariableBase)} instances must be initialized.");
}
instance.Initialize(this);
@@ -542,7 +558,7 @@ namespace Unity.Netcode
// 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);
var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, MessagingSystem.FRAGMENTED_MESSAGE_MAX_SIZE);
using (tmpWriter)
{
message.Serialize(tmpWriter);
@@ -550,7 +566,7 @@ namespace Unity.Netcode
}
else
{
NetworkManager.SendMessage(message, m_DeliveryTypesForNetworkVariableGroups[j], clientId);
NetworkManager.SendMessage(ref message, m_DeliveryTypesForNetworkVariableGroups[j], clientId);
}
}
}

View File

@@ -85,11 +85,11 @@ namespace Unity.Netcode
m_NetworkManager = manager;
}
public void OnBeforeSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery)
public void OnBeforeSendMessage<T>(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage
{
}
public void OnAfterSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery, int messageSizeBytes)
public void OnAfterSendMessage<T>(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage
{
}
@@ -139,6 +139,14 @@ namespace Unity.Netcode
return !m_NetworkManager.m_StopProcessingMessages;
}
public void OnBeforeHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage
{
}
public void OnAfterHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage
{
}
}
private class NetworkManagerMessageSender : IMessageSender
@@ -555,6 +563,8 @@ namespace Unity.Netcode
return;
}
NetworkConfig.NetworkTransport.NetworkMetrics = NetworkMetrics;
//This 'if' should never enter
if (SnapshotSystem != null)
{
@@ -583,6 +593,7 @@ namespace Unity.Netcode
// Always clear our prefab override links before building
NetworkConfig.NetworkPrefabOverrideLinks.Clear();
NetworkConfig.OverrideToNetworkPrefab.Clear();
// Build the NetworkPrefabOverrideLinks dictionary
for (int i = 0; i < NetworkConfig.NetworkPrefabs.Count; i++)
@@ -779,7 +790,7 @@ namespace Unity.Netcode
NetworkConfig.NetworkTransport.OnTransportEvent += HandleRawTransportPoll;
NetworkConfig.NetworkTransport.Initialize();
NetworkConfig.NetworkTransport.Initialize(this);
}
/// <summary>
@@ -1299,7 +1310,7 @@ namespace Unity.Netcode
ShouldSendConnectionData = NetworkConfig.ConnectionApproval,
ConnectionData = NetworkConfig.ConnectionData
};
SendMessage(message, NetworkDelivery.ReliableSequenced, ServerClientId);
SendMessage(ref message, NetworkDelivery.ReliableSequenced, ServerClientId);
}
private IEnumerator ApprovalTimeout(ulong clientId)
@@ -1324,12 +1335,12 @@ namespace Unity.Netcode
}
}
private ulong TransportIdToClientId(ulong transportId)
internal ulong TransportIdToClientId(ulong transportId)
{
return transportId == m_ServerTransportId ? ServerClientId : m_TransportIdToClientIdMap[transportId];
}
private ulong ClientIdToTransportId(ulong clientId)
internal ulong ClientIdToTransportId(ulong clientId)
{
return clientId == ServerClientId ? m_ServerTransportId : m_ClientIdToTransportIdMap[clientId];
}
@@ -1344,7 +1355,20 @@ namespace Unity.Netcode
s_TransportConnect.Begin();
#endif
clientId = m_NextClientId++;
// Assumptions:
// - When server receives a connection, it *must be* a client
// - When client receives one, it *must be* the server
// Client's can't connect to or talk to other clients.
// Server is a sentinel so only one exists, if we are server, we can't be
// connecting to it.
if (IsServer)
{
clientId = m_NextClientId++;
}
else
{
clientId = ServerClientId;
}
m_ClientIdToTransportIdMap[clientId] = transportId;
m_TransportIdToClientIdMap[transportId] = clientId;
@@ -1422,7 +1446,7 @@ namespace Unity.Netcode
}
}
internal unsafe int SendMessage<TMessageType, TClientIdListType>(in TMessageType message, NetworkDelivery delivery, in TClientIdListType clientIds)
internal unsafe int SendMessage<TMessageType, TClientIdListType>(ref TMessageType message, NetworkDelivery delivery, in TClientIdListType clientIds)
where TMessageType : INetworkMessage
where TClientIdListType : IReadOnlyList<ulong>
{
@@ -1445,12 +1469,18 @@ namespace Unity.Netcode
{
return 0;
}
return MessagingSystem.SendMessage(message, delivery, nonServerIds, newIdx);
return MessagingSystem.SendMessage(ref message, delivery, nonServerIds, newIdx);
}
return MessagingSystem.SendMessage(message, delivery, clientIds);
// else
if (clientIds.Count != 1 || clientIds[0] != ServerClientId)
{
throw new ArgumentException($"Clients may only send messages to {nameof(ServerClientId)}");
}
return MessagingSystem.SendMessage(ref message, delivery, clientIds);
}
internal unsafe int SendMessage<T>(in T message, NetworkDelivery delivery,
internal unsafe int SendMessage<T>(ref T message, NetworkDelivery delivery,
ulong* clientIds, int numClientIds)
where T : INetworkMessage
{
@@ -1473,19 +1503,24 @@ namespace Unity.Netcode
{
return 0;
}
return MessagingSystem.SendMessage(message, delivery, nonServerIds, newIdx);
return MessagingSystem.SendMessage(ref message, delivery, nonServerIds, newIdx);
}
// else
if (numClientIds != 1 || clientIds[0] != ServerClientId)
{
throw new ArgumentException($"Clients may only send messages to {nameof(ServerClientId)}");
}
return MessagingSystem.SendMessage(message, delivery, clientIds, numClientIds);
return MessagingSystem.SendMessage(ref message, delivery, clientIds, numClientIds);
}
internal unsafe int SendMessage<T>(in T message, NetworkDelivery delivery, in NativeArray<ulong> clientIds)
internal unsafe int SendMessage<T>(ref T message, NetworkDelivery delivery, in NativeArray<ulong> clientIds)
where T : INetworkMessage
{
return SendMessage(message, delivery, (ulong*)clientIds.GetUnsafePtr(), clientIds.Length);
return SendMessage(ref message, delivery, (ulong*)clientIds.GetUnsafePtr(), clientIds.Length);
}
internal int SendMessage<T>(in T message, NetworkDelivery delivery, ulong clientId)
internal int SendMessage<T>(ref T message, NetworkDelivery delivery, ulong clientId)
where T : INetworkMessage
{
// Prevent server sending to itself
@@ -1493,7 +1528,18 @@ namespace Unity.Netcode
{
return 0;
}
return MessagingSystem.SendMessage(message, delivery, clientId);
if (!IsServer && clientId != ServerClientId)
{
throw new ArgumentException($"Clients may only send messages to {nameof(ServerClientId)}");
}
return MessagingSystem.SendMessage(ref message, delivery, clientId);
}
internal int SendPreSerializedMessage<T>(in FastBufferWriter writer, int maxSize, ref T message, NetworkDelivery delivery, ulong clientId)
where T : INetworkMessage
{
return MessagingSystem.SendPreSerializedMessage(writer, maxSize, ref message, delivery, clientId);
}
internal void HandleIncomingData(ulong clientId, ArraySegment<byte> payload, float receiveTime)
@@ -1517,7 +1563,7 @@ namespace Unity.Netcode
{
if (!IsServer)
{
throw new NotServerException("Only server can disconnect remote clients. Use StopClient instead.");
throw new NotServerException($"Only server can disconnect remote clients. Please use `{nameof(Shutdown)}()` instead.");
}
OnClientDisconnectFromServer(clientId);
@@ -1614,7 +1660,7 @@ namespace Unity.Netcode
{
Tick = NetworkTickSystem.ServerTime.Tick
};
SendMessage(message, NetworkDelivery.Unreliable, ConnectedClientsIds);
SendMessage(ref message, NetworkDelivery.Unreliable, ConnectedClientsIds);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
s_SyncTime.End();
#endif
@@ -1661,12 +1707,11 @@ namespace Unity.Netcode
{
if (SpawnManager.SpawnedObjectsList.Count != 0)
{
message.SceneObjectCount = SpawnManager.SpawnedObjectsList.Count;
message.SpawnedObjectsList = SpawnManager.SpawnedObjectsList;
}
}
SendMessage(message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);
SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);
// If scene management is enabled, then let NetworkSceneManager handle the initial scene and NetworkObject synchronization
if (!NetworkConfig.EnableSceneManagement)
@@ -1726,7 +1771,7 @@ namespace Unity.Netcode
message.ObjectInfo.Header.HasParent = false;
message.ObjectInfo.Header.IsPlayerObject = true;
message.ObjectInfo.Header.OwnerClientId = clientId;
var size = SendMessage(message, NetworkDelivery.ReliableFragmentedSequenced, clientPair.Key);
var size = SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientPair.Key);
NetworkMetrics.TrackObjectSpawnSent(clientPair.Key, ConnectedClients[clientId].PlayerObject, size);
}
}

View File

@@ -39,7 +39,7 @@ namespace Unity.Netcode
var globalObjectIdString = UnityEditor.GlobalObjectId.GetGlobalObjectIdSlow(this).ToString();
GlobalObjectIdHash = XXHash.Hash32(globalObjectIdString);
}
#endif
#endif // UNITY_EDITOR
/// <summary>
/// Gets the NetworkManager that owns this NetworkObject instance
@@ -328,7 +328,7 @@ namespace Unity.Netcode
NetworkObjectId = NetworkObjectId
};
// Send destroy call
var size = NetworkManager.SendMessage(message, NetworkDelivery.ReliableSequenced, clientId);
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId);
NetworkManager.NetworkMetrics.TrackObjectDestroySent(clientId, this, size);
}
}
@@ -714,7 +714,7 @@ namespace Unity.Netcode
}
}
NetworkManager.SendMessage(message, NetworkDelivery.ReliableSequenced, clientIds, idx);
NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientIds, idx);
}
}
@@ -1142,8 +1142,7 @@ namespace Unity.Netcode
var globalObjectIdHash = NetworkManager.PrefabHandler.GetSourceGlobalObjectIdHash(GlobalObjectIdHash);
return globalObjectIdHash == 0 ? GlobalObjectIdHash : globalObjectIdHash;
}
else
if (NetworkManager.NetworkConfig.OverrideToNetworkPrefab.ContainsKey(GlobalObjectIdHash))
else if (NetworkManager.NetworkConfig.OverrideToNetworkPrefab.ContainsKey(GlobalObjectIdHash))
{
return NetworkManager.NetworkConfig.OverrideToNetworkPrefab[GlobalObjectIdHash];
}

View File

@@ -87,7 +87,7 @@ namespace Unity.Netcode
internal List<SentSpawn> SentSpawns = new List<SentSpawn>();
}
internal delegate int MockSendMessage(in SnapshotDataMessage message, NetworkDelivery delivery, ulong clientId);
internal delegate int MockSendMessage(ref SnapshotDataMessage message, NetworkDelivery delivery, ulong clientId);
internal delegate int MockSpawnObject(SnapshotSpawnCommand spawnCommand);
internal delegate int MockDespawnObject(SnapshotDespawnCommand despawnCommand);
@@ -813,13 +813,22 @@ namespace Unity.Netcode
WriteIndex(ref message);
WriteSpawns(ref message, clientId);
if (m_NetworkManager)
try
{
m_NetworkManager.SendMessage(message, NetworkDelivery.Unreliable, clientId);
if (m_NetworkManager)
{
m_NetworkManager.SendMessage(ref message, NetworkDelivery.Unreliable, clientId);
}
else
{
MockSendMessage(ref message, NetworkDelivery.Unreliable, clientId);
}
}
else
finally
{
MockSendMessage(message, NetworkDelivery.Unreliable, clientId);
message.Entries.Dispose();
message.Spawns.Dispose();
message.Despawns.Dispose();
}
m_ClientData[clientId].LastReceivedSequence = 0;

View File

@@ -62,8 +62,7 @@ namespace Unity.Netcode
LogType = logType,
Message = message
};
var size = NetworkManager.Singleton.SendMessage(networkMessage, NetworkDelivery.ReliableFragmentedSequenced,
NetworkManager.Singleton.ServerClientId);
var size = NetworkManager.Singleton.SendMessage(ref networkMessage, NetworkDelivery.ReliableFragmentedSequenced, NetworkManager.Singleton.ServerClientId);
NetworkManager.Singleton.NetworkMetrics.TrackServerLogSent(NetworkManager.Singleton.ServerClientId, (uint)logType, size);
}

View File

@@ -73,9 +73,9 @@ namespace Unity.Netcode
var message = new UnnamedMessage
{
Data = messageBuffer
SendData = messageBuffer
};
var size = m_NetworkManager.SendMessage(message, networkDelivery, clientIds);
var size = m_NetworkManager.SendMessage(ref message, networkDelivery, clientIds);
// Size is zero if we were only sending the message to ourself in which case it isn't sent.
if (size != 0)
@@ -94,9 +94,9 @@ namespace Unity.Netcode
{
var message = new UnnamedMessage
{
Data = messageBuffer
SendData = messageBuffer
};
var size = m_NetworkManager.SendMessage(message, networkDelivery, clientId);
var size = m_NetworkManager.SendMessage(ref message, networkDelivery, clientId);
// Size is zero if we were only sending the message to ourself in which case it isn't sent.
if (size != 0)
{
@@ -223,9 +223,9 @@ namespace Unity.Netcode
var message = new NamedMessage
{
Hash = hash,
Data = messageStream
SendData = messageStream,
};
var size = m_NetworkManager.SendMessage(message, networkDelivery, clientId);
var size = m_NetworkManager.SendMessage(ref message, networkDelivery, clientId);
// Size is zero if we were only sending the message to ourself in which case it isn't sent.
if (size != 0)
@@ -266,9 +266,9 @@ namespace Unity.Netcode
var message = new NamedMessage
{
Hash = hash,
Data = messageStream
SendData = messageStream
};
var size = m_NetworkManager.SendMessage(message, networkDelivery, clientIds);
var size = m_NetworkManager.SendMessage(ref message, networkDelivery, clientIds);
// Size is zero if we were only sending the message to ourself in which case it isn't sent.
if (size != 0)

View File

@@ -13,18 +13,18 @@ namespace Unity.Netcode
/// Called before an individual message is sent.
/// </summary>
/// <param name="clientId">The destination clientId</param>
/// <param name="messageType">The type of the message being sent</param>
/// <param name="message">The message being sent</param>
/// <param name="delivery"></param>
void OnBeforeSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery);
void OnBeforeSendMessage<T>(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage;
/// <summary>
/// Called after an individual message is sent.
/// </summary>
/// <param name="clientId">The destination clientId</param>
/// <param name="messageType">The type of the message being sent</param>
/// <param name="message">The message being sent</param>
/// <param name="delivery"></param>
/// <param name="messageSizeBytes">Number of bytes in the message, not including the message header</param>
void OnAfterSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery, int messageSizeBytes);
void OnAfterSendMessage<T>(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage;
/// <summary>
/// Called before an individual message is received.
@@ -93,5 +93,23 @@ namespace Unity.Netcode
/// <param name="messageType">The type of the message</param>
/// <returns></returns>
bool OnVerifyCanReceive(ulong senderId, Type messageType);
/// <summary>
/// Called after a message is serialized, but before it's handled.
/// Differs from OnBeforeReceiveMessage in that the actual message object is passed and can be inspected.
/// </summary>
/// <param name="message">The message object</param>
/// <param name="context">The network context the message is being ahandled in</param>
/// <typeparam name="T"></typeparam>
void OnBeforeHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage;
/// <summary>
/// Called after a message is serialized and handled.
/// Differs from OnAfterReceiveMessage in that the actual message object is passed and can be inspected.
/// </summary>
/// <param name="message">The message object</param>
/// <param name="context">The network context the message is being ahandled in</param>
/// <typeparam name="T"></typeparam>
void OnAfterHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage;
}
}

View File

@@ -15,7 +15,7 @@ namespace Unity.Netcode
/// static message handler for receiving messages of the following name and signature:
///
/// <code>
/// public static void Receive(FastBufferReader reader, in NetworkContext context)
/// public static void Receive(FastBufferReader reader, ref NetworkContext context)
/// </code>
///
/// It is the responsibility of the Serialize and Receive methods to ensure there is enough buffer space
@@ -40,10 +40,8 @@ namespace Unity.Netcode
/// </summary>
internal interface INetworkMessage
{
/// <summary>
/// Used to serialize the message.
/// </summary>
/// <param name="writer"></param>
void Serialize(FastBufferWriter writer);
bool Deserialize(FastBufferReader reader, ref NetworkContext context);
void Handle(ref NetworkContext context);
}
}

View File

@@ -10,24 +10,28 @@ namespace Unity.Netcode
writer.WriteValueSafe(this);
}
public static void Receive(FastBufferReader reader, in NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
{
return;
return false;
}
reader.ReadValueSafe(out ChangeOwnershipMessage message);
message.Handle(reader, context, context.SenderId, networkManager, reader.Length);
reader.ReadValueSafe(out this);
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
{
networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, reader, ref context);
return false;
}
return true;
}
public void Handle(FastBufferReader reader, in NetworkContext context, ulong senderId, NetworkManager networkManager, int messageSize)
public void Handle(ref NetworkContext context)
{
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
{
networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, reader, context);
return;
}
var networkManager = (NetworkManager)context.SystemOwner;
var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId];
if (networkObject.OwnerClientId == networkManager.LocalClientId)
{
@@ -43,7 +47,7 @@ namespace Unity.Netcode
networkObject.InvokeBehaviourOnGainedOwnership();
}
networkManager.NetworkMetrics.TrackOwnershipChangeReceived(senderId, networkObject, messageSize);
networkManager.NetworkMetrics.TrackOwnershipChangeReceived(context.SenderId, networkObject, context.MessageSize);
}
}
}

View File

@@ -7,24 +7,27 @@ namespace Unity.Netcode
{
public ulong OwnerClientId;
public int NetworkTick;
public int SceneObjectCount;
// Not serialized, held as references to serialize NetworkVariable data
public HashSet<NetworkObject> SpawnedObjectsList;
private FastBufferReader m_ReceivedSceneObjectData;
public void Serialize(FastBufferWriter writer)
{
if (!writer.TryBeginWrite(sizeof(ulong) + sizeof(int) + sizeof(int)))
{
throw new OverflowException(
$"Not enough space in the write buffer to serialize {nameof(ConnectionApprovedMessage)}");
throw new OverflowException($"Not enough space in the write buffer to serialize {nameof(ConnectionApprovedMessage)}");
}
writer.WriteValue(OwnerClientId);
writer.WriteValue(NetworkTick);
writer.WriteValue(SceneObjectCount);
if (SceneObjectCount != 0)
uint sceneObjectCount = 0;
if (SpawnedObjectsList != null)
{
var pos = writer.Position;
writer.Seek(writer.Position + FastBufferWriter.GetWriteSize(sceneObjectCount));
// Serialize NetworkVariable data
foreach (var sobj in SpawnedObjectsList)
{
@@ -33,34 +36,41 @@ namespace Unity.Netcode
sobj.Observers.Add(OwnerClientId);
var sceneObject = sobj.GetMessageSceneObject(OwnerClientId);
sceneObject.Serialize(writer);
++sceneObjectCount;
}
}
writer.Seek(pos);
writer.WriteValue(sceneObjectCount);
writer.Seek(writer.Length);
}
else
{
writer.WriteValue(sceneObjectCount);
}
}
public static void Receive(FastBufferReader reader, in NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
{
return;
return false;
}
if (!reader.TryBeginRead(sizeof(ulong) + sizeof(int) + sizeof(int)))
{
throw new OverflowException(
$"Not enough space in the buffer to read {nameof(ConnectionApprovedMessage)}");
throw new OverflowException($"Not enough space in the buffer to read {nameof(ConnectionApprovedMessage)}");
}
var message = new ConnectionApprovedMessage();
reader.ReadValue(out message.OwnerClientId);
reader.ReadValue(out message.NetworkTick);
reader.ReadValue(out message.SceneObjectCount);
message.Handle(reader, context.SenderId, networkManager);
reader.ReadValue(out OwnerClientId);
reader.ReadValue(out NetworkTick);
m_ReceivedSceneObjectData = reader;
return true;
}
public void Handle(FastBufferReader reader, ulong clientId, NetworkManager networkManager)
public void Handle(ref NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
networkManager.LocalClientId = OwnerClientId;
networkManager.NetworkMetrics.SetConnectionId(networkManager.LocalClientId);
@@ -74,20 +84,21 @@ namespace Unity.Netcode
if (!networkManager.NetworkConfig.EnableSceneManagement)
{
networkManager.SpawnManager.DestroySceneObjects();
m_ReceivedSceneObjectData.ReadValue(out uint sceneObjectCount);
// Deserializing NetworkVariable data is deferred from Receive() to Handle to avoid needing
// to create a list to hold the data. This is a breach of convention for performance reasons.
for (ushort i = 0; i < SceneObjectCount; i++)
for (ushort i = 0; i < sceneObjectCount; i++)
{
var sceneObject = new NetworkObject.SceneObject();
sceneObject.Deserialize(reader);
NetworkObject.AddSceneObject(sceneObject, reader, networkManager);
sceneObject.Deserialize(m_ReceivedSceneObjectData);
NetworkObject.AddSceneObject(sceneObject, m_ReceivedSceneObjectData, networkManager);
}
// Mark the client being connected
networkManager.IsConnectedClient = true;
// When scene management is disabled we notify after everything is synchronized
networkManager.InvokeOnClientConnectedCallback(clientId);
networkManager.InvokeOnClientConnectedCallback(context.SenderId);
}
}
}

View File

@@ -21,19 +21,17 @@ namespace Unity.Netcode
}
}
public static void Receive(FastBufferReader reader, in NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsServer)
{
return;
return false;
}
var message = new ConnectionRequestMessage();
if (networkManager.NetworkConfig.ConnectionApproval)
{
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(message.ConfigHash) +
FastBufferWriter.GetWriteSize<int>()))
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(ConfigHash) + FastBufferWriter.GetWriteSize<int>()))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
@@ -41,11 +39,12 @@ namespace Unity.Netcode
}
networkManager.DisconnectClient(context.SenderId);
return;
return false;
}
reader.ReadValue(out message.ConfigHash);
if (!networkManager.NetworkConfig.CompareConfig(message.ConfigHash))
reader.ReadValue(out ConfigHash);
if (!networkManager.NetworkConfig.CompareConfig(ConfigHash))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
@@ -53,14 +52,14 @@ namespace Unity.Netcode
}
networkManager.DisconnectClient(context.SenderId);
return;
return false;
}
reader.ReadValueSafe(out message.ConnectionData);
reader.ReadValueSafe(out ConnectionData);
}
else
{
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(message.ConfigHash)))
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(ConfigHash)))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
@@ -68,11 +67,11 @@ namespace Unity.Netcode
}
networkManager.DisconnectClient(context.SenderId);
return;
return false;
}
reader.ReadValue(out message.ConfigHash);
reader.ReadValue(out ConfigHash);
if (!networkManager.NetworkConfig.CompareConfig(message.ConfigHash))
if (!networkManager.NetworkConfig.CompareConfig(ConfigHash))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
@@ -80,14 +79,18 @@ namespace Unity.Netcode
}
networkManager.DisconnectClient(context.SenderId);
return;
return false;
}
}
message.Handle(networkManager, context.SenderId);
return true;
}
public void Handle(NetworkManager networkManager, ulong senderId)
public void Handle(ref NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
var senderId = context.SenderId;
if (networkManager.PendingClients.TryGetValue(senderId, out PendingClient client))
{
// Set to pending approval to prevent future connection requests from being approved
@@ -102,8 +105,7 @@ namespace Unity.Netcode
(createPlayerObject, playerPrefabHash, approved, position, rotation) =>
{
var localCreatePlayerObject = createPlayerObject;
networkManager.HandleApproval(senderId, localCreatePlayerObject, playerPrefabHash, approved,
position, rotation);
networkManager.HandleApproval(senderId, localCreatePlayerObject, playerPrefabHash, approved, position, rotation);
});
}
else

View File

@@ -3,28 +3,33 @@ namespace Unity.Netcode
internal struct CreateObjectMessage : INetworkMessage
{
public NetworkObject.SceneObject ObjectInfo;
private FastBufferReader m_ReceivedNetworkVariableData;
public void Serialize(FastBufferWriter writer)
{
ObjectInfo.Serialize(writer);
}
public static void Receive(FastBufferReader reader, in NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
{
return;
return false;
}
var message = new CreateObjectMessage();
message.ObjectInfo.Deserialize(reader);
message.Handle(context.SenderId, reader, networkManager);
ObjectInfo.Deserialize(reader);
m_ReceivedNetworkVariableData = reader;
return true;
}
public void Handle(ulong senderId, FastBufferReader reader, NetworkManager networkManager)
public void Handle(ref NetworkContext context)
{
var networkObject = NetworkObject.AddSceneObject(ObjectInfo, reader, networkManager);
networkManager.NetworkMetrics.TrackObjectSpawnReceived(senderId, networkObject, reader.Length);
var networkManager = (NetworkManager)context.SystemOwner;
var networkObject = NetworkObject.AddSceneObject(ObjectInfo, m_ReceivedNetworkVariableData, networkManager);
networkManager.NetworkMetrics.TrackObjectSpawnReceived(context.SenderId, networkObject, context.MessageSize);
}
}
}

View File

@@ -9,32 +9,27 @@ namespace Unity.Netcode
writer.WriteValueSafe(this);
}
public static void Receive(FastBufferReader reader, in NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
{
return;
return false;
}
reader.ReadValueSafe(out DestroyObjectMessage message);
message.Handle(context.SenderId, networkManager, reader.Length);
reader.ReadValueSafe(out this);
return true;
}
public void Handle(ulong senderId, NetworkManager networkManager, int messageSize)
public void Handle(ref NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
{
// This is the same check and log message that happens inside OnDespawnObject, but we have to do it here
// while we still have access to the network ID, otherwise the log message will be less useful.
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"Trying to destroy {nameof(NetworkObject)} #{NetworkObjectId} but it does not exist in {nameof(NetworkSpawnManager.SpawnedObjects)} anymore!");
}
return;
}
networkManager.NetworkMetrics.TrackObjectDestroyReceived(senderId, networkObject, messageSize);
networkManager.NetworkMetrics.TrackObjectDestroyReceived(context.SenderId, networkObject, context.MessageSize);
networkManager.SpawnManager.OnDespawnObject(networkObject, true);
}
}

View File

@@ -3,20 +3,26 @@ namespace Unity.Netcode
internal struct NamedMessage : INetworkMessage
{
public ulong Hash;
public FastBufferWriter Data;
public FastBufferWriter SendData;
private FastBufferReader m_ReceiveData;
public unsafe void Serialize(FastBufferWriter writer)
{
writer.WriteValueSafe(Hash);
writer.WriteBytesSafe(Data.GetUnsafePtr(), Data.Length);
writer.WriteBytesSafe(SendData.GetUnsafePtr(), SendData.Length);
}
public static void Receive(FastBufferReader reader, in NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
{
var message = new NamedMessage();
reader.ReadValueSafe(out message.Hash);
reader.ReadValueSafe(out Hash);
m_ReceiveData = reader;
return true;
}
((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeNamedMessage(message.Hash, context.SenderId, reader, context.SerializedHeaderSize);
public void Handle(ref NetworkContext context)
{
((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeNamedMessage(Hash, context.SenderId, m_ReceiveData, context.SerializedHeaderSize);
}
}
}

View File

@@ -19,16 +19,18 @@ namespace Unity.Netcode
public ulong ClientId;
public NetworkBehaviour NetworkBehaviour;
private FastBufferReader m_ReceivedNetworkVariableData;
public void Serialize(FastBufferWriter writer)
{
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(NetworkObjectId) +
FastBufferWriter.GetWriteSize(NetworkBehaviourIndex)))
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(NetworkObjectId) + FastBufferWriter.GetWriteSize(NetworkBehaviourIndex)))
{
throw new OverflowException(
$"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}");
throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}");
}
writer.WriteValue(NetworkObjectId);
writer.WriteValue(NetworkBehaviourIndex);
for (int k = 0; k < NetworkBehaviour.NetworkVariableFields.Count; k++)
{
if (!DeliveryMappedNetworkVariableIndex.Contains(k))
@@ -36,7 +38,7 @@ namespace Unity.Netcode
// This var does not belong to the currently iterating delivery group.
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
writer.WriteValueSafe((short)0);
writer.WriteValueSafe((ushort)0);
}
else
{
@@ -69,7 +71,12 @@ namespace Unity.Netcode
var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, short.MaxValue);
NetworkBehaviour.NetworkVariableFields[k].WriteDelta(tmpWriter);
writer.WriteValueSafe((ushort)tmpWriter.Length);
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize<ushort>() + tmpWriter.Length))
{
throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}");
}
writer.WriteValue((ushort)tmpWriter.Length);
tmpWriter.CopyTo(writer);
}
else
@@ -93,24 +100,25 @@ namespace Unity.Netcode
}
}
public static void Receive(FastBufferReader reader, in NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
{
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(NetworkObjectId) + FastBufferWriter.GetWriteSize(NetworkBehaviourIndex)))
{
throw new OverflowException($"Not enough data in the buffer to read {nameof(NetworkVariableDeltaMessage)}");
}
reader.ReadValue(out NetworkObjectId);
reader.ReadValue(out NetworkBehaviourIndex);
m_ReceivedNetworkVariableData = reader;
return true;
}
public void Handle(ref NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
var message = new NetworkVariableDeltaMessage();
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(message.NetworkObjectId) +
FastBufferWriter.GetWriteSize(message.NetworkBehaviourIndex)))
{
throw new OverflowException(
$"Not enough data in the buffer to read {nameof(NetworkVariableDeltaMessage)}");
}
reader.ReadValue(out message.NetworkObjectId);
reader.ReadValue(out message.NetworkBehaviourIndex);
message.Handle(context.SenderId, reader, context, networkManager);
}
public void Handle(ulong senderId, FastBufferReader reader, in NetworkContext context, NetworkManager networkManager)
{
if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out NetworkObject networkObject))
{
NetworkBehaviour behaviour = networkObject.GetNetworkBehaviourAtOrderIndex(NetworkBehaviourIndex);
@@ -130,7 +138,7 @@ namespace Unity.Netcode
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
reader.ReadValueSafe(out varSize);
m_ReceivedNetworkVariableData.ReadValueSafe(out varSize);
if (varSize == 0)
{
@@ -139,7 +147,7 @@ namespace Unity.Netcode
}
else
{
reader.ReadValueSafe(out bool deltaExists);
m_ReceivedNetworkVariableData.ReadValueSafe(out bool deltaExists);
if (!deltaExists)
{
continue;
@@ -157,7 +165,7 @@ namespace Unity.Netcode
NetworkLog.LogError($"[{behaviour.NetworkVariableFields[i].GetType().Name}]");
}
reader.Seek(reader.Position + varSize);
m_ReceivedNetworkVariableData.Seek(m_ReceivedNetworkVariableData.Position + varSize);
continue;
}
@@ -176,39 +184,37 @@ namespace Unity.Netcode
return;
}
int readStartPos = reader.Position;
int readStartPos = m_ReceivedNetworkVariableData.Position;
behaviour.NetworkVariableFields[i].ReadDelta(reader, networkManager.IsServer);
behaviour.NetworkVariableFields[i].ReadDelta(m_ReceivedNetworkVariableData, networkManager.IsServer);
networkManager.NetworkMetrics.TrackNetworkVariableDeltaReceived(
senderId,
context.SenderId,
networkObject,
behaviour.NetworkVariableFields[i].Name,
behaviour.__getTypeName(),
reader.Length);
context.MessageSize);
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
if (reader.Position > (readStartPos + varSize))
if (m_ReceivedNetworkVariableData.Position > (readStartPos + varSize))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning(
$"Var delta read too far. {reader.Position - (readStartPos + varSize)} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}");
NetworkLog.LogWarning($"Var delta read too far. {m_ReceivedNetworkVariableData.Position - (readStartPos + varSize)} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}");
}
reader.Seek(readStartPos + varSize);
m_ReceivedNetworkVariableData.Seek(readStartPos + varSize);
}
else if (reader.Position < (readStartPos + varSize))
else if (m_ReceivedNetworkVariableData.Position < (readStartPos + varSize))
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning(
$"Var delta read too little. {(readStartPos + varSize) - reader.Position} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}");
NetworkLog.LogWarning($"Var delta read too little. {(readStartPos + varSize) - m_ReceivedNetworkVariableData.Position} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}");
}
reader.Seek(readStartPos + varSize);
m_ReceivedNetworkVariableData.Seek(readStartPos + varSize);
}
}
}
@@ -216,7 +222,7 @@ namespace Unity.Netcode
}
else
{
networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, reader, context);
networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, m_ReceivedNetworkVariableData, ref context);
}
}
}

View File

@@ -26,42 +26,41 @@ namespace Unity.Netcode
}
}
public static void Receive(FastBufferReader reader, in NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
{
return;
return false;
}
var message = new ParentSyncMessage();
reader.ReadValueSafe(out message.NetworkObjectId);
reader.ReadValueSafe(out message.IsReparented);
if (message.IsReparented)
reader.ReadValueSafe(out NetworkObjectId);
reader.ReadValueSafe(out IsReparented);
if (IsReparented)
{
reader.ReadValueSafe(out message.IsLatestParentSet);
if (message.IsLatestParentSet)
reader.ReadValueSafe(out IsLatestParentSet);
if (IsLatestParentSet)
{
reader.ReadValueSafe(out ulong latestParent);
message.LatestParent = latestParent;
LatestParent = latestParent;
}
}
message.Handle(reader, context, networkManager);
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
{
networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, reader, ref context);
return false;
}
return true;
}
public void Handle(FastBufferReader reader, in NetworkContext context, NetworkManager networkManager)
public void Handle(ref NetworkContext context)
{
if (networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
{
var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId];
networkObject.SetNetworkParenting(IsReparented, LatestParent);
networkObject.ApplyNetworkParenting();
}
else
{
networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, reader, context);
}
var networkManager = (NetworkManager)context.SystemOwner;
var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId];
networkObject.SetNetworkParenting(IsReparented, LatestParent);
networkObject.ApplyNetworkParenting();
}
}
}

View File

@@ -1,109 +0,0 @@
using System;
namespace Unity.Netcode
{
internal struct RpcMessage : INetworkMessage
{
public enum RpcType : byte
{
Server,
Client
}
public struct HeaderData
{
public RpcType Type;
public ulong NetworkObjectId;
public ushort NetworkBehaviourId;
public uint NetworkMethodId;
}
public HeaderData Header;
public FastBufferWriter RpcData;
public unsafe void Serialize(FastBufferWriter writer)
{
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(Header) + RpcData.Length))
{
throw new OverflowException("Not enough space in the buffer to store RPC data.");
}
writer.WriteValue(Header);
writer.WriteBytes(RpcData.GetUnsafePtr(), RpcData.Length);
}
public static void Receive(FastBufferReader reader, in NetworkContext context)
{
var message = new RpcMessage();
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(message.Header)))
{
throw new OverflowException("Not enough space in the buffer to read RPC data.");
}
reader.ReadValue(out message.Header);
message.Handle(reader, context, (NetworkManager)context.SystemOwner, context.SenderId, true);
}
public void Handle(FastBufferReader reader, in NetworkContext context, NetworkManager networkManager, ulong senderId, bool canDefer)
{
if (NetworkManager.__rpc_func_table.ContainsKey(Header.NetworkMethodId))
{
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(Header.NetworkObjectId))
{
if (canDefer)
{
networkManager.SpawnManager.TriggerOnSpawn(Header.NetworkObjectId, reader, context);
}
else
{
NetworkLog.LogError($"Tried to invoke an RPC on a non-existent {nameof(NetworkObject)} with {nameof(canDefer)}=false");
}
return;
}
var networkObject = networkManager.SpawnManager.SpawnedObjects[Header.NetworkObjectId];
var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(Header.NetworkBehaviourId);
if (networkBehaviour == null)
{
return;
}
var rpcParams = new __RpcParams();
switch (Header.Type)
{
case RpcType.Server:
rpcParams.Server = new ServerRpcParams
{
Receive = new ServerRpcReceiveParams
{
SenderClientId = senderId
}
};
break;
case RpcType.Client:
rpcParams.Client = new ClientRpcParams
{
Receive = new ClientRpcReceiveParams
{
}
};
break;
}
NetworkManager.__rpc_func_table[Header.NetworkMethodId](networkBehaviour, reader, rpcParams);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (NetworkManager.__rpc_name_table.TryGetValue(Header.NetworkMethodId, out var rpcMethodName))
{
networkManager.NetworkMetrics.TrackRpcReceived(
senderId,
networkObject,
rpcMethodName,
networkBehaviour.__getTypeName(),
reader.Length);
}
#endif
}
}
}
}

View File

@@ -0,0 +1,157 @@
using System;
using UnityEngine;
using Unity.Collections;
namespace Unity.Netcode
{
internal static class RpcMessageHelpers
{
public static unsafe void Serialize(ref FastBufferWriter writer, ref RpcMetadata metadata, ref FastBufferWriter payload)
{
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize<RpcMetadata>() + payload.Length))
{
throw new OverflowException("Not enough space in the buffer to store RPC data.");
}
writer.WriteValue(metadata);
writer.WriteBytes(payload.GetUnsafePtr(), payload.Length);
}
public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkContext context, ref RpcMetadata metadata, ref FastBufferReader payload)
{
int metadataSize = FastBufferWriter.GetWriteSize<RpcMetadata>();
if (!reader.TryBeginRead(metadataSize))
{
throw new InvalidOperationException("Not enough data in the buffer to read RPC meta.");
}
reader.ReadValue(out metadata);
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(metadata.NetworkObjectId))
{
networkManager.SpawnManager.TriggerOnSpawn(metadata.NetworkObjectId, reader, ref context);
return false;
}
var networkObject = networkManager.SpawnManager.SpawnedObjects[metadata.NetworkObjectId];
var networkBehaviour = networkManager.SpawnManager.SpawnedObjects[metadata.NetworkObjectId].GetNetworkBehaviourAtOrderIndex(metadata.NetworkBehaviourId);
if (networkBehaviour == null)
{
return false;
}
if (!NetworkManager.__rpc_func_table.ContainsKey(metadata.NetworkRpcMethodId))
{
return false;
}
payload = new FastBufferReader(reader.GetUnsafePtr() + metadataSize, Allocator.None, reader.Length - metadataSize);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
if (NetworkManager.__rpc_name_table.TryGetValue(metadata.NetworkRpcMethodId, out var rpcMethodName))
{
networkManager.NetworkMetrics.TrackRpcReceived(
context.SenderId,
networkObject,
rpcMethodName,
networkBehaviour.__getTypeName(),
reader.Length);
}
#endif
return true;
}
public static void Handle(ref NetworkContext context, ref RpcMetadata metadata, ref FastBufferReader payload, ref __RpcParams rpcParams)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(metadata.NetworkObjectId, out var networkObject))
{
throw new InvalidOperationException($"An RPC called on a {nameof(NetworkObject)} that is not in the spawned objects list. Please make sure the {nameof(NetworkObject)} is spawned before calling RPCs.");
}
var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(metadata.NetworkBehaviourId);
try
{
NetworkManager.__rpc_func_table[metadata.NetworkRpcMethodId](networkBehaviour, payload, rpcParams);
}
catch (Exception ex)
{
Debug.LogException(new Exception("Unhandled RPC exception!", ex));
}
}
}
internal struct RpcMetadata
{
public ulong NetworkObjectId;
public ushort NetworkBehaviourId;
public uint NetworkRpcMethodId;
}
internal struct ServerRpcMessage : INetworkMessage
{
public RpcMetadata Metadata;
public FastBufferWriter WriteBuffer;
public FastBufferReader ReadBuffer;
public unsafe void Serialize(FastBufferWriter writer)
{
RpcMessageHelpers.Serialize(ref writer, ref Metadata, ref WriteBuffer);
}
public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context)
{
return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer);
}
public void Handle(ref NetworkContext context)
{
var rpcParams = new __RpcParams
{
Server = new ServerRpcParams
{
Receive = new ServerRpcReceiveParams
{
SenderClientId = context.SenderId
}
}
};
RpcMessageHelpers.Handle(ref context, ref Metadata, ref ReadBuffer, ref rpcParams);
}
}
internal struct ClientRpcMessage : INetworkMessage
{
public RpcMetadata Metadata;
public FastBufferWriter WriteBuffer;
public FastBufferReader ReadBuffer;
public void Serialize(FastBufferWriter writer)
{
RpcMessageHelpers.Serialize(ref writer, ref Metadata, ref WriteBuffer);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
{
return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer);
}
public void Handle(ref NetworkContext context)
{
var rpcParams = new __RpcParams
{
Client = new ClientRpcParams
{
Receive = new ClientRpcReceiveParams
{
}
}
};
RpcMessageHelpers.Handle(ref context, ref Metadata, ref ReadBuffer, ref rpcParams);
}
}
}

View File

@@ -6,14 +6,22 @@ namespace Unity.Netcode
{
public SceneEventData EventData;
private FastBufferReader m_ReceivedData;
public void Serialize(FastBufferWriter writer)
{
EventData.Serialize(writer);
}
public static void Receive(FastBufferReader reader, in NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
{
((NetworkManager)context.SystemOwner).SceneManager.HandleSceneEvent(context.SenderId, reader);
m_ReceivedData = reader;
return true;
}
public void Handle(ref NetworkContext context)
{
((NetworkManager)context.SystemOwner).SceneManager.HandleSceneEvent(context.SenderId, m_ReceivedData);
}
}
}

View File

@@ -17,21 +17,25 @@ namespace Unity.Netcode
BytePacker.WriteValuePacked(writer, Message);
}
public static void Receive(FastBufferReader reader, in NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (networkManager.IsServer && networkManager.NetworkConfig.EnableNetworkLogs)
{
var message = new ServerLogMessage();
reader.ReadValueSafe(out message.LogType);
ByteUnpacker.ReadValuePacked(reader, out message.Message);
message.Handle(context.SenderId, networkManager, reader.Length);
reader.ReadValueSafe(out LogType);
ByteUnpacker.ReadValuePacked(reader, out Message);
return true;
}
return false;
}
public void Handle(ulong senderId, NetworkManager networkManager, int messageSize)
public void Handle(ref NetworkContext context)
{
networkManager.NetworkMetrics.TrackServerLogReceived(senderId, (uint)LogType, messageSize);
var networkManager = (NetworkManager)context.SystemOwner;
var senderId = context.SenderId;
networkManager.NetworkMetrics.TrackServerLogReceived(senderId, (uint)LogType, context.MessageSize);
switch (LogType)
{

View File

@@ -76,9 +76,6 @@ namespace Unity.Netcode
Despawns.Length * sizeof(DespawnData)
))
{
Entries.Dispose();
Spawns.Dispose();
Despawns.Dispose();
throw new OverflowException($"Not enough space to serialize {nameof(SnapshotDataMessage)}");
}
writer.WriteValue(CurrentTick);
@@ -96,76 +93,67 @@ namespace Unity.Netcode
writer.WriteValue((ushort)Despawns.Length);
writer.WriteBytes((byte*)Despawns.GetUnsafePtr(), Despawns.Length * sizeof(DespawnData));
Entries.Dispose();
Spawns.Dispose();
Despawns.Dispose();
}
public static unsafe void Receive(FastBufferReader reader, in NetworkContext context)
public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context)
{
var message = new SnapshotDataMessage();
if (!reader.TryBeginRead(
FastBufferWriter.GetWriteSize(message.CurrentTick) +
FastBufferWriter.GetWriteSize(message.Sequence) +
FastBufferWriter.GetWriteSize(message.Range)
FastBufferWriter.GetWriteSize(CurrentTick) +
FastBufferWriter.GetWriteSize(Sequence) +
FastBufferWriter.GetWriteSize(Range)
))
{
throw new OverflowException($"Not enough space to deserialize {nameof(SnapshotDataMessage)}");
}
reader.ReadValue(out message.CurrentTick);
reader.ReadValue(out message.Sequence);
reader.ReadValue(out CurrentTick);
reader.ReadValue(out Sequence);
reader.ReadValue(out message.Range);
message.ReceiveMainBuffer = new NativeArray<byte>(message.Range, Allocator.Temp);
reader.ReadBytesSafe((byte*)message.ReceiveMainBuffer.GetUnsafePtr(), message.Range);
reader.ReadValueSafe(out message.Ack);
reader.ReadValue(out Range);
ReceiveMainBuffer = new NativeArray<byte>(Range, Allocator.Temp);
reader.ReadBytesSafe((byte*)ReceiveMainBuffer.GetUnsafePtr(), Range);
reader.ReadValueSafe(out Ack);
reader.ReadValueSafe(out ushort length);
message.Entries = new NativeList<EntryData>(length, Allocator.Temp);
message.Entries.Length = length;
reader.ReadBytesSafe((byte*)message.Entries.GetUnsafePtr(), message.Entries.Length * sizeof(EntryData));
Entries = new NativeList<EntryData>(length, Allocator.Temp) { Length = length };
reader.ReadBytesSafe((byte*)Entries.GetUnsafePtr(), Entries.Length * sizeof(EntryData));
reader.ReadValueSafe(out length);
message.Spawns = new NativeList<SpawnData>(length, Allocator.Temp);
message.Spawns.Length = length;
reader.ReadBytesSafe((byte*)message.Spawns.GetUnsafePtr(), message.Spawns.Length * sizeof(SpawnData));
Spawns = new NativeList<SpawnData>(length, Allocator.Temp) { Length = length };
reader.ReadBytesSafe((byte*)Spawns.GetUnsafePtr(), Spawns.Length * sizeof(SpawnData));
reader.ReadValueSafe(out length);
message.Despawns = new NativeList<DespawnData>(length, Allocator.Temp);
message.Despawns.Length = length;
reader.ReadBytesSafe((byte*)message.Despawns.GetUnsafePtr(), message.Despawns.Length * sizeof(DespawnData));
Despawns = new NativeList<DespawnData>(length, Allocator.Temp) { Length = length };
reader.ReadBytesSafe((byte*)Despawns.GetUnsafePtr(), Despawns.Length * sizeof(DespawnData));
using (message.ReceiveMainBuffer)
using (message.Entries)
using (message.Spawns)
using (message.Despawns)
{
message.Handle(context.SenderId, context.SystemOwner);
}
return true;
}
public void Handle(ulong senderId, object systemOwner)
public void Handle(ref NetworkContext context)
{
if (systemOwner is NetworkManager)
using (ReceiveMainBuffer)
using (Entries)
using (Spawns)
using (Despawns)
{
var networkManager = (NetworkManager)systemOwner;
// todo: temporary hack around bug
if (!networkManager.IsServer)
var systemOwner = context.SystemOwner;
var senderId = context.SenderId;
if (systemOwner is NetworkManager networkManager)
{
senderId = networkManager.ServerClientId;
}
// todo: temporary hack around bug
if (!networkManager.IsServer)
{
senderId = networkManager.ServerClientId;
}
var snapshotSystem = networkManager.SnapshotSystem;
snapshotSystem.HandleSnapshot(senderId, this);
}
else
{
var ownerData = (Tuple<SnapshotSystem, ulong>)systemOwner;
var snapshotSystem = ownerData.Item1;
snapshotSystem.HandleSnapshot(ownerData.Item2, this);
return;
var snapshotSystem = networkManager.SnapshotSystem;
snapshotSystem.HandleSnapshot(senderId, this);
}
else
{
var ownerData = (Tuple<SnapshotSystem, ulong>)systemOwner;
var snapshotSystem = ownerData.Item1;
snapshotSystem.HandleSnapshot(ownerData.Item2, this);
}
}
}
}

View File

@@ -9,21 +9,22 @@ namespace Unity.Netcode
writer.WriteValueSafe(this);
}
public static void Receive(FastBufferReader reader, in NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
{
return;
return false;
}
reader.ReadValueSafe(out TimeSyncMessage message);
message.Handle(context.SenderId, networkManager);
reader.ReadValueSafe(out this);
return true;
}
public void Handle(ulong senderId, NetworkManager networkManager)
public void Handle(ref NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
var time = new NetworkTime(networkManager.NetworkTickSystem.TickRate, Tick);
networkManager.NetworkTimeSystem.Sync(time.Time, networkManager.NetworkConfig.NetworkTransport.GetCurrentRtt(senderId) / 1000d);
networkManager.NetworkTimeSystem.Sync(time.Time, networkManager.NetworkConfig.NetworkTransport.GetCurrentRtt(context.SenderId) / 1000d);
}
}
}

View File

@@ -2,16 +2,23 @@ namespace Unity.Netcode
{
internal struct UnnamedMessage : INetworkMessage
{
public FastBufferWriter Data;
public FastBufferWriter SendData;
private FastBufferReader m_ReceivedData;
public unsafe void Serialize(FastBufferWriter writer)
{
writer.WriteBytesSafe(Data.GetUnsafePtr(), Data.Length);
writer.WriteBytesSafe(SendData.GetUnsafePtr(), SendData.Length);
}
public static void Receive(FastBufferReader reader, in NetworkContext context)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
{
((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeUnnamedMessage(context.SenderId, reader, context.SerializedHeaderSize);
m_ReceivedData = reader;
return true;
}
public void Handle(ref NetworkContext context)
{
((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeUnnamedMessage(context.SenderId, m_ReceivedData, context.SerializedHeaderSize);
}
}
}

View File

@@ -40,7 +40,7 @@ namespace Unity.Netcode
}
}
internal delegate void MessageHandler(FastBufferReader reader, in NetworkContext context);
internal delegate void MessageHandler(FastBufferReader reader, ref NetworkContext context, MessagingSystem system);
private NativeList<ReceiveQueueItem> m_IncomingMessageQueue = new NativeList<ReceiveQueueItem>(16, Allocator.Persistent);
@@ -118,7 +118,7 @@ namespace Unity.Netcode
for (var queueIndex = 0; queueIndex < m_IncomingMessageQueue.Length; ++queueIndex)
{
// Avoid copies...
ref var item = ref m_IncomingMessageQueue.GetUnsafeList()->ElementAt(queueIndex);
ref var item = ref m_IncomingMessageQueue.ElementAt(queueIndex);
item.Reader.Dispose();
}
@@ -236,6 +236,7 @@ namespace Unity.Netcode
Timestamp = timestamp,
Header = header,
SerializedHeaderSize = serializedHeaderSize,
MessageSize = header.MessageSize,
};
var type = m_ReverseTypeMap[header.MessageType];
@@ -260,7 +261,7 @@ namespace Unity.Netcode
// for some dynamic-length value.
try
{
handler.Invoke(reader, context);
handler.Invoke(reader, ref context, this);
}
catch (Exception e)
{
@@ -278,7 +279,7 @@ namespace Unity.Netcode
for (var index = 0; index < m_IncomingMessageQueue.Length; ++index)
{
// Avoid copies...
ref var item = ref m_IncomingMessageQueue.GetUnsafeList()->ElementAt(index);
ref var item = ref m_IncomingMessageQueue.ElementAt(index);
HandleMessage(item.Header, item.Reader, item.SenderId, item.Timestamp, item.MessageHeaderSerializedSize);
if (m_Disposed)
{
@@ -313,12 +314,31 @@ namespace Unity.Netcode
var queue = m_SendQueues[clientId];
for (var i = 0; i < queue.Length; ++i)
{
queue.GetUnsafeList()->ElementAt(i).Writer.Dispose();
queue.ElementAt(i).Writer.Dispose();
}
queue.Dispose();
}
public static void ReceiveMessage<T>(FastBufferReader reader, ref NetworkContext context, MessagingSystem system) where T : INetworkMessage, new()
{
var message = new T();
if (message.Deserialize(reader, ref context))
{
for (var hookIdx = 0; hookIdx < system.m_Hooks.Count; ++hookIdx)
{
system.m_Hooks[hookIdx].OnBeforeHandleMessage(ref message, ref context);
}
message.Handle(ref context);
for (var hookIdx = 0; hookIdx < system.m_Hooks.Count; ++hookIdx)
{
system.m_Hooks[hookIdx].OnAfterHandleMessage(ref message, ref context);
}
}
}
private bool CanSend(ulong clientId, Type messageType, NetworkDelivery delivery)
{
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
@@ -332,7 +352,7 @@ namespace Unity.Netcode
return true;
}
internal unsafe int SendMessage<TMessageType, TClientIdListType>(in TMessageType message, NetworkDelivery delivery, in TClientIdListType clientIds)
internal int SendMessage<TMessageType, TClientIdListType>(ref TMessageType message, NetworkDelivery delivery, in TClientIdListType clientIds)
where TMessageType : INetworkMessage
where TClientIdListType : IReadOnlyList<ulong>
{
@@ -347,11 +367,17 @@ namespace Unity.Netcode
message.Serialize(tmpSerializer);
return SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, clientIds);
}
internal unsafe int SendPreSerializedMessage<TMessageType>(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, in IReadOnlyList<ulong> clientIds)
where TMessageType : INetworkMessage
{
using var headerSerializer = new FastBufferWriter(FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp);
var header = new MessageHeader
{
MessageSize = (ushort)tmpSerializer.Length,
MessageSize = (uint)tmpSerializer.Length,
MessageType = m_MessageTypes[typeof(TMessageType)],
};
BytePacker.WriteValueBitPacked(headerSerializer, header.MessageType);
@@ -368,7 +394,7 @@ namespace Unity.Netcode
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnBeforeSendMessage(clientId, typeof(TMessageType), delivery);
m_Hooks[hookIdx].OnBeforeSendMessage(clientId, ref message, delivery);
}
var sendQueueItem = m_SendQueues[clientId];
@@ -376,22 +402,22 @@ namespace Unity.Netcode
{
sendQueueItem.Add(new SendQueueItem(delivery, NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.TempJob,
maxSize));
sendQueueItem.GetUnsafeList()->ElementAt(0).Writer.Seek(sizeof(BatchHeader));
sendQueueItem.ElementAt(0).Writer.Seek(sizeof(BatchHeader));
}
else
{
ref var lastQueueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1);
ref var lastQueueItem = ref sendQueueItem.ElementAt(sendQueueItem.Length - 1);
if (lastQueueItem.NetworkDelivery != delivery ||
lastQueueItem.Writer.MaxCapacity - lastQueueItem.Writer.Position
< tmpSerializer.Length + headerSerializer.Length)
{
sendQueueItem.Add(new SendQueueItem(delivery, NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.TempJob,
maxSize));
sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1).Writer.Seek(sizeof(BatchHeader));
sendQueueItem.ElementAt(sendQueueItem.Length - 1).Writer.Seek(sizeof(BatchHeader));
}
}
ref var writeQueueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1);
ref var writeQueueItem = ref sendQueueItem.ElementAt(sendQueueItem.Length - 1);
writeQueueItem.Writer.TryBeginWrite(tmpSerializer.Length + headerSerializer.Length);
writeQueueItem.Writer.WriteBytes(headerSerializer.GetUnsafePtr(), headerSerializer.Length);
@@ -399,13 +425,20 @@ namespace Unity.Netcode
writeQueueItem.BatchHeader.BatchSize++;
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnAfterSendMessage(clientId, typeof(TMessageType), delivery, tmpSerializer.Length + headerSerializer.Length);
m_Hooks[hookIdx].OnAfterSendMessage(clientId, ref message, delivery, tmpSerializer.Length + headerSerializer.Length);
}
}
return tmpSerializer.Length + headerSerializer.Length;
}
internal unsafe int SendPreSerializedMessage<TMessageType>(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, ulong clientId)
where TMessageType : INetworkMessage
{
ulong* clientIds = stackalloc ulong[] { clientId };
return SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, new PointerListWrapper<ulong>(clientIds, 1));
}
private struct PointerListWrapper<T> : IReadOnlyList<T>
where T : unmanaged
{
@@ -441,24 +474,24 @@ namespace Unity.Netcode
}
}
internal unsafe int SendMessage<T>(in T message, NetworkDelivery delivery,
internal unsafe int SendMessage<T>(ref T message, NetworkDelivery delivery,
ulong* clientIds, int numClientIds)
where T : INetworkMessage
{
return SendMessage(message, delivery, new PointerListWrapper<ulong>(clientIds, numClientIds));
return SendMessage(ref message, delivery, new PointerListWrapper<ulong>(clientIds, numClientIds));
}
internal unsafe int SendMessage<T>(in T message, NetworkDelivery delivery, ulong clientId)
internal unsafe int SendMessage<T>(ref T message, NetworkDelivery delivery, ulong clientId)
where T : INetworkMessage
{
ulong* clientIds = stackalloc ulong[] { clientId };
return SendMessage(message, delivery, new PointerListWrapper<ulong>(clientIds, 1));
return SendMessage(ref message, delivery, new PointerListWrapper<ulong>(clientIds, 1));
}
internal unsafe int SendMessage<T>(in T message, NetworkDelivery delivery, in NativeArray<ulong> clientIds)
internal unsafe int SendMessage<T>(ref T message, NetworkDelivery delivery, in NativeArray<ulong> clientIds)
where T : INetworkMessage
{
return SendMessage(message, delivery, new PointerListWrapper<ulong>((ulong*)clientIds.GetUnsafePtr(), clientIds.Length));
return SendMessage(ref message, delivery, new PointerListWrapper<ulong>((ulong*)clientIds.GetUnsafePtr(), clientIds.Length));
}
internal unsafe void ProcessSendQueues()
@@ -469,7 +502,7 @@ namespace Unity.Netcode
var sendQueueItem = kvp.Value;
for (var i = 0; i < sendQueueItem.Length; ++i)
{
ref var queueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(i);
ref var queueItem = ref sendQueueItem.ElementAt(i);
if (queueItem.BatchHeader.BatchSize == 0)
{
queueItem.Writer.Dispose();

View File

@@ -30,5 +30,10 @@ namespace Unity.Netcode
/// The actual serialized size of the header when packed into the buffer
/// </summary>
public int SerializedHeaderSize;
/// <summary>
/// The size of the message in the buffer, header excluded
/// </summary>
public uint MessageSize;
}
}

View File

@@ -83,6 +83,12 @@ namespace Unity.Netcode
void TrackSceneEventReceived(ulong senderClientId, uint sceneEventType, string sceneName, long bytesCount);
void TrackPacketSent(uint packetCount);
void TrackPacketReceived(uint packetCount);
void TrackRttToServer(int rtt);
void DispatchFrame();
}
}

View File

@@ -11,14 +11,13 @@ namespace Unity.Netcode
m_NetworkManager = networkManager;
}
public void OnBeforeSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery)
public void OnBeforeSendMessage<T>(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage
{
}
public void OnAfterSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery, int messageSizeBytes)
public void OnAfterSendMessage<T>(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage
{
m_NetworkManager.NetworkMetrics.TrackNetworkMessageSent(clientId, messageType.Name, messageSizeBytes);
m_NetworkManager.NetworkMetrics.TrackNetworkMessageSent(clientId, typeof(T).Name, messageSizeBytes);
}
public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes)
@@ -57,5 +56,15 @@ namespace Unity.Netcode
{
return true;
}
public void OnBeforeHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage
{
// TODO: Per-message metrics recording moved here
}
public void OnAfterHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage
{
// TODO: Per-message metrics recording moved here
}
}
}

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using Unity.Multiplayer.Tools;
using Unity.Multiplayer.Tools.MetricTypes;
using Unity.Multiplayer.Tools.NetStats;
using Unity.Profiling;
using UnityEngine;
namespace Unity.Netcode
@@ -14,6 +15,8 @@ namespace Unity.Netcode
static Dictionary<uint, string> s_SceneEventTypeNames;
static ProfilerMarker s_FrameDispatch = new ProfilerMarker($"{nameof(NetworkMetrics)}.DispatchFrame");
static NetworkMetrics()
{
s_SceneEventTypeNames = new Dictionary<uint, string>();
@@ -63,6 +66,21 @@ namespace Unity.Netcode
private readonly EventMetric<SceneEventMetric> m_SceneEventSentEvent = new EventMetric<SceneEventMetric>(NetworkMetricTypes.SceneEventSent.Id);
private readonly EventMetric<SceneEventMetric> m_SceneEventReceivedEvent = new EventMetric<SceneEventMetric>(NetworkMetricTypes.SceneEventReceived.Id);
#if MULTIPLAYER_TOOLS_1_0_0_PRE_4
private readonly Counter m_PacketSentCounter = new Counter(NetworkMetricTypes.PacketsSent.Id)
{
ShouldResetOnDispatch = true,
};
private readonly Counter m_PacketReceivedCounter = new Counter(NetworkMetricTypes.PacketsReceived.Id)
{
ShouldResetOnDispatch = true,
};
private readonly Gauge m_RttToServerGauge = new Gauge(NetworkMetricTypes.RttToServer.Id)
{
ShouldResetOnDispatch = true,
};
#endif
private ulong m_NumberOfMetricsThisFrame;
public NetworkMetrics()
@@ -79,6 +97,10 @@ namespace Unity.Netcode
.WithMetricEvents(m_RpcSentEvent, m_RpcReceivedEvent)
.WithMetricEvents(m_ServerLogSentEvent, m_ServerLogReceivedEvent)
.WithMetricEvents(m_SceneEventSentEvent, m_SceneEventReceivedEvent)
#if MULTIPLAYER_TOOLS_1_0_0_PRE_4
.WithCounters(m_PacketSentCounter, m_PacketReceivedCounter)
.WithGauges(m_RttToServerGauge)
#endif
.Build();
Dispatcher.RegisterObserver(NetcodeObserver.Observer);
@@ -404,9 +426,49 @@ namespace Unity.Netcode
IncrementMetricCount();
}
public void TrackPacketSent(uint packetCount)
{
#if MULTIPLAYER_TOOLS_1_0_0_PRE_4
if (!CanSendMetrics)
{
return;
}
m_PacketSentCounter.Increment(packetCount);
IncrementMetricCount();
#endif
}
public void TrackPacketReceived(uint packetCount)
{
#if MULTIPLAYER_TOOLS_1_0_0_PRE_4
if (!CanSendMetrics)
{
return;
}
m_PacketReceivedCounter.Increment(packetCount);
IncrementMetricCount();
#endif
}
public void TrackRttToServer(int rtt)
{
#if MULTIPLAYER_TOOLS_1_0_0_PRE_4
if (!CanSendMetrics)
{
return;
}
m_RttToServerGauge.Set(rtt);
#endif
}
public void DispatchFrame()
{
s_FrameDispatch.Begin();
Dispatcher.Dispatch();
s_FrameDispatch.End();
m_NumberOfMetricsThisFrame = 0;
}
@@ -421,7 +483,7 @@ namespace Unity.Netcode
}
}
internal class NetcodeObserver
internal class NetcodeObserver
{
public static IMetricObserver Observer { get; } = MetricObserverFactory.Construct();
}

View File

@@ -137,6 +137,18 @@ namespace Unity.Netcode
{
}
public void TrackPacketSent(uint packetCount)
{
}
public void TrackPacketReceived(uint packetCount)
{
}
public void TrackRttToServer(int rtt)
{
}
public void DispatchFrame()
{
}

View File

@@ -90,18 +90,18 @@ namespace Unity.Netcode
{
case NetworkListEvent<T>.EventType.Add:
{
writer.WriteValueSafe(m_DirtyEvents[i].Value);
NetworkVariable<T>.Write(writer, m_DirtyEvents[i].Value);
}
break;
case NetworkListEvent<T>.EventType.Insert:
{
writer.WriteValueSafe(m_DirtyEvents[i].Index);
writer.WriteValueSafe(m_DirtyEvents[i].Value);
NetworkVariable<T>.Write(writer, m_DirtyEvents[i].Value);
}
break;
case NetworkListEvent<T>.EventType.Remove:
{
writer.WriteValueSafe(m_DirtyEvents[i].Value);
NetworkVariable<T>.Write(writer, m_DirtyEvents[i].Value);
}
break;
case NetworkListEvent<T>.EventType.RemoveAt:
@@ -112,7 +112,7 @@ namespace Unity.Netcode
case NetworkListEvent<T>.EventType.Value:
{
writer.WriteValueSafe(m_DirtyEvents[i].Index);
writer.WriteValueSafe(m_DirtyEvents[i].Value);
NetworkVariable<T>.Write(writer, m_DirtyEvents[i].Value);
}
break;
case NetworkListEvent<T>.EventType.Clear:
@@ -130,7 +130,7 @@ namespace Unity.Netcode
writer.WriteValueSafe((ushort)m_List.Length);
for (int i = 0; i < m_List.Length; i++)
{
writer.WriteValueSafe(m_List[i]);
NetworkVariable<T>.Write(writer, m_List[i]);
}
}
@@ -141,7 +141,7 @@ namespace Unity.Netcode
reader.ReadValueSafe(out ushort count);
for (int i = 0; i < count; i++)
{
reader.ReadValueSafe(out T value);
NetworkVariable<T>.Read(reader, out T value);
m_List.Add(value);
}
}
@@ -157,7 +157,7 @@ namespace Unity.Netcode
{
case NetworkListEvent<T>.EventType.Add:
{
reader.ReadValueSafe(out T value);
NetworkVariable<T>.Read(reader, out T value);
m_List.Add(value);
if (OnListChanged != null)
@@ -184,7 +184,7 @@ namespace Unity.Netcode
case NetworkListEvent<T>.EventType.Insert:
{
reader.ReadValueSafe(out int index);
reader.ReadValueSafe(out T value);
NetworkVariable<T>.Read(reader, out T value);
m_List.InsertRangeWithBeginEnd(index, index + 1);
m_List[index] = value;
@@ -211,7 +211,7 @@ namespace Unity.Netcode
break;
case NetworkListEvent<T>.EventType.Remove:
{
reader.ReadValueSafe(out T value);
NetworkVariable<T>.Read(reader, out T value);
int index = m_List.IndexOf(value);
if (index == -1)
{
@@ -271,7 +271,7 @@ namespace Unity.Netcode
case NetworkListEvent<T>.EventType.Value:
{
reader.ReadValueSafe(out int index);
reader.ReadValueSafe(out T value);
NetworkVariable<T>.Read(reader, out T value);
if (index >= m_List.Length)
{
throw new Exception("Shouldn't be here, index is higher than list length");

View File

@@ -10,7 +10,7 @@ namespace Unity.Netcode
public class NetworkVariable<T> : NetworkVariableBase where T : unmanaged
{
// Functions that know how to serialize INetworkSerializable
internal static void WriteNetworkSerializable<TForMethod>(FastBufferWriter writer, ref TForMethod value)
internal static void WriteNetworkSerializable<TForMethod>(FastBufferWriter writer, in TForMethod value)
where TForMethod : INetworkSerializable, new()
{
writer.WriteNetworkSerializable(value);
@@ -22,7 +22,7 @@ namespace Unity.Netcode
}
// Functions that serialize other types
private static void WriteValue<TForMethod>(FastBufferWriter writer, ref TForMethod value) where TForMethod : unmanaged
private static void WriteValue<TForMethod>(FastBufferWriter writer, in TForMethod value) where TForMethod : unmanaged
{
writer.WriteValueSafe(value);
}
@@ -33,7 +33,7 @@ namespace Unity.Netcode
reader.ReadValueSafe(out value);
}
internal delegate void WriteDelegate<TForMethod>(FastBufferWriter writer, ref TForMethod value);
internal delegate void WriteDelegate<TForMethod>(FastBufferWriter writer, in TForMethod value);
internal delegate void ReadDelegate<TForMethod>(FastBufferReader reader, out TForMethod value);
@@ -174,7 +174,7 @@ namespace Unity.Netcode
/// <inheritdoc />
public override void WriteField(FastBufferWriter writer)
{
Write(writer, ref m_InternalValue);
Write(writer, m_InternalValue);
}
}
}

View File

@@ -10,7 +10,7 @@ namespace Unity.Netcode
/// <summary>
/// The delivery type (QoS) to send data with
/// </summary>
internal const NetworkDelivery Delivery = NetworkDelivery.ReliableSequenced;
internal const NetworkDelivery Delivery = NetworkDelivery.ReliableFragmentedSequenced;
private protected NetworkBehaviour m_NetworkBehaviour;

View File

@@ -37,14 +37,14 @@ namespace Unity.Netcode
return marker;
}
public void OnBeforeSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery)
public void OnBeforeSendMessage<T>(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage
{
GetSenderProfilerMarker(messageType).Begin();
GetSenderProfilerMarker(typeof(T)).Begin();
}
public void OnAfterSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery, int messageSizeBytes)
public void OnAfterSendMessage<T>(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage
{
GetSenderProfilerMarker(messageType).End();
GetSenderProfilerMarker(typeof(T)).End();
}
public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes)
@@ -86,5 +86,15 @@ namespace Unity.Netcode
{
return true;
}
public void OnBeforeHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage
{
// nop
}
public void OnAfterHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage
{
// nop
}
}
}

View File

@@ -0,0 +1,28 @@
using System;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Unity.Netcode
{
/// <summary>
/// Used to override the LoadSceneAsync and UnloadSceneAsync methods called
/// within the NetworkSceneManager.
/// </summary>
internal interface ISceneManagerHandler
{
// Generic action to call when a scene is finished loading/unloading
struct SceneEventAction
{
internal uint SceneEventId;
internal Action<uint> EventAction;
internal void Invoke()
{
EventAction.Invoke(SceneEventId);
}
}
AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventAction sceneEventAction);
AsyncOperation UnloadSceneAsync(Scene scene, SceneEventAction sceneEventAction);
}
}

View File

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

View File

@@ -139,13 +139,7 @@ namespace Unity.Netcode
/// Used to detect if a scene event is underway
/// Only 1 scene event can occur on the server at a time for now.
/// </summary>
private static bool s_IsSceneEventActive = false;
// TODO: Remove `m_IsRunningUnitTest` entirely after we switch to multi-process testing
// In MultiInstance tests, we cannot allow clients to load additional scenes as they're sharing the same scene space / Unity instance.
#if UNITY_INCLUDE_TESTS
private readonly bool m_IsRunningUnitTest = SceneManager.GetActiveScene().name.StartsWith("InitTestScene");
#endif
private bool m_IsSceneEventActive = false;
/// <summary>
/// The delegate callback definition for scene event notifications.<br/>
@@ -324,6 +318,31 @@ namespace Unity.Netcode
/// </summary>
public VerifySceneBeforeLoadingDelegateHandler VerifySceneBeforeLoading;
/// <summary>
/// Proof of concept: Interface version that allows for the decoupling from
/// the SceneManager's Load and Unload methods for testing purposes (potentially other future usage)
/// </summary>
private class DefaultSceneManagerHandler : ISceneManagerHandler
{
public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, ISceneManagerHandler.SceneEventAction sceneEventAction)
{
var operation = SceneManager.LoadSceneAsync(sceneName, loadSceneMode);
operation.completed += new Action<AsyncOperation>(asyncOp2 => { sceneEventAction.Invoke(); });
return operation;
}
public AsyncOperation UnloadSceneAsync(Scene scene, ISceneManagerHandler.SceneEventAction sceneEventAction)
{
var operation = SceneManager.UnloadSceneAsync(scene);
operation.completed += new Action<AsyncOperation>(asyncOp2 => { sceneEventAction.Invoke(); });
return operation;
}
}
internal ISceneManagerHandler SceneManagerHandler = new DefaultSceneManagerHandler();
/// End of Proof of Concept
internal readonly Dictionary<Guid, SceneEventProgress> SceneEventProgressTracking = new Dictionary<Guid, SceneEventProgress>();
/// <summary>
@@ -723,10 +742,9 @@ namespace Unity.Netcode
{
EventData = SceneEventDataStore[sceneEventId]
};
var size = m_NetworkManager.SendMessage(message, k_DeliveryType, targetClientIds);
var size = m_NetworkManager.SendMessage(ref message, k_DeliveryType, targetClientIds);
m_NetworkManager.NetworkMetrics.TrackSceneEventSent(
targetClientIds, (uint)SceneEventDataStore[sceneEventId].SceneEventType, SceneNameFromHash(SceneEventDataStore[sceneEventId].SceneHash), size);
m_NetworkManager.NetworkMetrics.TrackSceneEventSent(targetClientIds, (uint)SceneEventDataStore[sceneEventId].SceneEventType, SceneNameFromHash(SceneEventDataStore[sceneEventId].SceneHash), size);
}
/// <summary>
@@ -789,7 +807,7 @@ namespace Unity.Netcode
private SceneEventProgress ValidateSceneEvent(string sceneName, bool isUnloading = false)
{
// Return scene event already in progress if one is already in progress
if (s_IsSceneEventActive)
if (m_IsSceneEventActive)
{
return new SceneEventProgress(null, SceneEventProgressStatus.SceneEventInProgress);
}
@@ -818,7 +836,7 @@ namespace Unity.Netcode
IsSpawnedObjectsPendingInDontDestroyOnLoad = true;
}
s_IsSceneEventActive = true;
m_IsSceneEventActive = true;
// Set our callback delegate handler for completion
sceneEventProgress.OnComplete = OnSceneEventProgressCompleted;
@@ -845,12 +863,12 @@ namespace Unity.Netcode
{
EventData = sceneEventData
};
var size = m_NetworkManager.SendMessage(message, k_DeliveryType, m_NetworkManager.ConnectedClientsIds);
var size = m_NetworkManager.SendMessage(ref message, k_DeliveryType, m_NetworkManager.ConnectedClientsIds);
m_NetworkManager.NetworkMetrics.TrackSceneEventSent(
m_NetworkManager.ConnectedClientsIds,
(uint)sceneEventProgress.SceneEventType,
SceneNameFromHash(sceneEventProgress.SceneHash),
SceneNameFromHash(sceneEventProgress.SceneHash),
size);
// Send a local notification to the server that all clients are done loading or unloading
@@ -917,8 +935,9 @@ namespace Unity.Netcode
ScenesLoaded.Remove(scene.handle);
AsyncOperation sceneUnload = SceneManager.UnloadSceneAsync(scene);
sceneUnload.completed += (AsyncOperation asyncOp2) => { OnSceneUnloaded(sceneEventData.SceneEventId); };
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene,
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneUnloaded });
sceneEventProgress.SetSceneLoadOperation(sceneUnload);
// Notify local server that a scene is going to be unloaded
@@ -948,8 +967,10 @@ namespace Unity.Netcode
if (!ServerSceneHandleToClientSceneHandle.ContainsKey(sceneEventData.SceneHandle))
{
throw new Exception($"Client failed to unload scene {sceneName} " +
$"because we are missing the client scene handle due to the server scene handle {sceneEventData.SceneHandle} not being found!");
Debug.Log($"Client failed to unload scene {sceneName} " +
$"because we are missing the client scene handle due to the server scene handle {sceneEventData.SceneHandle} not being found.");
EndSceneEvent(sceneEventId);
return;
}
var sceneHandle = ServerSceneHandleToClientSceneHandle[sceneEventData.SceneHandle];
@@ -960,22 +981,11 @@ namespace Unity.Netcode
throw new Exception($"Client failed to unload scene {sceneName} " +
$"because the client scene handle {sceneHandle} was not found in ScenesLoaded!");
}
s_IsSceneEventActive = true;
var sceneUnload = (AsyncOperation)null;
#if UNITY_INCLUDE_TESTS
if (m_IsRunningUnitTest)
{
sceneUnload = new AsyncOperation();
}
else
{
sceneUnload = SceneManager.UnloadSceneAsync(ScenesLoaded[sceneHandle]);
sceneUnload.completed += asyncOp2 => OnSceneUnloaded(sceneEventId);
}
#else
sceneUnload = SceneManager.UnloadSceneAsync(ScenesLoaded[sceneHandle]);
sceneUnload.completed += asyncOp2 => OnSceneUnloaded(sceneEventId);
#endif
m_IsSceneEventActive = true;
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(ScenesLoaded[sceneHandle],
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneUnloaded });
ScenesLoaded.Remove(sceneHandle);
// Remove our server to scene handle lookup
@@ -992,13 +1002,6 @@ namespace Unity.Netcode
});
OnUnload?.Invoke(m_NetworkManager.LocalClientId, sceneName, sceneUnload);
#if UNITY_INCLUDE_TESTS
if (m_IsRunningUnitTest)
{
OnSceneUnloaded(sceneEventId);
}
#endif
}
/// <summary>
@@ -1045,7 +1048,12 @@ namespace Unity.Netcode
EndSceneEvent(sceneEventId);
// This scene event is now considered "complete"
s_IsSceneEventActive = false;
m_IsSceneEventActive = false;
}
private void EmptySceneUnloadedOperation(uint sceneEventId)
{
// Do nothing (this is a stub call since it is only used to flush all currently loaded scenes)
}
/// <summary>
@@ -1053,17 +1061,21 @@ namespace Unity.Netcode
/// Since we assume a single mode loaded scene will be considered the "currently active scene",
/// we only unload any additively loaded scenes.
/// </summary>
internal void UnloadAdditivelyLoadedScenes()
internal void UnloadAdditivelyLoadedScenes(uint sceneEventId)
{
// Unload all additive scenes while making sure we don't try to unload the base scene ( loaded in single mode ).
var currentActiveScene = SceneManager.GetActiveScene();
foreach (var keyHandleEntry in ScenesLoaded)
{
if (currentActiveScene.name != keyHandleEntry.Value.name)
// Validate the scene as well as ignore the DDOL (which will have a negative buildIndex)
if (currentActiveScene.name != keyHandleEntry.Value.name && keyHandleEntry.Value.buildIndex >= 0)
{
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value,
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = EmptySceneUnloadedOperation });
OnSceneEvent?.Invoke(new SceneEvent()
{
AsyncOperation = SceneManager.UnloadSceneAsync(keyHandleEntry.Value),
AsyncOperation = sceneUnload,
SceneEventType = SceneEventType.Unload,
SceneName = keyHandleEntry.Value.name,
LoadSceneMode = LoadSceneMode.Additive, // The only scenes unloaded are scenes that were additively loaded
@@ -1103,8 +1115,8 @@ namespace Unity.Netcode
sceneEventData.LoadSceneMode = loadSceneMode;
// This both checks to make sure the scene is valid and if not resets the active scene event
s_IsSceneEventActive = ValidateSceneBeforeLoading(sceneEventData.SceneHash, loadSceneMode);
if (!s_IsSceneEventActive)
m_IsSceneEventActive = ValidateSceneBeforeLoading(sceneEventData.SceneHash, loadSceneMode);
if (!m_IsSceneEventActive)
{
EndSceneEvent(sceneEventData.SceneEventId);
return SceneEventProgressStatus.SceneFailedVerification;
@@ -1119,12 +1131,13 @@ namespace Unity.Netcode
MoveObjectsToDontDestroyOnLoad();
// Now Unload all currently additively loaded scenes
UnloadAdditivelyLoadedScenes();
UnloadAdditivelyLoadedScenes(sceneEventData.SceneEventId);
}
// Now start loading the scene
AsyncOperation sceneLoad = SceneManager.LoadSceneAsync(sceneName, loadSceneMode);
sceneLoad.completed += (AsyncOperation asyncOp2) => { OnSceneLoaded(sceneEventData.SceneEventId, sceneName); };
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode,
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneLoaded });
sceneEventProgress.SetSceneLoadOperation(sceneLoad);
// Notify the local server that a scene loading event has begun
@@ -1160,44 +1173,13 @@ namespace Unity.Netcode
return;
}
#if UNITY_INCLUDE_TESTS
if (m_IsRunningUnitTest)
{
// Send the loading message
OnSceneEvent?.Invoke(new SceneEvent()
{
AsyncOperation = new AsyncOperation(),
SceneEventType = sceneEventData.SceneEventType,
LoadSceneMode = sceneEventData.LoadSceneMode,
SceneName = sceneName,
ClientId = m_NetworkManager.LocalClientId
});
// Only for testing
OnLoad?.Invoke(m_NetworkManager.ServerClientId, sceneName, sceneEventData.LoadSceneMode, new AsyncOperation());
// Unit tests must mirror the server's scenes loaded dictionary, otherwise this portion will fail
if (ScenesLoaded.ContainsKey(sceneEventData.SceneHandle))
{
OnClientLoadedScene(sceneEventId, ScenesLoaded[sceneEventData.SceneHandle]);
}
else
{
EndSceneEvent(sceneEventId);
throw new Exception($"Could not find the scene handle {sceneEventData.SceneHandle} for scene {sceneName} " +
$"during unit test. Did you forget to register this in the unit test?");
}
return;
}
#endif
if (sceneEventData.LoadSceneMode == LoadSceneMode.Single)
{
// Move ALL NetworkObjects to the temp scene
MoveObjectsToDontDestroyOnLoad();
// Now Unload all currently additively loaded scenes
UnloadAdditivelyLoadedScenes();
UnloadAdditivelyLoadedScenes(sceneEventData.SceneEventId);
}
// The Condition: While a scene is asynchronously loaded in single loading scene mode, if any new NetworkObjects are spawned
@@ -1205,13 +1187,14 @@ namespace Unity.Netcode
// When it is set: Just before starting the asynchronous loading call
// When it is unset: After the scene has loaded, the PopulateScenePlacedObjects is called, and all NetworkObjects in the do
// not destroy temporary scene are moved into the active scene
// TODO: When Snapshot scene spawning is enabled this needs to be removed.
if (sceneEventData.LoadSceneMode == LoadSceneMode.Single)
{
IsSpawnedObjectsPendingInDontDestroyOnLoad = true;
}
var sceneLoad = SceneManager.LoadSceneAsync(sceneName, sceneEventData.LoadSceneMode);
sceneLoad.completed += asyncOp2 => OnSceneLoaded(sceneEventId, sceneName);
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, sceneEventData.LoadSceneMode,
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = OnSceneLoaded });
OnSceneEvent?.Invoke(new SceneEvent()
{
@@ -1230,10 +1213,10 @@ namespace Unity.Netcode
/// Client and Server:
/// Generic on scene loaded callback method to be called upon a scene loading
/// </summary>
private void OnSceneLoaded(uint sceneEventId, string sceneName)
private void OnSceneLoaded(uint sceneEventId)
{
var sceneEventData = SceneEventDataStore[sceneEventId];
var nextScene = GetAndAddNewlyLoadedSceneByName(sceneName);
var nextScene = GetAndAddNewlyLoadedSceneByName(SceneNameFromHash(sceneEventData.SceneHash));
if (!nextScene.isLoaded || !nextScene.IsValid())
{
throw new Exception($"Failed to find valid scene internal Unity.Netcode for {nameof(GameObject)}s error!");
@@ -1314,12 +1297,12 @@ namespace Unity.Netcode
{
EventData = sceneEventData
};
var size = m_NetworkManager.SendMessage(message, k_DeliveryType, clientId);
var size = m_NetworkManager.SendMessage(ref message, k_DeliveryType, clientId);
m_NetworkManager.NetworkMetrics.TrackSceneEventSent(clientId, (uint)sceneEventData.SceneEventType, scene.name, size);
}
}
s_IsSceneEventActive = false;
m_IsSceneEventActive = false;
//First, notify local server that the scene was loaded
OnSceneEvent?.Invoke(new SceneEvent()
{
@@ -1351,7 +1334,7 @@ namespace Unity.Netcode
sceneEventData.SceneEventType = SceneEventType.LoadComplete;
SendSceneEventData(sceneEventId, new ulong[] { m_NetworkManager.ServerClientId });
s_IsSceneEventActive = false;
m_IsSceneEventActive = false;
// Notify local client that the scene was loaded
OnSceneEvent?.Invoke(new SceneEvent()
@@ -1421,9 +1404,8 @@ namespace Unity.Netcode
{
EventData = sceneEventData
};
var size = m_NetworkManager.SendMessage(message, k_DeliveryType, clientId);
m_NetworkManager.NetworkMetrics.TrackSceneEventSent(
clientId, (uint)sceneEventData.SceneEventType, "", size);
var size = m_NetworkManager.SendMessage(ref message, k_DeliveryType, clientId);
m_NetworkManager.NetworkMetrics.TrackSceneEventSent(clientId, (uint)sceneEventData.SceneEventType, "", size);
// Notify the local server that the client has been sent the synchronize event
OnSceneEvent?.Invoke(new SceneEvent()
@@ -1474,6 +1456,10 @@ namespace Unity.Netcode
ScenePlacedObjects.Clear();
}
// Store the sceneHandle and hash
sceneEventData.ClientSceneHandle = sceneHandle;
sceneEventData.ClientSceneHash = sceneHash;
var shouldPassThrough = false;
var sceneLoad = (AsyncOperation)null;
@@ -1485,38 +1471,28 @@ namespace Unity.Netcode
shouldPassThrough = true;
}
#if UNITY_INCLUDE_TESTS
if (m_IsRunningUnitTest)
{
// In unit tests, we don't allow clients to load additional scenes since
// MultiInstance unit tests share the same scene space.
shouldPassThrough = true;
sceneLoad = new AsyncOperation();
}
#endif
if (!shouldPassThrough)
{
// If not, then load the scene
sceneLoad = SceneManager.LoadSceneAsync(sceneName, loadSceneMode);
sceneLoad.completed += asyncOp2 => ClientLoadedSynchronization(sceneEventId, sceneHash, sceneHandle);
sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode,
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = ClientLoadedSynchronization });
// Notify local client that a scene load has begun
OnSceneEvent?.Invoke(new SceneEvent()
{
AsyncOperation = sceneLoad,
SceneEventType = SceneEventType.Load,
LoadSceneMode = loadSceneMode,
SceneName = sceneName,
ClientId = m_NetworkManager.LocalClientId,
});
OnLoad?.Invoke(m_NetworkManager.LocalClientId, sceneName, loadSceneMode, sceneLoad);
}
// Notify local client that a scene load has begun
OnSceneEvent?.Invoke(new SceneEvent()
{
AsyncOperation = sceneLoad,
SceneEventType = SceneEventType.Load,
LoadSceneMode = loadSceneMode,
SceneName = sceneName,
ClientId = m_NetworkManager.LocalClientId,
});
OnLoad?.Invoke(m_NetworkManager.LocalClientId, sceneName, loadSceneMode, sceneLoad);
if (shouldPassThrough)
else
{
// If so, then pass through
ClientLoadedSynchronization(sceneEventId, sceneHash, sceneHandle);
ClientLoadedSynchronization(sceneEventId);
}
}
@@ -1525,10 +1501,10 @@ namespace Unity.Netcode
/// This handles all of the in-scene and dynamically spawned NetworkObject synchronization
/// </summary>
/// <param name="sceneIndex">Netcode scene index that was loaded</param>
private void ClientLoadedSynchronization(uint sceneEventId, uint sceneHash, int sceneHandle)
private void ClientLoadedSynchronization(uint sceneEventId)
{
var sceneEventData = SceneEventDataStore[sceneEventId];
var sceneName = SceneNameFromHash(sceneHash);
var sceneName = SceneNameFromHash(sceneEventData.ClientSceneHash);
var nextScene = GetAndAddNewlyLoadedSceneByName(sceneName);
if (!nextScene.isLoaded || !nextScene.IsValid())
@@ -1536,7 +1512,7 @@ namespace Unity.Netcode
throw new Exception($"Failed to find valid scene internal Unity.Netcode for {nameof(GameObject)}s error!");
}
var loadSceneMode = (sceneHash == sceneEventData.SceneHash ? sceneEventData.LoadSceneMode : LoadSceneMode.Additive);
var loadSceneMode = (sceneEventData.ClientSceneHash == sceneEventData.SceneHash ? sceneEventData.LoadSceneMode : LoadSceneMode.Additive);
// For now, during a synchronization event, we will make the first scene the "base/master" scene that denotes a "complete scene switch"
if (loadSceneMode == LoadSceneMode.Single)
@@ -1544,9 +1520,9 @@ namespace Unity.Netcode
SceneManager.SetActiveScene(nextScene);
}
if (!ServerSceneHandleToClientSceneHandle.ContainsKey(sceneHandle))
if (!ServerSceneHandleToClientSceneHandle.ContainsKey(sceneEventData.ClientSceneHandle))
{
ServerSceneHandleToClientSceneHandle.Add(sceneHandle, nextScene.handle);
ServerSceneHandleToClientSceneHandle.Add(sceneEventData.ClientSceneHandle, nextScene.handle);
}
else
{
@@ -1561,14 +1537,14 @@ namespace Unity.Netcode
var responseSceneEventData = BeginSceneEvent();
responseSceneEventData.LoadSceneMode = loadSceneMode;
responseSceneEventData.SceneEventType = SceneEventType.LoadComplete;
responseSceneEventData.SceneHash = sceneHash;
responseSceneEventData.SceneHash = sceneEventData.ClientSceneHash;
var message = new SceneEventMessage
{
EventData = responseSceneEventData
};
var size = m_NetworkManager.SendMessage(message, k_DeliveryType, m_NetworkManager.ServerClientId);
var size = m_NetworkManager.SendMessage(ref message, k_DeliveryType, m_NetworkManager.ServerClientId);
m_NetworkManager.NetworkMetrics.TrackSceneEventSent(m_NetworkManager.ServerClientId, (uint)responseSceneEventData.SceneEventType, sceneName, size);
@@ -1824,6 +1800,11 @@ namespace Unity.Netcode
var objectsToKeep = new HashSet<NetworkObject>(m_NetworkManager.SpawnManager.SpawnedObjectsList);
foreach (var sobj in objectsToKeep)
{
if (sobj == null)
{
continue;
}
if (!sobj.DestroyWithScene || sobj.gameObject.scene == DontDestroyOnLoadScene)
{
// Only move dynamically spawned network objects with no parent as child objects will follow
@@ -1864,7 +1845,7 @@ namespace Unity.Netcode
// at the end of scene loading we use this list to soft synchronize all in-scene placed NetworkObjects
foreach (var networkObjectInstance in networkObjects)
{
// We check to make sure the NetworkManager instance is the same one to be "MultiInstanceHelpers" compatible and filter the list on a per scene basis (additive scenes)
// We check to make sure the NetworkManager instance is the same one to be "NetcodeIntegrationTestHelpers" compatible and filter the list on a per scene basis (additive scenes)
if (networkObjectInstance.IsSceneObject == null && networkObjectInstance.NetworkManager == m_NetworkManager && networkObjectInstance.gameObject.scene == sceneToFilterBy &&
networkObjectInstance.gameObject.scene.handle == sceneToFilterBy.handle)
{
@@ -1899,6 +1880,10 @@ namespace Unity.Netcode
foreach (var sobj in objectsToKeep)
{
if (sobj == null)
{
continue;
}
// If it is in the DDOL then
if (sobj.gameObject.scene == DontDestroyOnLoadScene)
{

View File

@@ -4,7 +4,6 @@ using System.Linq;
using Unity.Collections;
using UnityEngine.SceneManagement;
namespace Unity.Netcode
{
/// <summary>
@@ -100,6 +99,10 @@ namespace Unity.Netcode
internal uint SceneHash;
internal int SceneHandle;
// Used by the client during synchronization
internal uint ClientSceneHash;
internal int ClientSceneHandle;
/// Only used for <see cref="SceneEventType.Synchronize"/> scene events, this assures permissions when writing
/// NetworkVariable information. If that process changes, then we need to update this
internal ulong TargetClientId;
@@ -230,7 +233,14 @@ namespace Unity.Netcode
internal void AddSpawnedNetworkObjects()
{
m_NetworkObjectsSync = m_NetworkManager.SpawnManager.SpawnedObjectsList.ToList();
m_NetworkObjectsSync.Clear();
foreach (var sobj in m_NetworkManager.SpawnManager.SpawnedObjectsList)
{
if (sobj.Observers.Contains(TargetClientId))
{
m_NetworkObjectsSync.Add(sobj);
}
}
m_NetworkObjectsSync.Sort(SortNetworkObjects);
}

View File

@@ -93,7 +93,7 @@ namespace Unity.Netcode
/// <param name="offset"></param>
public unsafe FastBufferReader(NativeArray<byte> buffer, Allocator allocator, int length = -1, int offset = 0)
{
Handle = CreateHandle((byte*)buffer.GetUnsafePtr(), Math.Max(1, length == -1 ? buffer.Length : length), offset, allocator);
Handle = CreateHandle((byte*)buffer.GetUnsafePtr(), length == -1 ? buffer.Length : length, offset, allocator);
}
/// <summary>
@@ -117,7 +117,7 @@ namespace Unity.Netcode
}
fixed (byte* data = buffer.Array)
{
Handle = CreateHandle(data, Math.Max(1, length == -1 ? buffer.Count : length), offset, allocator);
Handle = CreateHandle(data, length == -1 ? buffer.Count : length, offset, allocator);
}
}
@@ -142,7 +142,7 @@ namespace Unity.Netcode
}
fixed (byte* data = buffer)
{
Handle = CreateHandle(data, Math.Max(1, length == -1 ? buffer.Length : length), offset, allocator);
Handle = CreateHandle(data, length == -1 ? buffer.Length : length, offset, allocator);
}
}
@@ -165,7 +165,7 @@ namespace Unity.Netcode
/// <param name="offset">The offset of the buffer to start copying from</param>
public unsafe FastBufferReader(byte* buffer, Allocator allocator, int length, int offset = 0)
{
Handle = CreateHandle(buffer, Math.Max(1, length), offset, allocator);
Handle = CreateHandle(buffer, length, offset, allocator);
}
/// <summary>
@@ -187,7 +187,7 @@ namespace Unity.Netcode
/// <param name="offset">The offset of the buffer to start copying from</param>
public unsafe FastBufferReader(FastBufferWriter writer, Allocator allocator, int length = -1, int offset = 0)
{
Handle = CreateHandle(writer.GetUnsafePtr(), Math.Max(1, length == -1 ? writer.Length : length), offset, allocator);
Handle = CreateHandle(writer.GetUnsafePtr(), length == -1 ? writer.Length : length, offset, allocator);
}
/// <summary>

View File

@@ -102,7 +102,7 @@ namespace Unity.Netcode
/// There is a one second maximum lifetime of triggers to avoid memory leaks. After one second has passed
/// without the requested object ID being spawned, the triggers for it are automatically deleted.
/// </summary>
internal unsafe void TriggerOnSpawn(ulong networkObjectId, FastBufferReader reader, in NetworkContext context)
internal unsafe void TriggerOnSpawn(ulong networkObjectId, FastBufferReader reader, ref NetworkContext context)
{
if (!m_Triggers.ContainsKey(networkObjectId))
{
@@ -212,7 +212,7 @@ namespace Unity.Netcode
NetworkObjectId = networkObject.NetworkObjectId,
OwnerClientId = networkObject.OwnerClientId
};
var size = NetworkManager.SendMessage(message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
foreach (var client in NetworkManager.ConnectedClients)
{
@@ -280,13 +280,17 @@ namespace Unity.Netcode
networkObject.OwnerClientId = clientId;
if (TryGetNetworkClient(clientId, out NetworkClient newNetworkClient))
{
newNetworkClient.OwnedObjects.Add(networkObject);
}
var message = new ChangeOwnershipMessage
{
NetworkObjectId = networkObject.NetworkObjectId,
OwnerClientId = networkObject.OwnerClientId
};
var size = NetworkManager.SendMessage(message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
foreach (var client in NetworkManager.ConnectedClients)
{
@@ -422,6 +426,15 @@ namespace Unity.Netcode
throw new SpawnStateException("Object is already spawned");
}
if (!sceneObject)
{
var networkObjectChildren = networkObject.GetComponentsInChildren<NetworkObject>();
if (networkObjectChildren.Length > 1)
{
Debug.LogError("Spawning NetworkObjects with nested NetworkObjects is only supported for scene objects. Child NetworkObjects will not be spawned over the network!");
}
}
SpawnNetworkObjectLocallyCommon(networkObject, networkId, sceneObject, playerObject, ownerClientId, destroyWithScene);
}
@@ -525,6 +538,13 @@ namespace Unity.Netcode
triggerInfo.TriggerData.Dispose();
m_Triggers.Remove(networkId);
}
// propagate the IsSceneObject setting to child NetworkObjects
var children = networkObject.GetComponentsInChildren<NetworkObject>();
foreach (var childObject in children)
{
childObject.IsSceneObject = sceneObject;
}
}
internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject)
@@ -543,7 +563,7 @@ namespace Unity.Netcode
{
ObjectInfo = networkObject.GetMessageSceneObject(clientId)
};
var size = NetworkManager.SendMessage(message, NetworkDelivery.ReliableFragmentedSequenced, clientId);
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientId);
NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size);
networkObject.MarkVariablesDirty();
@@ -678,6 +698,7 @@ namespace Unity.Netcode
internal void ServerSpawnSceneObjectsOnStartSweep()
{
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
var networkObjectsToSpawn = new List<NetworkObject>();
for (int i = 0; i < networkObjects.Length; i++)
{
@@ -685,10 +706,15 @@ namespace Unity.Netcode
{
if (networkObjects[i].IsSceneObject == null)
{
SpawnNetworkObjectLocally(networkObjects[i], GetNetworkObjectId(), true, false, null, true);
networkObjectsToSpawn.Add(networkObjects[i]);
}
}
}
foreach (var networkObject in networkObjectsToSpawn)
{
SpawnNetworkObjectLocally(networkObject, GetNetworkObjectId(), true, false, null, true);
}
}
internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObject)
@@ -783,7 +809,7 @@ namespace Unity.Netcode
{
NetworkObjectId = networkObject.NetworkObjectId
};
var size = NetworkManager.SendMessage(message, NetworkDelivery.ReliableSequenced, m_TargetClientIds);
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, m_TargetClientIds);
foreach (var targetClientId in m_TargetClientIds)
{
NetworkManager.NetworkMetrics.TrackObjectDestroySent(targetClientId, networkObject, size);
@@ -822,7 +848,7 @@ namespace Unity.Netcode
{
foreach (var sobj in SpawnedObjectsList)
{
if (sobj.CheckObjectVisibility == null || NetworkManager.IsServer)
if (sobj.CheckObjectVisibility == null)
{
if (!sobj.Observers.Contains(clientId))
{

View File

@@ -18,6 +18,8 @@ namespace Unity.Netcode
/// <value><c>true</c> if is supported; otherwise, <c>false</c>.</value>
public virtual bool IsSupported => true;
internal INetworkMetrics NetworkMetrics;
/// <summary>
/// Delegate for transport network events
/// </summary>
@@ -95,6 +97,14 @@ namespace Unity.Netcode
/// <summary>
/// Initializes the transport
/// </summary>
public abstract void Initialize();
/// /// <param name="networkManager">optionally pass in NetworkManager</param>
public abstract void Initialize(NetworkManager networkManager = null);
}
#if UNITY_INCLUDE_TESTS
public abstract class TestingNetworkTransport : NetworkTransport
{
}
#endif
}

View File

@@ -1,3 +1,4 @@
#if UNITY_UNET_PRESENT
using System;
#if UNITY_EDITOR
using UnityEditor;
@@ -50,3 +51,4 @@ namespace Unity.Netcode.Transports.UNET
#endif
}
}
#endif

View File

@@ -1,3 +1,4 @@
#if UNITY_UNET_PRESENT
#pragma warning disable 618 // disable is obsolete
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
using System;
@@ -41,6 +42,8 @@ namespace Unity.Netcode.Transports.UNET
public override ulong ServerClientId => GetNetcodeClientId(0, 0, true);
internal NetworkManager NetworkManager;
protected void LateUpdate()
{
if (UnityEngine.Networking.NetworkTransport.IsStarted && MessageSendMode == SendMode.Queued)
@@ -48,7 +51,7 @@ namespace Unity.Netcode.Transports.UNET
#if UNITY_WEBGL
Debug.LogError("Cannot use queued sending mode for WebGL");
#else
if (NetworkManager.Singleton.IsServer)
if (NetworkManager != null && NetworkManager.IsServer)
{
foreach (var targetClient in NetworkManager.Singleton.ConnectedClientsList)
{
@@ -230,8 +233,10 @@ namespace Unity.Netcode.Transports.UNET
UnityEngine.Networking.NetworkTransport.Shutdown();
}
public override void Initialize()
public override void Initialize(NetworkManager networkManager = null)
{
NetworkManager = networkManager;
m_MessageBuffer = new byte[MessageBufferSize];
UnityEngine.Networking.NetworkTransport.Init();
@@ -279,3 +284,4 @@ namespace Unity.Netcode.Transports.UNET
}
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
#pragma warning restore 618 // restore is obsolete
#endif

View File

@@ -6,21 +6,28 @@
"Unity.Multiplayer.NetStats",
"Unity.Multiplayer.NetStatsReporting",
"Unity.Multiplayer.NetworkSolutionInterface",
"Unity.Multiplayer.Tools.MetricTypes",
"Unity.Multiplayer.Tools.NetStats",
"Unity.Multiplayer.Tools.NetStatsReporting",
"Unity.Multiplayer.Tools.NetworkSolutionInterface",
"Unity.Collections"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [
{
"name": "com.unity.multiplayer.tools",
"expression": "",
"define": "MULTIPLAYER_TOOLS"
},
{
"name": "Unity",
"expression": "(0,2022.2.0a5)",
"define": "UNITY_UNET_PRESENT"
},
{
"name": "com.unity.multiplayer.tools",
"expression": "1.0.0-pre.4",
"define": "MULTIPLAYER_TOOLS_1_0_0_PRE_4"
}
],
"noEngineReferences": false
]
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: fb1b6e801936c7f4a9af28dbed5ea2ff
guid: d627e2fb516d92242a4930e5cd9291e3
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 4e60372130aba464f9f9ae4a24bb9fe0
guid: e9af0202c9057c944b67aad6e4cdac96
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@@ -0,0 +1,6 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("TestProject.EditorTests")]
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")]
[assembly: InternalsVisibleTo("TestProject.ToolsIntegration.RuntimeTests")]
[assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")]

View File

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

View File

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

View File

@@ -0,0 +1,93 @@
namespace Unity.Netcode.TestHelpers.Runtime
{
public class ObjectNameIdentifier : NetworkBehaviour
{
private ulong m_CurrentOwner;
private ulong m_CurrentNetworkObjectId;
private bool m_IsRegistered;
/// <summary>
/// Keep a reference to the assigned NetworkObject
/// <see cref="OnDestroy"/>
/// </summary>
private NetworkObject m_NetworkObject;
public override void OnNetworkSpawn()
{
RegisterAndLabelNetworkObject();
}
protected void RegisterAndLabelNetworkObject()
{
if (!m_IsRegistered)
{
// This is required otherwise it will try to continue to update the NetworkBehaviour even if
// it has been destroyed.
m_NetworkObject = NetworkObject;
m_CurrentOwner = OwnerClientId;
m_CurrentNetworkObjectId = NetworkObjectId;
var objectOriginalName = gameObject.name.Replace("(Clone)", "");
var serverOrClient = IsServer ? "Server" : "Client";
if (NetworkObject.IsPlayerObject)
{
gameObject.name = NetworkManager.LocalClientId == OwnerClientId ? $"{objectOriginalName}({OwnerClientId})-Local{objectOriginalName}" :
$"{objectOriginalName}({OwnerClientId})-On{serverOrClient}({NetworkManager.LocalClientId})";
}
else
{
gameObject.name = $"{objectOriginalName}({NetworkObjectId})-On{serverOrClient}({NetworkManager.LocalClientId})";
}
// Don't add the player objects to the global list of NetworkObjects
if (!NetworkObject.IsPlayerObject)
{
NetcodeIntegrationTest.RegisterNetworkObject(NetworkObject);
}
m_IsRegistered = true;
}
}
protected void DeRegisterNetworkObject()
{
if (m_IsRegistered)
{
NetcodeIntegrationTest.DeregisterNetworkObject(m_CurrentOwner, m_CurrentNetworkObjectId);
m_IsRegistered = false;
}
}
public override void OnLostOwnership()
{
DeRegisterNetworkObject();
RegisterAndLabelNetworkObject();
}
public override void OnGainedOwnership()
{
DeRegisterNetworkObject();
RegisterAndLabelNetworkObject();
}
public override void OnNetworkDespawn()
{
DeRegisterNetworkObject();
}
public override void OnDestroy()
{
if (m_NetworkObject != null)
{
DeRegisterNetworkObject();
// This is required otherwise it will try to continue to update the NetworkBehaviour even if
// it has been destroyed (most likely integration test specific)
if (m_NetworkObject.ChildNetworkBehaviours != null && m_NetworkObject.ChildNetworkBehaviours.Contains(this))
{
NetworkObject.ChildNetworkBehaviours.Remove(this);
}
m_NetworkObject = null;
}
base.OnDestroy();
}
}
}

View File

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

View File

@@ -0,0 +1,60 @@
namespace Unity.Netcode.TestHelpers.Runtime
{
/// <summary>
/// Derive from this class to create your own conditional handling for your <see cref="NetcodeIntegrationTest"/>
/// integration tests when dealing with more complicated scenarios where initializing values, storing state to be
/// used across several integration tests.
/// </summary>
public class ConditionalPredicateBase : IConditionalPredicate
{
private bool m_TimedOut;
public bool TimedOut { get { return m_TimedOut; } }
protected virtual bool OnHasConditionBeenReached()
{
return true;
}
public bool HasConditionBeenReached()
{
return OnHasConditionBeenReached();
}
protected virtual void OnStarted() { }
public void Started()
{
OnStarted();
}
protected virtual void OnFinished() { }
public void Finished(bool timedOut)
{
m_TimedOut = timedOut;
OnFinished();
}
}
public interface IConditionalPredicate
{
/// <summary>
/// Test the conditions of the test to be reached
/// </summary>
bool HasConditionBeenReached();
/// <summary>
/// Wait for condition has started
/// </summary>
void Started();
/// <summary>
/// Wait for condition has finished:
/// Condition(s) met or timed out
/// </summary>
void Finished(bool timedOut);
}
}

View File

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

View File

@@ -0,0 +1,115 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;
namespace Unity.Netcode.TestHelpers.Runtime
{
/// <summary>
/// The default SceneManagerHandler used for all NetcodeIntegrationTest derived children.
/// </summary>
internal class IntegrationTestSceneHandler : ISceneManagerHandler, IDisposable
{
internal CoroutineRunner CoroutineRunner;
// Default client simulated delay time
protected const float k_ClientLoadingSimulatedDelay = 0.02f;
// Controls the client simulated delay time
protected float m_ClientLoadingSimulatedDelay = k_ClientLoadingSimulatedDelay;
public delegate bool CanClientsLoadUnloadDelegateHandler();
public event CanClientsLoadUnloadDelegateHandler CanClientsLoad;
public event CanClientsLoadUnloadDelegateHandler CanClientsUnload;
internal List<Coroutine> CoroutinesRunning = new List<Coroutine>();
/// <summary>
/// Used to control when clients should attempt to fake-load a scene
/// Note: Unit/Integration tests that only use <see cref="NetcodeIntegrationTestHelpers"/>
/// need to subscribe to the CanClientsLoad and CanClientsUnload events
/// in order to control when clients can fake-load.
/// Tests that derive from <see cref="NetcodeIntegrationTest"/> already have integrated
/// support and you can override <see cref="NetcodeIntegrationTest.CanClientsLoad"/> and
/// <see cref="NetcodeIntegrationTest.CanClientsUnload"/>.
/// </summary>
protected bool OnCanClientsLoad()
{
if (CanClientsLoad != null)
{
return CanClientsLoad.Invoke();
}
return true;
}
/// <summary>
/// Fake-Loads a scene for a client
/// </summary>
internal IEnumerator ClientLoadSceneCoroutine(string sceneName, ISceneManagerHandler.SceneEventAction sceneEventAction)
{
yield return new WaitForSeconds(m_ClientLoadingSimulatedDelay);
while (!OnCanClientsLoad())
{
yield return new WaitForSeconds(m_ClientLoadingSimulatedDelay);
}
sceneEventAction.Invoke();
}
protected bool OnCanClientsUnload()
{
if (CanClientsUnload != null)
{
return CanClientsUnload.Invoke();
}
return true;
}
/// <summary>
/// Fake-Unloads a scene for a client
/// </summary>
internal IEnumerator ClientUnloadSceneCoroutine(ISceneManagerHandler.SceneEventAction sceneEventAction)
{
yield return new WaitForSeconds(m_ClientLoadingSimulatedDelay);
while (!OnCanClientsUnload())
{
yield return new WaitForSeconds(m_ClientLoadingSimulatedDelay);
}
sceneEventAction.Invoke();
}
public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, ISceneManagerHandler.SceneEventAction sceneEventAction)
{
CoroutinesRunning.Add(CoroutineRunner.StartCoroutine(ClientLoadSceneCoroutine(sceneName, sceneEventAction)));
// This is OK to return a "nothing" AsyncOperation since we are simulating client loading
return new AsyncOperation();
}
public AsyncOperation UnloadSceneAsync(Scene scene, ISceneManagerHandler.SceneEventAction sceneEventAction)
{
CoroutinesRunning.Add(CoroutineRunner.StartCoroutine(ClientUnloadSceneCoroutine(sceneEventAction)));
// This is OK to return a "nothing" AsyncOperation since we are simulating client loading
return new AsyncOperation();
}
public IntegrationTestSceneHandler()
{
if (CoroutineRunner == null)
{
CoroutineRunner = new GameObject("UnitTestSceneHandlerCoroutine").AddComponent<CoroutineRunner>();
}
}
public void Dispose()
{
foreach (var coroutine in CoroutinesRunning)
{
CoroutineRunner.StopCoroutine(coroutine);
}
CoroutineRunner.StopAllCoroutines();
Object.Destroy(CoroutineRunner.gameObject);
}
}
}

View File

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

View File

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

View File

@@ -1,20 +1,11 @@
#if MULTIPLAYER_TOOLS
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Unity.Multiplayer.Tools.MetricTypes;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests.Metrics.Utility
namespace Unity.Netcode.TestHelpers.Runtime.Metrics
{
internal abstract class SingleClientMetricTestBase : BaseMultiInstanceTest
internal abstract class SingleClientMetricTestBase : NetcodeIntegrationTest
{
protected override int NbClients => 1;
protected virtual Action<GameObject> UpdatePlayerPrefab => _ => { };
protected override int NumberOfClients => 1;
internal NetworkManager Server { get; private set; }
@@ -24,23 +15,24 @@ namespace Unity.Netcode.RuntimeTests.Metrics.Utility
internal NetworkMetrics ClientMetrics { get; private set; }
[UnitySetUp]
public override IEnumerator Setup()
protected override void OnServerAndClientsCreated()
{
yield return StartSomeClientsAndServerWithPlayers(true, NbClients, UpdatePlayerPrefab);
Server = m_ServerNetworkManager;
ServerMetrics = Server.NetworkMetrics as NetworkMetrics;
Client = m_ClientNetworkManagers[0];
base.OnServerAndClientsCreated();
}
protected override IEnumerator OnStartedServerAndClients()
{
ServerMetrics = Server.NetworkMetrics as NetworkMetrics;
ClientMetrics = Client.NetworkMetrics as NetworkMetrics;
yield return base.OnStartedServerAndClients();
}
}
public abstract class DualClientMetricTestBase : BaseMultiInstanceTest
public abstract class DualClientMetricTestBase : NetcodeIntegrationTest
{
protected override int NbClients => 2;
protected virtual Action<GameObject> UpdatePlayerPrefab => _ => { };
protected override int NumberOfClients => 2;
internal NetworkManager Server { get; private set; }
@@ -54,17 +46,20 @@ namespace Unity.Netcode.RuntimeTests.Metrics.Utility
internal NetworkMetrics SecondClientMetrics { get; private set; }
[UnitySetUp]
public override IEnumerator Setup()
protected override void OnServerAndClientsCreated()
{
yield return StartSomeClientsAndServerWithPlayers(true, NbClients, UpdatePlayerPrefab);
Server = m_ServerNetworkManager;
ServerMetrics = Server.NetworkMetrics as NetworkMetrics;
FirstClient = m_ClientNetworkManagers[0];
SecondClient = m_ClientNetworkManagers[1];
base.OnServerAndClientsCreated();
}
protected override IEnumerator OnStartedServerAndClients()
{
ServerMetrics = Server.NetworkMetrics as NetworkMetrics;
FirstClientMetrics = FirstClient.NetworkMetrics as NetworkMetrics;
SecondClient = m_ClientNetworkManagers[0];
SecondClientMetrics = SecondClient.NetworkMetrics as NetworkMetrics;
yield return base.OnStartedServerAndClients();
}
}
}

View File

@@ -1,7 +1,7 @@
#if MULTIPLAYER_TOOLS
using UnityEngine;
namespace Unity.Netcode.RuntimeTests.Metrics.Utility
namespace Unity.Netcode.TestHelpers.Runtime.Metrics
{
public class NetworkVariableComponent : NetworkBehaviour
{

View File

@@ -1,6 +1,6 @@
using System;
namespace Unity.Netcode.RuntimeTests.Metrics.Utility
namespace Unity.Netcode.TestHelpers.Runtime.Metrics
{
public class RpcTestComponent : NetworkBehaviour
{

View File

@@ -0,0 +1,50 @@
#if MULTIPLAYER_TOOLS
using Unity.Multiplayer.Tools.MetricTypes;
using Unity.Multiplayer.Tools.NetStats;
namespace Unity.Netcode.TestHelpers.Runtime.Metrics
{
internal class WaitForCounterMetricValue : WaitForMetricValues<Counter>
{
private long m_Value;
public delegate bool CounterFilter(long metric);
private CounterFilter m_CounterFilterDelegate;
public WaitForCounterMetricValue(IMetricDispatcher dispatcher, DirectionalMetricInfo directionalMetricName)
: base(dispatcher, directionalMetricName)
{
}
public WaitForCounterMetricValue(IMetricDispatcher dispatcher, DirectionalMetricInfo directionalMetricName, CounterFilter counterFilter)
: this(dispatcher, directionalMetricName)
{
m_CounterFilterDelegate = counterFilter;
}
public long AssertMetricValueHaveBeenFound()
{
AssertHasError();
AssertIsFound();
return m_Value;
}
public override void Observe(MetricCollection collection)
{
if (FindMetric(collection, out var metric))
{
var typedMetric = metric as Counter;
if (typedMetric == default)
{
SetError(metric);
return;
}
m_Value = typedMetric.Value;
m_Found = m_CounterFilterDelegate != null ? m_CounterFilterDelegate(m_Value) : true;
}
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: aa1d3026d48b43bfa4c76e253b08b3ae
timeCreated: 1644269156

View File

@@ -0,0 +1,60 @@
#if MULTIPLAYER_TOOLS
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using NUnit.Framework;
using Unity.Multiplayer.Tools.MetricTypes;
using Unity.Multiplayer.Tools.NetStats;
namespace Unity.Netcode.TestHelpers.Runtime.Metrics
{
internal class WaitForEventMetricValues<TMetric> : WaitForMetricValues<TMetric>
{
IReadOnlyCollection<TMetric> m_EventValues;
public delegate bool EventFilter(TMetric metric);
EventFilter m_EventFilterDelegate;
public WaitForEventMetricValues(IMetricDispatcher dispatcher, DirectionalMetricInfo directionalMetricName)
: base(dispatcher, directionalMetricName)
{
}
public WaitForEventMetricValues(IMetricDispatcher dispatcher, DirectionalMetricInfo directionalMetricName, EventFilter eventFilter)
: this(dispatcher, directionalMetricName)
{
m_EventFilterDelegate = eventFilter;
}
public IReadOnlyCollection<TMetric> AssertMetricValuesHaveBeenFound()
{
AssertHasError();
AssertIsFound();
return m_EventValues;
}
public override void Observe(MetricCollection collection)
{
if (FindMetric(collection, out var metric))
{
var typedMetric = metric as IEventMetric<TMetric>;
if (typedMetric == default)
{
SetError(metric);
return;
}
if (typedMetric.Values.Any())
{
// Apply filter if one was provided
m_EventValues = m_EventFilterDelegate != null ? typedMetric.Values.Where(x => m_EventFilterDelegate(x)).ToList() : typedMetric.Values.ToList();
m_Found = m_EventValues.Count > 0;
}
}
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 319c55f92728431283c9e888d8f9d70e
timeCreated: 1644269156

View File

@@ -0,0 +1,55 @@
#if MULTIPLAYER_TOOLS
using Unity.Multiplayer.Tools.MetricTypes;
using Unity.Multiplayer.Tools.NetStats;
namespace Unity.Netcode.TestHelpers.Runtime.Metrics
{
internal class WaitForGaugeMetricValues : WaitForMetricValues<Gauge>
{
private double m_Value;
public delegate bool GaugeFilter(double metric);
private GaugeFilter m_GaugeFilterDelegate;
public WaitForGaugeMetricValues(IMetricDispatcher dispatcher, DirectionalMetricInfo directionalMetricName)
: base(dispatcher, directionalMetricName)
{
}
public WaitForGaugeMetricValues(IMetricDispatcher dispatcher, DirectionalMetricInfo directionalMetricName, GaugeFilter counterFilter)
: this(dispatcher, directionalMetricName)
{
m_GaugeFilterDelegate = counterFilter;
}
public bool MetricFound()
{
return m_Found;
}
public double AssertMetricValueHaveBeenFound()
{
AssertHasError();
AssertIsFound();
return m_Value;
}
public override void Observe(MetricCollection collection)
{
if (FindMetric(collection, out var metric))
{
var typedMetric = metric as Gauge;
if (typedMetric == default)
{
SetError(metric);
return;
}
m_Value = typedMetric.Value;
m_Found = m_GaugeFilterDelegate != null ? m_GaugeFilterDelegate(m_Value) : true;
}
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1d76c4e546c546a3b9d63b2c74fcbbca
timeCreated: 1644269156

View File

@@ -1,59 +1,27 @@
#if MULTIPLAYER_TOOLS
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Unity.Multiplayer.Tools.MetricTypes;
using Unity.Multiplayer.Tools.NetStats;
namespace Unity.Netcode.RuntimeTests.Metrics.Utility
namespace Unity.Netcode.TestHelpers.Runtime.Metrics
{
internal class WaitForMetricValues<TMetric> : IMetricObserver
internal abstract class WaitForMetricValues<TMetric> : IMetricObserver
{
readonly string m_MetricName;
bool m_Found;
bool m_HasError;
string m_Error;
uint m_NbFrames = 0;
IReadOnlyCollection<TMetric> m_Values;
public delegate bool Filter(TMetric metric);
Filter m_FilterDelegate;
protected readonly string m_MetricName;
protected bool m_Found;
protected bool m_HasError;
protected string m_Error;
protected uint m_NbFrames = 0;
public WaitForMetricValues(IMetricDispatcher dispatcher, DirectionalMetricInfo directionalMetricName)
{
m_MetricName = directionalMetricName.Id;
dispatcher.RegisterObserver(this);
}
public WaitForMetricValues(IMetricDispatcher dispatcher, DirectionalMetricInfo directionalMetricName, Filter filter)
: this(dispatcher, directionalMetricName)
{
m_FilterDelegate = filter;
}
public IEnumerator WaitForMetricsReceived()
{
yield return WaitForFrames(60);
}
public IReadOnlyCollection<TMetric> AssertMetricValuesHaveBeenFound()
{
if (m_HasError)
{
Assert.Fail(m_Error);
}
if (!m_Found)
{
Assert.Fail($"Found no matching values for metric of type '{typeof(TMetric).Name}', with name '{m_MetricName}' during '{m_NbFrames}' frames.");
}
return m_Values;
}
abstract public void Observe(MetricCollection collection);
public void AssertMetricValuesHaveNotBeenFound()
{
@@ -72,37 +40,51 @@ namespace Unity.Netcode.RuntimeTests.Metrics.Utility
}
}
public void Observe(MetricCollection collection)
public IEnumerator WaitForMetricsReceived()
{
yield return WaitForFrames(60);
}
protected void AssertHasError()
{
if (m_HasError)
{
Assert.Fail(m_Error);
}
}
protected void AssertIsFound()
{
if (!m_Found)
{
Assert.Fail($"Found no matching values for metric of type '{typeof(TMetric).Name}', with name '{m_MetricName}' during '{m_NbFrames}' frames.");
}
}
protected bool FindMetric(MetricCollection collection, out IMetric metric)
{
if (m_Found || m_HasError)
{
return;
metric = null;
return false;
}
var metric = collection.Metrics.SingleOrDefault(x => x.Name == m_MetricName);
metric = collection.Metrics.SingleOrDefault(x => x.Name == m_MetricName);
if (metric == default)
{
m_HasError = true;
m_Error = $"Metric collection does not contain metric named '{m_MetricName}'.";
return;
return false;
}
var typedMetric = metric as IEventMetric<TMetric>;
if (typedMetric == default)
{
m_HasError = true;
m_Error = $"Metric collection contains a metric of type '{metric.GetType().Name}' for name '{m_MetricName}', but was expecting '{typeof(TMetric).Name}'.";
return true;
}
return;
}
if (typedMetric.Values.Any())
{
// Apply filter if one was provided
m_Values = m_FilterDelegate != null ? typedMetric.Values.Where(x => m_FilterDelegate(x)).ToList() : typedMetric.Values.ToList();
m_Found = m_Values.Count > 0;
}
protected void SetError(IMetric metric)
{
m_HasError = true;
m_Error = $"Metric collection contains a metric of type '{metric.GetType().Name}' for name '{m_MetricName}', but was expecting '{typeof(TMetric).Name}'.";
}
private IEnumerator WaitForFrames(uint maxNbFrames)

View File

@@ -0,0 +1,727 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using Object = UnityEngine.Object;
namespace Unity.Netcode.TestHelpers.Runtime
{
/// <summary>
/// The default Netcode for GameObjects integration test helper class
/// </summary>
public abstract class NetcodeIntegrationTest
{
protected static TimeoutHelper s_GlobalTimeoutHelper = new TimeoutHelper(4.0f);
protected static WaitForSeconds s_DefaultWaitForTick = new WaitForSeconds(1.0f / k_DefaultTickRate);
/// <summary>
/// Registered list of all NetworkObjects spawned.
/// Format is as follows:
/// [ClientId-side where this NetworkObject instance resides][NetworkObjectId][NetworkObject]
/// Where finding the NetworkObject with a NetworkObjectId of 10 on ClientId of 2 would be:
/// s_GlobalNetworkObjects[2][10]
/// To find the client or server player objects please see:
/// <see cref="m_PlayerNetworkObjects"/>
/// </summary>
protected static Dictionary<ulong, Dictionary<ulong, NetworkObject>> s_GlobalNetworkObjects = new Dictionary<ulong, Dictionary<ulong, NetworkObject>>();
public static void RegisterNetworkObject(NetworkObject networkObject)
{
if (!s_GlobalNetworkObjects.ContainsKey(networkObject.NetworkManager.LocalClientId))
{
s_GlobalNetworkObjects.Add(networkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
}
if (s_GlobalNetworkObjects[networkObject.NetworkManager.LocalClientId].ContainsKey(networkObject.NetworkObjectId))
{
if (s_GlobalNetworkObjects[networkObject.NetworkManager.LocalClientId] == null)
{
Assert.False(s_GlobalNetworkObjects[networkObject.NetworkManager.LocalClientId][networkObject.NetworkObjectId] != null,
$"Duplicate NetworkObjectId {networkObject.NetworkObjectId} found in {nameof(s_GlobalNetworkObjects)} for client id {networkObject.NetworkManager.LocalClientId}!");
}
else
{
s_GlobalNetworkObjects[networkObject.NetworkManager.LocalClientId][networkObject.NetworkObjectId] = networkObject;
}
}
else
{
s_GlobalNetworkObjects[networkObject.NetworkManager.LocalClientId].Add(networkObject.NetworkObjectId, networkObject);
}
}
public static void DeregisterNetworkObject(NetworkObject networkObject)
{
if (networkObject.IsSpawned && networkObject.NetworkManager != null)
{
DeregisterNetworkObject(networkObject.NetworkManager.LocalClientId, networkObject.NetworkObjectId);
}
}
public static void DeregisterNetworkObject(ulong localClientId, ulong networkObjectId)
{
if (s_GlobalNetworkObjects.ContainsKey(localClientId) && s_GlobalNetworkObjects[localClientId].ContainsKey(networkObjectId))
{
s_GlobalNetworkObjects[localClientId].Remove(networkObjectId);
if (s_GlobalNetworkObjects[localClientId].Count == 0)
{
s_GlobalNetworkObjects.Remove(localClientId);
}
}
}
protected int TotalClients => m_UseHost ? NumberOfClients + 1 : NumberOfClients;
protected const uint k_DefaultTickRate = 30;
protected abstract int NumberOfClients { get; }
public enum NetworkManagerInstatiationMode
{
PerTest, // This will create and destroy new NetworkManagers for each test within a child derived class
AllTests, // This will create one set of NetworkManagers used for all tests within a child derived class (destroyed once all tests are finished)
DoNotCreate // This will not create any NetworkManagers, it is up to the derived class to manage.
}
public enum HostOrServer
{
Host,
Server
}
protected GameObject m_PlayerPrefab;
protected NetworkManager m_ServerNetworkManager;
protected NetworkManager[] m_ClientNetworkManagers;
/// <summary>
/// Contains each client relative set of player NetworkObject instances
/// [Client Relative set of player instances][The player instance ClientId][The player instance's NetworkObject]
/// Example:
/// To get the player instance with a ClientId of 3 that was instantiated (relative) on the player instance with a ClientId of 2
/// m_PlayerNetworkObjects[2][3]
/// </summary>
protected Dictionary<ulong, Dictionary<ulong, NetworkObject>> m_PlayerNetworkObjects = new Dictionary<ulong, Dictionary<ulong, NetworkObject>>();
protected bool m_UseHost = true;
protected int m_TargetFrameRate = 60;
protected NetcodeIntegrationTestHelpers.InstanceTransport m_NetworkTransport = NetcodeIntegrationTestHelpers.InstanceTransport.SIP;
private NetworkManagerInstatiationMode m_NetworkManagerInstatiationMode;
/// <summary>
/// The very first thing invoked during the <see cref="OneTimeSetup"/> that
/// determines how this integration test handles NetworkManager instantiation
/// and destruction. <see cref="NetworkManagerInstatiationMode"/>
/// Override this method to change the default mode:
/// <see cref="NetworkManagerInstatiationMode.AllTests"/>
/// </summary>
protected virtual NetworkManagerInstatiationMode OnSetIntegrationTestMode()
{
return NetworkManagerInstatiationMode.PerTest;
}
protected virtual void OnOneTimeSetup()
{
}
[OneTimeSetUp]
public void OneTimeSetup()
{
m_NetworkManagerInstatiationMode = OnSetIntegrationTestMode();
// Enable NetcodeIntegrationTest auto-label feature
NetcodeIntegrationTestHelpers.RegisterNetcodeIntegrationTest(true);
OnOneTimeSetup();
}
/// <summary>
/// Called before creating and starting the server and clients
/// Note: For <see cref="NetworkManagerInstatiationMode.AllTests"/> and
/// <see cref="NetworkManagerInstatiationMode.PerTest"/> mode integration tests.
/// For those two modes, if you want to have access to the server or client
/// <see cref="NetworkManager"/>s then override <see cref="OnServerAndClientsCreated"/>.
/// <see cref="m_ServerNetworkManager"/> and <see cref="m_ClientNetworkManagers"/>
/// </summary>
protected virtual IEnumerator OnSetup()
{
yield return null;
}
[UnitySetUp]
public IEnumerator SetUp()
{
yield return OnSetup();
if (m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.AllTests && m_ServerNetworkManager == null ||
m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.PerTest)
{
CreateServerAndClients();
yield return StartServerAndClients();
}
}
/// <summary>
/// Override this to add components or adjustments to the default player prefab
/// <see cref="m_PlayerPrefab"/>
/// </summary>
protected virtual void OnCreatePlayerPrefab()
{
}
private void CreatePlayerPrefab()
{
// Create playerPrefab
m_PlayerPrefab = new GameObject("Player");
NetworkObject networkObject = m_PlayerPrefab.AddComponent<NetworkObject>();
// Make it a prefab
NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject);
OnCreatePlayerPrefab();
}
/// <summary>
/// This is invoked before the server and client(s) are started.
/// Override this method if you want to make any adjustments to their
/// NetworkManager instances.
/// </summary>
protected virtual void OnServerAndClientsCreated()
{
}
/// <summary>
/// Will create <see cref="NumberOfClients"/> number of clients.
/// To create a specific number of clients <see cref="CreateServerAndClients(int)"/>
/// </summary>
protected void CreateServerAndClients()
{
CreateServerAndClients(NumberOfClients);
}
/// <summary>
/// Creates the server and clients
/// </summary>
/// <param name="numberOfClients"></param>
protected void CreateServerAndClients(int numberOfClients)
{
CreatePlayerPrefab();
// Create multiple NetworkManager instances
if (!NetcodeIntegrationTestHelpers.Create(numberOfClients, out NetworkManager server, out NetworkManager[] clients, m_TargetFrameRate, m_NetworkTransport))
{
Debug.LogError("Failed to create instances");
Assert.Fail("Failed to create instances");
}
m_ClientNetworkManagers = clients;
m_ServerNetworkManager = server;
if (m_ServerNetworkManager != null)
{
s_DefaultWaitForTick = new WaitForSeconds(1.0f / m_ServerNetworkManager.NetworkConfig.TickRate);
}
// Set the player prefab for the server and clients
m_ServerNetworkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab;
foreach (var client in m_ClientNetworkManagers)
{
client.NetworkConfig.PlayerPrefab = m_PlayerPrefab;
}
// Provides opportunity to allow child derived classes to
// modify the NetworkManager's configuration before starting.
OnServerAndClientsCreated();
}
/// <summary>
/// Override this method and return false in order to be able
/// to manually control when the server and clients are started.
/// </summary>
protected virtual bool CanStartServerAndClients()
{
return true;
}
/// <summary>
/// Invoked after the server and clients have started.
/// Note: No connection verification has been done at this point
/// </summary>
protected virtual IEnumerator OnStartedServerAndClients()
{
yield return null;
}
/// <summary>
/// Invoked after the server and clients have started and verified
/// their connections with each other.
/// </summary>
protected virtual IEnumerator OnServerAndClientsConnected()
{
yield return null;
}
/// <summary>
/// This starts the server and clients as long as <see cref="CanStartServerAndClients"/>
/// returns true.
/// </summary>
protected IEnumerator StartServerAndClients()
{
if (CanStartServerAndClients())
{
// Start the instances and pass in our SceneManagerInitialization action that is invoked immediately after host-server
// is started and after each client is started.
if (!NetcodeIntegrationTestHelpers.Start(m_UseHost, m_ServerNetworkManager, m_ClientNetworkManagers))
{
Debug.LogError("Failed to start instances");
Assert.Fail("Failed to start instances");
}
RegisterSceneManagerHandler();
// Notification that the server and clients have been started
yield return OnStartedServerAndClients();
// Wait for all clients to connect
yield return WaitForClientsConnectedOrTimeOut();
Assert.False(s_GlobalTimeoutHelper.TimedOut, $"{nameof(StartServerAndClients)} timed out waiting for all clients to be connected!");
if (s_GlobalTimeoutHelper.TimedOut)
{
yield return null;
}
if (m_UseHost || m_ServerNetworkManager.IsHost)
{
// Add the server player instance to all m_ClientSidePlayerNetworkObjects entries
var serverPlayerClones = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsPlayerObject && c.OwnerClientId == m_ServerNetworkManager.LocalClientId);
foreach (var playerNetworkObject in serverPlayerClones)
{
if (!m_PlayerNetworkObjects.ContainsKey(playerNetworkObject.NetworkManager.LocalClientId))
{
m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
}
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(m_ServerNetworkManager.LocalClientId, playerNetworkObject);
}
}
// Creates a dictionary for all player instances client and server relative
// This provides a simpler way to get a specific player instance relative to a client instance
foreach (var networkManager in m_ClientNetworkManagers)
{
Assert.NotNull(networkManager.LocalClient.PlayerObject, $"{nameof(StartServerAndClients)} detected that client {networkManager.LocalClientId} does not have an assigned player NetworkObject!");
// Get all player instances for the current client NetworkManager instance
var clientPlayerClones = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsPlayerObject && c.OwnerClientId == networkManager.LocalClientId);
// Add this player instance to each client player entry
foreach (var playerNetworkObject in clientPlayerClones)
{
// When the server is not the host this needs to be done
if (!m_PlayerNetworkObjects.ContainsKey(playerNetworkObject.NetworkManager.LocalClientId))
{
m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
}
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(networkManager.LocalClientId, playerNetworkObject);
}
}
// Notification that at this time the server and client(s) are instantiated,
// started, and connected on both sides.
yield return OnServerAndClientsConnected();
}
}
/// <summary>
/// Override this method to control when clients
/// can fake-load a scene.
/// </summary>
protected virtual bool CanClientsLoad()
{
return true;
}
/// <summary>
/// Override this method to control when clients
/// can fake-unload a scene.
/// </summary>
protected virtual bool CanClientsUnload()
{
return true;
}
/// <summary>
/// De-Registers from the CanClientsLoad and CanClientsUnload events of the
/// ClientSceneHandler (default is IntegrationTestSceneHandler).
/// </summary>
protected void DeRegisterSceneManagerHandler()
{
if (NetcodeIntegrationTestHelpers.ClientSceneHandler != null)
{
NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsLoad -= ClientSceneHandler_CanClientsLoad;
NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsUnload -= ClientSceneHandler_CanClientsUnload;
}
}
/// <summary>
/// Registers the CanClientsLoad and CanClientsUnload events of the
/// ClientSceneHandler.
/// The default is: <see cref="IntegrationTestSceneHandler"/>.
/// </summary>
protected void RegisterSceneManagerHandler()
{
if (NetcodeIntegrationTestHelpers.ClientSceneHandler != null)
{
NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsLoad += ClientSceneHandler_CanClientsLoad;
NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsUnload += ClientSceneHandler_CanClientsUnload;
}
}
private bool ClientSceneHandler_CanClientsUnload()
{
return CanClientsUnload();
}
private bool ClientSceneHandler_CanClientsLoad()
{
return CanClientsLoad();
}
/// <summary>
/// This shuts down all NetworkManager instances registered via the
/// <see cref="NetcodeIntegrationTestHelpers"/> class and cleans up
/// the test runner scene of any left over NetworkObjects.
/// <see cref="DestroySceneNetworkObjects"/>
/// </summary>
protected void ShutdownAndCleanUp()
{
// Shutdown and clean up both of our NetworkManager instances
try
{
if (NetcodeIntegrationTestHelpers.ClientSceneHandler != null)
{
NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsLoad -= ClientSceneHandler_CanClientsLoad;
NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsUnload -= ClientSceneHandler_CanClientsUnload;
}
NetcodeIntegrationTestHelpers.Destroy();
m_PlayerNetworkObjects.Clear();
s_GlobalNetworkObjects.Clear();
}
catch (Exception e) { throw e; }
finally
{
if (m_PlayerPrefab != null)
{
Object.Destroy(m_PlayerPrefab);
m_PlayerPrefab = null;
}
}
// Cleanup any remaining NetworkObjects
DestroySceneNetworkObjects();
// reset the m_ServerWaitForTick for the next test to initialize
s_DefaultWaitForTick = new WaitForSeconds(1.0f / k_DefaultTickRate);
}
/// <summary>
/// Note: For <see cref="NetworkManagerInstatiationMode.PerTest"/> mode
/// this is called before ShutdownAndCleanUp.
/// </summary>
protected virtual IEnumerator OnTearDown()
{
yield return null;
}
[UnityTearDown]
public IEnumerator TearDown()
{
yield return OnTearDown();
if (m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.PerTest)
{
ShutdownAndCleanUp();
}
}
/// <summary>
/// Override this method to do handle cleaning up once the test(s)
/// within the child derived class have completed
/// Note: For <see cref="NetworkManagerInstatiationMode.AllTests"/> mode
/// this is called before ShutdownAndCleanUp.
/// </summary>
protected virtual void OnOneTimeTearDown()
{
}
[OneTimeTearDown]
public void OneTimeTearDown()
{
OnOneTimeTearDown();
if (m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.AllTests)
{
ShutdownAndCleanUp();
}
// Disable NetcodeIntegrationTest auto-label feature
NetcodeIntegrationTestHelpers.RegisterNetcodeIntegrationTest(false);
}
/// <summary>
/// Override this to filter out the <see cref="NetworkObject"/>s that you
/// want to allow to persist between integration tests.
/// <see cref="DestroySceneNetworkObjects"/>
/// <see cref="ShutdownAndCleanUp"/>
/// </summary>
/// <param name="networkObject">the network object in question to be destroyed</param>
protected virtual bool CanDestroyNetworkObject(NetworkObject networkObject)
{
return true;
}
/// <summary>
/// Destroys all NetworkObjects at the end of a test cycle.
/// </summary>
protected void DestroySceneNetworkObjects()
{
var networkObjects = Object.FindObjectsOfType<NetworkObject>();
foreach (var networkObject in networkObjects)
{
// This can sometimes be null depending upon order of operations
// when dealing with parented NetworkObjects. If NetworkObjectB
// is a child of NetworkObjectA and NetworkObjectA comes before
// NetworkObjectB in the list of NeworkObjects found, then when
// NetworkObjectA's GameObject is destroyed it will also destroy
// NetworkObjectB's GameObject which will destroy NetworkObjectB.
// If there is a null entry in the list, this is the most likely
// scenario and so we just skip over it.
if (networkObject == null)
{
continue;
}
if (CanDestroyNetworkObject(networkObject))
{
// Destroy the GameObject that holds the NetworkObject component
Object.DestroyImmediate(networkObject.gameObject);
}
}
}
/// <summary>
/// Waits for the function condition to return true or it will time out.
/// This will operate at the current m_ServerNetworkManager.NetworkConfig.TickRate
/// and allow for a unique TimeoutHelper handler (if none then it uses the default)
/// Notes: This provides more stability when running integration tests that could be
/// impacted by:
/// -how the integration test is being executed (i.e. in editor or in a stand alone build)
/// -potential platform performance issues (i.e. VM is throttled or maxed)
/// Note: For more complex tests, <see cref="ConditionalPredicateBase"/> and the overloaded
/// version of this method
/// </summary>
public static IEnumerator WaitForConditionOrTimeOut(Func<bool> checkForCondition, TimeoutHelper timeOutHelper = null)
{
if (checkForCondition == null)
{
throw new ArgumentNullException($"checkForCondition cannot be null!");
}
// If none is provided we use the default global time out helper
if (timeOutHelper == null)
{
timeOutHelper = s_GlobalTimeoutHelper;
}
// Start checking for a timeout
timeOutHelper.Start();
while (!timeOutHelper.HasTimedOut())
{
// Update and check to see if the condition has been met
if (checkForCondition.Invoke())
{
break;
}
// Otherwise wait for 1 tick interval
yield return s_DefaultWaitForTick;
}
// Stop checking for a timeout
timeOutHelper.Stop();
}
/// <summary>
/// This version accepts an IConditionalPredicate implementation to provide
/// more flexibility for checking complex conditional cases.
/// </summary>
public static IEnumerator WaitForConditionOrTimeOut(IConditionalPredicate conditionalPredicate, TimeoutHelper timeOutHelper = null)
{
if (conditionalPredicate == null)
{
throw new ArgumentNullException($"checkForCondition cannot be null!");
}
// If none is provided we use the default global time out helper
if (timeOutHelper == null)
{
timeOutHelper = s_GlobalTimeoutHelper;
}
conditionalPredicate.Started();
yield return WaitForConditionOrTimeOut(conditionalPredicate.HasConditionBeenReached, timeOutHelper);
conditionalPredicate.Finished(timeOutHelper.TimedOut);
}
/// <summary>
/// Validates that all remote clients (i.e. non-server) detect they are connected
/// to the server and that the server reflects the appropriate number of clients
/// have connected or it will time out.
/// </summary>
/// <param name="clientsToCheck">An array of clients to be checked</param>
protected IEnumerator WaitForClientsConnectedOrTimeOut(NetworkManager[] clientsToCheck)
{
var remoteClientCount = clientsToCheck.Length;
var serverClientCount = m_ServerNetworkManager.IsHost ? remoteClientCount + 1 : remoteClientCount;
yield return WaitForConditionOrTimeOut(() => clientsToCheck.Where((c) => c.IsConnectedClient).Count() == remoteClientCount &&
m_ServerNetworkManager.ConnectedClients.Count == serverClientCount);
}
/// <summary>
/// Overloaded method that just passes in all clients to
/// <see cref="WaitForClientsConnectedOrTimeOut(NetworkManager[])"/>
/// </summary>
protected IEnumerator WaitForClientsConnectedOrTimeOut()
{
yield return WaitForClientsConnectedOrTimeOut(m_ClientNetworkManagers);
}
/// <summary>
/// Creates a basic NetworkObject test prefab, assigns it to a new
/// NetworkPrefab entry, and then adds it to the server and client(s)
/// NetworkManagers' NetworkConfig.NetworkPrefab lists.
/// </summary>
/// <param name="baseName">the basic name to be used for each instance</param>
/// <returns>NetworkObject of the GameObject assigned to the new NetworkPrefab entry</returns>
protected GameObject CreateNetworkObjectPrefab(string baseName)
{
var prefabCreateAssertError = $"You can only invoke this method during {nameof(OnServerAndClientsCreated)} " +
$"but before {nameof(OnStartedServerAndClients)}!";
Assert.IsNotNull(m_ServerNetworkManager, prefabCreateAssertError);
Assert.IsFalse(m_ServerNetworkManager.IsListening, prefabCreateAssertError);
var gameObject = new GameObject();
gameObject.name = baseName;
var networkObject = gameObject.AddComponent<NetworkObject>();
NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject);
var networkPrefab = new NetworkPrefab() { Prefab = gameObject };
m_ServerNetworkManager.NetworkConfig.NetworkPrefabs.Add(networkPrefab);
foreach (var clientNetworkManager in m_ClientNetworkManagers)
{
clientNetworkManager.NetworkConfig.NetworkPrefabs.Add(networkPrefab);
}
return gameObject;
}
/// <summary>
/// Overloaded method <see cref="SpawnObject(NetworkObject, NetworkManager, bool)"/>
/// </summary>
protected GameObject SpawnObject(GameObject prefabGameObject, NetworkManager owner, bool destroyWithScene = false)
{
var prefabNetworkObject = prefabGameObject.GetComponent<NetworkObject>();
Assert.IsNotNull(prefabNetworkObject, $"{nameof(GameObject)} {prefabGameObject.name} does not have a {nameof(NetworkObject)} component!");
return SpawnObject(prefabNetworkObject, owner, destroyWithScene);
}
/// <summary>
/// Spawn a NetworkObject prefab instance
/// </summary>
/// <param name="prefabNetworkObject">the prefab NetworkObject to spawn</param>
/// <param name="owner">the owner of the instance</param>
/// <param name="destroyWithScene">default is false</param>
/// <returns>GameObject instance spawned</returns>
private GameObject SpawnObject(NetworkObject prefabNetworkObject, NetworkManager owner, bool destroyWithScene = false)
{
Assert.IsTrue(prefabNetworkObject.GlobalObjectIdHash > 0, $"{nameof(GameObject)} {prefabNetworkObject.name} has a {nameof(NetworkObject.GlobalObjectIdHash)} value of 0! Make sure to make it a valid prefab before trying to spawn!");
var newInstance = Object.Instantiate(prefabNetworkObject.gameObject);
var networkObjectToSpawn = newInstance.GetComponent<NetworkObject>();
networkObjectToSpawn.NetworkManagerOwner = m_ServerNetworkManager; // Required to assure the server does the spawning
if (owner == m_ServerNetworkManager)
{
if (m_UseHost)
{
networkObjectToSpawn.SpawnWithOwnership(owner.LocalClientId, destroyWithScene);
}
else
{
networkObjectToSpawn.Spawn(destroyWithScene);
}
}
else
{
networkObjectToSpawn.SpawnWithOwnership(owner.LocalClientId, destroyWithScene);
}
return newInstance;
}
/// <summary>
/// Overloaded method <see cref="SpawnObjects(NetworkObject, NetworkManager, int, bool)"/>
/// </summary>
protected List<GameObject> SpawnObjects(GameObject prefabGameObject, NetworkManager owner, int count, bool destroyWithScene = false)
{
var prefabNetworkObject = prefabGameObject.GetComponent<NetworkObject>();
Assert.IsNotNull(prefabNetworkObject, $"{nameof(GameObject)} {prefabGameObject.name} does not have a {nameof(NetworkObject)} component!");
return SpawnObjects(prefabNetworkObject, owner, count, destroyWithScene);
}
/// <summary>
/// Will spawn (x) number of prefab NetworkObjects
/// <see cref="SpawnObject(NetworkObject, NetworkManager, bool)"/>
/// </summary>
/// <param name="prefabNetworkObject">the prefab NetworkObject to spawn</param>
/// <param name="owner">the owner of the instance</param>
/// <param name="count">number of instances to create and spawn</param>
/// <param name="destroyWithScene">default is false</param>
private List<GameObject> SpawnObjects(NetworkObject prefabNetworkObject, NetworkManager owner, int count, bool destroyWithScene = false)
{
var gameObjectsSpawned = new List<GameObject>();
for (int i = 0; i < count; i++)
{
gameObjectsSpawned.Add(SpawnObject(prefabNetworkObject, owner, destroyWithScene));
}
return gameObjectsSpawned;
}
/// <summary>
/// Default constructor
/// </summary>
public NetcodeIntegrationTest()
{
}
/// <summary>
/// Optional Host or Server integration tests
/// Constructor that allows you To break tests up as a host
/// and a server.
/// Example: Decorate your child derived class with TestFixture
/// and then create a constructor at the child level
/// [TestFixture(HostOrServer.Host)]
/// [TestFixture(HostOrServer.Server)]
/// public class MyChildClass : NetcodeIntegrationTest
/// {
/// MyChildClass(HostOrServer hostOrServer) : base(hostOrServer) { }
/// }
/// </summary>
/// <param name="hostOrServer"></param>
public NetcodeIntegrationTest(HostOrServer hostOrServer)
{
m_UseHost = hostOrServer == HostOrServer.Host ? true : false;
}
}
}

View File

@@ -7,22 +7,180 @@ using UnityEngine;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;
namespace Unity.Netcode.RuntimeTests
namespace Unity.Netcode.TestHelpers.Runtime
{
/// <summary>
/// Provides helpers for running multi instance tests.
/// </summary>
public static class MultiInstanceHelpers
public static class NetcodeIntegrationTestHelpers
{
public const int DefaultMinFrames = 1;
public const int DefaultMaxFrames = 64;
public const float DefaultTimeout = 1f;
private static List<NetworkManager> s_NetworkManagerInstances = new List<NetworkManager>();
private static Dictionary<NetworkManager, MultiInstanceHooks> s_Hooks = new Dictionary<NetworkManager, MultiInstanceHooks>();
private static bool s_IsStarted;
private static int s_ClientCount;
private static int s_OriginalTargetFrameRate = -1;
public delegate bool MessageHandleCheck(object receivedMessage);
internal class MessageHandleCheckWithResult
{
public MessageHandleCheck Check;
public bool Result;
}
private class MultiInstanceHooks : INetworkHooks
{
public Dictionary<Type, List<MessageHandleCheckWithResult>> HandleChecks = new Dictionary<Type, List<MessageHandleCheckWithResult>>();
public static bool CheckForMessageOfType<T>(object receivedMessage) where T : INetworkMessage
{
return receivedMessage.GetType() == typeof(T);
}
public void OnBeforeSendMessage<T>(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage
{
}
public void OnAfterSendMessage<T>(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage
{
}
public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes)
{
}
public void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes)
{
}
public void OnBeforeSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery)
{
}
public void OnAfterSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery)
{
}
public void OnBeforeReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes)
{
}
public void OnAfterReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes)
{
}
public bool OnVerifyCanSend(ulong destinationId, Type messageType, NetworkDelivery delivery)
{
return true;
}
public bool OnVerifyCanReceive(ulong senderId, Type messageType)
{
return true;
}
public void OnBeforeHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage
{
}
public void OnAfterHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage
{
if (HandleChecks.ContainsKey(typeof(T)))
{
foreach (var check in HandleChecks[typeof(T)])
{
if (check.Check(message))
{
check.Result = true;
HandleChecks[typeof(T)].Remove(check);
break;
}
}
}
}
}
private const string k_FirstPartOfTestRunnerSceneName = "InitTestScene";
public static List<NetworkManager> NetworkManagerInstances => s_NetworkManagerInstances;
public enum InstanceTransport
{
SIP,
#if UTP_ADAPTER
UTP
#endif
}
internal static IntegrationTestSceneHandler ClientSceneHandler = null;
/// <summary>
/// Registers the IntegrationTestSceneHandler for integration tests.
/// The default client behavior is to not load scenes on the client side.
/// </summary>
private static void RegisterSceneManagerHandler(NetworkManager networkManager)
{
if (!networkManager.IsServer)
{
if (ClientSceneHandler == null)
{
ClientSceneHandler = new IntegrationTestSceneHandler();
}
networkManager.SceneManager.SceneManagerHandler = ClientSceneHandler;
}
}
/// <summary>
/// Call this to clean up the IntegrationTestSceneHandler and destroy the s_CoroutineRunner.
/// Note:
/// If deriving from <see cref="NetcodeIntegrationTest"/> or using <see cref="Destroy"/> then you
/// typically won't need to do this.
/// </summary>
public static void CleanUpHandlers()
{
if (ClientSceneHandler != null)
{
ClientSceneHandler.Dispose();
ClientSceneHandler = null;
}
}
/// <summary>
/// Call this to register scene validation and the IntegrationTestSceneHandler
/// Note:
/// If deriving from <see cref="NetcodeIntegrationTest"/> or using <see cref="Destroy"/> then you
/// typically won't need to call this.
/// </summary>
public static void RegisterHandlers(NetworkManager networkManager, bool serverSideSceneManager = false)
{
SceneManagerValidationAndTestRunnerInitialization(networkManager);
if (!networkManager.IsServer || networkManager.IsServer && serverSideSceneManager)
{
RegisterSceneManagerHandler(networkManager);
}
}
/// <summary>
/// Create the correct NetworkTransport, attach it to the game object and return it.
/// Default value is SIPTransport.
/// </summary>
internal static NetworkTransport CreateInstanceTransport(InstanceTransport instanceTransport, GameObject go)
{
switch (instanceTransport)
{
case InstanceTransport.SIP:
default:
return go.AddComponent<SIPTransport>();
#if UTP_ADAPTER
case InstanceTransport.UTP:
return go.AddComponent<UnityTransport>();
#endif
}
}
/// <summary>
/// Creates NetworkingManagers and configures them for use in a multi instance setting.
/// </summary>
@@ -30,10 +188,10 @@ namespace Unity.Netcode.RuntimeTests
/// <param name="server">The server NetworkManager</param>
/// <param name="clients">The clients NetworkManagers</param>
/// <param name="targetFrameRate">The targetFrameRate of the Unity engine to use while the multi instance helper is running. Will be reset on shutdown.</param>
public static bool Create(int clientCount, out NetworkManager server, out NetworkManager[] clients, int targetFrameRate = 60)
public static bool Create(int clientCount, out NetworkManager server, out NetworkManager[] clients, int targetFrameRate = 60, InstanceTransport instanceTransport = InstanceTransport.SIP)
{
s_NetworkManagerInstances = new List<NetworkManager>();
CreateNewClients(clientCount, out clients);
CreateNewClients(clientCount, out clients, instanceTransport);
// Create gameObject
var go = new GameObject("NetworkManager - Server");
@@ -46,7 +204,7 @@ namespace Unity.Netcode.RuntimeTests
server.NetworkConfig = new NetworkConfig()
{
// Set transport
NetworkTransport = go.AddComponent<SIPTransport>()
NetworkTransport = CreateInstanceTransport(instanceTransport, go)
};
s_OriginalTargetFrameRate = Application.targetFrameRate;
@@ -60,8 +218,7 @@ namespace Unity.Netcode.RuntimeTests
/// </summary>
/// <param name="clientCount">The amount of clients</param>
/// <param name="clients"></param>
/// <returns></returns>
public static bool CreateNewClients(int clientCount, out NetworkManager[] clients)
public static bool CreateNewClients(int clientCount, out NetworkManager[] clients, InstanceTransport instanceTransport = InstanceTransport.SIP)
{
clients = new NetworkManager[clientCount];
var activeSceneName = SceneManager.GetActiveScene().name;
@@ -76,7 +233,7 @@ namespace Unity.Netcode.RuntimeTests
clients[i].NetworkConfig = new NetworkConfig()
{
// Set transport
NetworkTransport = go.AddComponent<SIPTransport>()
NetworkTransport = CreateInstanceTransport(instanceTransport, go)
};
}
@@ -91,6 +248,7 @@ namespace Unity.Netcode.RuntimeTests
public static void StopOneClient(NetworkManager clientToStop)
{
clientToStop.Shutdown();
s_Hooks.Remove(clientToStop);
Object.Destroy(clientToStop.gameObject);
NetworkManagerInstances.Remove(clientToStop);
}
@@ -112,6 +270,7 @@ namespace Unity.Netcode.RuntimeTests
foreach (var networkManager in NetworkManagerInstances)
{
networkManager.Shutdown();
s_Hooks.Remove(networkManager);
}
// Destroy the network manager instances
@@ -122,29 +281,71 @@ namespace Unity.Netcode.RuntimeTests
NetworkManagerInstances.Clear();
// Destroy the temporary GameObject used to run co-routines
if (s_CoroutineRunner != null)
{
s_CoroutineRunner.StopAllCoroutines();
Object.DestroyImmediate(s_CoroutineRunner);
}
CleanUpHandlers();
Application.targetFrameRate = s_OriginalTargetFrameRate;
}
/// <summary>
/// We want to exclude the TestRunner scene on the host-server side so it won't try to tell clients to
/// synchronize to this scene when they connect
/// </summary>
private static bool VerifySceneIsValidForClientsToLoad(int sceneIndex, string sceneName, LoadSceneMode loadSceneMode)
{
// exclude test runner scene
if (sceneName.StartsWith(k_FirstPartOfTestRunnerSceneName))
{
return false;
}
return true;
}
/// <summary>
/// This registers scene validation callback for the server to prevent it from telling connecting
/// clients to synchronize (i.e. load) the test runner scene. This will also register the test runner
/// scene and its handle for both client(s) and server-host.
/// </summary>
private static void SceneManagerValidationAndTestRunnerInitialization(NetworkManager networkManager)
{
// If VerifySceneBeforeLoading is not already set, then go ahead and set it so the host/server
// will not try to synchronize clients to the TestRunner scene. We only need to do this for the server.
if (networkManager.IsServer && networkManager.SceneManager.VerifySceneBeforeLoading == null)
{
networkManager.SceneManager.VerifySceneBeforeLoading = VerifySceneIsValidForClientsToLoad;
// If a unit/integration test does not handle this on their own, then Ignore the validation warning
networkManager.SceneManager.DisableValidationWarnings(true);
}
// Register the test runner scene so it will be able to synchronize NetworkObjects without logging a
// warning about using the currently active scene
var scene = SceneManager.GetActiveScene();
// As long as this is a test runner scene (or most likely a test runner scene)
if (scene.name.StartsWith(k_FirstPartOfTestRunnerSceneName))
{
// Register the test runner scene just so we avoid another warning about not being able to find the
// scene to synchronize NetworkObjects. Next, add the currently active test runner scene to the scenes
// loaded and register the server to client scene handle since host-server shares the test runner scene
// with the clients.
networkManager.SceneManager.GetAndAddNewlyLoadedSceneByName(scene.name);
networkManager.SceneManager.ServerSceneHandleToClientSceneHandle.Add(scene.handle, scene.handle);
}
}
public delegate void BeforeClientStartCallback();
/// <summary>
/// Starts NetworkManager instances created by the Create method.
/// </summary>
/// <param name="host">Whether or not to create a Host instead of Server</param>
/// <param name="server">The Server NetworkManager</param>
/// <param name="clients">The Clients NetworkManager</param>
/// <param name="startInitializationCallback">called immediately after server and client(s) are started</param>
/// <param name="callback">called immediately after server is started and before client(s) are started</param>
/// <returns></returns>
public static bool Start(bool host, NetworkManager server, NetworkManager[] clients, Action<NetworkManager> startInitializationCallback = null)
public static bool Start(bool host, NetworkManager server, NetworkManager[] clients, BeforeClientStartCallback callback = null)
{
if (s_IsStarted)
{
throw new InvalidOperationException("MultiInstanceHelper already started. Did you forget to Destroy?");
throw new InvalidOperationException($"{nameof(NetcodeIntegrationTestHelpers)} already thinks it is started. Did you forget to Destroy?");
}
s_IsStarted = true;
@@ -159,48 +360,52 @@ namespace Unity.Netcode.RuntimeTests
server.StartServer();
}
var hooks = new MultiInstanceHooks();
server.MessagingSystem.Hook(hooks);
s_Hooks[server] = hooks;
// if set, then invoke this for the server
startInitializationCallback?.Invoke(server);
RegisterHandlers(server);
callback?.Invoke();
for (int i = 0; i < clients.Length; i++)
{
clients[i].StartClient();
hooks = new MultiInstanceHooks();
clients[i].MessagingSystem.Hook(hooks);
s_Hooks[clients[i]] = hooks;
// if set, then invoke this for the client
startInitializationCallback?.Invoke(clients[i]);
RegisterHandlers(clients[i]);
}
return true;
}
// Empty MonoBehaviour that is a holder of coroutine
private class CoroutineRunner : MonoBehaviour
{
}
private static CoroutineRunner s_CoroutineRunner;
/// <summary>
/// Runs a IEnumerator as a Coroutine on a dummy GameObject. Used to get exceptions coming from the coroutine
/// Used to return a value of type T from a wait condition
/// </summary>
/// <param name="enumerator">The IEnumerator to run</param>
public static Coroutine Run(IEnumerator enumerator)
{
if (s_CoroutineRunner == null)
{
s_CoroutineRunner = new GameObject(nameof(CoroutineRunner)).AddComponent<CoroutineRunner>();
}
return s_CoroutineRunner.StartCoroutine(enumerator);
}
public class CoroutineResultWrapper<T>
public class ResultWrapper<T>
{
public T Result;
}
private static uint s_AutoIncrementGlobalObjectIdHashCounter = 111111;
public static uint GetNextGlobalIdHashValue()
{
return ++s_AutoIncrementGlobalObjectIdHashCounter;
}
public static bool IsNetcodeIntegrationTestRunning { get; internal set; }
public static void RegisterNetcodeIntegrationTest(bool registered)
{
IsNetcodeIntegrationTestRunning = registered;
}
/// <summary>
/// Normally we would only allow player prefabs to be set to a prefab. Not runtime created objects.
/// In order to prevent having a Resource folder full of a TON of prefabs that we have to maintain,
@@ -226,6 +431,14 @@ namespace Unity.Netcode.RuntimeTests
// Prevent object from being snapped up as a scene object
networkObject.IsSceneObject = false;
// To avoid issues with integration tests that forget to clean up,
// this feature only works with NetcodeIntegrationTest derived classes
if (IsNetcodeIntegrationTestRunning)
{
// Add the object identifier component
networkObject.gameObject.AddComponent<ObjectNameIdentifier>();
}
}
// We use GameObject instead of SceneObject to be able to keep hierarchy
@@ -254,15 +467,24 @@ namespace Unity.Netcode.RuntimeTests
}
}
/// <summary>
/// Waits (yields) until specified amount of network ticks has been passed.
/// </summary>
public static IEnumerator WaitForTicks(NetworkManager networkManager, int count)
{
var targetTick = networkManager.NetworkTickSystem.LocalTime.Tick + count;
yield return new WaitUntil(() => networkManager.NetworkTickSystem.LocalTime.Tick >= targetTick);
}
/// <summary>
/// Waits on the client side to be connected.
/// </summary>
/// <param name="client">The client</param>
/// <param name="result">The result. If null, it will automatically assert</param>
/// <param name="maxFrames">The max frames to wait for</param>
public static IEnumerator WaitForClientConnected(NetworkManager client, CoroutineResultWrapper<bool> result = null, int maxFrames = DefaultMaxFrames)
public static IEnumerator WaitForClientConnected(NetworkManager client, ResultWrapper<bool> result = null, float timeout = DefaultTimeout)
{
yield return WaitForClientsConnected(new NetworkManager[] { client }, result, maxFrames);
yield return WaitForClientsConnected(new NetworkManager[] { client }, result, timeout);
}
/// <summary>
@@ -272,7 +494,7 @@ namespace Unity.Netcode.RuntimeTests
/// <param name="result">The result. If null, it will automatically assert<</param>
/// <param name="maxFrames">The max frames to wait for</param>
/// <returns></returns>
public static IEnumerator WaitForClientsConnected(NetworkManager[] clients, CoroutineResultWrapper<bool> result = null, int maxFrames = DefaultMaxFrames)
public static IEnumerator WaitForClientsConnected(NetworkManager[] clients, ResultWrapper<bool> result = null, float timeout = DefaultTimeout)
{
// Make sure none are the host client
foreach (var client in clients)
@@ -283,9 +505,10 @@ namespace Unity.Netcode.RuntimeTests
}
}
var startFrameNumber = Time.frameCount;
var allConnected = true;
while (Time.frameCount - startFrameNumber <= maxFrames)
var startTime = Time.realtimeSinceStartup;
while (Time.realtimeSinceStartup - startTime < timeout)
{
allConnected = true;
foreach (var client in clients)
@@ -326,9 +549,9 @@ namespace Unity.Netcode.RuntimeTests
/// <param name="server">The server</param>
/// <param name="result">The result. If null, it will automatically assert</param>
/// <param name="maxFrames">The max frames to wait for</param>
public static IEnumerator WaitForClientConnectedToServer(NetworkManager server, CoroutineResultWrapper<bool> result = null, int maxFrames = DefaultMaxFrames)
public static IEnumerator WaitForClientConnectedToServer(NetworkManager server, ResultWrapper<bool> result = null, float timeout = DefaultTimeout)
{
yield return WaitForClientsConnectedToServer(server, server.IsHost ? s_ClientCount + 1 : s_ClientCount, result, maxFrames);
yield return WaitForClientsConnectedToServer(server, server.IsHost ? s_ClientCount + 1 : s_ClientCount, result, timeout);
}
/// <summary>
@@ -337,16 +560,16 @@ namespace Unity.Netcode.RuntimeTests
/// <param name="server">The server</param>
/// <param name="result">The result. If null, it will automatically assert</param>
/// <param name="maxFrames">The max frames to wait for</param>
public static IEnumerator WaitForClientsConnectedToServer(NetworkManager server, int clientCount = 1, CoroutineResultWrapper<bool> result = null, int maxFrames = DefaultMaxFrames)
public static IEnumerator WaitForClientsConnectedToServer(NetworkManager server, int clientCount = 1, ResultWrapper<bool> result = null, float timeout = DefaultTimeout)
{
if (!server.IsServer)
{
throw new InvalidOperationException("Cannot wait for connected as client");
}
var startFrameNumber = Time.frameCount;
var startTime = Time.realtimeSinceStartup;
while (Time.frameCount - startFrameNumber <= maxFrames && server.ConnectedClients.Count != clientCount)
while (Time.realtimeSinceStartup - startTime < timeout && server.ConnectedClients.Count != clientCount)
{
var nextFrameNumber = Time.frameCount + 1;
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
@@ -372,16 +595,16 @@ namespace Unity.Netcode.RuntimeTests
/// <param name="result">The result</param>
/// <param name="failIfNull">Whether or not to fail if no object is found and result is null</param>
/// <param name="maxFrames">The max frames to wait for</param>
public static IEnumerator GetNetworkObjectByRepresentation(ulong networkObjectId, NetworkManager representation, CoroutineResultWrapper<NetworkObject> result, bool failIfNull = true, int maxFrames = DefaultMaxFrames)
public static IEnumerator GetNetworkObjectByRepresentation(ulong networkObjectId, NetworkManager representation, ResultWrapper<NetworkObject> result, bool failIfNull = true, float timeout = DefaultTimeout)
{
if (result == null)
{
throw new ArgumentNullException("Result cannot be null");
}
var startFrameNumber = Time.frameCount;
var startTime = Time.realtimeSinceStartup;
while (Time.frameCount - startFrameNumber <= maxFrames && representation.SpawnManager.SpawnedObjects.All(x => x.Value.NetworkObjectId != networkObjectId))
while (Time.realtimeSinceStartup - startTime < timeout && representation.SpawnManager.SpawnedObjects.All(x => x.Value.NetworkObjectId != networkObjectId))
{
var nextFrameNumber = Time.frameCount + 1;
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
@@ -403,7 +626,7 @@ namespace Unity.Netcode.RuntimeTests
/// <param name="result">The result</param>
/// <param name="failIfNull">Whether or not to fail if no object is found and result is null</param>
/// <param name="maxFrames">The max frames to wait for</param>
public static IEnumerator GetNetworkObjectByRepresentation(Func<NetworkObject, bool> predicate, NetworkManager representation, CoroutineResultWrapper<NetworkObject> result, bool failIfNull = true, int maxFrames = DefaultMaxFrames)
public static IEnumerator GetNetworkObjectByRepresentation(Func<NetworkObject, bool> predicate, NetworkManager representation, ResultWrapper<NetworkObject> result, bool failIfNull = true, float timeout = DefaultTimeout)
{
if (result == null)
{
@@ -415,9 +638,9 @@ namespace Unity.Netcode.RuntimeTests
throw new ArgumentNullException("Predicate cannot be null");
}
var startFrame = Time.frameCount;
var startTime = Time.realtimeSinceStartup;
while (Time.frameCount - startFrame <= maxFrames && !representation.SpawnManager.SpawnedObjects.Any(x => predicate(x.Value)))
while (Time.realtimeSinceStartup - startTime < timeout && !representation.SpawnManager.SpawnedObjects.Any(x => predicate(x.Value)))
{
var nextFrameNumber = Time.frameCount + 1;
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
@@ -431,29 +654,6 @@ namespace Unity.Netcode.RuntimeTests
}
}
/// <summary>
/// Runs some code, then verifies the condition (combines 'Run' and 'WaitForCondition')
/// </summary>
/// <param name="workload">Action / code to run</param>
/// <param name="predicate">The predicate to wait for</param>
/// <param name="maxFrames">The max frames to wait for</param>
public static IEnumerator RunAndWaitForCondition(Action workload, Func<bool> predicate, int maxFrames = DefaultMaxFrames, int minFrames = DefaultMinFrames)
{
var waitResult = new CoroutineResultWrapper<bool>();
workload();
yield return Run(WaitForCondition(
predicate,
waitResult,
maxFrames: maxFrames,
minFrames: minFrames));
if (!waitResult.Result)
{
Assert.Fail("Predicate condition failed");
}
}
/// <summary>
/// Waits for a predicate condition to be met
/// </summary>
@@ -461,33 +661,26 @@ namespace Unity.Netcode.RuntimeTests
/// <param name="result">The result. If null, it will fail if the predicate is not met</param>
/// <param name="minFrames">The min frames to wait for</param>
/// <param name="maxFrames">The max frames to wait for</param>
public static IEnumerator WaitForCondition(Func<bool> predicate, CoroutineResultWrapper<bool> result = null, int maxFrames = DefaultMaxFrames, int minFrames = DefaultMinFrames)
public static IEnumerator WaitForCondition(Func<bool> predicate, ResultWrapper<bool> result = null, float timeout = DefaultTimeout, int minFrames = DefaultMinFrames)
{
if (predicate == null)
{
throw new ArgumentNullException("Predicate cannot be null");
}
var startFrameNumber = Time.frameCount;
var startTime = Time.realtimeSinceStartup;
if (minFrames > 0)
{
yield return new WaitUntil(() =>
{
return Time.frameCount >= minFrames;
});
yield return new WaitUntil(() => Time.frameCount >= minFrames);
}
while (Time.frameCount - startFrameNumber <= maxFrames &&
!predicate())
while (Time.realtimeSinceStartup - startTime < timeout && !predicate())
{
// Changed to 2 frames to avoid the scenario where it would take 1+ frames to
// see a value change (i.e. discovered in the NetworkTransformTests)
var nextFrameNumber = Time.frameCount + 2;
yield return new WaitUntil(() =>
{
return Time.frameCount >= nextFrameNumber;
});
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
}
var res = predicate();
@@ -501,5 +694,70 @@ namespace Unity.Netcode.RuntimeTests
Assert.True(res, "PREDICATE CONDITION");
}
}
/// <summary>
/// Waits for a message of the given type to be received
/// </summary>
/// <param name="result">The result. If null, it will fail if the predicate is not met</param>
/// <param name="timeout">The max time in seconds to wait for</param>
internal static IEnumerator WaitForMessageOfType<T>(NetworkManager toBeReceivedBy, ResultWrapper<bool> result = null, float timeout = 0.5f) where T : INetworkMessage
{
var hooks = s_Hooks[toBeReceivedBy];
if (!hooks.HandleChecks.ContainsKey(typeof(T)))
{
hooks.HandleChecks.Add(typeof(T), new List<MessageHandleCheckWithResult>());
}
var check = new MessageHandleCheckWithResult { Check = MultiInstanceHooks.CheckForMessageOfType<T> };
hooks.HandleChecks[typeof(T)].Add(check);
if (result == null)
{
result = new ResultWrapper<bool>();
}
yield return ExecuteWaitForHook(check, result, timeout);
Assert.True(result.Result, $"Expected message {typeof(T).Name} was not received within {timeout}s.");
}
/// <summary>
/// Waits for a specific message, defined by a user callback, to be received
/// </summary>
/// <param name="requirement">Called for each received message to check if it's the right one</param>
/// <param name="result">The result. If null, it will fail if the predicate is not met</param>
/// <param name="timeout">The max time in seconds to wait for</param>
internal static IEnumerator WaitForMessageMeetingRequirement<T>(NetworkManager toBeReceivedBy, MessageHandleCheck requirement, ResultWrapper<bool> result = null, float timeout = DefaultTimeout)
{
var hooks = s_Hooks[toBeReceivedBy];
if (!hooks.HandleChecks.ContainsKey(typeof(T)))
{
hooks.HandleChecks.Add(typeof(T), new List<MessageHandleCheckWithResult>());
}
var check = new MessageHandleCheckWithResult { Check = requirement };
hooks.HandleChecks[typeof(T)].Add(check);
if (result == null)
{
result = new ResultWrapper<bool>();
}
yield return ExecuteWaitForHook(check, result, timeout);
Assert.True(result.Result, $"Expected message meeting user requirements was not received within {timeout}s.");
}
private static IEnumerator ExecuteWaitForHook(MessageHandleCheckWithResult check, ResultWrapper<bool> result, float timeout)
{
var startTime = Time.realtimeSinceStartup;
while (!check.Result && Time.realtimeSinceStartup - startTime < timeout)
{
yield return null;
}
var res = check.Result;
result.Result = res;
}
}
// Empty MonoBehaviour that is a holder of coroutine
internal class CoroutineRunner : MonoBehaviour
{
}
}

View File

@@ -2,9 +2,8 @@ using System;
using System.Collections.Generic;
using UnityEngine;
using NUnit.Framework;
using Unity.Netcode.Transports.UNET;
namespace Unity.Netcode.RuntimeTests
namespace Unity.Netcode.TestHelpers.Runtime
{
/// <summary>
/// Helper class to instantiate a NetworkManager
@@ -22,9 +21,9 @@ namespace Unity.Netcode.RuntimeTests
public static NetworkManager NetworkManagerObject { get; internal set; }
public static GameObject NetworkManagerGameObject { get; internal set; }
internal static Dictionary<Guid, GameObject> InstantiatedGameObjects = new Dictionary<Guid, GameObject>();
internal static Dictionary<Guid, NetworkObject> InstantiatedNetworkObjects = new Dictionary<Guid, NetworkObject>();
internal static NetworkManagerOperatingMode CurrentNetworkManagerMode;
public static Dictionary<Guid, GameObject> InstantiatedGameObjects = new Dictionary<Guid, GameObject>();
public static Dictionary<Guid, NetworkObject> InstantiatedNetworkObjects = new Dictionary<Guid, NetworkObject>();
public static NetworkManagerOperatingMode CurrentNetworkManagerMode;
/// <summary>
/// This provides the ability to start NetworkManager in various modes
@@ -68,7 +67,11 @@ namespace Unity.Netcode.RuntimeTests
Debug.Log($"{nameof(NetworkManager)} Instantiated.");
var unetTransport = NetworkManagerGameObject.AddComponent<UNetTransport>();
// NOTE: For now we only use SIPTransport for tests until UnityTransport
// has been verified working in nightly builds
// TODO-MTT-2486: Provide support for other transports once tested and verified
// working on consoles.
var sipTransport = NetworkManagerGameObject.AddComponent<SIPTransport>();
if (networkConfig == null)
{
networkConfig = new NetworkConfig
@@ -78,14 +81,7 @@ namespace Unity.Netcode.RuntimeTests
}
NetworkManagerObject.NetworkConfig = networkConfig;
unetTransport.ConnectAddress = "127.0.0.1";
unetTransport.ConnectPort = 7777;
unetTransport.ServerListenPort = 7777;
unetTransport.MessageBufferSize = 65535;
unetTransport.MaxConnections = 100;
unetTransport.MessageSendMode = UNetTransport.SendMode.Immediately;
NetworkManagerObject.NetworkConfig.NetworkTransport = unetTransport;
NetworkManagerObject.NetworkConfig.NetworkTransport = sipTransport;
// Starts the network manager in the mode specified
StartNetworkManagerMode(managerMode);

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
namespace Unity.Netcode.RuntimeTests
namespace Unity.Netcode.TestHelpers.Runtime
{
/// <summary>
/// Will automatically register for the NetworkVariable OnValueChanged
@@ -13,7 +13,7 @@ namespace Unity.Netcode.RuntimeTests
/// From both we can then at least determine if the value indeed changed
/// </summary>
/// <typeparam name="T"></typeparam>
internal class NetworkVariableHelper<T> : NetworkVariableBaseHelper where T : unmanaged
public class NetworkVariableHelper<T> : NetworkVariableBaseHelper where T : unmanaged
{
private readonly NetworkVariable<T> m_NetworkVariable;
public delegate void OnMyValueChangedDelegateHandler(T previous, T next);
@@ -73,7 +73,7 @@ namespace Unity.Netcode.RuntimeTests
/// The number of times a specific NetworkVariable instance had its value changed (i.e. !Equal)
/// Note: This could be expanded for future tests focuses around NetworkVariables
/// </summary>
internal class NetworkVariableBaseHelper
public class NetworkVariableBaseHelper
{
private static Dictionary<NetworkVariableBaseHelper, NetworkVariableBase> s_Instances;
private static Dictionary<NetworkVariableBase, int> s_InstanceChangedCount;

View File

@@ -0,0 +1,42 @@
using UnityEngine;
namespace Unity.Netcode.TestHelpers.Runtime
{
/// <summary>
/// Can be used independently or assigned to <see cref="NetcodeIntegrationTest.WaitForConditionOrTimeOut"></see> in the
/// event the default timeout period needs to be adjusted
/// </summary>
public class TimeoutHelper
{
private const float k_DefaultTimeOutWaitPeriod = 2.0f;
private float m_MaximumTimeBeforeTimeOut;
private float m_TimeOutPeriod;
private bool m_IsStarted;
public bool TimedOut { get; internal set; }
public void Start()
{
m_MaximumTimeBeforeTimeOut = Time.realtimeSinceStartup + m_TimeOutPeriod;
m_IsStarted = true;
TimedOut = false;
}
public void Stop()
{
TimedOut = HasTimedOut();
m_IsStarted = false;
}
public bool HasTimedOut()
{
return m_IsStarted ? m_MaximumTimeBeforeTimeOut < Time.realtimeSinceStartup : TimedOut;
}
public TimeoutHelper(float timeOutPeriod = k_DefaultTimeOutWaitPeriod)
{
m_TimeOutPeriod = timeOutPeriod;
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,70 @@
using System;
namespace Unity.Netcode.TestHelpers.Runtime
{
internal class MessageHooks : INetworkHooks
{
public bool IsWaiting;
public delegate bool MessageReceiptCheck(object receivedMessage);
public MessageReceiptCheck ReceiptCheck;
public static bool CheckForMessageOfType<T>(object receivedMessage) where T : INetworkMessage
{
return receivedMessage is T;
}
public void OnBeforeSendMessage<T>(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage
{
}
public void OnAfterSendMessage<T>(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage
{
}
public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes)
{
}
public void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes)
{
}
public void OnBeforeSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery)
{
}
public void OnAfterSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery)
{
}
public void OnBeforeReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes)
{
}
public void OnAfterReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes)
{
}
public bool OnVerifyCanSend(ulong destinationId, Type messageType, NetworkDelivery delivery)
{
return true;
}
public bool OnVerifyCanReceive(ulong senderId, Type messageType)
{
return true;
}
public void OnBeforeHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage
{
}
public void OnAfterHandleMessage<T>(ref T message, ref NetworkContext context) where T : INetworkMessage
{
if (IsWaiting && (ReceiptCheck == null || ReceiptCheck.Invoke(message)))
{
IsWaiting = false;
}
}
}
}

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