4 Commits

Author SHA1 Message Date
Unity Technologies
5b4aaa8b59 com.unity.netcode.gameobjects@1.0.0-pre.6
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).

## [1.0.0-pre.6] - 2022-03-02

### Added
- NetworkAnimator now properly synchrhonizes all animation layers as well as runtime-adjusted weighting between them (#1765)
- Added first set of tests for NetworkAnimator - parameter syncing, trigger set / reset, override network animator (#1735)

### Changed

### Fixed
- Fixed an issue where sometimes the first client to connect to the server could see messages from the server as coming from itself. (#1683)
- Fixed an issue where clients seemed to be able to send messages to ClientId 1, but these messages would actually still go to the server (id 0) instead of that client. (#1683)
- Improved clarity of error messaging when a client attempts to send a message to a destination other than the server, which isn't allowed. (#1683)
- Disallowed async keyword in RPCs (#1681)
- Fixed an issue where Alpha release versions of Unity (version 2022.2.0a5 and later) will not compile due to the UNet Transport no longer existing (#1678)
- Fixed messages larger than 64k being written with incorrectly truncated message size in header (#1686) (credit: @kaen)
- Fixed overloading RPC methods causing collisions and failing on IL2CPP targets. (#1694)
- Fixed spawn flow to propagate `IsSceneObject` down to children NetworkObjects, decouple implicit relationship between object spawning & `IsSceneObject` flag (#1685)
- Fixed error when serializing ConnectionApprovalMessage with scene management disabled when one or more objects is hidden via the CheckObjectVisibility delegate (#1720)
- Fixed CheckObjectVisibility delegate not being properly invoked for connecting clients when Scene Management is enabled. (#1680)
- Fixed NetworkList to properly call INetworkSerializable's NetworkSerialize() method (#1682)
- Fixed NetworkVariables containing more than 1300 bytes of data (such as large NetworkLists) no longer cause an OverflowException (the limit on data size is now whatever limit the chosen transport imposes on fragmented NetworkDelivery mechanisms) (#1725)
- Fixed ServerRpcParams and ClientRpcParams must be the last parameter of an RPC in order to function properly. Added a compile-time check to ensure this is the case and trigger an error if they're placed elsewhere (#1721)
- Fixed FastBufferReader being created with a length of 1 if provided an input of length 0 (#1724)
- Fixed The NetworkConfig's checksum hash includes the NetworkTick so that clients with a different tickrate than the server are identified and not allowed to connect (#1728)
- Fixed OwnedObjects not being properly modified when using ChangeOwnership (#1731)
- Improved performance in NetworkAnimator (#1735)
- Removed the "always sync" network animator (aka "autosend") parameters (#1746)
2022-03-02 00:00:00 +00:00
Unity Technologies
4818405514 com.unity.netcode.gameobjects@1.0.0-pre.5
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).

## [1.0.0-pre.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)
2022-01-26 00:00:00 +00:00
Unity Technologies
36d07fad5e com.unity.netcode.gameobjects@1.0.0-pre.4
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).

## [1.0.0-pre.4] - 2021-01-04

### Added

- Added `com.unity.modules.physics` and `com.unity.modules.physics2d` package dependencies (#1565)

### Removed

- Removed `com.unity.modules.ai` package dependency (#1565)
- Removed `FixedQueue`, `StreamExtensions`, `TypeExtensions` (#1398)

### Fixed
- Fixed in-scene NetworkObjects that are moved into the DDOL scene not getting restored to their original active state (enabled/disabled) after a full scene transition (#1354)
- Fixed invalid IL code being generated when using `this` instead of `this ref` for the FastBufferReader/FastBufferWriter parameter of an extension method. (#1393)
- Fixed an issue where if you are running as a server (not host) the LoadEventCompleted and UnloadEventCompleted events would fire early by the NetworkSceneManager (#1379)
- Fixed a runtime error when sending an array of an INetworkSerializable type that's implemented as a struct (#1402)
- NetworkConfig will no longer throw an OverflowException in GetConfig() when ForceSamePrefabs is enabled and the number of prefabs causes the config blob size to exceed 1300 bytes. (#1385)
- Fixed NetworkVariable not calling NetworkSerialize on INetworkSerializable types (#1383)
- Fixed NullReferenceException on ImportReferences call in NetworkBehaviourILPP (#1434)
- Fixed NetworkObjects not being despawned before they are destroyed during shutdown for client, host, and server instances. (#1390)
- 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)

### 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)
2021-01-04 00:00:00 +00:00
Unity Technologies
f5664b4cc1 com.unity.netcode.gameobjects@1.0.0-pre.3
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).

## [1.0.0-pre.3] - 2021-10-22

### Added

- ResetTrigger function to NetworkAnimator (#1327)

### Fixed

- Overflow exception when syncing Animator state. (#1327)
- Added `try`/`catch` around RPC calls, preventing exception from causing further RPC calls to fail (#1329)
- Fixed an issue where ServerClientId and LocalClientId could have the same value, causing potential confusion, and also fixed an issue with the UNet where the server could be identified with two different values, one of which might be the same as LocalClientId, and the other of which would not.(#1368)
- IL2CPP would not properly compile (#1359)
2021-10-22 00:00:00 +00:00
231 changed files with 9101 additions and 3404 deletions

View File

@@ -6,7 +6,99 @@ 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.2] - 2020-12-20
## [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
- Added `com.unity.modules.physics` and `com.unity.modules.physics2d` package dependencies (#1565)
### Removed
- Removed `com.unity.modules.ai` package dependency (#1565)
- Removed `FixedQueue`, `StreamExtensions`, `TypeExtensions` (#1398)
### Fixed
- Fixed in-scene NetworkObjects that are moved into the DDOL scene not getting restored to their original active state (enabled/disabled) after a full scene transition (#1354)
- Fixed invalid IL code being generated when using `this` instead of `this ref` for the FastBufferReader/FastBufferWriter parameter of an extension method. (#1393)
- Fixed an issue where if you are running as a server (not host) the LoadEventCompleted and UnloadEventCompleted events would fire early by the NetworkSceneManager (#1379)
- Fixed a runtime error when sending an array of an INetworkSerializable type that's implemented as a struct (#1402)
- NetworkConfig will no longer throw an OverflowException in GetConfig() when ForceSamePrefabs is enabled and the number of prefabs causes the config blob size to exceed 1300 bytes. (#1385)
- Fixed NetworkVariable not calling NetworkSerialize on INetworkSerializable types (#1383)
- Fixed NullReferenceException on ImportReferences call in NetworkBehaviourILPP (#1434)
- Fixed NetworkObjects not being despawned before they are destroyed during shutdown for client, host, and server instances. (#1390)
- 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
### Added
- ResetTrigger function to NetworkAnimator (#1327)
### Fixed
- Overflow exception when syncing Animator state. (#1327)
- Added `try`/`catch` around RPC calls, preventing exception from causing further RPC calls to fail (#1329)
- Fixed an issue where ServerClientId and LocalClientId could have the same value, causing potential confusion, and also fixed an issue with the UNet where the server could be identified with two different values, one of which might be the same as LocalClientId, and the other of which would not.(#1368)
- IL2CPP would not properly compile (#1359)
## [1.0.0-pre.2] - 2021-10-19
### Added
@@ -16,7 +108,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
- Updated label for `1.0.0-pre.1` changelog section
## [1.0.0-pre.1] - 2020-12-20
## [1.0.0-pre.1] - 2021-10-19
### Added

View File

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

View File

@@ -13,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);
}
}
@@ -38,17 +33,16 @@ namespace Unity.Netcode.Components
internal struct AnimationTriggerMessage : INetworkSerializable
{
public int Hash;
public bool Reset;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref Hash);
serializer.SerializeValue(ref Reset);
}
}
[SerializeField] private Animator m_Animator;
[SerializeField] private uint m_ParameterSendBits;
[SerializeField] private float m_SendRate = 0.1f;
public Animator Animator
{
@@ -56,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
{
@@ -101,11 +69,11 @@ namespace Unity.Netcode.Components
public fixed byte Value[4]; // this is a max size of 4 bytes
}
// 128bytes per Animator
// 128 bytes per Animator
private FastBufferWriter m_ParameterWriter = new FastBufferWriter(K_MaxAnimationParams * sizeof(float), Allocator.Persistent);
private NativeArray<AnimatorParamCache> m_CachedAnimatorParameters;
// We cache these values because UnsafeUtility.EnumToInt use direct IL that allows a nonboxing conversion
// We cache these values because UnsafeUtility.EnumToInt uses direct IL that allows a non-boxing conversion
private struct AnimationParamEnumWrapper
{
public static readonly int AnimatorControllerParameterInt;
@@ -120,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)
@@ -151,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)
@@ -197,109 +157,100 @@ 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;
}
int stateHash;
float normalizedTime;
if (!CheckAnimStateChanged(out stateHash, out normalizedTime))
for (int layer = 0; layer < m_Animator.layerCount; 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;
}
var animMsg = new AnimationMessage();
animMsg.StateHash = stateHash;
animMsg.NormalizedTime = normalizedTime;
m_ParameterWriter.Seek(0);
m_ParameterWriter.Truncate();
WriteParameters(m_ParameterWriter, false);
animMsg.Parameters = m_ParameterWriter.ToArray();
SendAnimStateClientRpc(animMsg);
}
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)
{
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;
}
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(0);
if (st.fullPathHash != m_AnimationHash)
{
// first time in this animation state
if (m_AnimationHash != 0)
{
// came from another animation directly - from Play()
stateHash = st.fullPathHash;
normalizedTime = st.normalizedTime;
}
m_TransitionHash = 0;
m_AnimationHash = st.fullPathHash;
return true;
}
return false;
}
private unsafe bool WriteParameters(FastBufferWriter writer, bool autoSend)
{
if (m_CachedAnimatorParameters == null)
{
return false;
}
for (int i = 0; i < m_CachedAnimatorParameters.Length; i++)
{
if (autoSend && !GetParameterAutoSend(i))
int stateHash;
float normalizedTime;
if (!CheckAnimStateChanged(out stateHash, out normalizedTime, layer))
{
continue;
}
var animMsg = new AnimationMessage
{
StateHash = stateHash,
NormalizedTime = normalizedTime,
Layer = layer,
Weight = m_LayerWeights[layer]
};
m_ParameterWriter.Seek(0);
m_ParameterWriter.Truncate();
WriteParameters(m_ParameterWriter);
animMsg.Parameters = m_ParameterWriter.ToArray();
SendAnimStateClientRpc(animMsg);
}
}
private bool CheckAnimStateChanged(out int stateHash, out float normalizedTime, int layer)
{
bool shouldUpdate = false;
stateHash = 0;
normalizedTime = 0;
float layerWeightNow = m_Animator.GetLayerWeight(layer);
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[layer] != 0)
{
// came from another animation directly - from Play()
stateHash = st.fullPathHash;
normalizedTime = st.normalizedTime;
}
m_TransitionHash[layer] = 0;
m_AnimationHash[layer] = st.fullPathHash;
shouldUpdate = true;
}
}
return shouldUpdate;
}
/* $AS TODO: Right now we are not checking for changed values this is because
the read side of this function doesn't have similar logic which would cause
an overflow read because it doesn't know if the value is there or not. So
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 void WriteParameters(FastBufferWriter writer)
{
for (int i = 0; i < m_CachedAnimatorParameters.Length; i++)
{
ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef<AnimatorParamCache>(m_CachedAnimatorParameters.GetUnsafePtr(), i);
var hash = cacheValue.Hash;
@@ -308,12 +259,8 @@ namespace Unity.Netcode.Components
var valueInt = m_Animator.GetInteger(hash);
fixed (void* value = cacheValue.Value)
{
var oldValue = UnsafeUtility.AsRef<int>(value);
if (valueInt != oldValue)
{
UnsafeUtility.WriteArrayElement(value, 0, valueInt);
BytePacker.WriteValuePacked(writer, (uint)valueInt);
}
UnsafeUtility.WriteArrayElement(value, 0, valueInt);
BytePacker.WriteValuePacked(writer, (uint)valueInt);
}
}
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterBool)
@@ -321,12 +268,8 @@ namespace Unity.Netcode.Components
var valueBool = m_Animator.GetBool(hash);
fixed (void* value = cacheValue.Value)
{
var oldValue = UnsafeUtility.AsRef<bool>(value);
if (valueBool != oldValue)
{
UnsafeUtility.WriteArrayElement(value, 0, valueBool);
writer.WriteValueSafe(valueBool);
}
UnsafeUtility.WriteArrayElement(value, 0, valueBool);
writer.WriteValueSafe(valueBool);
}
}
else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat)
@@ -334,34 +277,18 @@ namespace Unity.Netcode.Components
var valueFloat = m_Animator.GetFloat(hash);
fixed (void* value = cacheValue.Value)
{
var oldValue = UnsafeUtility.AsRef<float>(value);
if (valueFloat != oldValue)
{
UnsafeUtility.WriteArrayElement(value, 0, valueFloat);
writer.WriteValueSafe(valueFloat);
}
UnsafeUtility.WriteArrayElement(value, 0, valueFloat);
writer.WriteValueSafe(valueFloat);
}
}
}
// 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;
@@ -395,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)
{
@@ -424,31 +343,91 @@ 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)
{
m_Animator.SetTrigger(animSnapshot.Hash);
if (animSnapshot.Reset)
{
m_Animator.ResetTrigger(animSnapshot.Hash);
}
else
{
m_Animator.SetTrigger(animSnapshot.Hash);
}
}
/// <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));
}
public void SetTrigger(int hash)
/// <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();
animMsg.Hash = hash;
animMsg.Reset = reset;
if (IsServer)
{
// trigger the animation locally on the server...
if (reset)
{
m_Animator.ResetTrigger(hash);
}
else
{
m_Animator.SetTrigger(hash);
}
// ...then tell all the clients to do the same
SendAnimTriggerClientRpc(animMsg);
}
else
{
Debug.LogWarning("Trying to call NetworkAnimator.SetTrigger on a client...ignoring");
}
}
/// <summary>
/// Resets the trigger for the associated animation. See note for SetTrigger(string)
/// </summary>
/// <param name="triggerName">The string name of the trigger to reset</param>
public void ResetTrigger(string triggerName)
{
ResetTrigger(Animator.StringToHash(triggerName));
}
/// <summary>
/// Resets the trigger for the associated animation. See note for SetTrigger(string)
/// </summary>
/// <param name="hash">The hash for the trigger to reset</param>
public void ResetTrigger(int hash)
{
SetTrigger(hash, true);
}
}
}

View File

@@ -32,7 +32,7 @@ namespace Unity.Netcode.Components
private void FixedUpdate()
{
if (NetworkManager.IsListening)
if (IsSpawned)
{
if (HasAuthority != m_IsAuthority)
{
@@ -74,7 +74,6 @@ namespace Unity.Netcode.Components
/// <inheritdoc />
public override void OnNetworkDespawn()
{
m_IsAuthority = false;
UpdateRigidbodyKinematicMode();
}
}

View File

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

View File

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

View File

@@ -22,6 +22,10 @@ namespace Unity.Netcode.Editor.CodeGen
public static readonly string ClientRpcAttribute_FullName = typeof(ClientRpcAttribute).FullName;
public static readonly string ServerRpcParams_FullName = typeof(ServerRpcParams).FullName;
public static readonly string ClientRpcParams_FullName = typeof(ClientRpcParams).FullName;
public static readonly string ClientRpcSendParams_FullName = typeof(ClientRpcSendParams).FullName;
public static readonly string ClientRpcReceiveParams_FullName = typeof(ClientRpcReceiveParams).FullName;
public static readonly string ServerRpcSendParams_FullName = typeof(ServerRpcSendParams).FullName;
public static readonly string ServerRpcReceiveParams_FullName = typeof(ServerRpcReceiveParams).FullName;
public static readonly string INetworkSerializable_FullName = typeof(INetworkSerializable).FullName;
public static readonly string UnityColor_FullName = typeof(Color).FullName;
public static readonly string UnityColor32_FullName = typeof(Color32).FullName;
@@ -264,6 +268,28 @@ namespace Unity.Netcode.Editor.CodeGen
});
}
public static void AddWarning(this List<DiagnosticMessage> diagnostics, string message)
{
diagnostics.AddWarning((SequencePoint)null, message);
}
public static void AddWarning(this List<DiagnosticMessage> diagnostics, MethodDefinition methodDefinition, string message)
{
diagnostics.AddWarning(methodDefinition.DebugInformation.SequencePoints.FirstOrDefault(), message);
}
public static void AddWarning(this List<DiagnosticMessage> diagnostics, SequencePoint sequencePoint, string message)
{
diagnostics.Add(new DiagnosticMessage
{
DiagnosticType = DiagnosticType.Warning,
File = sequencePoint?.Document.Url.Replace($"{Environment.CurrentDirectory}{Path.DirectorySeparatorChar}", ""),
Line = sequencePoint?.StartLine ?? 0,
Column = sequencePoint?.StartColumn ?? 0,
MessageData = $" - {message}"
});
}
public static void RemoveRecursiveReferences(this ModuleDefinition moduleDefinition)
{
// Weird behavior from Cecil: When importing a reference to a specific implementation of a generic

View File

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

View File

@@ -0,0 +1,167 @@
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using Unity.CompilationPipeline.Common.Diagnostics;
using Unity.CompilationPipeline.Common.ILPostProcessing;
using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor;
using MethodAttributes = Mono.Cecil.MethodAttributes;
namespace Unity.Netcode.Editor.CodeGen
{
internal sealed class INetworkSerializableILPP : ILPPInterface
{
public override ILPPInterface GetInstance() => this;
public override bool WillProcess(ICompiledAssembly compiledAssembly) =>
compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName ||
compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName);
private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>();
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
{
if (!WillProcess(compiledAssembly))
{
return null;
}
m_Diagnostics.Clear();
// read
var assemblyDefinition = CodeGenHelpers.AssemblyDefinitionFor(compiledAssembly, out var resolver);
if (assemblyDefinition == null)
{
m_Diagnostics.AddError($"Cannot read assembly definition: {compiledAssembly.Name}");
return null;
}
// process
var mainModule = assemblyDefinition.MainModule;
if (mainModule != null)
{
try
{
if (ImportReferences(mainModule))
{
var types = mainModule.GetTypes()
.Where(t => t.Resolve().HasInterface(CodeGenHelpers.INetworkSerializable_FullName) && !t.Resolve().IsAbstract && t.Resolve().IsValueType)
.ToList();
// process `INetworkMessage` types
if (types.Count == 0)
{
return null;
}
CreateModuleInitializer(assemblyDefinition, types);
}
else
{
m_Diagnostics.AddError($"Cannot import references into main module: {mainModule.Name}");
}
}
catch (Exception e)
{
m_Diagnostics.AddError((e.ToString() + e.StackTrace.ToString()).Replace("\n", "|").Replace("\r", "|"));
}
}
else
{
m_Diagnostics.AddError($"Cannot get main module from assembly definition: {compiledAssembly.Name}");
}
mainModule.RemoveRecursiveReferences();
// write
var pe = new MemoryStream();
var pdb = new MemoryStream();
var writerParameters = new WriterParameters
{
SymbolWriterProvider = new PortablePdbWriterProvider(),
SymbolStream = pdb,
WriteSymbols = true
};
assemblyDefinition.Write(pe, writerParameters);
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics);
}
private MethodReference m_InitializeDelegates_MethodRef;
private const string k_InitializeMethodName = nameof(NetworkVariableHelper.InitializeDelegates);
private bool ImportReferences(ModuleDefinition moduleDefinition)
{
var helperType = typeof(NetworkVariableHelper);
foreach (var methodInfo in helperType.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public))
{
switch (methodInfo.Name)
{
case k_InitializeMethodName:
m_InitializeDelegates_MethodRef = moduleDefinition.ImportReference(methodInfo);
break;
}
}
return true;
}
private MethodDefinition GetOrCreateStaticConstructor(TypeDefinition typeDefinition)
{
var staticCtorMethodDef = typeDefinition.GetStaticConstructor();
if (staticCtorMethodDef == null)
{
staticCtorMethodDef = new MethodDefinition(
".cctor", // Static Constructor (constant-constructor)
MethodAttributes.HideBySig |
MethodAttributes.SpecialName |
MethodAttributes.RTSpecialName |
MethodAttributes.Static,
typeDefinition.Module.TypeSystem.Void);
staticCtorMethodDef.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
typeDefinition.Methods.Add(staticCtorMethodDef);
}
return staticCtorMethodDef;
}
// Creates a static module constructor (which is executed when the module is loaded) that registers all the
// message types in the assembly with MessagingSystem.
// This is the same behavior as annotating a static method with [ModuleInitializer] in standardized
// C# (that attribute doesn't exist in Unity, but the static module constructor still works)
// https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.moduleinitializerattribute?view=net-5.0
// https://web.archive.org/web/20100212140402/http://blogs.msdn.com/junfeng/archive/2005/11/19/494914.aspx
private void CreateModuleInitializer(AssemblyDefinition assembly, List<TypeDefinition> networkSerializableTypes)
{
foreach (var typeDefinition in assembly.MainModule.Types)
{
if (typeDefinition.FullName == "<Module>")
{
var staticCtorMethodDef = GetOrCreateStaticConstructor(typeDefinition);
var processor = staticCtorMethodDef.Body.GetILProcessor();
var instructions = new List<Instruction>();
foreach (var type in networkSerializableTypes)
{
var method = new GenericInstanceMethod(m_InitializeDelegates_MethodRef);
method.GenericArguments.Add(type);
instructions.Add(processor.Create(OpCodes.Call, method));
}
instructions.ForEach(instruction => processor.Body.Instructions.Insert(processor.Body.Instructions.Count - 1, instruction));
break;
}
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 64a0c1e708fa46a389d64e7b4708e6c7
timeCreated: 1635535237

View File

@@ -7,7 +7,6 @@ using System.Runtime.CompilerServices;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
using Unity.Collections;
using Unity.CompilationPipeline.Common.Diagnostics;
using Unity.CompilationPipeline.Common.ILPostProcessing;
using UnityEngine;
@@ -17,7 +16,6 @@ using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostPr
namespace Unity.Netcode.Editor.CodeGen
{
internal sealed class NetworkBehaviourILPP : ILPPInterface
{
private const string k_ReadValueMethodName = nameof(FastBufferReader.ReadValueSafe);
@@ -25,7 +23,8 @@ namespace Unity.Netcode.Editor.CodeGen
public override ILPPInterface GetInstance() => this;
public override bool WillProcess(ICompiledAssembly compiledAssembly) => compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName);
public override bool WillProcess(ICompiledAssembly compiledAssembly) =>
compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName);
private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>();
@@ -109,8 +108,10 @@ namespace Unity.Netcode.Editor.CodeGen
private FieldReference m_NetworkManager_rpc_name_table_FieldRef;
private MethodReference m_NetworkManager_rpc_name_table_Add_MethodRef;
private TypeReference m_NetworkBehaviour_TypeRef;
private MethodReference m_NetworkBehaviour_SendServerRpc_MethodRef;
private MethodReference m_NetworkBehaviour_SendClientRpc_MethodRef;
private MethodReference m_NetworkBehaviour_beginSendServerRpc_MethodRef;
private MethodReference m_NetworkBehaviour_endSendServerRpc_MethodRef;
private MethodReference m_NetworkBehaviour_beginSendClientRpc_MethodRef;
private MethodReference m_NetworkBehaviour_endSendClientRpc_MethodRef;
private FieldReference m_NetworkBehaviour_rpc_exec_stage_FieldRef;
private MethodReference m_NetworkBehaviour_getNetworkManager_MethodRef;
private MethodReference m_NetworkBehaviour_getOwnerClientId_MethodRef;
@@ -124,8 +125,6 @@ namespace Unity.Netcode.Editor.CodeGen
private TypeReference m_ClientRpcParams_TypeRef;
private TypeReference m_FastBufferWriter_TypeRef;
private MethodReference m_FastBufferWriter_Constructor;
private MethodReference m_FastBufferWriter_Dispose;
private Dictionary<string, MethodReference> m_FastBufferWriter_WriteValue_MethodRefs = new Dictionary<string, MethodReference>();
private List<MethodReference> m_FastBufferWriter_ExtensionMethodRefs = new List<MethodReference>();
@@ -144,8 +143,10 @@ namespace Unity.Netcode.Editor.CodeGen
private const string k_NetworkManager_rpc_name_table = nameof(NetworkManager.__rpc_name_table);
private const string k_NetworkBehaviour_rpc_exec_stage = nameof(NetworkBehaviour.__rpc_exec_stage);
private const string k_NetworkBehaviour_SendServerRpc = nameof(NetworkBehaviour.__sendServerRpc);
private const string k_NetworkBehaviour_SendClientRpc = nameof(NetworkBehaviour.__sendClientRpc);
private const string k_NetworkBehaviour_beginSendServerRpc = nameof(NetworkBehaviour.__beginSendServerRpc);
private const string k_NetworkBehaviour_endSendServerRpc = nameof(NetworkBehaviour.__endSendServerRpc);
private const string k_NetworkBehaviour_beginSendClientRpc = nameof(NetworkBehaviour.__beginSendClientRpc);
private const string k_NetworkBehaviour_endSendClientRpc = nameof(NetworkBehaviour.__endSendClientRpc);
private const string k_NetworkBehaviour_NetworkManager = nameof(NetworkBehaviour.NetworkManager);
private const string k_NetworkBehaviour_OwnerClientId = nameof(NetworkBehaviour.OwnerClientId);
@@ -234,11 +235,17 @@ namespace Unity.Netcode.Editor.CodeGen
{
switch (methodInfo.Name)
{
case k_NetworkBehaviour_SendServerRpc:
m_NetworkBehaviour_SendServerRpc_MethodRef = moduleDefinition.ImportReference(methodInfo);
case k_NetworkBehaviour_beginSendServerRpc:
m_NetworkBehaviour_beginSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodInfo);
break;
case k_NetworkBehaviour_SendClientRpc:
m_NetworkBehaviour_SendClientRpc_MethodRef = moduleDefinition.ImportReference(methodInfo);
case k_NetworkBehaviour_endSendServerRpc:
m_NetworkBehaviour_endSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodInfo);
break;
case k_NetworkBehaviour_beginSendClientRpc:
m_NetworkBehaviour_beginSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodInfo);
break;
case k_NetworkBehaviour_endSendClientRpc:
m_NetworkBehaviour_endSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodInfo);
break;
}
}
@@ -299,24 +306,22 @@ 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)
{
assemblies.Add(m_AssemblyResolver.Resolve(reference));
var assembly = m_AssemblyResolver.Resolve(reference);
if (assembly != null)
{
assemblies.Add(assembly);
}
}
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)
@@ -328,6 +333,7 @@ namespace Unity.Netcode.Editor.CodeGen
{
continue;
}
foreach (var method in type.Methods)
{
if (!method.IsStatic)
@@ -352,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));
}
@@ -391,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)
{
@@ -473,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;
@@ -517,7 +531,6 @@ namespace Unity.Netcode.Editor.CodeGen
if (isValid)
{
isServerRpc = customAttributeType_FullName == CodeGenHelpers.ServerRpcAttribute_FullName;
rpcAttribute = customAttribute;
}
}
@@ -565,31 +578,36 @@ 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)
{
var meetsConstraints = true;
foreach (var constraint in method.GenericParameters[0].Constraints)
{
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)) ||
(resolvedConstraint.Name == "ValueType" && !checkType.IsValueType))
{
var instanceMethod = new GenericInstanceMethod(method);
instanceMethod.GenericArguments.Add(checkType);
return instanceMethod;
meetsConstraints = false;
break;
}
}
if (meetsConstraints)
{
var instanceMethod = new GenericInstanceMethod(method);
instanceMethod.GenericArguments.Add(checkType);
return instanceMethod;
}
}
}
}
@@ -613,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;
@@ -624,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;
@@ -696,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);
@@ -725,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;
@@ -761,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)
@@ -786,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)
@@ -843,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
@@ -856,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));
@@ -875,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);
@@ -884,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;
}
@@ -931,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));
@@ -945,22 +1014,28 @@ 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
instructions.Add(processor.Create(OpCodes.Ldloca, serializerLocIdx));
// 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;
if (checkParameter.ParameterType.Resolve() ==
m_FastBufferWriter_TypeRef.MakeByReferenceType().Resolve())
if (methodRef.Resolve().DeclaringType != m_FastBufferWriter_TypeRef.Resolve())
{
isExtensionMethod = true;
checkParameter = method.Parameters[1];
}
if (checkParameter.IsIn)
if (!isExtensionMethod || method.Parameters[0].ParameterType.IsByReference)
{
instructions.Add(processor.Create(OpCodes.Ldloca, bufWriterLocIdx));
}
else
{
instructions.Add(processor.Create(OpCodes.Ldloc, bufWriterLocIdx));
}
if (checkParameter.IsIn || checkParameter.IsOut || checkParameter.ParameterType.IsByReference)
{
instructions.Add(processor.Create(OpCodes.Ldarga, paramIndex + 1));
}
@@ -969,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));
}
@@ -998,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
@@ -1022,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
@@ -1051,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);
@@ -1115,20 +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 = rpcHandler.Body.GetILProcessor();
var processor = nhandler.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)
@@ -1139,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);
@@ -1211,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
@@ -1246,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);
@@ -1266,7 +1314,18 @@ namespace Unity.Netcode.Editor.CodeGen
if (foundMethodRef)
{
// reader.ReadValueSafe(out localVar);
processor.Emit(OpCodes.Ldarga, 1);
var checkParameter = methodRef.Resolve().Parameters[0];
var isExtensionMethod = methodRef.Resolve().DeclaringType != m_FastBufferReader_TypeRef.Resolve();
if (!isExtensionMethod || checkParameter.ParameterType.IsByReference)
{
processor.Emit(OpCodes.Ldarga, 1);
}
else
{
processor.Emit(OpCodes.Ldarg, 1);
}
processor.Emit(OpCodes.Ldloca, localIndex);
if (paramType == typeSystem.String)
{
@@ -1304,7 +1363,7 @@ namespace Unity.Netcode.Editor.CodeGen
processor.Emit(OpCodes.Stfld, m_NetworkBehaviour_rpc_exec_stage_FieldRef);
processor.Emit(OpCodes.Ret);
return nhandler;
return rpcHandler;
}
}
}

View File

@@ -52,6 +52,9 @@ namespace Unity.Netcode.Editor.CodeGen
case nameof(NetworkBehaviour):
ProcessNetworkBehaviour(typeDefinition);
break;
case nameof(NetworkVariableHelper):
ProcessNetworkVariableHelper(typeDefinition);
break;
case nameof(__RpcParams):
typeDefinition.IsPublic = true;
break;
@@ -100,6 +103,17 @@ namespace Unity.Netcode.Editor.CodeGen
}
}
private void ProcessNetworkVariableHelper(TypeDefinition typeDefinition)
{
foreach (var methodDefinition in typeDefinition.Methods)
{
if (methodDefinition.Name == nameof(NetworkVariableHelper.InitializeDelegates))
{
methodDefinition.IsPublic = true;
}
}
}
private void ProcessNetworkBehaviour(TypeDefinition typeDefinition)
{
foreach (var nestedType in typeDefinition.NestedTypes)
@@ -120,8 +134,10 @@ namespace Unity.Netcode.Editor.CodeGen
foreach (var methodDefinition in typeDefinition.Methods)
{
if (methodDefinition.Name == nameof(NetworkBehaviour.__sendServerRpc)
|| methodDefinition.Name == nameof(NetworkBehaviour.__sendClientRpc))
if (methodDefinition.Name == nameof(NetworkBehaviour.__beginSendServerRpc) ||
methodDefinition.Name == nameof(NetworkBehaviour.__endSendServerRpc) ||
methodDefinition.Name == nameof(NetworkBehaviour.__beginSendClientRpc) ||
methodDefinition.Name == nameof(NetworkBehaviour.__endSendClientRpc))
{
methodDefinition.IsFamily = true;
}

View File

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

View File

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

View File

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

View File

@@ -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();
@@ -363,7 +359,7 @@ namespace Unity.Netcode.Editor
const string getToolsText = "Access additional tools for multiplayer development by installing the Multiplayer Tools package in the Package Manager.";
const string openDocsButtonText = "Open Docs";
const string dismissButtonText = "Dismiss";
const string targetUrl = "https://docs-multiplayer.unity3d.com/docs/tutorials/goldenpath_series/goldenpath_foundation_module";
const string targetUrl = "https://docs-multiplayer.unity3d.com/docs/tools/install-tools";
const string infoIconName = "console.infoicon";
if (PlayerPrefs.GetInt(InstallMultiplayerToolsTipDismissedPlayerPrefKey, 0) != 0)

View 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
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: a8514b4eca0c7044d9b92faf9407ec93
guid: b26b53dc28ae1b5488bbbecc3e499bbc
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

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

View File

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

View File

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

View File

@@ -1,77 +0,0 @@
using System;
namespace Unity.Netcode
{
/// <summary>
/// Queue with a fixed size
/// </summary>
/// <typeparam name="T">The type of the queue</typeparam>
public sealed class FixedQueue<T>
{
private readonly T[] m_Queue;
private int m_QueueCount = 0;
private int m_QueueStart;
/// <summary>
/// The amount of enqueued objects
/// </summary>
public int Count => m_QueueCount;
/// <summary>
/// Gets the element at a given virtual index
/// </summary>
/// <param name="index">The virtual index to get the item from</param>
/// <returns>The element at the virtual index</returns>
public T this[int index] => m_Queue[(m_QueueStart + index) % m_Queue.Length];
/// <summary>
/// Creates a new FixedQueue with a given size
/// </summary>
/// <param name="maxSize">The size of the queue</param>
public FixedQueue(int maxSize)
{
m_Queue = new T[maxSize];
m_QueueStart = 0;
}
/// <summary>
/// Enqueues an object
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
public bool Enqueue(T t)
{
m_Queue[(m_QueueStart + m_QueueCount) % m_Queue.Length] = t;
if (++m_QueueCount > m_Queue.Length)
{
--m_QueueCount;
return true;
}
return false;
}
/// <summary>
/// Dequeues an object
/// </summary>
/// <returns></returns>
public T Dequeue()
{
if (--m_QueueCount == -1)
{
throw new IndexOutOfRangeException("Cannot dequeue empty queue!");
}
T res = m_Queue[m_QueueStart];
m_QueueStart = (m_QueueStart + 1) % m_Queue.Length;
return res;
}
/// <summary>
/// Gets the element at a given virtual index
/// </summary>
/// <param name="index">The virtual index to get the item from</param>
/// <returns>The element at the virtual index</returns>
public T ElementAt(int index) => m_Queue[(m_QueueStart + index) % m_Queue.Length];
}
}

View File

@@ -141,15 +141,15 @@ namespace Unity.Netcode
/// <summary>
/// Whether or not to enable Snapshot System for variable updates. Not supported in this version.
/// </summary>
public bool UseSnapshotDelta { get; } = false;
public bool UseSnapshotDelta { get; internal set; } = false;
/// <summary>
/// Whether or not to enable Snapshot System for spawn and despawn commands. Not supported in this version.
/// </summary>
public bool UseSnapshotSpawn { get; } = false;
public bool UseSnapshotSpawn { get; internal set; } = false;
/// <summary>
/// When Snapshot System spawn is enabled: max size of Snapshot Messages. Meant to fit MTU.
/// </summary>
public int SnapshotMaxSpawnUsage { get; } = 1200;
public int SnapshotMaxSpawnUsage { get; } = 1000;
public const int RttAverageSamples = 5; // number of RTT to keep an average of (plus one)
public const int RttWindowSize = 64; // number of slots to use for RTT computations (max number of in-flight packets)
@@ -224,7 +224,7 @@ namespace Unity.Netcode
return m_ConfigHash.Value;
}
var writer = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp);
var writer = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, int.MaxValue);
using (writer)
{
writer.WriteValueSafe(ProtocolVersion);
@@ -239,6 +239,8 @@ namespace Unity.Netcode
writer.WriteValueSafe(sortedEntry.Key);
}
}
writer.WriteValueSafe(TickRate);
writer.WriteValueSafe(ConnectionApproval);
writer.WriteValueSafe(ForceSamePrefabs);
writer.WriteValueSafe(EnableSceneManagement);

View File

@@ -5,6 +5,6 @@ namespace Unity.Netcode
/// </summary>
internal static class NetworkConstants
{
internal const string PROTOCOL_VERSION = "14.0.0";
internal const string PROTOCOL_VERSION = "15.0.0";
}
}

View File

@@ -15,41 +15,56 @@ namespace Unity.Netcode
#pragma warning disable IDE1006 // disable naming rule violation check
// RuntimeAccessModifiersILPP will make this `protected`
internal enum __RpcExecStage
#pragma warning restore IDE1006 // restore naming rule violation check
{
None = 0,
Server = 1,
Client = 2
}
#pragma warning disable IDE1006 // disable naming rule violation check
// NetworkBehaviourILPP will override this in derived classes to return the name of the concrete type
internal virtual string __getTypeName() => nameof(NetworkBehaviour);
#pragma warning restore IDE1006 // restore naming rule violation check
#pragma warning disable 414 // disable assigned but its value is never used
#pragma warning disable IDE1006 // disable naming rule violation check
[NonSerialized]
// RuntimeAccessModifiersILPP will make this `protected`
internal __RpcExecStage __rpc_exec_stage = __RpcExecStage.None;
#pragma warning restore 414 // restore assigned but its value is never used
#pragma warning restore IDE1006 // restore naming rule violation check
#pragma warning disable 414 // disable assigned but its value is never used
private const int k_RpcMessageDefaultSize = 1024; // 1k
private const int k_RpcMessageMaximumSize = 1024 * 64; // 64k
#pragma warning disable IDE1006 // disable naming rule violation check
// RuntimeAccessModifiersILPP will make this `protected`
internal void __sendServerRpc(FastBufferWriter writer, uint rpcMethodId, ServerRpcParams rpcParams, RpcDelivery delivery)
#pragma warning restore 414 // restore assigned but its value is never used
internal FastBufferWriter __beginSendServerRpc(uint rpcMethodId, ServerRpcParams serverRpcParams, RpcDelivery rpcDelivery)
#pragma warning restore IDE1006 // restore naming rule violation check
{
NetworkDelivery networkDelivery = NetworkDelivery.Reliable;
switch (delivery)
return new FastBufferWriter(k_RpcMessageDefaultSize, Allocator.Temp, k_RpcMessageMaximumSize);
}
#pragma warning disable IDE1006 // disable naming rule violation check
// RuntimeAccessModifiersILPP will make this `protected`
internal void __endSendServerRpc(ref FastBufferWriter bufferWriter, uint rpcMethodId, ServerRpcParams serverRpcParams, RpcDelivery rpcDelivery)
#pragma warning restore IDE1006 // restore naming rule violation check
{
var serverRpcMessage = new ServerRpcMessage
{
Metadata = new RpcMetadata
{
NetworkObjectId = NetworkObjectId,
NetworkBehaviourId = NetworkBehaviourId,
NetworkRpcMethodId = rpcMethodId,
},
WriteBuffer = bufferWriter
};
NetworkDelivery networkDelivery;
switch (rpcDelivery)
{
default:
case RpcDelivery.Reliable:
networkDelivery = NetworkDelivery.ReliableFragmentedSequenced;
break;
case RpcDelivery.Unreliable:
if (writer.Length > MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE - sizeof(RpcMessage.RpcType) - sizeof(ulong) - sizeof(uint) - sizeof(ushort))
if (bufferWriter.Length > MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE)
{
throw new OverflowException("RPC parameters are too large for unreliable delivery.");
}
@@ -57,41 +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.
Header = new MessageHeader()
// 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))
@@ -101,26 +108,44 @@ namespace Unity.Netcode
NetworkObject,
rpcMethodName,
__getTypeName(),
rpcMessageSize);
rpcWriteSize);
}
#endif
}
#pragma warning disable 414 // disable assigned but its value is never used
#pragma warning disable IDE1006 // disable naming rule violation check
// RuntimeAccessModifiersILPP will make this `protected`
internal unsafe void __sendClientRpc(FastBufferWriter writer, uint rpcMethodId, ClientRpcParams rpcParams, RpcDelivery delivery)
#pragma warning disable 414 // disable assigned but its value is never used
#pragma warning disable IDE1006 // disable naming rule violation check
internal FastBufferWriter __beginSendClientRpc(uint rpcMethodId, ClientRpcParams clientRpcParams, RpcDelivery rpcDelivery)
#pragma warning restore IDE1006 // restore naming rule violation check
{
NetworkDelivery networkDelivery = NetworkDelivery.Reliable;
switch (delivery)
return new FastBufferWriter(k_RpcMessageDefaultSize, Allocator.Temp, k_RpcMessageMaximumSize);
}
#pragma warning disable IDE1006 // disable naming rule violation check
// RuntimeAccessModifiersILPP will make this `protected`
internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMethodId, ClientRpcParams clientRpcParams, RpcDelivery rpcDelivery)
#pragma warning restore IDE1006 // restore naming rule violation check
{
var clientRpcMessage = new ClientRpcMessage
{
Metadata = new RpcMetadata
{
NetworkObjectId = NetworkObjectId,
NetworkBehaviourId = NetworkBehaviourId,
NetworkRpcMethodId = rpcMethodId,
},
WriteBuffer = bufferWriter
};
NetworkDelivery networkDelivery;
switch (rpcDelivery)
{
default:
case RpcDelivery.Reliable:
networkDelivery = NetworkDelivery.ReliableFragmentedSequenced;
break;
case RpcDelivery.Unreliable:
if (writer.Length > MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE - sizeof(RpcMessage.RpcType) - sizeof(ulong) - sizeof(uint) - sizeof(ushort))
if (bufferWriter.Length > MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE)
{
throw new OverflowException("RPC parameters are too large for unreliable delivery.");
}
@@ -128,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)
{
@@ -156,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)
{
@@ -169,31 +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.
Header = new MessageHeader()
// 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))
{
@@ -204,7 +222,7 @@ namespace Unity.Netcode
NetworkObject,
rpcMethodName,
__getTypeName(),
messageSize);
rpcWriteSize);
}
}
#endif
@@ -258,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>
@@ -282,9 +300,17 @@ namespace Unity.Netcode
m_NetworkObject = GetComponentInParent<NetworkObject>();
}
if (m_NetworkObject == null && NetworkLog.CurrentLogLevel <= LogLevel.Normal)
// 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))
{
NetworkLog.LogWarning($"Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?");
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?");
}
}
return m_NetworkObject;
@@ -343,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
@@ -422,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);
@@ -536,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);
@@ -544,7 +566,7 @@ namespace Unity.Netcode
}
else
{
NetworkManager.SendMessage(message, m_DeliveryTypesForNetworkVariableGroups[j], clientId);
NetworkManager.SendMessage(ref message, m_DeliveryTypesForNetworkVariableGroups[j], clientId);
}
}
}

View File

@@ -73,6 +73,9 @@ namespace Unity.Netcode
}
}
private bool m_ShuttingDown;
private bool m_StopProcessingMessages;
private class NetworkManagerHooks : INetworkHooks
{
private NetworkManager m_NetworkManager;
@@ -82,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
{
}
@@ -116,7 +119,7 @@ namespace Unity.Netcode
public bool OnVerifyCanSend(ulong destinationId, Type messageType, NetworkDelivery delivery)
{
return true;
return !m_NetworkManager.m_StopProcessingMessages;
}
public bool OnVerifyCanReceive(ulong senderId, Type messageType)
@@ -134,7 +137,15 @@ namespace Unity.Netcode
return false;
}
return true;
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
{
}
}
@@ -149,14 +160,9 @@ namespace Unity.Netcode
public void Send(ulong clientId, NetworkDelivery delivery, FastBufferWriter batchData)
{
var sendBuffer = batchData.ToTempByteArray();
var length = batchData.Length;
//TODO: Transport needs to have a way to send it data without copying and allocating here.
var bytes = batchData.ToArray();
var sendBuffer = new ArraySegment<byte>(bytes, 0, length);
m_NetworkManager.NetworkConfig.NetworkTransport.Send(clientId, sendBuffer, delivery);
m_NetworkManager.NetworkConfig.NetworkTransport.Send(m_NetworkManager.ClientIdToTransportId(clientId), sendBuffer, delivery);
}
}
@@ -199,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>
@@ -228,10 +229,12 @@ namespace Unity.Netcode
public NetworkSceneManager SceneManager { get; private set; }
public readonly ulong ServerClientId = 0;
/// <summary>
/// Gets the networkId of the server
/// </summary>
public ulong ServerClientId => NetworkConfig.NetworkTransport?.ServerClientId ??
private ulong m_ServerTransportId => NetworkConfig.NetworkTransport?.ServerClientId ??
throw new NullReferenceException(
$"The transport in the active {nameof(NetworkConfig)} is null");
@@ -248,6 +251,10 @@ namespace Unity.Netcode
private Dictionary<ulong, NetworkClient> m_ConnectedClients = new Dictionary<ulong, NetworkClient>();
private ulong m_NextClientId = 1;
private Dictionary<ulong, ulong> m_ClientIdToTransportIdMap = new Dictionary<ulong, ulong>();
private Dictionary<ulong, ulong> m_TransportIdToClientIdMap = new Dictionary<ulong, ulong>();
private List<NetworkClient> m_ConnectedClientsList = new List<NetworkClient>();
private List<ulong> m_ConnectedClientIds = new List<ulong>();
@@ -332,6 +339,9 @@ namespace Unity.Netcode
/// </summary>
public bool IsConnectedClient { get; internal set; }
public bool ShutdownInProgress { get { return m_ShuttingDown; } }
/// <summary>
/// The callback to invoke once a client connects. This callback is only ran on the server and on the local client that connects.
/// </summary>
@@ -344,8 +354,6 @@ namespace Unity.Netcode
/// </summary>
public event Action<ulong> OnClientDisconnectCallback = null;
internal void InvokeOnClientDisconnectCallback(ulong clientId) => OnClientDisconnectCallback?.Invoke(clientId);
/// <summary>
/// The callback to invoke once the server is ready
/// </summary>
@@ -485,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));
@@ -548,6 +563,8 @@ namespace Unity.Netcode
return;
}
NetworkConfig.NetworkTransport.NetworkMetrics = NetworkMetrics;
//This 'if' should never enter
if (SnapshotSystem != null)
{
@@ -555,8 +572,6 @@ namespace Unity.Netcode
SnapshotSystem = null;
}
SnapshotSystem = new SnapshotSystem(this);
if (server)
{
NetworkTimeSystem = NetworkTimeSystem.ServerTimeSystem();
@@ -569,6 +584,8 @@ namespace Unity.Netcode
NetworkTickSystem = new NetworkTickSystem(NetworkConfig.TickRate, 0, 0);
NetworkTickSystem.Tick += OnNetworkManagerTick;
SnapshotSystem = new SnapshotSystem(this, NetworkConfig, NetworkTickSystem);
this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate);
// This is used to remove entries not needed or invalid
@@ -576,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++)
@@ -772,7 +790,7 @@ namespace Unity.Netcode
NetworkConfig.NetworkTransport.OnTransportEvent += HandleRawTransportPoll;
NetworkConfig.NetworkTransport.Initialize();
NetworkConfig.NetworkTransport.Initialize(this);
}
/// <summary>
@@ -936,11 +954,6 @@ namespace Unity.Netcode
private void OnEnable()
{
if (DontDestroy)
{
DontDestroyOnLoad(gameObject);
}
if (RunInBackground)
{
Application.runInBackground = true;
@@ -950,6 +963,11 @@ namespace Unity.Netcode
{
SetSingleton();
}
if (!NetworkManagerCheckForParent())
{
DontDestroyOnLoad(gameObject);
}
}
private void Awake()
@@ -957,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)
{
@@ -975,7 +1035,7 @@ namespace Unity.Netcode
// Note that this gets also called manually by OnSceneUnloaded and OnApplicationQuit
private void OnDestroy()
{
Shutdown();
ShutdownInternal();
UnityEngine.SceneManagement.SceneManager.sceneUnloaded -= OnSceneUnloaded;
@@ -985,17 +1045,40 @@ namespace Unity.Netcode
}
}
private void DisconnectRemoteClient(ulong clientId)
{
var transportId = ClientIdToTransportId(clientId);
NetworkConfig.NetworkTransport.DisconnectRemoteClient(transportId);
}
/// <summary>
/// Globally shuts down the library.
/// Disconnects clients if connected and stops server if running.
/// </summary>
public void Shutdown()
/// <param name="discardMessageQueue">
/// If false, any messages that are currently in the incoming queue will be handled,
/// and any messages in the outgoing queue will be sent, before the shutdown is processed.
/// If true, NetworkManager will shut down immediately, and any unprocessed or unsent messages
/// will be discarded.
/// </param>
public void Shutdown(bool discardMessageQueue = false)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfo(nameof(Shutdown));
}
m_ShuttingDown = true;
m_StopProcessingMessages = discardMessageQueue;
}
internal void ShutdownInternal()
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
NetworkLog.LogInfo(nameof(ShutdownInternal));
}
if (IsServer)
{
// make sure all messages are flushed before transport disconnect clients
@@ -1019,7 +1102,7 @@ namespace Unity.Netcode
continue;
}
NetworkConfig.NetworkTransport.DisconnectRemoteClient(pair.Key);
DisconnectRemoteClient(pair.Key);
}
}
@@ -1033,7 +1116,7 @@ namespace Unity.Netcode
continue;
}
NetworkConfig.NetworkTransport.DisconnectRemoteClient(pair.Key);
DisconnectRemoteClient(pair.Key);
}
}
}
@@ -1068,11 +1151,15 @@ namespace Unity.Netcode
MessagingSystem = null;
}
NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll;
if (NetworkConfig?.NetworkTransport != null)
{
NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll;
}
if (SpawnManager != null)
{
SpawnManager.DestroyNonSceneObjects();
SpawnManager.CleanupAllTriggers();
SpawnManager.DespawnAndDestroyNetworkObjects();
SpawnManager.ServerResetShudownStateForSceneObjects();
SpawnManager = null;
@@ -1103,7 +1190,12 @@ namespace Unity.Netcode
NetworkConfig?.NetworkTransport?.Shutdown();
}
m_ClientIdToTransportIdMap.Clear();
m_TransportIdToClientIdMap.Clear();
IsListening = false;
m_ShuttingDown = false;
m_StopProcessingMessages = false;
}
// INetworkUpdateSystem
@@ -1157,6 +1249,11 @@ namespace Unity.Netcode
return;
}
if (m_ShuttingDown && m_StopProcessingMessages)
{
return;
}
// Only update RTT here, server time is updated by time sync messages
var reset = NetworkTimeSystem.Advance(Time.deltaTime);
if (reset)
@@ -1173,9 +1270,18 @@ namespace Unity.Netcode
private void OnNetworkPostLateUpdate()
{
MessagingSystem.ProcessSendQueues();
NetworkMetrics.DispatchFrame();
if (!m_ShuttingDown || !m_StopProcessingMessages)
{
MessagingSystem.ProcessSendQueues();
NetworkMetrics.DispatchFrame();
}
SpawnManager.CleanupStaleTriggers();
if (m_ShuttingDown)
{
ShutdownInternal();
}
}
/// <summary>
@@ -1204,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)
@@ -1229,14 +1335,43 @@ namespace Unity.Netcode
}
}
internal ulong TransportIdToClientId(ulong transportId)
{
return transportId == m_ServerTransportId ? ServerClientId : m_TransportIdToClientIdMap[transportId];
}
internal ulong ClientIdToTransportId(ulong clientId)
{
return clientId == ServerClientId ? m_ServerTransportId : m_ClientIdToTransportIdMap[clientId];
}
private void HandleRawTransportPoll(NetworkEvent networkEvent, ulong clientId, ArraySegment<byte> payload, float receiveTime)
{
var transportId = clientId;
switch (networkEvent)
{
case NetworkEvent.Connect:
#if DEVELOPMENT_BUILD || UNITY_EDITOR
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;
MessagingSystem.ClientConnected(clientId);
if (IsServer)
{
@@ -1275,6 +1410,8 @@ namespace Unity.Netcode
NetworkLog.LogInfo($"Incoming Data From {clientId}: {payload.Count} bytes");
}
clientId = TransportIdToClientId(clientId);
HandleIncomingData(clientId, payload, receiveTime);
break;
}
@@ -1282,6 +1419,12 @@ namespace Unity.Netcode
#if DEVELOPMENT_BUILD || UNITY_EDITOR
s_TransportDisconnect.Begin();
#endif
clientId = TransportIdToClientId(clientId);
OnClientDisconnectCallback?.Invoke(clientId);
m_TransportIdToClientIdMap.Remove(transportId);
m_ClientIdToTransportIdMap.Remove(clientId);
if (NetworkLog.CurrentLogLevel <= LogLevel.Developer)
{
@@ -1296,9 +1439,6 @@ namespace Unity.Netcode
{
Shutdown();
}
OnClientDisconnectCallback?.Invoke(clientId);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
s_TransportDisconnect.End();
#endif
@@ -1306,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>
{
@@ -1329,12 +1469,18 @@ namespace Unity.Netcode
{
return 0;
}
return MessagingSystem.SendMessage(message, delivery, nonServerIds, newIdx);
return MessagingSystem.SendMessage(ref message, delivery, nonServerIds, newIdx);
}
return MessagingSystem.SendMessage(message, delivery, clientIds);
// else
if (clientIds.Count != 1 || clientIds[0] != ServerClientId)
{
throw new ArgumentException($"Clients may only send messages to {nameof(ServerClientId)}");
}
return MessagingSystem.SendMessage(ref message, delivery, clientIds);
}
internal unsafe int SendMessage<T>(in T message, NetworkDelivery delivery,
internal unsafe int SendMessage<T>(ref T message, NetworkDelivery delivery,
ulong* clientIds, int numClientIds)
where T : INetworkMessage
{
@@ -1357,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
@@ -1377,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)
@@ -1401,12 +1563,11 @@ 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);
NetworkConfig.NetworkTransport.DisconnectRemoteClient(clientId);
DisconnectRemoteClient(clientId);
}
private void OnClientDisconnectFromServer(ulong clientId)
@@ -1499,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
@@ -1546,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)
@@ -1565,6 +1725,7 @@ namespace Unity.Netcode
}
else // Server just adds itself as an observer to all spawned NetworkObjects
{
LocalClient = client;
SpawnManager.UpdateObservedNetworkObjects(ownerClientId);
InvokeOnClientConnectedCallback(ownerClientId);
}
@@ -1580,7 +1741,7 @@ namespace Unity.Netcode
else
{
PendingClients.Remove(ownerClientId);
NetworkConfig.NetworkTransport.DisconnectRemoteClient(ownerClientId);
DisconnectRemoteClient(ownerClientId);
}
}
@@ -1610,7 +1771,7 @@ namespace Unity.Netcode
message.ObjectInfo.Header.HasParent = false;
message.ObjectInfo.Header.IsPlayerObject = true;
message.ObjectInfo.Header.OwnerClientId = clientId;
var size = SendMessage(message, NetworkDelivery.ReliableFragmentedSequenced, clientPair.Key);
var size = SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientPair.Key);
NetworkMetrics.TrackObjectSpawnSent(clientPair.Key, ConnectedClients[clientId].PlayerObject, size);
}
}

View File

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

View File

@@ -62,20 +62,63 @@ namespace Unity.Netcode
internal int TimesWritten;
}
internal class ClientData
{
internal struct SentSpawn // this struct also stores Despawns, not just Spawns
{
internal ulong SequenceNumber;
internal ulong ObjectId;
internal int Tick;
}
internal ushort SequenceNumber = 0; // the next sequence number to use for this client
internal ushort LastReceivedSequence = 0; // the last sequence number received by this client
internal ushort ReceivedSequenceMask = 0; // bitmask of the messages before the last one that we received.
internal int NextSpawnIndex = 0; // index of the last spawn sent. Used to cycle through spawns (LRU scheme)
internal int NextDespawnIndex = 0; // same as above, but for despawns.
// by objectId
// which spawns and despawns did this connection ack'ed ?
internal Dictionary<ulong, int> SpawnAck = new Dictionary<ulong, int>();
// list of spawn and despawns commands we sent, with sequence number
// need to manage acknowledgements
internal List<SentSpawn> SentSpawns = new List<SentSpawn>();
}
internal delegate int MockSendMessage(ref SnapshotDataMessage message, NetworkDelivery delivery, ulong clientId);
internal delegate int MockSpawnObject(SnapshotSpawnCommand spawnCommand);
internal delegate int MockDespawnObject(SnapshotDespawnCommand despawnCommand);
// A table of NetworkVariables that constitutes a Snapshot.
// Stores serialized NetworkVariables
// todo --M1--
// The Snapshot will change for M1b with memory management, instead of just FreeMemoryPosition, there will be data structure
// around available buffer, etc.
internal class Snapshot
internal class SnapshotSystem : INetworkUpdateSystem, IDisposable
{
// todo --M1-- functionality to grow these will be needed in a later milestone
private const int k_MaxVariables = 2000;
private int m_MaxSpawns = 100;
private int m_MaxDespawns = 100;
internal int SpawnsBufferCount { get; private set; } = 100;
internal int DespawnsBufferCount { get; private set; } = 100;
private const int k_BufferSize = 30000;
private NetworkManager m_NetworkManager = default;
// by clientId
private Dictionary<ulong, ClientData> m_ClientData = new Dictionary<ulong, ClientData>();
private Dictionary<ulong, ConnectionRtt> m_ConnectionRtts = new Dictionary<ulong, ConnectionRtt>();
private bool m_UseSnapshotDelta;
private bool m_UseSnapshotSpawn;
private int m_SnapshotMaxSpawnUsage;
private NetworkTickSystem m_NetworkTickSystem;
private int m_CurrentTick = NetworkTickSystem.NoTick;
internal byte[] MainBuffer = new byte[k_BufferSize]; // buffer holding a snapshot in memory
internal byte[] RecvBuffer = new byte[k_BufferSize]; // buffer holding the received snapshot message
@@ -90,23 +133,17 @@ namespace Unity.Netcode
internal SnapshotDespawnCommand[] Despawns;
internal int NumDespawns = 0;
internal NetworkManager NetworkManager;
// indexed by ObjectId
internal Dictionary<ulong, int> TickAppliedSpawn = new Dictionary<ulong, int>();
internal Dictionary<ulong, int> TickAppliedDespawn = new Dictionary<ulong, int>();
/// <summary>
/// Constructor
/// Allocated a MemoryStream to be reused for this Snapshot
/// </summary>
internal Snapshot()
{
// we ask for twice as many slots because there could end up being one free spot between each pair of slot used
Allocator = new IndexAllocator(k_BufferSize, k_MaxVariables * 2);
Spawns = new SnapshotSpawnCommand[m_MaxSpawns];
Despawns = new SnapshotDespawnCommand[m_MaxDespawns];
}
internal bool IsServer { get; set; }
internal bool IsConnectedClient { get; set; }
internal ulong ServerClientId { get; set; }
internal List<ulong> ConnectedClientsId { get; } = new List<ulong>();
internal MockSendMessage MockSendMessage { get; set; }
internal MockSpawnObject MockSpawnObject { get; set; }
internal MockDespawnObject MockDespawnObject { get; set; }
internal void Clear()
{
@@ -156,15 +193,15 @@ namespace Unity.Netcode
List<ulong> clientList;
clientList = new List<ulong>();
if (!NetworkManager.IsServer)
if (!IsServer)
{
clientList.Add(NetworkManager.ServerClientId);
clientList.Add(m_NetworkManager.ServerClientId);
}
else
{
foreach (var clientId in NetworkManager.ConnectedClientsIds)
foreach (var clientId in ConnectedClientsId)
{
if (clientId != NetworkManager.ServerClientId)
if (clientId != m_NetworkManager.ServerClientId)
{
clientList.Add(clientId);
}
@@ -176,14 +213,14 @@ namespace Unity.Netcode
internal void AddSpawn(SnapshotSpawnCommand command)
{
if (NumSpawns >= m_MaxSpawns)
if (NumSpawns >= SpawnsBufferCount)
{
Array.Resize(ref Spawns, 2 * m_MaxSpawns);
m_MaxSpawns = m_MaxSpawns * 2;
Array.Resize(ref Spawns, 2 * SpawnsBufferCount);
SpawnsBufferCount = SpawnsBufferCount * 2;
// Debug.Log($"[JEFF] spawn size is now {m_MaxSpawns}");
}
if (NumSpawns < m_MaxSpawns)
if (NumSpawns < SpawnsBufferCount)
{
if (command.TargetClientIds == default)
{
@@ -208,14 +245,14 @@ namespace Unity.Netcode
internal void AddDespawn(SnapshotDespawnCommand command)
{
if (NumDespawns >= m_MaxDespawns)
if (NumDespawns >= DespawnsBufferCount)
{
Array.Resize(ref Despawns, 2 * m_MaxDespawns);
m_MaxDespawns = m_MaxDespawns * 2;
Array.Resize(ref Despawns, 2 * DespawnsBufferCount);
DespawnsBufferCount = DespawnsBufferCount * 2;
// Debug.Log($"[JEFF] despawn size is now {m_MaxDespawns}");
}
if (NumDespawns < m_MaxDespawns)
if (NumDespawns < DespawnsBufferCount)
{
if (command.TargetClientIds == default)
{
@@ -229,6 +266,17 @@ namespace Unity.Netcode
}
}
internal void ReduceBufferUsage()
{
var count = Math.Max(1, NumDespawns);
Array.Resize(ref Despawns, count);
DespawnsBufferCount = count;
count = Math.Max(1, NumSpawns);
Array.Resize(ref Spawns, count);
SpawnsBufferCount = count;
}
internal ClientData.SentSpawn GetSpawnData(in ClientData clientData, in SnapshotSpawnCommand spawn, out SnapshotDataMessage.SpawnData data)
{
// remember which spawn we sent this connection with which sequence number
@@ -417,7 +465,54 @@ namespace Unity.Netcode
}
}
internal void ReadSpawns(in SnapshotDataMessage message)
internal void SpawnObject(SnapshotSpawnCommand spawnCommand, ulong srcClientId)
{
if (m_NetworkManager)
{
NetworkObject networkObject;
if (spawnCommand.ParentNetworkId == spawnCommand.NetworkObjectId)
{
networkObject = m_NetworkManager.SpawnManager.CreateLocalNetworkObject(false,
spawnCommand.GlobalObjectIdHash, spawnCommand.OwnerClientId, null, spawnCommand.ObjectPosition,
spawnCommand.ObjectRotation);
}
else
{
networkObject = m_NetworkManager.SpawnManager.CreateLocalNetworkObject(false,
spawnCommand.GlobalObjectIdHash, spawnCommand.OwnerClientId, spawnCommand.ParentNetworkId, spawnCommand.ObjectPosition,
spawnCommand.ObjectRotation);
}
m_NetworkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, spawnCommand.NetworkObjectId,
true, spawnCommand.IsPlayerObject, spawnCommand.OwnerClientId, false);
//todo: discuss with tools how to report shared bytes
m_NetworkManager.NetworkMetrics.TrackObjectSpawnReceived(srcClientId, networkObject, 8);
}
else
{
MockSpawnObject(spawnCommand);
}
}
internal void DespawnObject(SnapshotDespawnCommand despawnCommand, ulong srcClientId)
{
if (m_NetworkManager)
{
m_NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(despawnCommand.NetworkObjectId,
out NetworkObject networkObject);
m_NetworkManager.SpawnManager.OnDespawnObject(networkObject, true);
//todo: discuss with tools how to report shared bytes
m_NetworkManager.NetworkMetrics.TrackObjectDestroyReceived(srcClientId, networkObject, 8);
}
else
{
MockDespawnObject(despawnCommand);
}
}
internal void ReadSpawns(in SnapshotDataMessage message, ulong srcClientId)
{
SnapshotSpawnCommand spawnCommand;
SnapshotDespawnCommand despawnCommand;
@@ -436,16 +531,7 @@ namespace Unity.Netcode
// Debug.Log($"[Spawn] {spawnCommand.NetworkObjectId} {spawnCommand.TickWritten}");
if (spawnCommand.ParentNetworkId == spawnCommand.NetworkObjectId)
{
var networkObject = NetworkManager.SpawnManager.CreateLocalNetworkObject(false, spawnCommand.GlobalObjectIdHash, spawnCommand.OwnerClientId, null, spawnCommand.ObjectPosition, spawnCommand.ObjectRotation);
NetworkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, spawnCommand.NetworkObjectId, true, spawnCommand.IsPlayerObject, spawnCommand.OwnerClientId, false);
}
else
{
var networkObject = NetworkManager.SpawnManager.CreateLocalNetworkObject(false, spawnCommand.GlobalObjectIdHash, spawnCommand.OwnerClientId, spawnCommand.ParentNetworkId, spawnCommand.ObjectPosition, spawnCommand.ObjectRotation);
NetworkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, spawnCommand.NetworkObjectId, true, spawnCommand.IsPlayerObject, spawnCommand.OwnerClientId, false);
}
SpawnObject(spawnCommand, srcClientId);
}
for (var i = 0; i < message.Despawns.Length; i++)
{
@@ -461,10 +547,7 @@ namespace Unity.Netcode
// Debug.Log($"[DeSpawn] {despawnCommand.NetworkObjectId} {despawnCommand.TickWritten}");
NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(despawnCommand.NetworkObjectId,
out NetworkObject networkObject);
NetworkManager.SpawnManager.OnDespawnObject(networkObject, true);
DespawnObject(despawnCommand, srcClientId);
}
}
@@ -569,7 +652,7 @@ namespace Unity.Netcode
/// <param name="key">The key to search for</param>
private NetworkVariableBase FindNetworkVar(VariableKey key)
{
var spawnedObjects = NetworkManager.SpawnManager.SpawnedObjects;
var spawnedObjects = m_NetworkManager.SpawnManager.SpawnedObjects;
if (spawnedObjects.ContainsKey(key.NetworkObjectId))
{
@@ -580,61 +663,49 @@ namespace Unity.Netcode
return null;
}
}
internal class ClientData
{
internal struct SentSpawn // this struct also stores Despawns, not just Spawns
{
internal ulong SequenceNumber;
internal ulong ObjectId;
internal int Tick;
}
internal ushort SequenceNumber = 0; // the next sequence number to use for this client
internal ushort LastReceivedSequence = 0; // the last sequence number received by this client
internal ushort ReceivedSequenceMask = 0; // bitmask of the messages before the last one that we received.
internal int NextSpawnIndex = 0; // index of the last spawn sent. Used to cycle through spawns (LRU scheme)
internal int NextDespawnIndex = 0; // same as above, but for despawns.
// by objectId
// which spawns and despawns did this connection ack'ed ?
internal Dictionary<ulong, int> SpawnAck = new Dictionary<ulong, int>();
// list of spawn and despawns commands we sent, with sequence number
// need to manage acknowledgements
internal List<SentSpawn> SentSpawns = new List<SentSpawn>();
}
internal class SnapshotSystem : INetworkUpdateSystem, IDisposable
{
// temporary, debugging sentinels
internal const ushort SentinelBefore = 0x4246;
internal const ushort SentinelAfter = 0x89CE;
private NetworkManager m_NetworkManager = default;
private Snapshot m_Snapshot = default;
// by clientId
private Dictionary<ulong, ClientData> m_ClientData = new Dictionary<ulong, ClientData>();
private Dictionary<ulong, ConnectionRtt> m_ConnectionRtts = new Dictionary<ulong, ConnectionRtt>();
private int m_CurrentTick = NetworkTickSystem.NoTick;
/// <summary>
/// Constructor
/// </summary>
/// Registers the snapshot system for early updates, keeps reference to the NetworkManager
internal SnapshotSystem(NetworkManager networkManager)
internal SnapshotSystem(NetworkManager networkManager, NetworkConfig config, NetworkTickSystem networkTickSystem)
{
m_Snapshot = new Snapshot();
m_NetworkManager = networkManager;
m_Snapshot.NetworkManager = networkManager;
m_NetworkTickSystem = networkTickSystem;
m_UseSnapshotDelta = config.UseSnapshotDelta;
m_UseSnapshotSpawn = config.UseSnapshotSpawn;
m_SnapshotMaxSpawnUsage = config.SnapshotMaxSpawnUsage;
UpdateClientServerData();
this.RegisterNetworkUpdate(NetworkUpdateStage.EarlyUpdate);
// we ask for twice as many slots because there could end up being one free spot between each pair of slot used
Allocator = new IndexAllocator(k_BufferSize, k_MaxVariables * 2);
Spawns = new SnapshotSpawnCommand[SpawnsBufferCount];
Despawns = new SnapshotDespawnCommand[DespawnsBufferCount];
}
// since we don't want to access the NetworkManager directly, we refresh those values on Update
internal void UpdateClientServerData()
{
if (m_NetworkManager)
{
IsServer = m_NetworkManager.IsServer;
IsConnectedClient = m_NetworkManager.IsConnectedClient;
ServerClientId = m_NetworkManager.ServerClientId;
// todo: This is extremely inefficient. What is the efficient and idiomatic way ?
ConnectedClientsId.Clear();
if (IsServer)
{
foreach (var id in m_NetworkManager.ConnectedClientsIds)
{
ConnectedClientsId.Add(id);
}
}
}
}
internal ConnectionRtt GetConnectionRtt(ulong clientId)
@@ -658,34 +729,36 @@ namespace Unity.Netcode
public void NetworkUpdate(NetworkUpdateStage updateStage)
{
if (!m_NetworkManager.NetworkConfig.UseSnapshotDelta && !m_NetworkManager.NetworkConfig.UseSnapshotSpawn)
if (!m_UseSnapshotDelta && !m_UseSnapshotSpawn)
{
return;
}
if (updateStage == NetworkUpdateStage.EarlyUpdate)
{
var tick = m_NetworkManager.NetworkTickSystem.LocalTime.Tick;
UpdateClientServerData();
var tick = m_NetworkTickSystem.LocalTime.Tick;
if (tick != m_CurrentTick)
{
m_CurrentTick = tick;
if (m_NetworkManager.IsServer)
if (IsServer)
{
for (int i = 0; i < m_NetworkManager.ConnectedClientsList.Count; i++)
for (int i = 0; i < ConnectedClientsId.Count; i++)
{
var clientId = m_NetworkManager.ConnectedClientsList[i].ClientId;
var clientId = ConnectedClientsId[i];
// don't send to ourselves
if (clientId != m_NetworkManager.ServerClientId)
if (clientId != ServerClientId)
{
SendSnapshot(clientId);
}
}
}
else if (m_NetworkManager.IsConnectedClient)
else if (IsConnectedClient)
{
SendSnapshot(m_NetworkManager.ServerClientId);
SendSnapshot(ServerClientId);
}
}
@@ -721,12 +794,12 @@ namespace Unity.Netcode
{
CurrentTick = m_CurrentTick,
Sequence = sequence,
Range = (ushort)m_Snapshot.Allocator.Range,
Range = (ushort)Allocator.Range,
// todo --M1--
// this sends the whole buffer
// we'll need to build a per-client list
SendMainBuffer = m_Snapshot.MainBuffer,
SendMainBuffer = MainBuffer,
Ack = new SnapshotDataMessage.AckData
{
@@ -740,7 +813,23 @@ namespace Unity.Netcode
WriteIndex(ref message);
WriteSpawns(ref message, clientId);
m_NetworkManager.SendMessage(message, NetworkDelivery.Unreliable, clientId);
try
{
if (m_NetworkManager)
{
m_NetworkManager.SendMessage(ref message, NetworkDelivery.Unreliable, clientId);
}
else
{
MockSendMessage(ref message, NetworkDelivery.Unreliable, clientId);
}
}
finally
{
message.Entries.Dispose();
message.Spawns.Dispose();
message.Despawns.Dispose();
}
m_ClientData[clientId].LastReceivedSequence = 0;
@@ -791,51 +880,51 @@ namespace Unity.Netcode
ClientData clientData = m_ClientData[clientId];
// this is needed because spawns being removed may have reduce the size below LRU position
if (m_Snapshot.NumSpawns > 0)
if (NumSpawns > 0)
{
clientData.NextSpawnIndex %= m_Snapshot.NumSpawns;
clientData.NextSpawnIndex %= NumSpawns;
}
else
{
clientData.NextSpawnIndex = 0;
}
if (m_Snapshot.NumDespawns > 0)
if (NumDespawns > 0)
{
clientData.NextDespawnIndex %= m_Snapshot.NumDespawns;
clientData.NextDespawnIndex %= NumDespawns;
}
else
{
clientData.NextDespawnIndex = 0;
}
message.Spawns = new NativeList<SnapshotDataMessage.SpawnData>(m_Snapshot.NumSpawns, Allocator.TempJob);
message.Despawns = new NativeList<SnapshotDataMessage.DespawnData>(m_Snapshot.NumDespawns, Allocator.TempJob);
message.Spawns = new NativeList<SnapshotDataMessage.SpawnData>(NumSpawns, Collections.Allocator.TempJob);
message.Despawns = new NativeList<SnapshotDataMessage.DespawnData>(NumDespawns, Collections.Allocator.TempJob);
var spawnUsage = 0;
for (var j = 0; j < m_Snapshot.NumSpawns && !overSize; j++)
for (var j = 0; j < NumSpawns && !overSize; j++)
{
var index = clientData.NextSpawnIndex;
// todo: re-enable ShouldWriteSpawn, once we have a mechanism to not let despawn pass in front of spawns
if (m_Snapshot.Spawns[index].TargetClientIds.Contains(clientId) /*&& ShouldWriteSpawn(m_Snapshot.Spawns[index])*/)
if (Spawns[index].TargetClientIds.Contains(clientId) /*&& ShouldWriteSpawn(Spawns[index])*/)
{
spawnUsage += FastBufferWriter.GetWriteSize<SnapshotDataMessage.SpawnData>();
// limit spawn sizes, compare current pos to very first position we wrote to
if (spawnUsage > m_NetworkManager.NetworkConfig.SnapshotMaxSpawnUsage)
if (spawnUsage > m_SnapshotMaxSpawnUsage)
{
overSize = true;
break;
}
var sentSpawn = m_Snapshot.GetSpawnData(clientData, in m_Snapshot.Spawns[index], out var spawn);
var sentSpawn = GetSpawnData(clientData, in Spawns[index], out var spawn);
message.Spawns.Add(spawn);
m_Snapshot.Spawns[index].TimesWritten++;
Spawns[index].TimesWritten++;
clientData.SentSpawns.Add(sentSpawn);
spawnWritten++;
}
clientData.NextSpawnIndex = (clientData.NextSpawnIndex + 1) % m_Snapshot.NumSpawns;
clientData.NextSpawnIndex = (clientData.NextSpawnIndex + 1) % NumSpawns;
}
// even though we might have a spawn we could not fit, it's possible despawns will fit (they're smaller)
@@ -846,28 +935,28 @@ namespace Unity.Netcode
// As-is it is overly restrictive but allows us to go forward without the spawn/despawn dependency check
// overSize = false;
for (var j = 0; j < m_Snapshot.NumDespawns && !overSize; j++)
for (var j = 0; j < NumDespawns && !overSize; j++)
{
var index = clientData.NextDespawnIndex;
// todo: re-enable ShouldWriteSpawn, once we have a mechanism to not let despawn pass in front of spawns
if (m_Snapshot.Despawns[index].TargetClientIds.Contains(clientId) /*&& ShouldWriteDespawn(m_Snapshot.Despawns[index])*/)
if (Despawns[index].TargetClientIds.Contains(clientId) /*&& ShouldWriteDespawn(Despawns[index])*/)
{
spawnUsage += FastBufferWriter.GetWriteSize<SnapshotDataMessage.DespawnData>();
// limit spawn sizes, compare current pos to very first position we wrote to
if (spawnUsage > m_NetworkManager.NetworkConfig.SnapshotMaxSpawnUsage)
if (spawnUsage > m_SnapshotMaxSpawnUsage)
{
overSize = true;
break;
}
var sentDespawn = m_Snapshot.GetDespawnData(clientData, in m_Snapshot.Despawns[index], out var despawn);
var sentDespawn = GetDespawnData(clientData, in Despawns[index], out var despawn);
message.Despawns.Add(despawn);
m_Snapshot.Despawns[index].TimesWritten++;
Despawns[index].TimesWritten++;
clientData.SentSpawns.Add(sentDespawn);
despawnWritten++;
}
clientData.NextDespawnIndex = (clientData.NextDespawnIndex + 1) % m_Snapshot.NumDespawns;
clientData.NextDespawnIndex = (clientData.NextDespawnIndex + 1) % NumDespawns;
}
}
@@ -877,10 +966,10 @@ namespace Unity.Netcode
/// <param name="message">The message to write the index to</param>
private void WriteIndex(ref SnapshotDataMessage message)
{
message.Entries = new NativeList<SnapshotDataMessage.EntryData>(m_Snapshot.LastEntry, Allocator.TempJob);
for (var i = 0; i < m_Snapshot.LastEntry; i++)
message.Entries = new NativeList<SnapshotDataMessage.EntryData>(LastEntry, Collections.Allocator.TempJob);
for (var i = 0; i < LastEntry; i++)
{
var entryMeta = m_Snapshot.Entries[i];
var entryMeta = Entries[i];
var entry = entryMeta.Key;
message.Entries.Add(new SnapshotDataMessage.EntryData
{
@@ -897,7 +986,7 @@ namespace Unity.Netcode
internal void Spawn(SnapshotSpawnCommand command)
{
command.TickWritten = m_CurrentTick;
m_Snapshot.AddSpawn(command);
AddSpawn(command);
// Debug.Log($"[Spawn] {command.NetworkObjectId} {command.TickWritten}");
}
@@ -905,7 +994,7 @@ namespace Unity.Netcode
internal void Despawn(SnapshotDespawnCommand command)
{
command.TickWritten = m_CurrentTick;
m_Snapshot.AddDespawn(command);
AddDespawn(command);
// Debug.Log($"[DeSpawn] {command.NetworkObjectId} {command.TickWritten}");
}
@@ -922,35 +1011,35 @@ namespace Unity.Netcode
k.NetworkObjectId = networkObjectId;
k.BehaviourIndex = (ushort)behaviourIndex;
k.VariableIndex = (ushort)variableIndex;
k.TickWritten = m_NetworkManager.NetworkTickSystem.LocalTime.Tick;
k.TickWritten = m_NetworkTickSystem.LocalTime.Tick;
int pos = m_Snapshot.Find(k);
int pos = Find(k);
if (pos == Entry.NotFound)
{
pos = m_Snapshot.AddEntry(k);
pos = AddEntry(k);
}
m_Snapshot.Entries[pos].Key.TickWritten = k.TickWritten;
Entries[pos].Key.TickWritten = k.TickWritten;
WriteVariableToSnapshot(m_Snapshot, networkVariable, pos);
WriteVariable(networkVariable, pos);
}
private unsafe void WriteVariableToSnapshot(Snapshot snapshot, NetworkVariableBase networkVariable, int index)
private unsafe void WriteVariable(NetworkVariableBase networkVariable, int index)
{
// write var into buffer, possibly adjusting entry's position and Length
var varBuffer = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp);
var varBuffer = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Collections.Allocator.Temp);
using (varBuffer)
{
networkVariable.WriteDelta(varBuffer);
if (varBuffer.Length > snapshot.Entries[index].Length)
if (varBuffer.Length > Entries[index].Length)
{
// allocate this Entry's buffer
snapshot.AllocateEntry(ref snapshot.Entries[index], index, (int)varBuffer.Length);
AllocateEntry(ref Entries[index], index, (int)varBuffer.Length);
}
fixed (byte* buffer = snapshot.MainBuffer)
fixed (byte* buffer = MainBuffer)
{
UnsafeUtility.MemCpy(buffer + snapshot.Entries[index].Position, varBuffer.GetUnsafePtr(), varBuffer.Length);
UnsafeUtility.MemCpy(buffer + Entries[index].Position, varBuffer.GetUnsafePtr(), varBuffer.Length);
}
}
}
@@ -1009,10 +1098,10 @@ namespace Unity.Netcode
// without this, we incur extra retransmit, not a catastrophic failure
}
m_Snapshot.ReadBuffer(message);
m_Snapshot.ReadIndex(message);
m_Snapshot.ReadAcks(clientId, m_ClientData[clientId], message, GetConnectionRtt(clientId));
m_Snapshot.ReadSpawns(message);
ReadBuffer(message);
ReadIndex(message);
ReadAcks(clientId, m_ClientData[clientId], message, GetConnectionRtt(clientId));
ReadSpawns(message, clientId);
}
// todo --M1--
@@ -1024,14 +1113,14 @@ namespace Unity.Netcode
table += $"We're clientId {m_NetworkManager.LocalClientId}\n";
table += "=== Variables ===\n";
for (int i = 0; i < m_Snapshot.LastEntry; i++)
for (int i = 0; i < LastEntry; i++)
{
table += string.Format("NetworkVariable {0}:{1}:{2} written {5}, range [{3}, {4}] ", m_Snapshot.Entries[i].Key.NetworkObjectId, m_Snapshot.Entries[i].Key.BehaviourIndex,
m_Snapshot.Entries[i].Key.VariableIndex, m_Snapshot.Entries[i].Position, m_Snapshot.Entries[i].Position + m_Snapshot.Entries[i].Length, m_Snapshot.Entries[i].Key.TickWritten);
table += string.Format("NetworkVariable {0}:{1}:{2} written {5}, range [{3}, {4}] ", Entries[i].Key.NetworkObjectId, Entries[i].Key.BehaviourIndex,
Entries[i].Key.VariableIndex, Entries[i].Position, Entries[i].Position + Entries[i].Length, Entries[i].Key.TickWritten);
for (int j = 0; j < m_Snapshot.Entries[i].Length && j < 4; j++)
for (int j = 0; j < Entries[i].Length && j < 4; j++)
{
table += m_Snapshot.MainBuffer[m_Snapshot.Entries[i].Position + j].ToString("X2") + " ";
table += MainBuffer[Entries[i].Position + j].ToString("X2") + " ";
}
table += "\n";
@@ -1039,14 +1128,14 @@ namespace Unity.Netcode
table += "=== Spawns ===\n";
for (int i = 0; i < m_Snapshot.NumSpawns; i++)
for (int i = 0; i < NumSpawns; i++)
{
string targets = "";
foreach (var target in m_Snapshot.Spawns[i].TargetClientIds)
foreach (var target in Spawns[i].TargetClientIds)
{
targets += target.ToString() + ", ";
}
table += $"Spawn Object Id {m_Snapshot.Spawns[i].NetworkObjectId}, Tick {m_Snapshot.Spawns[i].TickWritten}, Target {targets}\n";
table += $"Spawn Object Id {Spawns[i].NetworkObjectId}, Tick {Spawns[i].TickWritten}, Target {targets}\n";
}
table += $"=== RTTs ===\n";

View File

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

View File

@@ -28,7 +28,7 @@ namespace Unity.Netcode
/// </summary>
public event UnnamedMessageDelegate OnUnnamedMessage;
internal void InvokeUnnamedMessage(ulong clientId, FastBufferReader reader)
internal void InvokeUnnamedMessage(ulong clientId, FastBufferReader reader, int serializedHeaderSize)
{
if (OnUnnamedMessage != null)
{
@@ -40,7 +40,7 @@ namespace Unity.Netcode
((UnnamedMessageDelegate)handler).Invoke(clientId, reader);
}
}
m_NetworkManager.NetworkMetrics.TrackUnnamedMessageReceived(clientId, reader.Length);
m_NetworkManager.NetworkMetrics.TrackUnnamedMessageReceived(clientId, reader.Length + serializedHeaderSize);
}
/// <summary>
@@ -53,7 +53,6 @@ namespace Unity.Netcode
SendUnnamedMessage(m_NetworkManager.ConnectedClientsIds, messageBuffer, networkDelivery);
}
/// <summary>
/// Sends unnamed message to a list of clients
/// </summary>
@@ -74,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)
@@ -95,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)
{
@@ -116,9 +115,9 @@ namespace Unity.Netcode
private Dictionary<ulong, string> m_MessageHandlerNameLookup32 = new Dictionary<ulong, string>();
private Dictionary<ulong, string> m_MessageHandlerNameLookup64 = new Dictionary<ulong, string>();
internal void InvokeNamedMessage(ulong hash, ulong sender, FastBufferReader reader)
internal void InvokeNamedMessage(ulong hash, ulong sender, FastBufferReader reader, int serializedHeaderSize)
{
var bytesCount = reader.Length;
var bytesCount = reader.Length + serializedHeaderSize;
if (m_NetworkManager == null)
{
@@ -224,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)
@@ -267,9 +266,9 @@ namespace Unity.Netcode
var message = new NamedMessage
{
Hash = hash,
Data = messageStream
SendData = messageStream
};
var size = m_NetworkManager.SendMessage(message, networkDelivery, clientIds);
var size = m_NetworkManager.SendMessage(ref message, networkDelivery, clientIds);
// Size is zero if we were only sending the message to ourself in which case it isn't sent.
if (size != 0)

View File

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

View File

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

View File

@@ -11,11 +11,12 @@ namespace Unity.Netcode
/// unchanged - if new messages are added or messages are removed, MessageType assignments may be
/// calculated differently.
/// </summary>
public byte MessageType;
public uint MessageType;
/// <summary>
/// The total size of the message, NOT including the header.
/// Stored as a uint to avoid zig-zag encoding, but capped at int.MaxValue.
/// </summary>
public ushort MessageSize;
public uint MessageSize;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -76,9 +76,6 @@ namespace Unity.Netcode
Despawns.Length * sizeof(DespawnData)
))
{
Entries.Dispose();
Spawns.Dispose();
Despawns.Dispose();
throw new OverflowException($"Not enough space to serialize {nameof(SnapshotDataMessage)}");
}
writer.WriteValue(CurrentTick);
@@ -96,66 +93,68 @@ 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 networkManager = (NetworkManager)context.SystemOwner;
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, networkManager);
}
return true;
}
public void Handle(ulong senderId, NetworkManager networkManager)
public void Handle(ref NetworkContext context)
{
// todo: temporary hack around bug
if (!networkManager.IsServer)
using (ReceiveMainBuffer)
using (Entries)
using (Spawns)
using (Despawns)
{
senderId = networkManager.ServerClientId;
}
var systemOwner = context.SystemOwner;
var senderId = context.SenderId;
if (systemOwner is NetworkManager networkManager)
{
// todo: temporary hack around bug
if (!networkManager.IsServer)
{
senderId = networkManager.ServerClientId;
}
var snapshotSystem = networkManager.SnapshotSystem;
snapshotSystem.HandleSnapshot(senderId, this);
var snapshotSystem = networkManager.SnapshotSystem;
snapshotSystem.HandleSnapshot(senderId, this);
}
else
{
var ownerData = (Tuple<SnapshotSystem, ulong>)systemOwner;
var snapshotSystem = ownerData.Item1;
snapshotSystem.HandleSnapshot(ownerData.Item2, this);
}
}
}
}
}

View File

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

View File

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

View File

@@ -23,6 +23,7 @@ namespace Unity.Netcode
public MessageHeader Header;
public ulong SenderId;
public float Timestamp;
public int MessageHeaderSerializedSize;
}
private struct SendQueueItem
@@ -39,34 +40,34 @@ 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);
private MessageHandler[] m_MessageHandlers = new MessageHandler[255];
private Type[] m_ReverseTypeMap = new Type[255];
private Dictionary<Type, byte> m_MessageTypes = new Dictionary<Type, byte>();
private Dictionary<Type, uint> m_MessageTypes = new Dictionary<Type, uint>();
private Dictionary<ulong, NativeList<SendQueueItem>> m_SendQueues = new Dictionary<ulong, NativeList<SendQueueItem>>();
private List<INetworkHooks> m_Hooks = new List<INetworkHooks>();
private byte m_HighMessageType;
private uint m_HighMessageType;
private object m_Owner;
private IMessageSender m_MessageSender;
private bool m_Disposed;
internal Type[] MessageTypes => m_ReverseTypeMap;
internal MessageHandler[] MessageHandlers => m_MessageHandlers;
internal int MessageHandlerCount => m_HighMessageType;
internal uint MessageHandlerCount => m_HighMessageType;
internal byte GetMessageType(Type t)
internal uint GetMessageType(Type t)
{
return m_MessageTypes[t];
}
public const int NON_FRAGMENTED_MESSAGE_MAX_SIZE = 1300;
public const int FRAGMENTED_MESSAGE_MAX_SIZE = 64000;
public const int FRAGMENTED_MESSAGE_MAX_SIZE = int.MaxValue;
internal struct MessageWithHandler
{
@@ -100,7 +101,7 @@ namespace Unity.Netcode
}
}
public void Dispose()
public unsafe void Dispose()
{
if (m_Disposed)
{
@@ -113,6 +114,14 @@ namespace Unity.Netcode
{
CleanupDisconnectedClient(kvp.Key);
}
for (var queueIndex = 0; queueIndex < m_IncomingMessageQueue.Length; ++queueIndex)
{
// Avoid copies...
ref var item = ref m_IncomingMessageQueue.ElementAt(queueIndex);
item.Reader.Dispose();
}
m_IncomingMessageQueue.Dispose();
m_Disposed = true;
}
@@ -141,7 +150,7 @@ namespace Unity.Netcode
fixed (byte* nativeData = data.Array)
{
var batchReader =
new FastBufferReader(nativeData, Allocator.None, data.Count, data.Offset);
new FastBufferReader(nativeData + data.Offset, Allocator.None, data.Count);
if (!batchReader.TryBeginRead(sizeof(BatchHeader)))
{
NetworkLog.LogWarning("Received a packet too small to contain a BatchHeader. Ignoring it.");
@@ -157,14 +166,23 @@ namespace Unity.Netcode
for (var messageIdx = 0; messageIdx < batchHeader.BatchSize; ++messageIdx)
{
if (!batchReader.TryBeginRead(sizeof(MessageHeader)))
var messageHeader = new MessageHeader();
var position = batchReader.Position;
try
{
ByteUnpacker.ReadValueBitPacked(batchReader, out messageHeader.MessageType);
ByteUnpacker.ReadValueBitPacked(batchReader, out messageHeader.MessageSize);
}
catch (OverflowException)
{
NetworkLog.LogWarning("Received a batch that didn't have enough data for all of its batches, ending early!");
return;
throw;
}
batchReader.ReadValue(out MessageHeader messageHeader);
if (!batchReader.TryBeginRead(messageHeader.MessageSize))
var receivedHeaderSize = batchReader.Position - position;
if (!batchReader.TryBeginRead((int)messageHeader.MessageSize))
{
NetworkLog.LogWarning("Received a message that claimed a size larger than the packet, ending early!");
return;
@@ -177,9 +195,10 @@ namespace Unity.Netcode
// Copy the data for this message into a new FastBufferReader that owns that memory.
// We can't guarantee the memory in the ArraySegment stays valid because we don't own it,
// so we must move it to memory we do own.
Reader = new FastBufferReader(batchReader.GetUnsafePtrAtCurrentPosition(), Allocator.TempJob, messageHeader.MessageSize)
Reader = new FastBufferReader(batchReader.GetUnsafePtrAtCurrentPosition(), Allocator.TempJob, (int)messageHeader.MessageSize),
MessageHeaderSerializedSize = receivedHeaderSize,
});
batchReader.Seek(batchReader.Position + messageHeader.MessageSize);
batchReader.Seek(batchReader.Position + (int)messageHeader.MessageSize);
}
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
@@ -202,7 +221,7 @@ namespace Unity.Netcode
return true;
}
public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp)
public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp, int serializedHeaderSize)
{
if (header.MessageType >= m_HighMessageType)
{
@@ -215,8 +234,11 @@ namespace Unity.Netcode
SystemOwner = m_Owner,
SenderId = senderId,
Timestamp = timestamp,
Header = header
Header = header,
SerializedHeaderSize = serializedHeaderSize,
MessageSize = header.MessageSize,
};
var type = m_ReverseTypeMap[header.MessageType];
if (!CanReceive(senderId, type))
{
@@ -226,8 +248,9 @@ namespace Unity.Netcode
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnBeforeReceiveMessage(senderId, type, reader.Length);
m_Hooks[hookIdx].OnBeforeReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
}
var handler = m_MessageHandlers[header.MessageType];
using (reader)
{
@@ -238,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)
{
@@ -247,17 +270,21 @@ namespace Unity.Netcode
}
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnAfterReceiveMessage(senderId, type, reader.Length);
m_Hooks[hookIdx].OnAfterReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
}
}
internal unsafe void ProcessIncomingMessageQueue()
{
for (var i = 0; i < m_IncomingMessageQueue.Length; ++i)
for (var index = 0; index < m_IncomingMessageQueue.Length; ++index)
{
// Avoid copies...
ref var item = ref m_IncomingMessageQueue.GetUnsafeList()->ElementAt(i);
HandleMessage(item.Header, item.Reader, item.SenderId, item.Timestamp);
ref var item = ref m_IncomingMessageQueue.ElementAt(index);
HandleMessage(item.Header, item.Reader, item.SenderId, item.Timestamp, item.MessageHeaderSerializedSize);
if (m_Disposed)
{
return;
}
}
m_IncomingMessageQueue.Clear();
@@ -287,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)
@@ -306,69 +352,91 @@ 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>
{
var maxSize = delivery == NetworkDelivery.ReliableFragmentedSequenced ? FRAGMENTED_MESSAGE_MAX_SIZE : NON_FRAGMENTED_MESSAGE_MAX_SIZE;
var tmpSerializer = new FastBufferWriter(NON_FRAGMENTED_MESSAGE_MAX_SIZE - sizeof(MessageHeader), Allocator.Temp, maxSize - sizeof(MessageHeader));
using (tmpSerializer)
if (clientIds.Count == 0)
{
message.Serialize(tmpSerializer);
return 0;
}
for (var i = 0; i < clientIds.Count; ++i)
var maxSize = delivery == NetworkDelivery.ReliableFragmentedSequenced ? FRAGMENTED_MESSAGE_MAX_SIZE : NON_FRAGMENTED_MESSAGE_MAX_SIZE;
using var tmpSerializer = new FastBufferWriter(NON_FRAGMENTED_MESSAGE_MAX_SIZE - FastBufferWriter.GetWriteSize<MessageHeader>(), Allocator.Temp, maxSize - FastBufferWriter.GetWriteSize<MessageHeader>());
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 = (uint)tmpSerializer.Length,
MessageType = m_MessageTypes[typeof(TMessageType)],
};
BytePacker.WriteValueBitPacked(headerSerializer, header.MessageType);
BytePacker.WriteValueBitPacked(headerSerializer, header.MessageSize);
for (var i = 0; i < clientIds.Count; ++i)
{
var clientId = clientIds[i];
if (!CanSend(clientId, typeof(TMessageType), delivery))
{
var clientId = clientIds[i];
continue;
}
if (!CanSend(clientId, typeof(TMessageType), delivery))
{
continue;
}
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnBeforeSendMessage(clientId, ref message, delivery);
}
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnBeforeSendMessage(clientId, typeof(TMessageType), delivery);
}
var sendQueueItem = m_SendQueues[clientId];
if (sendQueueItem.Length == 0)
var sendQueueItem = m_SendQueues[clientId];
if (sendQueueItem.Length == 0)
{
sendQueueItem.Add(new SendQueueItem(delivery, NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.TempJob,
maxSize));
sendQueueItem.ElementAt(0).Writer.Seek(sizeof(BatchHeader));
}
else
{
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(0).Writer.Seek(sizeof(BatchHeader));
}
else
{
ref var lastQueueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1);
if (lastQueueItem.NetworkDelivery != delivery ||
lastQueueItem.Writer.MaxCapacity - lastQueueItem.Writer.Position
< tmpSerializer.Length + sizeof(MessageHeader))
{
sendQueueItem.Add(new SendQueueItem(delivery, NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.TempJob,
maxSize));
sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1).Writer.Seek(sizeof(BatchHeader));
}
}
ref var writeQueueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1);
writeQueueItem.Writer.TryBeginWrite(sizeof(MessageHeader) + tmpSerializer.Length);
var header = new MessageHeader
{
MessageSize = (ushort)tmpSerializer.Length,
MessageType = m_MessageTypes[typeof(TMessageType)],
};
writeQueueItem.Writer.WriteValue(header);
writeQueueItem.Writer.WriteBytes(tmpSerializer.GetUnsafePtr(), tmpSerializer.Length);
writeQueueItem.BatchHeader.BatchSize++;
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnAfterSendMessage(clientId, typeof(TMessageType), delivery, tmpSerializer.Length + sizeof(MessageHeader));
sendQueueItem.ElementAt(sendQueueItem.Length - 1).Writer.Seek(sizeof(BatchHeader));
}
}
return tmpSerializer.Length;
ref var writeQueueItem = ref sendQueueItem.ElementAt(sendQueueItem.Length - 1);
writeQueueItem.Writer.TryBeginWrite(tmpSerializer.Length + headerSerializer.Length);
writeQueueItem.Writer.WriteBytes(headerSerializer.GetUnsafePtr(), headerSerializer.Length);
writeQueueItem.Writer.WriteBytes(tmpSerializer.GetUnsafePtr(), tmpSerializer.Length);
writeQueueItem.BatchHeader.BatchSize++;
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
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>
@@ -406,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()
@@ -434,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();
@@ -456,16 +524,16 @@ namespace Unity.Netcode
try
{
m_MessageSender.Send(clientId, queueItem.NetworkDelivery, queueItem.Writer);
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnAfterSendBatch(clientId, queueItem.BatchHeader.BatchSize, queueItem.Writer.Length, queueItem.NetworkDelivery);
}
}
finally
{
queueItem.Writer.Dispose();
}
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnAfterSendBatch(clientId, queueItem.BatchHeader.BatchSize, queueItem.Writer.Length, queueItem.NetworkDelivery);
}
}
sendQueueItem.Clear();
}

View File

@@ -25,5 +25,15 @@ namespace Unity.Netcode
/// The header data that was sent with the message
/// </summary>
public MessageHeader Header;
/// <summary>
/// The actual serialized size of the header when packed into the buffer
/// </summary>
public int SerializedHeaderSize;
/// <summary>
/// The size of the message in the buffer, header excluded
/// </summary>
public uint MessageSize;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,12 +0,0 @@
using System.IO;
namespace Unity.Netcode
{
public static class StreamExtensions
{
public static long SafeGetLengthOrDefault(this Stream stream)
{
return stream.CanSeek ? stream.Length : 0;
}
}
}

View File

@@ -90,18 +90,18 @@ namespace Unity.Netcode
{
case NetworkListEvent<T>.EventType.Add:
{
writer.WriteValueSafe(m_DirtyEvents[i].Value);
NetworkVariable<T>.Write(writer, m_DirtyEvents[i].Value);
}
break;
case NetworkListEvent<T>.EventType.Insert:
{
writer.WriteValueSafe(m_DirtyEvents[i].Index);
writer.WriteValueSafe(m_DirtyEvents[i].Value);
NetworkVariable<T>.Write(writer, m_DirtyEvents[i].Value);
}
break;
case NetworkListEvent<T>.EventType.Remove:
{
writer.WriteValueSafe(m_DirtyEvents[i].Value);
NetworkVariable<T>.Write(writer, m_DirtyEvents[i].Value);
}
break;
case NetworkListEvent<T>.EventType.RemoveAt:
@@ -112,7 +112,7 @@ namespace Unity.Netcode
case NetworkListEvent<T>.EventType.Value:
{
writer.WriteValueSafe(m_DirtyEvents[i].Index);
writer.WriteValueSafe(m_DirtyEvents[i].Value);
NetworkVariable<T>.Write(writer, m_DirtyEvents[i].Value);
}
break;
case NetworkListEvent<T>.EventType.Clear:
@@ -130,7 +130,7 @@ namespace Unity.Netcode
writer.WriteValueSafe((ushort)m_List.Length);
for (int i = 0; i < m_List.Length; i++)
{
writer.WriteValueSafe(m_List[i]);
NetworkVariable<T>.Write(writer, m_List[i]);
}
}
@@ -141,7 +141,7 @@ namespace Unity.Netcode
reader.ReadValueSafe(out ushort count);
for (int i = 0; i < count; i++)
{
reader.ReadValueSafe(out T value);
NetworkVariable<T>.Read(reader, out T value);
m_List.Add(value);
}
}
@@ -157,7 +157,7 @@ namespace Unity.Netcode
{
case NetworkListEvent<T>.EventType.Add:
{
reader.ReadValueSafe(out T value);
NetworkVariable<T>.Read(reader, out T value);
m_List.Add(value);
if (OnListChanged != null)
@@ -184,7 +184,7 @@ namespace Unity.Netcode
case NetworkListEvent<T>.EventType.Insert:
{
reader.ReadValueSafe(out int index);
reader.ReadValueSafe(out T value);
NetworkVariable<T>.Read(reader, out T value);
m_List.InsertRangeWithBeginEnd(index, index + 1);
m_List[index] = value;
@@ -211,7 +211,7 @@ namespace Unity.Netcode
break;
case NetworkListEvent<T>.EventType.Remove:
{
reader.ReadValueSafe(out T value);
NetworkVariable<T>.Read(reader, out T value);
int index = m_List.IndexOf(value);
if (index == -1)
{
@@ -271,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>

View File

@@ -9,6 +9,55 @@ namespace Unity.Netcode
[Serializable]
public class NetworkVariable<T> : NetworkVariableBase where T : unmanaged
{
// Functions that know how to serialize INetworkSerializable
internal static void WriteNetworkSerializable<TForMethod>(FastBufferWriter writer, in TForMethod value)
where TForMethod : INetworkSerializable, new()
{
writer.WriteNetworkSerializable(value);
}
internal static void ReadNetworkSerializable<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : INetworkSerializable, new()
{
reader.ReadNetworkSerializable(out value);
}
// Functions that serialize other types
private static void WriteValue<TForMethod>(FastBufferWriter writer, in TForMethod value) where TForMethod : unmanaged
{
writer.WriteValueSafe(value);
}
private static void ReadValue<TForMethod>(FastBufferReader reader, out TForMethod value)
where TForMethod : unmanaged
{
reader.ReadValueSafe(out value);
}
internal delegate void WriteDelegate<TForMethod>(FastBufferWriter writer, in TForMethod value);
internal delegate void ReadDelegate<TForMethod>(FastBufferReader reader, out TForMethod value);
// These static delegates provide the right implementation for writing and reading a particular network variable
// type.
//
// For most types, these default to WriteValue() and ReadValue(), which perform simple memcpy operations.
//
// INetworkSerializableILPP will generate startup code that will set it to WriteNetworkSerializable()
// and ReadNetworkSerializable() for INetworkSerializable types, which will call NetworkSerialize().
//
// In the future we may be able to use this to provide packing implementations for floats and integers to
// optimize bandwidth usage.
//
// The reason this is done is to avoid runtime reflection and boxing in NetworkVariable - without this,
// NetworkVariable would need to do a `var is INetworkSerializable` check, and then cast to INetworkSerializable,
// *both* of which would cause a boxing allocation. Alternatively, NetworkVariable could have been split into
// NetworkVariable and NetworkSerializableVariable or something like that, which would have caused a poor
// user experience and an API that's easier to get wrong than right. This is a bit ugly on the implementation
// side, but it gets the best achievable user experience and performance.
internal static WriteDelegate<T> Write = WriteValue;
internal static ReadDelegate<T> Read = ReadValue;
/// <summary>
/// Delegate type for value changed event
/// </summary>
@@ -106,7 +155,7 @@ namespace Unity.Netcode
public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
{
T previousValue = m_InternalValue;
reader.ReadValueSafe(out m_InternalValue);
Read(reader, out m_InternalValue);
if (keepDirtyDelta)
{
@@ -119,13 +168,13 @@ namespace Unity.Netcode
/// <inheritdoc />
public override void ReadField(FastBufferReader reader)
{
reader.ReadValueSafe(out m_InternalValue);
Read(reader, out m_InternalValue);
}
/// <inheritdoc />
public override void WriteField(FastBufferWriter writer)
{
writer.WriteValueSafe(m_InternalValue);
Write(writer, m_InternalValue);
}
}
}

View File

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

View File

@@ -0,0 +1,22 @@
namespace Unity.Netcode
{
public class NetworkVariableHelper
{
// This is called by ILPP during module initialization for all unmanaged INetworkSerializable types
// This sets up NetworkVariable so that it properly calls NetworkSerialize() when wrapping an INetworkSerializable value
//
// The reason this is done is to avoid runtime reflection and boxing in NetworkVariable - without this,
// NetworkVariable would need to do a `var is INetworkSerializable` check, and then cast to INetworkSerializable,
// *both* of which would cause a boxing allocation. Alternatively, NetworkVariable could have been split into
// NetworkVariable and NetworkSerializableVariable or something like that, which would have caused a poor
// user experience and an API that's easier to get wrong than right. This is a bit ugly on the implementation
// side, but it gets the best achievable user experience and performance.
//
// RuntimeAccessModifiersILPP will make this `public`
internal static void InitializeDelegates<T>() where T : unmanaged, INetworkSerializable
{
NetworkVariable<T>.Write = NetworkVariable<T>.WriteNetworkSerializable;
NetworkVariable<T>.Read = NetworkVariable<T>.ReadNetworkSerializable;
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 61dd9b1558f6d7c46ad323b2c2c03c29
guid: e54b65208bd3bbe4eaf62ca0384ae21f
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

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

View File

@@ -1,31 +0,0 @@
using System;
namespace Unity.Netcode
{
internal static class TypeExtensions
{
internal static bool HasInterface(this Type type, Type interfaceType)
{
var ifaces = type.GetInterfaces();
for (int i = 0; i < ifaces.Length; i++)
{
if (ifaces[i] == interfaceType)
{
return true;
}
}
return false;
}
internal static bool IsNullable(this Type type)
{
if (!type.IsValueType)
{
return true; // ref-type
}
return Nullable.GetUnderlyingType(type) != null;
}
}
}

View File

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

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 3e168a2bc1a1e2642af0369780fb560c
guid: de907a9fb8151e240800dbcc97f8e745
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -8,17 +8,22 @@ using UnityEngine.SceneManagement;
namespace Unity.Netcode
{
/// <summary>
/// Used for local notifications of various scene events.
/// The <see cref="NetworkSceneManager.OnSceneEvent"/> of delegate type <see cref="NetworkSceneManager.SceneEventDelegate"/> uses this class to provide
/// scene event status/state.
/// Used for local notifications of various scene events. The <see cref="NetworkSceneManager.OnSceneEvent"/> of
/// delegate type <see cref="NetworkSceneManager.SceneEventDelegate"/> uses this class to provide
/// scene event status.<br/>
/// <em>Note: This is only when <see cref="NetworkConfig.EnableSceneManagement"/> is enabled.</em><br/>
/// See also: <br/>
/// <seealso cref="SceneEventType"/>
/// </summary>
public class SceneEvent
{
/// <summary>
/// The <see cref="UnityEngine.AsyncOperation"/> returned by <see cref="SceneManager"/>
/// The <see cref="UnityEngine.AsyncOperation"/> returned by <see cref="SceneManager"/><BR/>
/// This is set for the following <see cref="Netcode.SceneEventType"/>s:
/// <see cref="SceneEventType.Load"/>
/// <see cref="SceneEventType.Unload"/>
/// <list type="bullet">
/// <item><term><see cref="SceneEventType.Load"/></term></item>
/// <item><term><see cref="SceneEventType.Unload"/></term></item>
/// </list>
/// </summary>
public AsyncOperation AsyncOperation;
@@ -28,71 +33,92 @@ namespace Unity.Netcode
public SceneEventType SceneEventType;
/// <summary>
/// If applicable, this reflects the type of scene loading or unloading that is occurring.
/// If applicable, this reflects the type of scene loading or unloading that is occurring.<BR/>
/// This is set for the following <see cref="Netcode.SceneEventType"/>s:
/// <see cref="SceneEventType.Load"/>
/// <see cref="SceneEventType.Unload"/>
/// <see cref="SceneEventType.LoadComplete"/>
/// <see cref="SceneEventType.UnloadComplete"/>
/// <see cref="SceneEventType.LoadEventCompleted"/>
/// <see cref="SceneEventType.UnloadEventCompleted"/>
/// <list type="bullet">
/// <item><term><see cref="SceneEventType.Load"/></term></item>
/// <item><term><see cref="SceneEventType.Unload"/></term></item>
/// <item><term><see cref="SceneEventType.LoadComplete"/></term></item>
/// <item><term><see cref="SceneEventType.UnloadComplete"/></term></item>
/// <item><term><see cref="SceneEventType.LoadEventCompleted"/></term></item>
/// <item><term><see cref="SceneEventType.UnloadEventCompleted"/></term></item>
/// </list>
/// </summary>
public LoadSceneMode LoadSceneMode;
/// <summary>
/// This will be set to the scene name that the event pertains to.
/// This will be set to the scene name that the event pertains to.<BR/>
/// This is set for the following <see cref="Netcode.SceneEventType"/>s:
/// <see cref="SceneEventType.Load"/>
/// <see cref="SceneEventType.Unload"/>
/// <see cref="SceneEventType.LoadComplete"/>
/// <see cref="SceneEventType.UnloadComplete"/>
/// <see cref="SceneEventType.LoadEventCompleted"/>
/// <see cref="SceneEventType.UnloadEventCompleted"/>
/// <list type="bullet">
/// <item><term><see cref="SceneEventType.Load"/></term></item>
/// <item><term><see cref="SceneEventType.Unload"/></term></item>
/// <item><term><see cref="SceneEventType.LoadComplete"/></term></item>
/// <item><term><see cref="SceneEventType.UnloadComplete"/></term></item>
/// <item><term><see cref="SceneEventType.LoadEventCompleted"/></term></item>
/// <item><term><see cref="SceneEventType.UnloadEventCompleted"/></term></item>
/// </list>
/// </summary>
public string SceneName;
/// <summary>
/// When a scene is loaded, the Scene structure is returned.
/// When a scene is loaded, the Scene structure is returned.<BR/>
/// This is set for the following <see cref="Netcode.SceneEventType"/>s:
/// <see cref="SceneEventType.LoadComplete"/>
/// <list type="bullet">
/// <item><term><see cref="SceneEventType.LoadComplete"/></term></item>
/// </list>
/// </summary>
public Scene Scene;
/// <summary>
/// Events that always set the <see cref="ClientId"/> to the local client identifier,
/// are initiated (and processed locally) by the server-host, and sent to all clients
/// to be processed:
/// <see cref="SceneEventType.Load"/>
/// <see cref="SceneEventType.Unload"/>
/// <see cref="SceneEventType.Synchronize"/>
/// <see cref="SceneEventType.ReSynchronize"/>
///
/// Events that always set the <see cref="ClientId"/> to the local client identifier,
/// The client identifier can vary depending upon the following conditions: <br/>
/// <list type="number">
/// <item><term><see cref="Netcode.SceneEventType"/>s that always set the <see cref="ClientId"/>
/// to the local client identifier, are initiated (and processed locally) by the
/// server-host, and sent to all clients to be processed.<br/>
/// <list type="bullet">
/// <item><term><see cref="SceneEventType.Load"/></term></item>
/// <item><term><see cref="SceneEventType.Unload"/></term></item>
/// <item><term><see cref="SceneEventType.Synchronize"/></term></item>
/// <item><term><see cref="SceneEventType.ReSynchronize"/></term></item>
/// </list>
/// </term></item>
/// <item><term>Events that always set the <see cref="ClientId"/> to the local client identifier,
/// are initiated (and processed locally) by a client or server-host, and if initiated
/// by a client will always be sent to and processed on the server-host:
/// <see cref="SceneEventType.LoadComplete"/>
/// <see cref="SceneEventType.UnloadComplete"/>
/// <see cref="SceneEventType.SynchronizeComplete"/>
///
/// <list type="bullet">
/// <item><term><see cref="SceneEventType.LoadComplete"/></term></item>
/// <item><term><see cref="SceneEventType.UnloadComplete"/></term></item>
/// <item><term><see cref="SceneEventType.SynchronizeComplete"/></term></item>
/// </list>
/// </term></item>
/// <item><term>
/// Events that always set the <see cref="ClientId"/> to the ServerId:
/// <see cref="SceneEventType.LoadEventCompleted"/>
/// <see cref="SceneEventType.UnloadEventCompleted"/>
/// <list type="bullet">
/// <item><term><see cref="SceneEventType.LoadEventCompleted"/></term></item>
/// <item><term><see cref="SceneEventType.UnloadEventCompleted"/></term></item>
/// </list>
/// </term></item>
/// </list>
/// </summary>
public ulong ClientId;
/// <summary>
/// List of clients that completed a loading or unloading event
/// List of clients that completed a loading or unloading event.<br/>
/// This is set for the following <see cref="Netcode.SceneEventType"/>s:
/// <see cref="SceneEventType.LoadEventCompleted"/>
/// <see cref="SceneEventType.UnloadEventCompleted"/>
/// <list type="bullet">
/// <item><term><see cref="SceneEventType.LoadEventCompleted"/></term></item>
/// <item><term><see cref="SceneEventType.UnloadEventCompleted"/></term></item>
/// </list>
/// </summary>
public List<ulong> ClientsThatCompleted;
/// <summary>
/// List of clients that timed out during a loading or unloading event
/// List of clients that timed out during a loading or unloading event.<br/>
/// This is set for the following <see cref="Netcode.SceneEventType"/>s:
/// <see cref="SceneEventType.LoadEventCompleted"/>
/// <see cref="SceneEventType.UnloadEventCompleted"/>
/// <list type="bullet">
/// <item><term><see cref="SceneEventType.LoadEventCompleted"/></term></item>
/// <item><term><see cref="SceneEventType.UnloadEventCompleted"/></term></item>
/// </list>
/// </summary>
public List<ulong> ClientsThatTimedOut;
}
@@ -113,43 +139,40 @@ 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
/// For more details review over <see cref="SceneEvent"/> and <see cref="SceneEventData"/>
/// The delegate callback definition for scene event notifications.<br/>
/// See also: <br/>
/// <seealso cref="SceneEvent"/><br/>
/// <seealso cref="SceneEventData"/>
/// </summary>
/// <param name="sceneEvent"></param>
public delegate void SceneEventDelegate(SceneEvent sceneEvent);
/// <summary>
/// Event that will notify the local client or server of all scene events that take place
/// For more details review over <see cref="SceneEvent"/>, <see cref="SceneEventData"/>, and <see cref="SceneEventType"/>
/// Subscribe to this event to receive all <see cref="SceneEventType"/> notifications
///
/// Alternate Single Event Type Notification Registration Options
/// Subscribe to this event to receive all <see cref="SceneEventType"/> notifications.<br/>
/// For more details review over <see cref="SceneEvent"/> and <see cref="SceneEventType"/>.<br/>
/// <b>Alternate Single Event Type Notification Registration Options</b><br/>
/// To receive only a specific event type notification or a limited set of notifications you can alternately subscribe to
/// each notification type individually via the following events:
/// -- <see cref="OnLoad"/> Invoked only when a <see cref="SceneEventType.Load"/> event is being processed
/// -- <see cref="OnUnload"/> Invoked only when an <see cref="SceneEventType.Unload"/> event is being processed
/// -- <see cref="OnSynchronize"/> Invoked only when a <see cref="SceneEventType.Synchronize"/> event is being processed
/// -- <see cref="OnLoadEventCompleted"/> Invoked only when a <see cref="SceneEventType.LoadEventCompleted"/> event is being processed
/// -- <see cref="OnUnloadEventCompleted"/> Invoked only when an <see cref="SceneEventType.UnloadEventCompleted"/> event is being processed
/// -- <see cref="OnLoadComplete"/> Invoked only when a <see cref="SceneEventType.LoadComplete"/> event is being processed
/// -- <see cref="OnUnloadComplete"/> Invoked only when an <see cref="SceneEventType.UnloadComplete"/> event is being processed
/// -- <see cref="OnSynchronizeComplete"/> Invoked only when a <see cref="SceneEventType.SynchronizeComplete"/> event is being processed
/// each notification type individually via the following events:<br/>
/// <list type="bullet">
/// <item><term><see cref="OnLoad"/> Invoked only when a <see cref="SceneEventType.Load"/> event is being processed</term></item>
/// <item><term><see cref="OnUnload"/> Invoked only when an <see cref="SceneEventType.Unload"/> event is being processed</term></item>
/// <item><term><see cref="OnSynchronize"/> Invoked only when a <see cref="SceneEventType.Synchronize"/> event is being processed</term></item>
/// <item><term><see cref="OnLoadEventCompleted"/> Invoked only when a <see cref="SceneEventType.LoadEventCompleted"/> event is being processed</term></item>
/// <item><term><see cref="OnUnloadEventCompleted"/> Invoked only when an <see cref="SceneEventType.UnloadEventCompleted"/> event is being processed</term></item>
/// <item><term><see cref="OnLoadComplete"/> Invoked only when a <see cref="SceneEventType.LoadComplete"/> event is being processed</term></item>
/// <item><term><see cref="OnUnloadComplete"/> Invoked only when an <see cref="SceneEventType.UnloadComplete"/> event is being processed</term></item>
/// <item><term><see cref="OnSynchronizeComplete"/> Invoked only when a <see cref="SceneEventType.SynchronizeComplete"/> event is being processed</term></item>
/// </list>
/// </summary>
public event SceneEventDelegate OnSceneEvent;
/// <summary>
/// Delegate declaration for the OnLoad event
/// View <see cref="SceneEventType.Load"/> for more information
/// Delegate declaration for the OnLoad event.<br/>
/// See also: <br/>
/// <seealso cref="SceneEventType.Load"/>for more information
/// </summary>
/// <param name="clientId">the client that is processing this event (the server will receive all of these events for every client and itself)</param>
/// <param name="sceneName">name of the scene being processed</param>
@@ -158,8 +181,9 @@ namespace Unity.Netcode
public delegate void OnLoadDelegateHandler(ulong clientId, string sceneName, LoadSceneMode loadSceneMode, AsyncOperation asyncOperation);
/// <summary>
/// Delegate declaration for the OnUnload event
/// View <see cref="SceneEventType.Unload"/> for more information
/// Delegate declaration for the OnUnload event.<br/>
/// See also: <br/>
/// <seealso cref="SceneEventType.Unload"/> for more information
/// </summary>
/// <param name="clientId">the client that is processing this event (the server will receive all of these events for every client and itself)</param>
/// <param name="sceneName">name of the scene being processed</param>
@@ -167,16 +191,18 @@ namespace Unity.Netcode
public delegate void OnUnloadDelegateHandler(ulong clientId, string sceneName, AsyncOperation asyncOperation);
/// <summary>
/// Delegate declaration for the OnSynchronize event
/// View <see cref="SceneEventType.Synchronize"/> for more information
/// Delegate declaration for the OnSynchronize event.<br/>
/// See also: <br/>
/// <seealso cref="SceneEventType.Synchronize"/> for more information
/// </summary>
/// <param name="clientId">the client that is processing this event (the server will receive all of these events for every client and itself)</param>
public delegate void OnSynchronizeDelegateHandler(ulong clientId);
/// <summary>
/// Delegate declaration for the OnLoadEventCompleted and OnUnloadEventCompleted events
/// View <see cref="SceneEventType.LoadEventCompleted"/> for more information
/// View <see cref="SceneEventType.UnloadEventCompleted"/> for more information
/// Delegate declaration for the OnLoadEventCompleted and OnUnloadEventCompleted events.<br/>
/// See also:<br/>
/// <seealso cref="SceneEventType.LoadEventCompleted"/><br/>
/// <seealso cref="SceneEventType.UnloadEventCompleted"/>
/// </summary>
/// <param name="sceneName">scene pertaining to this event</param>
/// <param name="loadSceneMode"><see cref="LoadSceneMode"/> of the associated event completed</param>
@@ -185,8 +211,9 @@ namespace Unity.Netcode
public delegate void OnEventCompletedDelegateHandler(string sceneName, LoadSceneMode loadSceneMode, List<ulong> clientsCompleted, List<ulong> clientsTimedOut);
/// <summary>
/// Delegate declaration for the OnLoadComplete event
/// View <see cref="SceneEventType.LoadComplete"/> for more information
/// Delegate declaration for the OnLoadComplete event.<br/>
/// See also:<br/>
/// <seealso cref="SceneEventType.LoadComplete"/> for more information
/// </summary>
/// <param name="clientId">the client that is processing this event (the server will receive all of these events for every client and itself)</param>
/// <param name="sceneName">the scene name pertaining to this event</param>
@@ -194,38 +221,40 @@ namespace Unity.Netcode
public delegate void OnLoadCompleteDelegateHandler(ulong clientId, string sceneName, LoadSceneMode loadSceneMode);
/// <summary>
/// Delegate declaration for the OnUnloadComplete event
/// View <see cref="SceneEventType.UnloadComplete"/> for more information
/// Delegate declaration for the OnUnloadComplete event.<br/>
/// See also:<br/>
/// <seealso cref="SceneEventType.UnloadComplete"/> for more information
/// </summary>
/// <param name="clientId">the client that is processing this event (the server will receive all of these events for every client and itself)</param>
/// <param name="sceneName">the scene name pertaining to this event</param>
public delegate void OnUnloadCompleteDelegateHandler(ulong clientId, string sceneName);
/// <summary>
/// Delegate declaration for the OnSynchronizeComplete event
/// View <see cref="SceneEventType.SynchronizeComplete"/> for more information
/// Delegate declaration for the OnSynchronizeComplete event.<br/>
/// See also:<br/>
/// <seealso cref="SceneEventType.SynchronizeComplete"/> for more information
/// </summary>
/// <param name="clientId">the client that completed this event</param>
public delegate void OnSynchronizeCompleteDelegateHandler(ulong clientId);
/// <summary>
/// Invoked when a <see cref="SceneEventType.Load"/> event is started by the server
/// The server and client(s) will receive this notification
/// Invoked when a <see cref="SceneEventType.Load"/> event is started by the server.<br/>
/// <em>Note: The server and connected client(s) will always receive this notification.</em>
/// </summary>
public event OnLoadDelegateHandler OnLoad;
/// <summary>
/// Invoked when a <see cref="SceneEventType.Unload"/> event is started by the server
/// The server and client(s) will receive this notification
/// Invoked when a <see cref="SceneEventType.Unload"/> event is started by the server.<br/>
/// <em>Note: The server and connected client(s) will always receive this notification.</em>
/// </summary>
public event OnUnloadDelegateHandler OnUnload;
/// <summary>
/// Invoked when a <see cref="SceneEventType.Synchronize"/> event is started by the server
/// after a client is approved for connection in order to synchronize the client with the currently loaded
/// scenes and NetworkObjects. This event signifies the beginning of the synchronization event.
/// The server and client will receive this notification
/// Note: this event is generated on a per newly connected and approved client basis
/// scenes and NetworkObjects. This event signifies the beginning of the synchronization event.<br/>
/// <em>Note: The server and connected client(s) will always receive this notification.
/// This event is generated on a per newly connected and approved client basis.</em>
/// </summary>
public event OnSynchronizeDelegateHandler OnSynchronize;
@@ -233,8 +262,8 @@ namespace Unity.Netcode
/// Invoked when a <see cref="SceneEventType.LoadEventCompleted"/> event is generated by the server.
/// This event signifies the end of an existing <see cref="SceneEventType.Load"/> event as it pertains
/// to all clients connected when the event was started. This event signifies that all clients (and server) have
/// finished the <see cref="SceneEventType.Load"/> event.
/// Note: this is useful to know when all clients have loaded the same scene (single or additive mode)
/// finished the <see cref="SceneEventType.Load"/> event.<br/>
/// <em>Note: this is useful to know when all clients have loaded the same scene (single or additive mode)</em>
/// </summary>
public event OnEventCompletedDelegateHandler OnLoadEventCompleted;
@@ -242,32 +271,31 @@ namespace Unity.Netcode
/// Invoked when a <see cref="SceneEventType.UnloadEventCompleted"/> event is generated by the server.
/// This event signifies the end of an existing <see cref="SceneEventType.Unload"/> event as it pertains
/// to all clients connected when the event was started. This event signifies that all clients (and server) have
/// finished the <see cref="SceneEventType.Unload"/> event.
/// Note: this is useful to know when all clients have unloaded a specific scene. The <see cref="LoadSceneMode"/> will
/// always be <see cref="LoadSceneMode.Additive"/> for this event
/// finished the <see cref="SceneEventType.Unload"/> event.<br/>
/// <em>Note: this is useful to know when all clients have unloaded a specific scene. The <see cref="LoadSceneMode"/> will
/// always be <see cref="LoadSceneMode.Additive"/> for this event.</em>
/// </summary>
public event OnEventCompletedDelegateHandler OnUnloadEventCompleted;
/// <summary>
/// Invoked when a <see cref="SceneEventType.LoadComplete"/> event is generated by a client or server.
/// The server receives this message from all clients (including itself).
/// Each client receives their own notification sent to the server.
/// Invoked when a <see cref="SceneEventType.LoadComplete"/> event is generated by a client or server.<br/>
/// <em>Note: The server receives this message from all clients (including itself).
/// Each client receives their own notification sent to the server.</em>
/// </summary>
public event OnLoadCompleteDelegateHandler OnLoadComplete;
/// <summary>
/// Invoked when a <see cref="SceneEventType.UnloadComplete"/> event is generated by a client or server.
/// The server receives this message from all clients (including itself).
/// Each client receives their own notification sent to the server.
/// Invoked when a <see cref="SceneEventType.UnloadComplete"/> event is generated by a client or server.<br/>
/// <em>Note: The server receives this message from all clients (including itself).
/// Each client receives their own notification sent to the server.</em>
/// </summary>
public event OnUnloadCompleteDelegateHandler OnUnloadComplete;
/// <summary>
/// Invoked when a <see cref="SceneEventType.SynchronizeComplete"/> event is generated by a client.
/// The server receives this message from the client, but will never generate this event for itself.
/// Each client receives their own notification sent to the server.
/// Note: This is useful to know that a client has completed the entire connection sequence, loaded all scenes, and
/// synchronized all NetworkObjects.
/// Invoked when a <see cref="SceneEventType.SynchronizeComplete"/> event is generated by a client. <br/>
/// <em> Note: The server receives this message from the client, but will never generate this event for itself.
/// Each client receives their own notification sent to the server. This is useful to know that a client has
/// completed the entire connection sequence, loaded all scenes, and synchronized all NetworkObjects.</em>
/// </summary>
public event OnSynchronizeCompleteDelegateHandler OnSynchronizeComplete;
@@ -284,12 +312,37 @@ namespace Unity.Netcode
/// <summary>
/// Delegate handler defined by <see cref="VerifySceneBeforeLoadingDelegateHandler"/> that is invoked before the
/// server or client loads a scene during an active netcode game session.
/// Client Side: In order for clients to be notified of this condition you must assign the <see cref="VerifySceneBeforeLoading"/> delegate handler.
/// Server Side: <see cref="LoadScene(string, LoadSceneMode)"/> will return <see cref="SceneEventProgressStatus.SceneFailedVerification"/>.
/// server or client loads a scene during an active netcode game session.<br/>
/// <b>Client Side:</b> In order for clients to be notified of this condition you must assign the <see cref="VerifySceneBeforeLoading"/> delegate handler.<br/>
/// <b>Server Side:</b> <see cref="LoadScene(string, LoadSceneMode)"/> will return <see cref="SceneEventProgressStatus"/>.
/// </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>
@@ -353,11 +406,10 @@ namespace Unity.Netcode
internal Scene DontDestroyOnLoadScene;
/// <summary>
/// LoadSceneMode.Single: All currently loaded scenes on the client will be unloaded and
/// <b>LoadSceneMode.Single:</b> All currently loaded scenes on the client will be unloaded and
/// the server's currently active scene will be loaded in single mode on the client
/// unless it was already loaded.
///
/// LoadSceneMode.Additive: All currently loaded scenes are left as they are and any newly loaded
/// unless it was already loaded.<br/>
/// <b>LoadSceneMode.Additive:</b> All currently loaded scenes are left as they are and any newly loaded
/// scenes will be loaded additively. Users need to determine which scenes are valid to load via the
/// <see cref="VerifySceneBeforeLoading"/> method.
/// </summary>
@@ -506,12 +558,11 @@ namespace Unity.Netcode
}
/// <summary>
/// This will change how clients are initially synchronized.
/// LoadSceneMode.Single: All currently loaded scenes on the client will be unloaded and
/// This will change how clients are initially synchronized.<br/>
/// <b>LoadSceneMode.Single:</b> All currently loaded scenes on the client will be unloaded and
/// the server's currently active scene will be loaded in single mode on the client
/// unless it was already loaded.
///
/// LoadSceneMode.Additive: All currently loaded scenes are left as they are and any newly loaded
/// unless it was already loaded. <br/>
/// <b>LoadSceneMode.Additive:</b> All currently loaded scenes are left as they are and any newly loaded
/// scenes will be loaded additively. Users need to determine which scenes are valid to load via the
/// <see cref="VerifySceneBeforeLoading"/> method.
/// </summary>
@@ -533,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)
{
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);
}
// Since NetworkManager is now always migrated to the DDOL we will use this to get the DDOL scene
DontDestroyOnLoadScene = networkManager.gameObject.scene;
ServerSceneHandleToClientSceneHandle.Add(DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene.handle);
ScenesLoaded.Add(DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene);
@@ -703,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>
@@ -769,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);
}
@@ -798,7 +836,7 @@ namespace Unity.Netcode
IsSpawnedObjectsPendingInDontDestroyOnLoad = true;
}
s_IsSceneEventActive = true;
m_IsSceneEventActive = true;
// Set our callback delegate handler for completion
sceneEventProgress.OnComplete = OnSceneEventProgressCompleted;
@@ -825,12 +863,12 @@ namespace Unity.Netcode
{
EventData = sceneEventData
};
var size = m_NetworkManager.SendMessage(message, k_DeliveryType, m_NetworkManager.ConnectedClientsIds);
var size = m_NetworkManager.SendMessage(ref message, k_DeliveryType, m_NetworkManager.ConnectedClientsIds);
m_NetworkManager.NetworkMetrics.TrackSceneEventSent(
m_NetworkManager.ConnectedClientsIds,
(uint)sceneEventProgress.SceneEventType,
SceneNameFromHash(sceneEventProgress.SceneHash),
SceneNameFromHash(sceneEventProgress.SceneHash),
size);
// Send a local notification to the server that all clients are done loading or unloading
@@ -858,7 +896,7 @@ namespace Unity.Netcode
}
/// <summary>
/// Server Side:
/// <b>Server Side:</b>
/// Unloads an additively loaded scene. If you want to unload a <see cref="LoadSceneMode.Single"/> mode loaded scene load another <see cref="LoadSceneMode.Single"/> scene.
/// When applicable, the <see cref="AsyncOperation"/> is delivered within the <see cref="SceneEvent"/> via the <see cref="OnSceneEvent"/>
/// </summary>
@@ -897,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
@@ -918,7 +957,7 @@ namespace Unity.Netcode
}
/// <summary>
/// Client Side:
/// <b>Client Side:</b>
/// Handles <see cref="SceneEventType.Unload"/> scene events.
/// </summary>
private void OnClientUnloadScene(uint sceneEventId)
@@ -928,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];
@@ -940,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
@@ -972,13 +1002,6 @@ namespace Unity.Netcode
});
OnUnload?.Invoke(m_NetworkManager.LocalClientId, sceneName, sceneUnload);
#if UNITY_INCLUDE_TESTS
if (m_IsRunningUnitTest)
{
OnSceneUnloaded(sceneEventId);
}
#endif
}
/// <summary>
@@ -996,8 +1019,8 @@ namespace Unity.Netcode
// despawned that no longer exists
SendSceneEventData(sceneEventId, m_NetworkManager.ConnectedClientsIds.Where(c => c != m_NetworkManager.ServerClientId).ToArray());
//Second, server sets itself as having finished unloading
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId))
//Only if we are a host do we want register having loaded for the associated SceneEventProgress
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost)
{
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(m_NetworkManager.ServerClientId);
}
@@ -1025,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>
@@ -1033,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
@@ -1056,7 +1088,7 @@ namespace Unity.Netcode
}
/// <summary>
/// Server side:
/// <b>Server side:</b>
/// Loads the scene name in either additive or single loading mode.
/// When applicable, the <see cref="AsyncOperation"/> is delivered within the <see cref="SceneEvent"/> via <see cref="OnSceneEvent"/>
/// </summary>
@@ -1083,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;
@@ -1099,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
@@ -1140,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
@@ -1185,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()
{
@@ -1210,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!");
@@ -1294,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()
{
@@ -1312,8 +1315,8 @@ namespace Unity.Netcode
OnLoadComplete?.Invoke(m_NetworkManager.ServerClientId, SceneNameFromHash(sceneEventData.SceneHash), sceneEventData.LoadSceneMode);
//Second, set the server as having loaded for the associated SceneEventProgress
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId))
//Second, only if we are a host do we want register having loaded for the associated SceneEventProgress
if (SceneEventProgressTracking.ContainsKey(sceneEventData.SceneEventProgressId) && m_NetworkManager.IsHost)
{
SceneEventProgressTracking[sceneEventData.SceneEventProgressId].AddClientAsDone(m_NetworkManager.ServerClientId);
}
@@ -1331,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()
@@ -1401,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()
@@ -1454,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;
@@ -1465,38 +1471,28 @@ namespace Unity.Netcode
shouldPassThrough = true;
}
#if UNITY_INCLUDE_TESTS
if (m_IsRunningUnitTest)
{
// In unit tests, we don't allow clients to load additional scenes since
// MultiInstance unit tests share the same scene space.
shouldPassThrough = true;
sceneLoad = new AsyncOperation();
}
#endif
if (!shouldPassThrough)
{
// If not, then load the scene
sceneLoad = SceneManager.LoadSceneAsync(sceneName, loadSceneMode);
sceneLoad.completed += asyncOp2 => ClientLoadedSynchronization(sceneEventId, sceneHash, sceneHandle);
sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode,
new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = ClientLoadedSynchronization });
// Notify local client that a scene load has begun
OnSceneEvent?.Invoke(new SceneEvent()
{
AsyncOperation = sceneLoad,
SceneEventType = SceneEventType.Load,
LoadSceneMode = loadSceneMode,
SceneName = sceneName,
ClientId = m_NetworkManager.LocalClientId,
});
OnLoad?.Invoke(m_NetworkManager.LocalClientId, sceneName, loadSceneMode, sceneLoad);
}
// Notify local client that a scene load has begun
OnSceneEvent?.Invoke(new SceneEvent()
{
AsyncOperation = sceneLoad,
SceneEventType = SceneEventType.Load,
LoadSceneMode = loadSceneMode,
SceneName = sceneName,
ClientId = m_NetworkManager.LocalClientId,
});
OnLoad?.Invoke(m_NetworkManager.LocalClientId, sceneName, loadSceneMode, sceneLoad);
if (shouldPassThrough)
else
{
// If so, then pass through
ClientLoadedSynchronization(sceneEventId, sceneHash, sceneHandle);
ClientLoadedSynchronization(sceneEventId);
}
}
@@ -1505,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())
@@ -1516,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)
@@ -1524,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
{
@@ -1541,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);
@@ -1798,21 +1794,23 @@ namespace Unity.Netcode
/// Moves all NetworkObjects that don't have the <see cref="NetworkObject.DestroyWithScene"/> set to
/// the "Do not destroy on load" scene.
/// </summary>
private void MoveObjectsToDontDestroyOnLoad()
internal void MoveObjectsToDontDestroyOnLoad()
{
// Move ALL NetworkObjects to the temp scene
// Move ALL NetworkObjects marked to persist scene transitions into the DDOL scene
var objectsToKeep = new HashSet<NetworkObject>(m_NetworkManager.SpawnManager.SpawnedObjectsList);
foreach (var sobj in objectsToKeep)
{
if (!sobj.DestroyWithScene || (sobj.IsSceneObject != null && sobj.IsSceneObject.Value && sobj.gameObject.scene == DontDestroyOnLoadScene))
if (sobj == null)
{
// Only move objects with no parent as child objects will follow
if (sobj.gameObject.transform.parent == null)
continue;
}
if (!sobj.DestroyWithScene || sobj.gameObject.scene == DontDestroyOnLoadScene)
{
// Only move dynamically spawned network objects with no parent as child objects will follow
if (sobj.gameObject.transform.parent == null && sobj.IsSceneObject != null && !sobj.IsSceneObject.Value)
{
UnityEngine.Object.DontDestroyOnLoad(sobj.gameObject);
// Since we are doing a scene transition, disable the GameObject until the next scene is loaded
sobj.gameObject.SetActive(false);
}
}
else if (m_NetworkManager.IsServer)
@@ -1847,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)
{
@@ -1875,24 +1873,26 @@ namespace Unity.Netcode
/// Moves all spawned NetworkObjects (from do not destroy on load) to the scene specified
/// </summary>
/// <param name="scene">scene to move the NetworkObjects to</param>
private void MoveObjectsFromDontDestroyOnLoadToScene(Scene scene)
internal void MoveObjectsFromDontDestroyOnLoadToScene(Scene scene)
{
// Move ALL NetworkObjects to the temp scene
var objectsToKeep = m_NetworkManager.SpawnManager.SpawnedObjectsList;
foreach (var sobj in objectsToKeep)
{
if (sobj.gameObject.scene == DontDestroyOnLoadScene && (sobj.IsSceneObject == null || sobj.IsSceneObject.Value))
if (sobj == null)
{
continue;
}
// Only move objects with no parent as child objects will follow
if (sobj.gameObject.transform.parent == null)
// If it is in the DDOL then
if (sobj.gameObject.scene == DontDestroyOnLoadScene)
{
// set it back to active at this point
sobj.gameObject.SetActive(true);
SceneManager.MoveGameObjectToScene(sobj.gameObject, scene);
// only move dynamically spawned network objects, with no parent as child objects will follow,
// back into the currently active scene
if (sobj.gameObject.transform.parent == null && sobj.IsSceneObject != null && !sobj.IsSceneObject.Value)
{
SceneManager.MoveGameObjectToScene(sobj.gameObject, scene);
}
}
}
}

View File

@@ -4,88 +4,89 @@ using System.Linq;
using Unity.Collections;
using UnityEngine.SceneManagement;
namespace Unity.Netcode
{
/// <summary>
/// The different types of scene events communicated between a server and client.
/// Used by <see cref="NetworkSceneManager"/> for <see cref="SceneEventMessage"/> messages
/// Note: This is only when <see cref="NetworkConfig.EnableSceneManagement"/> is enabled
/// See also: <see cref="SceneEvent"/>
/// The different types of scene events communicated between a server and client. <br/>
/// Used by <see cref="NetworkSceneManager"/> for <see cref="SceneEventMessage"/> messages.<br/>
/// <em>Note: This is only when <see cref="NetworkConfig.EnableSceneManagement"/> is enabled.</em><br/>
/// See also: <br/>
/// <seealso cref="SceneEvent"/>
/// </summary>
public enum SceneEventType : byte
{
/// <summary>
/// Load a scene
/// Invocation: Server Side
/// Message Flow: Server to client
/// Event Notification: Both server and client are notified a load scene event started
/// Load a scene<br/>
/// <b>Invocation:</b> Server Side<br/>
/// <b>Message Flow:</b> Server to client<br/>
/// <b>Event Notification:</b> Both server and client are notified a load scene event started
/// </summary>
Load,
/// <summary>
/// Unload a scene
/// Invocation: Server Side
/// Message Flow: Server to client
/// Event Notification: Both server and client are notified an unload scene event started
/// Unload a scene<br/>
/// <b>Invocation:</b> Server Side<br/>
/// <b>Message Flow:</b> Server to client<br/>
/// <b>Event Notification:</b> Both server and client are notified an unload scene event started.
/// </summary>
Unload,
/// <summary>
/// Synchronize current game session state for approved clients
/// Invocation: Server Side
/// Message Flow: Server to client
/// Event Notification: Server and Client receives a local notification (server receives the ClientId being synchronized)
/// Synchronizes current game session state for newly approved clients<br/>
/// <b>Invocation:</b> Server Side<br/>
/// <b>Message Flow:</b> Server to client<br/>
/// <b>Event Notification:</b> Server and Client receives a local notification (<em>server receives the ClientId being synchronized</em>).
/// </summary>
Synchronize,
/// <summary>
/// Game session re-synchronization of NetworkObjects that were destroyed during a <see cref="Synchronize"/> event
/// Invocation: Server Side
/// Message Flow: Server to client
/// Event Notification: Both server and client receive a local notification
/// Note: This will be removed once snapshot and buffered messages are finalized as it will no longer be needed at that point
/// Game session re-synchronization of NetworkObjects that were destroyed during a <see cref="Synchronize"/> event<br/>
/// <b>Invocation:</b> Server Side<br/>
/// <b>Message Flow:</b> Server to client<br/>
/// <b>Event Notification:</b> Both server and client receive a local notification<br/>
/// <em>Note: This will be removed once snapshot and buffered messages are finalized as it will no longer be needed at that point.</em>
/// </summary>
ReSynchronize,
/// <summary>
/// All clients have finished loading a scene
/// Invocation: Server Side
/// Message Flow: Server to Client
/// Event Notification: Both server and client receive a local notification containing the clients that finished
/// as well as the clients that timed out (if any).
/// All clients have finished loading a scene<br/>
/// <b>Invocation:</b> Server Side<br/>
/// <b>Message Flow:</b> Server to Client<br/>
/// <b>Event Notification:</b> Both server and client receive a local notification containing the clients that finished
/// as well as the clients that timed out(<em>if any</em>).
/// </summary>
LoadEventCompleted,
/// <summary>
/// All clients have unloaded a scene
/// Invocation: Server Side
/// Message Flow: Server to Client
/// Event Notification: Both server and client receive a local notification containing the clients that finished
/// as well as the clients that timed out (if any).
/// All clients have unloaded a scene<br/>
/// <b>Invocation:</b> Server Side<br/>
/// <b>Message Flow:</b> Server to Client<br/>
/// <b>Event Notification:</b> Both server and client receive a local notification containing the clients that finished
/// as well as the clients that timed out(<em>if any</em>).
/// </summary>
UnloadEventCompleted,
/// <summary>
/// A client has finished loading a scene
/// Invocation: Client Side
/// Message Flow: Client to Server
/// Event Notification: Both server and client receive a local notification
/// A client has finished loading a scene<br/>
/// <b>Invocation:</b> Client Side<br/>
/// <b>Message Flow:</b> Client to Server<br/>
/// <b>Event Notification:</b> Both server and client receive a local notification.
/// </summary>
LoadComplete,
/// <summary>
/// A client has finished unloading a scene
/// Invocation: Client Side
/// Message Flow: Client to Server
/// Event Notification: Both server and client receive a local notification
/// A client has finished unloading a scene<br/>
/// <b>Invocation:</b> Client Side<br/>
/// <b>Message Flow:</b> Client to Server<br/>
/// <b>Event Notification:</b> Both server and client receive a local notification.
/// </summary>
UnloadComplete,
/// <summary>
/// A client has finished synchronizing from a <see cref="Synchronize"/> event
/// Invocation: Client Side
/// Message Flow: Client to Server
/// Event Notification: Both server and client receive a local notification
/// A client has finished synchronizing from a <see cref="Synchronize"/> event<br/>
/// <b>Invocation:</b> Client Side<br/>
/// <b>Message Flow:</b> Client to Server<br/>
/// <b>Event Notification:</b> Both server and client receive a local notification.
/// </summary>
SynchronizeComplete,
}
/// <summary>
/// Used by <see cref="NetworkSceneManager"/> for <see cref="SceneEventMessage"/> messages
/// Note: This is only when <see cref="NetworkConfig.EnableSceneManagement"/> is enabled
/// <em>Note: This is only when <see cref="NetworkConfig.EnableSceneManagement"/> is enabled.</em><br/>
/// See also: <seealso cref="SceneEvent"/>
/// </summary>
internal class SceneEventData : IDisposable
{
@@ -98,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;
@@ -228,7 +233,14 @@ namespace Unity.Netcode
internal void AddSpawnedNetworkObjects()
{
m_NetworkObjectsSync = m_NetworkManager.SpawnManager.SpawnedObjectsList.ToList();
m_NetworkObjectsSync.Clear();
foreach (var sobj in m_NetworkManager.SpawnManager.SpawnedObjectsList)
{
if (sobj.Observers.Contains(TargetClientId))
{
m_NetworkObjectsSync.Add(sobj);
}
}
m_NetworkObjectsSync.Sort(SortNetworkObjects);
}

View File

@@ -9,10 +9,10 @@ namespace Unity.Netcode
{
/// <summary>
/// Used by <see cref="NetworkSceneManager"/> to determine if a server invoked scene event has started.
/// The returned status is stored in the <see cref="SceneEventProgress.Status"/> property.
/// Note: This was formally known as SwitchSceneProgress which contained the <see cref="AsyncOperation"/>.
/// The returned status is stored in the <see cref="SceneEventProgress.Status"/> property.<br/>
/// <em>Note: This was formally known as SwitchSceneProgress which contained the <see cref="AsyncOperation"/>.
/// All <see cref="AsyncOperation"/>s are now delivered by the <see cref="NetworkSceneManager.OnSceneEvent"/> event handler
/// via the <see cref="SceneEvent"/> parameter.
/// via the <see cref="SceneEvent"/> parameter.</em>
/// </summary>
public enum SceneEventProgressStatus
{
@@ -21,31 +21,30 @@ namespace Unity.Netcode
/// </summary>
None,
/// <summary>
/// The scene event was successfully started
/// The scene event was successfully started.
/// </summary>
Started,
/// <summary>
/// Returned if you try to unload a scene that was not yet loaded
/// Returned if you try to unload a scene that was not yet loaded.
/// </summary>
SceneNotLoaded,
/// <summary>
/// Returned if you try to start a new scene event before a previous one is finished
/// Returned if you try to start a new scene event before a previous one is finished.
/// </summary>
SceneEventInProgress,
/// <summary>
/// Returned if the scene name used with <see cref="NetworkSceneManager.LoadScene(string, LoadSceneMode)"/>
/// or <see cref="NetworkSceneManager.UnloadScene(Scene)"/>is invalid
/// or <see cref="NetworkSceneManager.UnloadScene(Scene)"/>is invalid.
/// </summary>
InvalidSceneName,
/// <summary>
/// Server side: Returned if the <see cref="NetworkSceneManager.VerifySceneBeforeLoading"/> delegate handler returns false
/// (i.e. scene is considered not valid/safe to load)
/// (<em>i.e. scene is considered not valid/safe to load</em>).
/// </summary>
SceneFailedVerification,
/// <summary>
/// This is used for internal error notifications.
/// If you receive this event then it is most likely due to a bug.
/// If you receive this event repeatedly, then please open a GitHub issue with steps to replicate
/// This is used for internal error notifications.<br/>
/// If you receive this event then it is most likely due to a bug (<em>please open a GitHub issue with steps to replicate</em>).<br/>
/// </summary>
InternalNetcodeError,
}

View File

@@ -76,7 +76,7 @@ namespace Unity.Netcode
/// <summary>
/// Create a FastBufferReader from a NativeArray.
///
///
/// A new buffer will be created using the given allocator and the value will be copied in.
/// FastBufferReader will then own the data.
///
@@ -93,12 +93,12 @@ 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>
/// Create a FastBufferReader from an ArraySegment.
///
///
/// A new buffer will be created using the given allocator and the value will be copied in.
/// FastBufferReader will then own the data.
///
@@ -117,13 +117,13 @@ 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);
}
}
/// <summary>
/// Create a FastBufferReader from an existing byte array.
///
///
/// A new buffer will be created using the given allocator and the value will be copied in.
/// FastBufferReader will then own the data.
///
@@ -142,13 +142,13 @@ 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);
}
}
/// <summary>
/// Create a FastBufferReader from an existing byte buffer.
///
///
/// A new buffer will be created using the given allocator and the value will be copied in.
/// FastBufferReader will then own the data.
///
@@ -165,12 +165,12 @@ 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>
/// Create a FastBufferReader from a FastBufferWriter.
///
///
/// A new buffer will be created using the given allocator and the value will be copied in.
/// FastBufferReader will then own the data.
///
@@ -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>
@@ -250,7 +250,7 @@ namespace Unity.Netcode
/// When you know you will be reading multiple fields back-to-back and you know the total size,
/// you can call TryBeginRead() once on the total size, and then follow it with calls to
/// ReadValue() instead of ReadValueSafe() for faster serialization.
///
///
/// Unsafe read operations will throw OverflowException in editor and development builds if you
/// go past the point you've marked using TryBeginRead(). In release builds, OverflowException will not be thrown
/// for performance reasons, since the point of using TryBeginRead is to avoid bounds checking in the following
@@ -284,7 +284,7 @@ namespace Unity.Netcode
/// When you know you will be reading multiple fields back-to-back and you know the total size,
/// you can call TryBeginRead() once on the total size, and then follow it with calls to
/// ReadValue() instead of ReadValueSafe() for faster serialization.
///
///
/// Unsafe read operations will throw OverflowException in editor and development builds if you
/// go past the point you've marked using TryBeginRead(). In release builds, OverflowException will not be thrown
/// for performance reasons, since the point of using TryBeginRead is to avoid bounds checking in the following

