Compare commits
2 Commits
1.0.0-pre.
...
1.0.0-pre.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b4aaa8b59 | ||
|
|
4818405514 |
50
CHANGELOG.md
50
CHANGELOG.md
@@ -6,6 +6,53 @@ 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
|
||||
|
||||
- Added `PreviousValue` in `NetworkListEvent`, when `Value` has changed (#1528)
|
||||
|
||||
### 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)'
|
||||
|
||||
### 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
|
||||
|
||||
### Added
|
||||
@@ -29,10 +76,13 @@ Additional documentation and release notes are available at [Multiplayer Documen
|
||||
- Fixed KeyNotFound exception when removing ownership of a newly spawned NetworkObject that is already owned by the server. (#1500)
|
||||
- Fixed NetworkManager.LocalClient not being set when starting as a host. (#1511)
|
||||
- Fixed a few memory leak cases when shutting down NetworkManager during Incoming Message Queue processing. (#1323)
|
||||
- Fixed network tick value sometimes being duplicated or skipped. (#1614)
|
||||
|
||||
### Changed
|
||||
- The SDK no longer limits message size to 64k. (The transport may still impose its own limits, but the SDK no longer does.) (#1384)
|
||||
- Updated com.unity.collections to 1.1.0 (#1451)
|
||||
- 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)
|
||||
|
||||
## [1.0.0-pre.3] - 2021-10-22
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -107,7 +73,7 @@ namespace Unity.Netcode.Components
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
m_SendMessagesAllowed = false;
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (!sendMessagesAllowed)
|
||||
if (!m_SendMessagesAllowed || !m_Animator || !m_Animator.enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int layer = 0; layer < m_Animator.layerCount; layer++)
|
||||
{
|
||||
int stateHash;
|
||||
float normalizedTime;
|
||||
if (!CheckAnimStateChanged(out stateHash, out normalizedTime))
|
||||
if (!CheckAnimStateChanged(out stateHash, out normalizedTime, layer))
|
||||
{
|
||||
// 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;
|
||||
continue;
|
||||
}
|
||||
|
||||
var animMsg = new AnimationMessage();
|
||||
animMsg.StateHash = stateHash;
|
||||
animMsg.NormalizedTime = normalizedTime;
|
||||
var animMsg = new AnimationMessage
|
||||
{
|
||||
StateHash = stateHash,
|
||||
NormalizedTime = normalizedTime,
|
||||
Layer = layer,
|
||||
Weight = m_LayerWeights[layer]
|
||||
};
|
||||
|
||||
m_ParameterWriter.Seek(0);
|
||||
m_ParameterWriter.Truncate();
|
||||
|
||||
WriteParameters(m_ParameterWriter, false);
|
||||
WriteParameters(m_ParameterWriter);
|
||||
animMsg.Parameters = m_ParameterWriter.ToArray();
|
||||
|
||||
SendAnimStateClientRpc(animMsg);
|
||||
}
|
||||
|
||||
private void CheckAndSend()
|
||||
{
|
||||
var networkTime = NetworkManager.ServerTime.Time;
|
||||
if (sendMessagesAllowed && m_SendRate != 0 && m_NextSendTime < networkTime)
|
||||
{
|
||||
m_NextSendTime = networkTime + m_SendRate;
|
||||
|
||||
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()
|
||||
};
|
||||
|
||||
SendParamsClientRpc(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))
|
||||
{
|
||||
AnimatorTransitionInfo tt = m_Animator.GetAnimatorTransitionInfo(0);
|
||||
if (tt.fullPathHash != m_TransitionHash)
|
||||
{
|
||||
// first time in this transition
|
||||
m_TransitionHash = tt.fullPathHash;
|
||||
m_AnimationHash = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
float layerWeightNow = m_Animator.GetLayerWeight(layer);
|
||||
|
||||
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(0);
|
||||
if (st.fullPathHash != m_AnimationHash)
|
||||
if (!Mathf.Approximately(layerWeightNow, m_LayerWeights[layer]))
|
||||
{
|
||||
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 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 != 0)
|
||||
if (m_AnimationHash[layer] != 0)
|
||||
{
|
||||
// came from another animation directly - from Play()
|
||||
stateHash = st.fullPathHash;
|
||||
normalizedTime = st.normalizedTime;
|
||||
}
|
||||
m_TransitionHash = 0;
|
||||
m_AnimationHash = st.fullPathHash;
|
||||
return true;
|
||||
m_TransitionHash[layer] = 0;
|
||||
m_AnimationHash[layer] = st.fullPathHash;
|
||||
shouldUpdate = 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);
|
||||
|
||||
@@ -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,8 +369,12 @@ namespace Unity.Netcode.Components
|
||||
private void CommitLocallyAndReplicate(NetworkTransformState networkState)
|
||||
{
|
||||
m_ReplicatedNetworkState.Value = 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;
|
||||
|
||||
if (Interpolate)
|
||||
{
|
||||
foreach (var interpolator in m_AllFloatInterpolators)
|
||||
{
|
||||
interpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime);
|
||||
}
|
||||
|
||||
m_RotationInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime);
|
||||
}
|
||||
|
||||
if (!CanCommitToTransform)
|
||||
{
|
||||
|
||||
@@ -5,13 +5,5 @@
|
||||
"Unity.Netcode.Runtime",
|
||||
"Unity.Collections"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
"allowUnsafeCode": true
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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,40 +156,20 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
}
|
||||
}
|
||||
|
||||
var messagingSystemType = typeof(MessagingSystem);
|
||||
foreach (var methodInfo in messagingSystemType.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public))
|
||||
{
|
||||
switch (methodInfo.Name)
|
||||
{
|
||||
case k_ReceiveMessageName:
|
||||
m_MessagingSystem_ReceiveMessage_MethodRef = moduleDefinition.ImportReference(methodInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private MethodReference GetNetworkMessageRecieveHandler(TypeDefinition typeDefinition)
|
||||
{
|
||||
SequencePoint typeSequence = null;
|
||||
foreach (var method in typeDefinition.Methods)
|
||||
{
|
||||
var resolved = method.Resolve();
|
||||
var methodSequence = resolved.DebugInformation.SequencePoints.FirstOrDefault();
|
||||
if (typeSequence == null || methodSequence.StartLine < typeSequence.StartLine)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
m_Diagnostics.AddError(typeSequence, $"Class {typeDefinition.FullName} does not implement required method: `public static void Receive(FastBufferReader, in NetworkContext)`");
|
||||
return null;
|
||||
}
|
||||
|
||||
private MethodDefinition GetOrCreateStaticConstructor(TypeDefinition typeDefinition)
|
||||
{
|
||||
var staticCtorMethodDef = typeDefinition.GetStaticConstructor();
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
{
|
||||
public class DontShowInTransportDropdownAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f097067d4254dc7ad018d7ad90df7c3
|
||||
timeCreated: 1620386886
|
||||
@@ -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();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var label = new GUIContent("Animator", "The Animator component to synchronize");
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("m_Animator"), label);
|
||||
EditorGUI.EndChangeCheck();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private void DrawControls()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_AnimatorProperty, m_AnimatorLabel);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
m_AnimSync.ResetParameterOptions();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace Unity.Netcode.Editor
|
||||
private static GUIStyle s_HelpBoxStyle;
|
||||
|
||||
// Properties
|
||||
private SerializedProperty m_DontDestroyOnLoadProperty;
|
||||
private SerializedProperty m_RunInBackgroundProperty;
|
||||
private SerializedProperty m_LogLevelProperty;
|
||||
|
||||
@@ -58,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);
|
||||
}
|
||||
@@ -85,7 +84,6 @@ namespace Unity.Netcode.Editor
|
||||
m_NetworkManager = (NetworkManager)target;
|
||||
|
||||
// Base properties
|
||||
m_DontDestroyOnLoadProperty = serializedObject.FindProperty(nameof(NetworkManager.DontDestroy));
|
||||
m_RunInBackgroundProperty = serializedObject.FindProperty(nameof(NetworkManager.RunInBackground));
|
||||
m_LogLevelProperty = serializedObject.FindProperty(nameof(NetworkManager.LogLevel));
|
||||
m_NetworkConfigProperty = serializedObject.FindProperty(nameof(NetworkManager.NetworkConfig));
|
||||
@@ -112,7 +110,6 @@ namespace Unity.Netcode.Editor
|
||||
private void CheckNullProperties()
|
||||
{
|
||||
// Base properties
|
||||
m_DontDestroyOnLoadProperty = serializedObject.FindProperty(nameof(NetworkManager.DontDestroy));
|
||||
m_RunInBackgroundProperty = serializedObject.FindProperty(nameof(NetworkManager.RunInBackground));
|
||||
m_LogLevelProperty = serializedObject.FindProperty(nameof(NetworkManager.LogLevel));
|
||||
m_NetworkConfigProperty = serializedObject.FindProperty(nameof(NetworkManager.NetworkConfig));
|
||||
@@ -223,7 +220,6 @@ namespace Unity.Netcode.Editor
|
||||
if (!m_NetworkManager.IsServer && !m_NetworkManager.IsClient)
|
||||
{
|
||||
serializedObject.Update();
|
||||
EditorGUILayout.PropertyField(m_DontDestroyOnLoadProperty);
|
||||
EditorGUILayout.PropertyField(m_RunInBackgroundProperty);
|
||||
EditorGUILayout.PropertyField(m_LogLevelProperty);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
108
Editor/NetworkManagerHelper.cs
Normal file
108
Editor/NetworkManagerHelper.cs
Normal file
@@ -0,0 +1,108 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// Specialized editor specific NetworkManager code
|
||||
/// </summary>
|
||||
public class NetworkManagerHelper : NetworkManager.INetworkManagerHelper
|
||||
{
|
||||
internal static NetworkManagerHelper Singleton;
|
||||
|
||||
// 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>
|
||||
/// Initializes the singleton instance and registers for:
|
||||
/// Hierarchy changed notification: to notify the user when they nest a NetworkManager
|
||||
/// Play mode state change notification: to capture when entering or exiting play mode (currently only exiting)
|
||||
/// </summary>
|
||||
[InitializeOnLoadMethod]
|
||||
private static void InitializeOnload()
|
||||
{
|
||||
Singleton = new NetworkManagerHelper();
|
||||
NetworkManager.NetworkManagerHelper = Singleton;
|
||||
|
||||
EditorApplication.playModeStateChanged -= EditorApplication_playModeStateChanged;
|
||||
EditorApplication.hierarchyChanged -= EditorApplication_hierarchyChanged;
|
||||
|
||||
EditorApplication.playModeStateChanged += EditorApplication_playModeStateChanged;
|
||||
EditorApplication.hierarchyChanged += EditorApplication_hierarchyChanged;
|
||||
}
|
||||
|
||||
private static void EditorApplication_playModeStateChanged(PlayModeStateChange playModeStateChange)
|
||||
{
|
||||
switch (playModeStateChange)
|
||||
{
|
||||
case PlayModeStateChange.ExitingEditMode:
|
||||
{
|
||||
s_LastKnownNetworkManagerParents.Clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void EditorApplication_hierarchyChanged()
|
||||
{
|
||||
var allNetworkManagers = Resources.FindObjectsOfTypeAll<NetworkManager>();
|
||||
foreach (var networkManager in allNetworkManagers)
|
||||
{
|
||||
networkManager.NetworkManagerCheckForParent();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles notifying the user, via display dialog window, that they have nested a NetworkManager.
|
||||
/// When in edit mode it provides the option to automatically fix the issue
|
||||
/// When in play mode it just notifies the user when entering play mode as well as when the user
|
||||
/// tries to start a network session while a NetworkManager is still nested.
|
||||
/// </summary>
|
||||
public bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool ignoreNetworkManagerCache = false, bool editorTest = false)
|
||||
{
|
||||
var gameObject = networkManager.gameObject;
|
||||
var transform = networkManager.transform;
|
||||
var isParented = transform.root != transform;
|
||||
|
||||
var message = NetworkManager.GenerateNestedNetworkManagerMessage(transform);
|
||||
if (s_LastKnownNetworkManagerParents.ContainsKey(networkManager) && !ignoreNetworkManagerCache)
|
||||
{
|
||||
// If we have already notified the user, then don't notify them again
|
||||
if (s_LastKnownNetworkManagerParents[networkManager] == transform.root)
|
||||
{
|
||||
return isParented;
|
||||
}
|
||||
else // If we are no longer a child, then we can remove ourself from this list
|
||||
if (transform.root == gameObject.transform)
|
||||
{
|
||||
s_LastKnownNetworkManagerParents.Remove(networkManager);
|
||||
}
|
||||
}
|
||||
if (!EditorApplication.isUpdating && isParented)
|
||||
{
|
||||
if (!EditorApplication.isPlaying && !editorTest)
|
||||
{
|
||||
message += $"Click 'Auto-Fix' to automatically remove it from {transform.root.gameObject.name} or 'Manual-Fix' to fix it yourself in the hierarchy view.";
|
||||
if (EditorUtility.DisplayDialog("Invalid Nested NetworkManager", message, "Auto-Fix", "Manual-Fix"))
|
||||
{
|
||||
transform.parent = null;
|
||||
isParented = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError(message);
|
||||
}
|
||||
|
||||
if (!s_LastKnownNetworkManagerParents.ContainsKey(networkManager) && isParented)
|
||||
{
|
||||
s_LastKnownNetworkManagerParents.Add(networkManager, networkManager.transform.root);
|
||||
}
|
||||
}
|
||||
return isParented;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
11
Editor/NetworkManagerHelper.cs.meta
Normal file
11
Editor/NetworkManagerHelper.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b26b53dc28ae1b5488bbbecc3e499bbc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
]
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
# Netcode for GameObjects
|
||||
|
||||
[](https://forum.unity.com/forums/multiplayer.26/) [](https://discord.gg/FM8SE9E)
|
||||
[](https://docs-multiplayer.unity3d.com/) [](https://docs-multiplayer.unity3d.com/docs/mlapi-api/introduction)
|
||||
|
||||
Netcode for GameObjects provides networking capabilities to GameObject & MonoBehaviour Unity workflows. The framework is interoperable with many low-level transports, including the official [Unity Transport Package](https://docs.unity3d.com/Packages/com.unity.transport@1.0/manual/index.html).
|
||||
Netcode for GameObjects provides networking capabilities to GameObject & MonoBehaviour Unity workflows. The framework is interoperable with many low-level transports, including the official [Unity Transport Package](https://docs-multiplayer.unity3d.com/transport/1.0.0/introduction).
|
||||
|
||||
### Getting Started
|
||||
Visit the [Multiplayer Docs Site](https://docs-multiplayer.unity3d.com/) for package & API documentation, as well as information about several samples which leverage the Netcode for GameObjects package.
|
||||
|
||||
@@ -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")]
|
||||
|
||||
|
||||
@@ -239,6 +239,8 @@ namespace Unity.Netcode
|
||||
writer.WriteValueSafe(sortedEntry.Key);
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteValueSafe(TickRate);
|
||||
writer.WriteValueSafe(ConnectionApproval);
|
||||
writer.WriteValueSafe(ForceSamePrefabs);
|
||||
writer.WriteValueSafe(EnableSceneManagement);
|
||||
|
||||
@@ -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
|
||||
internal FastBufferWriter __beginSendClientRpc(uint rpcMethodId, ClientRpcParams clientRpcParams, RpcDelivery rpcDelivery)
|
||||
#pragma warning restore IDE1006 // restore naming rule violation check
|
||||
{
|
||||
return new FastBufferWriter(k_RpcMessageDefaultSize, Allocator.Temp, k_RpcMessageMaximumSize);
|
||||
}
|
||||
|
||||
#pragma warning disable IDE1006 // disable naming rule violation check
|
||||
// RuntimeAccessModifiersILPP will make this `protected`
|
||||
internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMethodId, ClientRpcParams clientRpcParams, RpcDelivery rpcDelivery)
|
||||
#pragma warning restore IDE1006 // restore naming rule violation check
|
||||
{
|
||||
NetworkDelivery networkDelivery = NetworkDelivery.Reliable;
|
||||
switch (delivery)
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -197,11 +205,6 @@ namespace Unity.Netcode
|
||||
|
||||
public NetworkTime ServerTime => NetworkTickSystem?.ServerTime ?? default;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if the NetworkManager should be marked as DontDestroyOnLoad
|
||||
/// </summary>
|
||||
[HideInInspector] public bool DontDestroy = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets if the application should be set to run in background
|
||||
/// </summary>
|
||||
@@ -490,6 +493,13 @@ namespace Unity.Netcode
|
||||
|
||||
private void Initialize(bool server)
|
||||
{
|
||||
// Don't allow the user to start a network session if the NetworkManager is
|
||||
// still parented under another GameObject
|
||||
if (NetworkManagerCheckForParent(true))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
|
||||
{
|
||||
NetworkLog.LogInfo(nameof(Initialize));
|
||||
@@ -553,6 +563,8 @@ namespace Unity.Netcode
|
||||
return;
|
||||
}
|
||||
|
||||
NetworkConfig.NetworkTransport.NetworkMetrics = NetworkMetrics;
|
||||
|
||||
//This 'if' should never enter
|
||||
if (SnapshotSystem != null)
|
||||
{
|
||||
@@ -581,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++)
|
||||
@@ -777,7 +790,7 @@ namespace Unity.Netcode
|
||||
|
||||
NetworkConfig.NetworkTransport.OnTransportEvent += HandleRawTransportPoll;
|
||||
|
||||
NetworkConfig.NetworkTransport.Initialize();
|
||||
NetworkConfig.NetworkTransport.Initialize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -941,11 +954,6 @@ namespace Unity.Netcode
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (DontDestroy)
|
||||
{
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
|
||||
if (RunInBackground)
|
||||
{
|
||||
Application.runInBackground = true;
|
||||
@@ -955,6 +963,11 @@ namespace Unity.Netcode
|
||||
{
|
||||
SetSingleton();
|
||||
}
|
||||
|
||||
if (!NetworkManagerCheckForParent())
|
||||
{
|
||||
DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
@@ -962,6 +975,48 @@ namespace Unity.Netcode
|
||||
UnityEngine.SceneManagement.SceneManager.sceneUnloaded += OnSceneUnloaded;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle runtime detection for parenting the NetworkManager's GameObject under another GameObject
|
||||
/// </summary>
|
||||
private void OnTransformParentChanged()
|
||||
{
|
||||
NetworkManagerCheckForParent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the NetworkManager's GameObject is parented under another GameObject and
|
||||
/// notifies the user that this is not allowed for the NetworkManager.
|
||||
/// </summary>
|
||||
internal bool NetworkManagerCheckForParent(bool ignoreNetworkManagerCache = false)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
var isParented = NetworkManagerHelper.NotifyUserOfNestedNetworkManager(this, ignoreNetworkManagerCache);
|
||||
#else
|
||||
var isParented = transform.root != transform;
|
||||
if (isParented)
|
||||
{
|
||||
throw new Exception(GenerateNestedNetworkManagerMessage(transform));
|
||||
}
|
||||
#endif
|
||||
return isParented;
|
||||
}
|
||||
|
||||
static internal string GenerateNestedNetworkManagerMessage(Transform transform)
|
||||
{
|
||||
return $"{transform.name} is nested under {transform.root.name}. NetworkManager cannot be nested.\n";
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
static internal INetworkManagerHelper NetworkManagerHelper;
|
||||
/// <summary>
|
||||
/// Interface for NetworkManagerHelper
|
||||
/// </summary>
|
||||
internal interface INetworkManagerHelper
|
||||
{
|
||||
bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool ignoreNetworkManagerCache = false, bool editorTest = false);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Ensures that the NetworkManager is cleaned up before OnDestroy is run on NetworkObjects and NetworkBehaviours when unloading a scene with a NetworkManager
|
||||
private void OnSceneUnloaded(Scene scene)
|
||||
{
|
||||
@@ -1255,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)
|
||||
@@ -1280,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];
|
||||
}
|
||||
@@ -1300,7 +1355,20 @@ namespace Unity.Netcode
|
||||
s_TransportConnect.Begin();
|
||||
#endif
|
||||
|
||||
// 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;
|
||||
|
||||
@@ -1378,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>
|
||||
{
|
||||
@@ -1401,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)}");
|
||||
}
|
||||
|
||||
internal unsafe int SendMessage<T>(in T message, NetworkDelivery delivery,
|
||||
return MessagingSystem.SendMessage(ref message, delivery, clientIds);
|
||||
}
|
||||
|
||||
internal unsafe int SendMessage<T>(ref T message, NetworkDelivery delivery,
|
||||
ulong* clientIds, int numClientIds)
|
||||
where T : INetworkMessage
|
||||
{
|
||||
@@ -1429,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
|
||||
@@ -1449,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)
|
||||
@@ -1473,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);
|
||||
@@ -1570,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
|
||||
@@ -1617,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)
|
||||
@@ -1682,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
try
|
||||
{
|
||||
if (m_NetworkManager)
|
||||
{
|
||||
m_NetworkManager.SendMessage(message, NetworkDelivery.Unreliable, clientId);
|
||||
m_NetworkManager.SendMessage(ref message, NetworkDelivery.Unreliable, clientId);
|
||||
}
|
||||
else
|
||||
{
|
||||
MockSendMessage(message, NetworkDelivery.Unreliable, clientId);
|
||||
MockSendMessage(ref message, NetworkDelivery.Unreliable, clientId);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
message.Entries.Dispose();
|
||||
message.Spawns.Dispose();
|
||||
message.Despawns.Dispose();
|
||||
}
|
||||
|
||||
m_ClientData[clientId].LastReceivedSequence = 0;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,25 +10,29 @@ 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;
|
||||
}
|
||||
|
||||
public void Handle(FastBufferReader reader, in NetworkContext context, ulong senderId, NetworkManager networkManager, int messageSize)
|
||||
{
|
||||
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
|
||||
{
|
||||
networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, reader, context);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId];
|
||||
|
||||
if (networkObject.OwnerClientId == networkManager.LocalClientId)
|
||||
{
|
||||
//We are current owner.
|
||||
@@ -43,7 +47,7 @@ namespace Unity.Netcode
|
||||
networkObject.InvokeBehaviourOnGainedOwnership();
|
||||
}
|
||||
|
||||
networkManager.NetworkMetrics.TrackOwnershipChangeReceived(senderId, networkObject, messageSize);
|
||||
networkManager.NetworkMetrics.TrackOwnershipChangeReceived(context.SenderId, networkObject, context.MessageSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
public void Handle(NetworkManager networkManager, ulong senderId)
|
||||
return true;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
var message = new CreateObjectMessage();
|
||||
message.ObjectInfo.Deserialize(reader);
|
||||
message.Handle(context.SenderId, reader, networkManager);
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Handle(ulong senderId, FastBufferReader reader, NetworkManager networkManager)
|
||||
ObjectInfo.Deserialize(reader);
|
||||
m_ReceivedNetworkVariableData = reader;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
public void Handle(FastBufferReader reader, in NetworkContext context, NetworkManager networkManager)
|
||||
{
|
||||
if (networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Handle(ref NetworkContext context)
|
||||
{
|
||||
var networkManager = (NetworkManager)context.SystemOwner;
|
||||
var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId];
|
||||
networkObject.SetNetworkParenting(IsReparented, LatestParent);
|
||||
networkObject.ApplyNetworkParenting();
|
||||
}
|
||||
else
|
||||
{
|
||||
networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, reader, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
157
Runtime/Messaging/Messages/RpcMessages.cs
Normal file
157
Runtime/Messaging/Messages/RpcMessages.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
public void Handle(ulong senderId, NetworkManager networkManager, int messageSize)
|
||||
return false;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -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,61 +93,52 @@ 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 systemOwner = context.SystemOwner;
|
||||
var senderId = context.SenderId;
|
||||
if (systemOwner is NetworkManager networkManager)
|
||||
{
|
||||
var networkManager = (NetworkManager)systemOwner;
|
||||
|
||||
// todo: temporary hack around bug
|
||||
if (!networkManager.IsServer)
|
||||
{
|
||||
@@ -165,7 +153,7 @@ namespace Unity.Netcode
|
||||
var ownerData = (Tuple<SnapshotSystem, ulong>)systemOwner;
|
||||
var snapshotSystem = ownerData.Item1;
|
||||
snapshotSystem.HandleSnapshot(ownerData.Item2, this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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,19 +271,23 @@ namespace Unity.Netcode
|
||||
case NetworkListEvent<T>.EventType.Value:
|
||||
{
|
||||
reader.ReadValueSafe(out int index);
|
||||
reader.ReadValueSafe(out T value);
|
||||
if (index < m_List.Length)
|
||||
NetworkVariable<T>.Read(reader, out T value);
|
||||
if (index >= m_List.Length)
|
||||
{
|
||||
m_List[index] = value;
|
||||
throw new Exception("Shouldn't be here, index is higher than list length");
|
||||
}
|
||||
|
||||
var previousValue = m_List[index];
|
||||
m_List[index] = value;
|
||||
|
||||
if (OnListChanged != null)
|
||||
{
|
||||
OnListChanged(new NetworkListEvent<T>
|
||||
{
|
||||
Type = eventType,
|
||||
Index = index,
|
||||
Value = value
|
||||
Value = value,
|
||||
PreviousValue = previousValue
|
||||
});
|
||||
}
|
||||
|
||||
@@ -293,7 +297,8 @@ namespace Unity.Netcode
|
||||
{
|
||||
Type = eventType,
|
||||
Index = index,
|
||||
Value = value
|
||||
Value = value,
|
||||
PreviousValue = previousValue
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -368,7 +373,7 @@ namespace Unity.Netcode
|
||||
public bool Contains(T item)
|
||||
{
|
||||
int index = NativeArrayExtensions.IndexOf(m_List, item);
|
||||
return index == -1;
|
||||
return index != -1;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -528,6 +533,11 @@ namespace Unity.Netcode
|
||||
/// </summary>
|
||||
public T Value;
|
||||
|
||||
/// <summary>
|
||||
/// The previous value when "Value" has changed, if available.
|
||||
/// </summary>
|
||||
public T PreviousValue;
|
||||
|
||||
/// <summary>
|
||||
/// the index changed, added or removed if available
|
||||
/// </summary>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
28
Runtime/SceneManagement/ISceneManagerHandler.cs
Normal file
28
Runtime/SceneManagement/ISceneManagerHandler.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
11
Runtime/SceneManagement/ISceneManagerHandler.cs.meta
Normal file
11
Runtime/SceneManagement/ISceneManagerHandler.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de907a9fb8151e240800dbcc97f8e745
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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>
|
||||
@@ -565,20 +584,8 @@ namespace Unity.Netcode
|
||||
|
||||
GenerateScenesInBuild();
|
||||
|
||||
// If NetworkManager has this set to true, then we can get the DDOL (DontDestroyOnLoad) from its GaemObject
|
||||
if (networkManager.DontDestroy)
|
||||
{
|
||||
// Since NetworkManager is now always migrated to the DDOL we will use this to get the DDOL scene
|
||||
DontDestroyOnLoadScene = networkManager.gameObject.scene;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, we have to create a GameObject and move it into the DDOL in order to
|
||||
// register the DDOL scene handle with NetworkSceneManager
|
||||
var myDDOLObject = new GameObject("DDOL-NWSM");
|
||||
UnityEngine.Object.DontDestroyOnLoad(myDDOLObject);
|
||||
DontDestroyOnLoadScene = myDDOLObject.scene;
|
||||
UnityEngine.Object.Destroy(myDDOLObject);
|
||||
}
|
||||
|
||||
ServerSceneHandleToClientSceneHandle.Add(DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene.handle);
|
||||
ScenesLoaded.Add(DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene);
|
||||
@@ -735,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>
|
||||
@@ -801,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);
|
||||
}
|
||||
@@ -830,7 +836,7 @@ namespace Unity.Netcode
|
||||
IsSpawnedObjectsPendingInDontDestroyOnLoad = true;
|
||||
}
|
||||
|
||||
s_IsSceneEventActive = true;
|
||||
m_IsSceneEventActive = true;
|
||||
|
||||
// Set our callback delegate handler for completion
|
||||
sceneEventProgress.OnComplete = OnSceneEventProgressCompleted;
|
||||
@@ -857,7 +863,7 @@ 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,
|
||||
@@ -929,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
|
||||
@@ -960,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];
|
||||
@@ -972,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
|
||||
@@ -1004,13 +1002,6 @@ namespace Unity.Netcode
|
||||
});
|
||||
|
||||
OnUnload?.Invoke(m_NetworkManager.LocalClientId, sceneName, sceneUnload);
|
||||
|
||||
#if UNITY_INCLUDE_TESTS
|
||||
if (m_IsRunningUnitTest)
|
||||
{
|
||||
OnSceneUnloaded(sceneEventId);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1057,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>
|
||||
@@ -1065,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
|
||||
@@ -1115,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;
|
||||
@@ -1131,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
|
||||
@@ -1172,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
|
||||
@@ -1217,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()
|
||||
{
|
||||
@@ -1242,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!");
|
||||
@@ -1326,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()
|
||||
{
|
||||
@@ -1363,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()
|
||||
@@ -1433,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()
|
||||
@@ -1486,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;
|
||||
|
||||
@@ -1497,21 +1471,11 @@ 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()
|
||||
@@ -1524,11 +1488,11 @@ namespace Unity.Netcode
|
||||
});
|
||||
|
||||
OnLoad?.Invoke(m_NetworkManager.LocalClientId, sceneName, loadSceneMode, sceneLoad);
|
||||
|
||||
if (shouldPassThrough)
|
||||
}
|
||||
else
|
||||
{
|
||||
// If so, then pass through
|
||||
ClientLoadedSynchronization(sceneEventId, sceneHash, sceneHandle);
|
||||
ClientLoadedSynchronization(sceneEventId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1537,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())
|
||||
@@ -1548,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)
|
||||
@@ -1556,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
|
||||
{
|
||||
@@ -1573,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);
|
||||
|
||||
@@ -1836,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
|
||||
@@ -1876,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)
|
||||
{
|
||||
@@ -1911,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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -114,6 +114,11 @@ namespace Unity.Netcode
|
||||
{
|
||||
double d = m_TimeSec / m_TickInterval;
|
||||
m_CachedTick = (int)d;
|
||||
// This check is needed due to double division imprecision of large numbers
|
||||
if ((d - m_CachedTick) >= 0.999999999999)
|
||||
{
|
||||
m_CachedTick++;
|
||||
}
|
||||
m_CachedTickOffset = ((d - Math.Truncate(d)) * m_TickInterval);
|
||||
|
||||
// This handles negative time, decreases tick by 1 and makes offset positive.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
]
|
||||
}
|
||||
@@ -26,6 +26,7 @@ namespace Unity.Netcode.Samples
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
CanCommitToTransform = IsOwner;
|
||||
base.Update();
|
||||
if (NetworkManager.Singleton != null && (NetworkManager.Singleton.IsConnectedClient || NetworkManager.Singleton.IsListening))
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fb1b6e801936c7f4a9af28dbed5ea2ff
|
||||
guid: d627e2fb516d92242a4930e5cd9291e3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4e60372130aba464f9f9ae4a24bb9fe0
|
||||
guid: e9af0202c9057c944b67aad6e4cdac96
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
6
TestHelpers/Runtime/AssemblyInfo.cs
Normal file
6
TestHelpers/Runtime/AssemblyInfo.cs
Normal 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")]
|
||||
11
TestHelpers/Runtime/AssemblyInfo.cs.meta
Normal file
11
TestHelpers/Runtime/AssemblyInfo.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 10ca1ce26995e754599c9eedc2c228d8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
TestHelpers/Runtime/Components.meta
Normal file
8
TestHelpers/Runtime/Components.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 19fbc3f43e13a9144a9c66c68a1c43c1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
93
TestHelpers/Runtime/Components/ObjectNameIdentifier.cs
Normal file
93
TestHelpers/Runtime/Components/ObjectNameIdentifier.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
TestHelpers/Runtime/Components/ObjectNameIdentifier.cs.meta
Normal file
11
TestHelpers/Runtime/Components/ObjectNameIdentifier.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a915cfb2e4f748e4f9526a8bf5ee84f2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
60
TestHelpers/Runtime/ConditionalPredicate.cs
Normal file
60
TestHelpers/Runtime/ConditionalPredicate.cs
Normal 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);
|
||||
|
||||
}
|
||||
}
|
||||
11
TestHelpers/Runtime/ConditionalPredicate.cs.meta
Normal file
11
TestHelpers/Runtime/ConditionalPredicate.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dada6cae693646a4095924917e5e707a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
115
TestHelpers/Runtime/IntegrationTestSceneHandler.cs
Normal file
115
TestHelpers/Runtime/IntegrationTestSceneHandler.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
TestHelpers/Runtime/IntegrationTestSceneHandler.cs.meta
Normal file
11
TestHelpers/Runtime/IntegrationTestSceneHandler.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 384935cc0ae40d641910e4c3924038c6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
TestHelpers/Runtime/Metrics.meta
Normal file
8
TestHelpers/Runtime/Metrics.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ebacdb7d8cb876a43b4a908dd6d83aa9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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];
|
||||
ClientMetrics = Client.NetworkMetrics as NetworkMetrics;
|
||||
}
|
||||
base.OnServerAndClientsCreated();
|
||||
}
|
||||
|
||||
public abstract class DualClientMetricTestBase : BaseMultiInstanceTest
|
||||
protected override IEnumerator OnStartedServerAndClients()
|
||||
{
|
||||
protected override int NbClients => 2;
|
||||
ServerMetrics = Server.NetworkMetrics as NetworkMetrics;
|
||||
ClientMetrics = Client.NetworkMetrics as NetworkMetrics;
|
||||
yield return base.OnStartedServerAndClients();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual Action<GameObject> UpdatePlayerPrefab => _ => { };
|
||||
public abstract class DualClientMetricTestBase : NetcodeIntegrationTest
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.Netcode.RuntimeTests.Metrics.Utility
|
||||
namespace Unity.Netcode.TestHelpers.Runtime.Metrics
|
||||
{
|
||||
public class RpcTestComponent : NetworkBehaviour
|
||||
{
|
||||
50
TestHelpers/Runtime/Metrics/WaitForCounterMetricValue.cs
Normal file
50
TestHelpers/Runtime/Metrics/WaitForCounterMetricValue.cs
Normal 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
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa1d3026d48b43bfa4c76e253b08b3ae
|
||||
timeCreated: 1644269156
|
||||
60
TestHelpers/Runtime/Metrics/WaitForEventMetricValues.cs
Normal file
60
TestHelpers/Runtime/Metrics/WaitForEventMetricValues.cs
Normal 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
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 319c55f92728431283c9e888d8f9d70e
|
||||
timeCreated: 1644269156
|
||||
55
TestHelpers/Runtime/Metrics/WaitForGaugeMetricValues.cs
Normal file
55
TestHelpers/Runtime/Metrics/WaitForGaugeMetricValues.cs
Normal 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
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d76c4e546c546a3b9d63b2c74fcbbca
|
||||
timeCreated: 1644269156
|
||||
@@ -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)
|
||||
return true;
|
||||
}
|
||||
|
||||
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}'.";
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator WaitForFrames(uint maxNbFrames)
|
||||
727
TestHelpers/Runtime/NetcodeIntegrationTest.cs
Normal file
727
TestHelpers/Runtime/NetcodeIntegrationTest.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
42
TestHelpers/Runtime/TimeoutHelper.cs
Normal file
42
TestHelpers/Runtime/TimeoutHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user