View File

@@ -24,6 +24,8 @@ namespace Unity.Netcode
internal readonly unsafe WriterHandle* Handle;
private static byte[] s_ByteArrayCache = new byte[65535];
/// <summary>
/// The current write position
/// </summary>
@@ -78,6 +80,10 @@ namespace Unity.Netcode
/// <param name="maxSize">Maximum size the buffer can grow to. If less than size, buffer cannot grow.</param>
public unsafe FastBufferWriter(int size, Allocator allocator, int maxSize = -1)
{
// Allocating both the Handle struct and the buffer in a single allocation - sizeof(WriterHandle) + size
// The buffer for the initial allocation is the next block of memory after the handle itself.
// If the buffer grows, a new buffer will be allocated and the handle pointer pointed at the new location...
// The original buffer won't be deallocated until the writer is destroyed since it's part of the handle allocation.
Handle = (WriterHandle*)UnsafeUtility.Malloc(sizeof(WriterHandle) + size, UnsafeUtility.AlignOf<WriterHandle>(), allocator);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
UnsafeUtility.MemSet(Handle, 0, sizeof(WriterHandle) + size);
@@ -183,7 +189,7 @@ namespace Unity.Netcode
var newSize = Math.Min(desiredSize, Handle->MaxCapacity);
byte* newBuffer = (byte*)UnsafeUtility.Malloc(newSize, UnsafeUtility.AlignOf<byte>(), Handle->Allocator);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
UnsafeUtility.MemSet(newBuffer, 0, sizeof(WriterHandle) + newSize);
UnsafeUtility.MemSet(newBuffer, 0, newSize);
#endif
UnsafeUtility.MemCpy(newBuffer, Handle->BufferPointer, Length);
if (Handle->BufferGrew)
@@ -349,6 +355,29 @@ namespace Unity.Netcode
return ret;
}
/// <summary>
/// Uses a static cached array to create an array segment with no allocations.
/// This array can only be used until the next time ToTempByteArray() is called on ANY FastBufferWriter,
/// as the cached buffer is shared by all of them and will be overwritten.
/// As such, this should be used with care.
/// </summary>
/// <returns></returns>
internal unsafe ArraySegment<byte> ToTempByteArray()
{
var length = Length;
if (length > s_ByteArrayCache.Length)
{
return new ArraySegment<byte>(ToArray(), 0, length);
}
fixed (byte* b = s_ByteArrayCache)
{
UnsafeUtility.MemCpy(b, Handle->BufferPointer, length);
}
return new ArraySegment<byte>(s_ByteArrayCache, 0, length);
}
/// <summary>
/// Gets a direct pointer to the underlying buffer
/// </summary>
@@ -399,7 +428,7 @@ namespace Unity.Netcode
/// <param name="count"></param>
/// <param name="offset"></param>
/// <typeparam name="T"></typeparam>
public void WriteNetworkSerializable<T>(INetworkSerializable[] array, int count = -1, int offset = 0) where T : INetworkSerializable
public void WriteNetworkSerializable<T>(T[] array, int count = -1, int offset = 0) where T : INetworkSerializable
{
int sizeInTs = count != -1 ? count : array.Length - offset;
WriteValueSafe(sizeInTs);

View File

@@ -27,6 +27,7 @@ namespace Unity.Netcode
public MessageHeader Header;
public ulong SenderId;
public float Timestamp;
public int SerializedHeaderSize;
}
private struct TriggerInfo
{
@@ -101,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))
{
@@ -117,7 +118,8 @@ namespace Unity.Netcode
Reader = new FastBufferReader(reader.GetUnsafePtr(), Allocator.Persistent, reader.Length),
Header = context.Header,
Timestamp = context.Timestamp,
SenderId = context.SenderId
SenderId = context.SenderId,
SerializedHeaderSize = context.SerializedHeaderSize
});
}
@@ -154,6 +156,24 @@ namespace Unity.Netcode
m_Triggers.Remove(staleKeys[i]);
}
}
/// <summary>
/// Cleans up any trigger that's existed for more than a second.
/// These triggers were probably for situations where a request was received after a despawn rather than before a spawn.
/// </summary>
internal void CleanupAllTriggers()
{
foreach (var kvp in m_Triggers)
{
foreach (var data in kvp.Value.TriggerData)
{
data.Reader.Dispose();
}
kvp.Value.TriggerData.Dispose();
}
m_Triggers.Clear();
}
internal void RemoveOwnership(NetworkObject networkObject)
{
@@ -167,28 +187,44 @@ namespace Unity.Netcode
throw new SpawnStateException("Object is not spawned");
}
for (int i = NetworkManager.ConnectedClients[networkObject.OwnerClientId].OwnedObjects.Count - 1;
i > -1;
i--)
// If we made it here then we are the server and if the server is determined to already be the owner
// then ignore the RemoveOwnership invocation.
if (networkObject.OwnerClientId == NetworkManager.ServerClientId)
{
if (NetworkManager.ConnectedClients[networkObject.OwnerClientId].OwnedObjects[i] == networkObject)
{
NetworkManager.ConnectedClients[networkObject.OwnerClientId].OwnedObjects.RemoveAt(i);
}
return;
}
networkObject.OwnerClientIdInternal = null;
var message = new ChangeOwnershipMessage
// Make sure the connected client entry exists before trying to remove ownership.
if (TryGetNetworkClient(networkObject.OwnerClientId, out NetworkClient networkClient))
{
NetworkObjectId = networkObject.NetworkObjectId,
OwnerClientId = networkObject.OwnerClientId
};
var size = NetworkManager.SendMessage(message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
for (int i = networkClient.OwnedObjects.Count - 1; i > -1; i--)
{
if (networkClient.OwnedObjects[i] == networkObject)
{
networkClient.OwnedObjects.RemoveAt(i);
}
}
foreach (var client in NetworkManager.ConnectedClients)
networkObject.OwnerClientIdInternal = null;
var message = new ChangeOwnershipMessage
{
NetworkObjectId = networkObject.NetworkObjectId,
OwnerClientId = networkObject.OwnerClientId
};
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds);
foreach (var client in NetworkManager.ConnectedClients)
{
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
}
}
else
{
NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size);
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"No connected clients prior to removing ownership for {networkObject.name}. Make sure you are not initializing or shutting down when removing ownership.");
}
}
}
@@ -207,7 +243,7 @@ namespace Unity.Netcode
return NetworkManager.ConnectedClients.TryGetValue(clientId, out networkClient);
}
if (clientId == NetworkManager.LocalClient.ClientId)
if (NetworkManager.LocalClient != null && clientId == NetworkManager.LocalClient.ClientId)
{
networkClient = NetworkManager.LocalClient;
return true;
@@ -244,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)
{
@@ -386,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);
}
@@ -483,12 +532,19 @@ namespace Unity.Netcode
foreach (var trigger in triggerInfo.TriggerData)
{
// Reader will be disposed within HandleMessage
NetworkManager.MessagingSystem.HandleMessage(trigger.Header, trigger.Reader, trigger.SenderId, trigger.Timestamp);
NetworkManager.MessagingSystem.HandleMessage(trigger.Header, trigger.Reader, trigger.SenderId, trigger.Timestamp, trigger.SerializedHeaderSize);
}
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)
@@ -507,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();
@@ -580,7 +636,7 @@ namespace Unity.Netcode
}
}
internal void DestroyNonSceneObjects()
internal void DespawnAndDestroyNetworkObjects()
{
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>();
@@ -588,17 +644,25 @@ namespace Unity.Netcode
{
if (networkObjects[i].NetworkManager == NetworkManager)
{
if (networkObjects[i].IsSceneObject != null && networkObjects[i].IsSceneObject.Value == false)
if (NetworkManager.PrefabHandler.ContainsHandler(networkObjects[i]))
{
if (NetworkManager.PrefabHandler.ContainsHandler(networkObjects[i]))
{
NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObjects[i]);
OnDespawnObject(networkObjects[i], false);
}
else
{
UnityEngine.Object.Destroy(networkObjects[i].gameObject);
}
OnDespawnObject(networkObjects[i], false);
// Leave destruction up to the handler
NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObjects[i]);
}
else if (networkObjects[i].IsSpawned)
{
// If it is an in-scene placed NetworkObject then just despawn
// and let it be destroyed when the scene is unloaded. Otherwise,
// despawn and destroy it.
var shouldDestroy = !(networkObjects[i].IsSceneObject != null
&& networkObjects[i].IsSceneObject.Value);
OnDespawnObject(networkObjects[i], shouldDestroy);
}
else
{
UnityEngine.Object.Destroy(networkObjects[i].gameObject);
}
}
}
@@ -634,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++)
{
@@ -641,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)
@@ -668,17 +738,21 @@ namespace Unity.Netcode
return;
}
// Move child NetworkObjects to the root when parent NetworkObject is destroyed
foreach (var spawnedNetObj in SpawnedObjectsList)
// If we are shutting down the NetworkManager, then ignore resetting the parent
if (!NetworkManager.ShutdownInProgress)
{
var (isReparented, latestParent) = spawnedNetObj.GetNetworkParenting();
if (isReparented && latestParent == networkObject.NetworkObjectId)
// Move child NetworkObjects to the root when parent NetworkObject is destroyed
foreach (var spawnedNetObj in SpawnedObjectsList)
{
spawnedNetObj.gameObject.transform.parent = null;
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
var (isReparented, latestParent) = spawnedNetObj.GetNetworkParenting();
if (isReparented && latestParent == networkObject.NetworkObjectId)
{
NetworkLog.LogWarning($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} moved to the root because its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} is destroyed");
spawnedNetObj.gameObject.transform.parent = null;
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} moved to the root because its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} is destroyed");
}
}
}
}
@@ -735,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);
@@ -774,7 +848,7 @@ namespace Unity.Netcode
{
foreach (var sobj in SpawnedObjectsList)
{
if (sobj.CheckObjectVisibility == null || NetworkManager.IsServer)
if (sobj.CheckObjectVisibility == null)
{
if (!sobj.Observers.Contains(clientId))
{

View File

@@ -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.

View File

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

View File

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

View File

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

View File

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

View File

@@ -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))
{

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: e946a6fdcfcb9dd48b76b38871c0a77b
guid: 10ca1ce26995e754599c9eedc2c228d8
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: f2ef964afcae91248b2298b479ed1b53
guid: 19fbc3f43e13a9144a9c66c68a1c43c1
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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